kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Chef で mackerel-check-plugins をプロビジョニングする

「Mackerel Advent Calendar 2016」3日目の記事を書くぞー!昨日も僕が担当していて,拡張グラフパーマリンクを試した話を書いた.

kakakakakku.hatenablog.com

もっと枠が埋まってくれると良いのになぁ!

qiita.com

今日は一般的なユースケースとして,Mackerel でプロセス監視を行う方法と,その設定を Chef でプロビジョニングする話を書こうと思う.初歩的な話だけど参考になればと思って.

check-procs

Mackerel でプロセス監視を行う場合,公式に提供されている Check Plugins に入ってる check-procs を使うことになる.シンタックスはシンプルで,--pattern でプロセス名(部分一致)を指定する.実際にはオプションも多く用意されていて,--user でプロセスの実行ユーザーを指定したり,-w-c でプロセス数ごとに監視閾値を変えて設定することもできる.詳しくは README 参照で!

github.com

mackerel-agent-plugins と一緒で,check-procs もバイナリで配布されたコマンドなので,コマンドを叩いて動作確認をすることができる.以下は Fluentd のプロセス監視をする例で,プロセス起動中と停止中でチェック結果が異なっている.

ちなみに --patterntd-agent など曖昧に書いてしまうと誤検知に繋がることにもなるため,絶対パスなど特定して書くのと,--user で実行ユーザーを指定するのは Tips として覚えておくと良いと思う.

$ /usr/bin/check-procs --pattern /usr/sbin/td-agent --user td-agent
Procs OK: Found 2 matching processes; cmd //usr/sbin/td-agent/; user /td-agent/

$ /usr/bin/check-procs --pattern /usr/sbin/td-agent --user td-agent
Procs CRITICAL: Found 0 matching processes; cmd //usr/sbin/td-agent/; user /td-agent/

Chef で mackerel-check-plugins をインストールする

Chef で mackerel-agent と mackerel-agent-plugins をインストールする場合はコミュニティクックブック (cookbook-mackerel-agent) を Berkshelf 経由でインストールする.さらに拡張用のクックブックを cookbooks/my-mackerel に用意して,metadata.rbdepends 'mackerel-agent' と書く.今回紹介するクックブックは全て cookbooks/my-mackerel を前提とする.

github.com

残念ながら mackerel-check-plugins はコミュニティクックブックに含まれていないため,以下のように独自に package でインストールする必要がある.コミュニティクックブックに含めても良いような気がするんだけどなぁー.どうなんだろ?

include_recipe 'mackerel-agent'
include_recipe 'mackerel-agent::plugins'

package 'mackerel-check-plugins' do
  action :install
end

Chef で check-procs を設定する

言ってしまうと簡単で mackerel-agent-plugins と同じように attributes に書くことができる.以下に nginx と php-fpm と td-agent を監視する mackerel-agent-plugins と check-procs の設定を定義した.

node.default['mackerel-agent']['conf']['plugin.metrics.nginx'] = {
  command: '/usr/bin/mackerel-plugin-nginx -port 80'
}

node.default['mackerel-agent']['conf']['plugin.metrics.php-fpm'] = {
  command: '/usr/bin/mackerel-plugin-php-fpm -url=http://localhost/php_fpm_status?json'
}

node.default['mackerel-agent']['conf']['plugin.metrics.fluentd'] = {
  command: '/usr/bin/mackerel-plugin-fluentd'
}

node.default['mackerel-agent']['conf']['plugin.checks.check_nginx'] = {
  command: '/usr/bin/check-procs --pattern /usr/local/nginx/sbin/nginx --user nginx'
}

node.default['mackerel-agent']['conf']['plugin.checks.check_php-fpm'] = {
  command: '/usr/bin/check-procs --pattern "php-fpm: master process"'
}

node.default['mackerel-agent']['conf']['plugin.checks.check_td-agent'] = {
  command: '/usr/bin/check-procs --pattern /usr/sbin/td-agent --user td-agent'
}

このまま Chef でプロビジョニングをすると /etc/mackerel/mackerel.conf に反映される.

$ cat /etc/mackerel-agent/mackerel-agent.conf
apikey = "xxx"
roles = ["xxx"]

[plugin.checks.check_nginx]
command = "/usr/bin/check-procs --pattern /usr/local/nginx/sbin/nginx --user nginx"

[plugin.checks.check_php-fpm]
command = "/usr/bin/check-procs --pattern \"php-fpm: master process\" --user root"

[plugin.checks.check_td-agent]
command = "/usr/bin/check-procs --pattern /usr/sbin/td-agent --user td-agent"

[plugin.metrics.fluentd]
command = "/usr/bin/mackerel-plugin-fluentd"

[plugin.metrics.nginx]
command = "/usr/bin/mackerel-plugin-nginx -port 80"

[plugin.metrics.php-fpm]
command = "/usr/bin/mackerel-plugin-php-fpm -url=http://localhost/php_fpm_status?json"

アラート通知

実際にプロセスが止まると以下のようにアラートが通知される.

f:id:kakku22:20161202230900p:plain

まとめると

Chef で Mackerel の設定を全て管理してプロビジョニングできるようにしておくと運用が楽になるのでオススメ!

Mackerel の「拡張グラフパーマリンク」を使ってファイルシステム使用率をグラフにしようと試行錯誤した話

さぁ「Mackerel Advent Calendar 2016」2日目の記事を書くぞ!まだ枠空いてますよ〜

qiita.com

最近作った最高のダッシュボードの話

10月末に mkr dashboards コマンドを使って,ダッシュボードの定義を YAML 形式でバージョン管理しながら運用している話をまとめた.実際にチーム内で積極的に活用していて,障害時/テレビ砲待機時/定常モニタリング時など,気になったらすぐに Mackerel のダッシュボードを見る習慣が根付いている.もしダッシュボードの運用に悩んでいる人がいたら参考になるはず!!!

kakakakakku.hatenablog.com

メトリック一覧取得 API を叩いてみた

上の記事を書いたときに @songmu さんに「メトリック一覧取得 API と組み合わせると良いかも」とコメントを頂いた.「メトリック一覧取得 API」とは,今年の5月にリリースされた新機能で,簡単に言うと「サービスとホストに紐付くメトリック名の一覧を取得するだけの API」で,マッシュアップしてこそ効果を発揮する系の API だとは思う.具体的には以下の2個のエンドポイントで叩くことができる.

  • サービス(メトリック名の一覧を取得する)
    • /api/v0/services/<serviceName>/metric-names
  • ホスト(メトリック名の一覧を取得する)
    • /api/v0/hosts/<hostId>/metric-names

mackerel.io

ホストメトリック名の一覧を取得してみたところ,filesystem.xvde.sizefilesystem.xvde.used を発見した.上の記事にも書いた通り,実は「ロールごとにファイルシステムのメトリックを見れない」ことにずっと悩んでいたんだけど,もしかしたら filesystem.xvde.sizefilesystem.xvde.used で解決するのでは?と考えた.ちなみに xvde に関してはまぁルートデバイスのアタッチ次第なので,そこは読み替えてもらう前提として.

# 実際にはカスタムメトリック名も取得できる(量が多く割愛している)
$ curl -H 'X-Api-Key: xxx' -s https://mackerel.io/api/v0/hosts/xxx/metric-names | jq .
{
  "names": [
    "cpu.guest.percentage",
    "cpu.idle.percentage",
    "cpu.iowait.percentage",
    "cpu.irq.percentage",
    "cpu.nice.percentage",
    "cpu.softirq.percentage",
    "cpu.steal.percentage",
    "cpu.system.percentage",
    "cpu.user.percentage",
    "disk.xvde.reads.delta",
    "disk.xvde.writes.delta",
    "filesystem.xvde.size",
    "filesystem.xvde.used",
    "interface.eth0.rxBytes.delta",
    "interface.eth0.txBytes.delta",
    "loadavg5",
    "memory.active",
    "memory.buffers",
    "memory.cached",
    "memory.free",
    "memory.inactive",
    "memory.swap_cached",
    "memory.swap_free",
    "memory.swap_total",
    "memory.total",
    "memory.used"
  ]
}

ダッシュボードにグラフを貼ってみた

以下の iframe を用意してダッシュボードにグラフを貼ってみたところ,予想通り,ロール毎に filesystem.xvde.sizefilesystem.xvde.used を描画することができた.

<iframe src="https://mackerel.io/embed/orgs/xxx/services/my-service/web?graph=filesystem.xvde.size&period=1d&simplified=false&stacked=false" height="200" width="400" frameborder="0"></iframe>
<iframe src="https://mackerel.io/embed/orgs/xxx/services/my-service/web?graph=filesystem.xvde.used&period=1d&simplified=false&stacked=false" height="200" width="400" frameborder="0"></iframe>

f:id:kakku22:20161129235321p:plain

実際に必要なのは「使用率 (%)」だ!

実際にモニタリングするなら「ファイルシステム使用率 (%)」を描画したいなと思って,メトリックの計算を試してみようと思った.

Zabbix 2.0 でファイルシステム系のメトリックを取得する場合は,ローレベルディスカバリ (LDD) を使うことも多くて少し仕組みが違うけど,例えば Zabbix で Memcached のキャッシュヒット率を取得する場合は「計算フィールド」を使うと思う.この「計算フィールド」と同じことを Mackerel でやりたいなと思った.

拡張グラフパーマリンクを使う(実験的機能)

Mackerel でメトリックを計算するためには「拡張グラフパーマリンク」を使う.「拡張グラフパーマリンク」は表現の幅を大きく広げられる可能性があって,素晴らしい機能だと思ってる.例えば,メトリックを1週間前と比較してグラフに描画をすることができたりする.ただし,現在もまだ実験的機能で,もし使う場合は事前に実験的機能を有効にしておく必要がある.

やっと本題に入れる.今回「拡張グラフパーマリンク」にハマって試行錯誤した話をまとめる.

mackerel.io

group 関数を使うと複数のメトリックを同じグラフに描画できる

group 関数を使うと複数のメトリックを同じグラフに描画できるため,さっきまで別々のグラフだった filesystem.xvde.sizefilesystem.xvde.used を同じグラフに展開できた.

<iframe src="https://mackerel.io/embed/orgs/xxx/advanced-graph?query=group(role('my-service:web', 'filesystem.xvde.used'), role('my-service:web', 'filesystem.xvde.size'))&title=filesystem&period=1h&simplified=false&stacked=false" height="200" width="400" frameborder="0"></iframe>

クエリ部分を整形すると SQL 的に読めると思う.

group(
  role('my-service:web', 'filesystem.xvde.used'),
  role('my-service:web', 'filesystem.xvde.size')
)

f:id:kakku22:20161130001133p:plain

divide 関数と scale 関数を使えば「使用率 (%)」も簡単に表現できそう

次に divide 関数で除算をして,scale 関数で乗算をすれば,使用率 (%) の計算も楽勝じゃん!と思ったけど,結論として無理だった.最初は単純に以下のように書いた.

<iframe src="https://mackerel.io/embed/orgs/xxx/advanced-graph?query=scale(divide(role('my-service:web', 'filesystem.xvde.used'), role('my-service:web', 'filesystem.xvde.size')), 100.0)&title=filesystem&period=1h&simplified=false&stacked=false" height="200" width="400" frameborder="0"></iframe>

クエリ部分を整形すると SQL 的に読めると思う(実際には動かない例だけど).

scale(
  divide(
    role('my-service:web', 'filesystem.xvde.used'),
    role('my-service:web', 'filesystem.xvde.size')
  ),
  100.0
)

ようするに ( filesystem.xvde.size / filesystem.xvde.used * 100 ) を実現するクエリを書いた.ドキュメントに詳しく書かれて無くてハマってしまったけど,試行錯誤して気付いたこととして,divide 関数はホストメトリックのような単一の値だけをサポートしていて,role 関数を使った複数ホストの値はサポートしていないようだった.よって僕が書いたクエリだと,多対多の divide になってしまうため,エラーになる.

ワークアラウンドを考えた

諦めるのはまだ早いぞ!と思って,全ホストごとに集計して group 関数を使うワークアラウンドを考えてみた.実際にうまくできた.ただし,ホストを固定しているため,ロールの意味が全くないし,オートスケールなどをしている場合には使えないし,制約ばかりになってしまった.URL 長すぎワロタwww

ちなみに &unit=%25 を付けることで,グラフ上の単位が % になる.細かな工夫も重要!

<iframe src="https://mackerel.io/embed/orgs/xxx/advanced-graph?query=group(alias(scale(divide(host('host1', 'filesystem.xvde.used'), host('host1', 'filesystem.xvde.size')), 100.0), 'host1'), alias(scale(divide(host('host2', 'filesystem.xvde.used'), host('host2', 'filesystem.xvde.size')), 100.0), 'host2'), alias(scale(divide(host('host3', 'filesystem.xvde.used'), host('host3', 'filesystem.xvde.size')), 100.0), 'host3'), alias(scale(divide(host('host4', 'filesystem.xvde.used'), host('host4', 'filesystem.xvde.size')), 100.0), 'host4'), alias(scale(divide(host('host5', 'filesystem.xvde.used'), host('host5', 'filesystem.xvde.size')), 100.0), 'host5'))&unit=%25&title=filesystem&period=1h&simplified=false&stacked=false" height="200" width="400" frameborder="0"></iframe>

クエリ部分を整形すると SQL 的に読めると思う.ただまぁ…ツライ!

group(
  alias(
    scale(
      divide(
        host('host1', 'filesystem.xvde.used'),
        host('host1', 'filesystem.xvde.size')
      ),
      100.0
    ),
    'host1'
  ),
  alias(
    scale(
      divide(
        host('host2', 'filesystem.xvde.used'),
        host('host2', 'filesystem.xvde.size')
      ),
      100.0
    ),
    'host2'
  ),
  alias(
    scale(
      divide(
        host('host3', 'filesystem.xvde.used'),
        host('host3', 'filesystem.xvde.size')
      ),
      100.0
    ),
    'host3'
  ),
  alias(
    scale(
      divide(
        host('host4', 'filesystem.xvde.used'),
        host('host4', 'filesystem.xvde.size')
      ),
      100.0
    ),
    'host4'
  ),
  alias(
    scale(
      divide(
        host('host5', 'filesystem.xvde.used'),
        host('host5', 'filesystem.xvde.size')
      ),
      100.0
    ),
    'host5'
  )
)

一応期待していたグラフにはなったけど…むむむ!

f:id:kakku22:20161130235002p:plain

まとめると

今回は Mackerel の「拡張グラフパーマリンク」を試してみた.クエリで使える関数は他にもたくさんあって,もっと応用できそうだなと感じた.「拡張グラフパーマリンク」に凄く未来がある!

ただし,今回試行錯誤した「ロールごとにファイルシステム使用率 (%) をグラフに描画する件」は結論うまくできなかった.もしかしたら僕の勘違いかもしれないから,普通にこうすると実現できるよ!というアドバイスがあったら是非お願いします 🙇

引き続き Mackerel を使っていくぞ!

「Mackerel Advent Calendar 2016」3日目

明日も引き続き僕が書くよ…!

まだ枠空いてるし,仲間募集中〜!

Piculet の動作を詳細に確認して運用フローを考えてみた

前回は Piculet を使ってセキュリティグループ設定と Groupfile の比較を CircleCI でチェックできるところまで試した.興味があったら是非以下の記事を見てもらえると!

kakakakakku.hatenablog.com

今回は前回の続きで,主に --apply オプションを使った反映を試してみた.最後に Piculet を運用に活用するならどんなフローになるか?を考えてみた.

新規セキュリティグループを反映してみる

まず,以下のような Groupfile を用意した."dev-web" という名前のセキュリティグループで,特定の IP アドレス(今回は 1.2.3.4)から http アクセスを許可する設定にしている.

# -*- mode: ruby -*-
# vi: set ft=ruby :
ec2 "vpc-xxxxxxxx" do
  security_group "dev-web" do
    description "web for dev env"

    ingress do
      permission :tcp, 80..80 do
        ip_ranges(
          "1.2.3.4/32"
        )
      end
    end

    egress do
      permission :any do
        ip_ranges(
          "0.0.0.0/0"
        )
      end
    end
  end
end

反映する前に --apply オプションと合わせて使える --dry-run オプションを使って検証をしてみた.ちなみに --dry-run オプションは Piculet 独自のオプションではなくて AWS SDK for Ruby の API で用意されたオプションだ.以下のように意図した通りにセキュリティグループが反映されるようなログが出力された.

$ piculet --apply --dry-run --names dev-web
Apply `Groupfile` to SecurityGroup (dry-run)
Create SecurityGroup: vpc-xxxxxxxx > dev-web (dry-run)
Create Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 80..80 (dry-run)
  authorize 1.2.3.4/32 (dry-run)
No change

次に実際に反映してみた.成功!

$ piculet --apply --names dev-web
Apply `Groupfile` to SecurityGroup
Create SecurityGroup: vpc-xxxxxxxx > dev-web
Create Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 80..80
  authorize 1.2.3.4/32

正しく反映されていた.

f:id:kakku22:20161127171913p:plain

ちなみに Piculet では冪等性の考慮がされていて,全く同じ DSL であれば何度反映してもスキップとなる.ただし,後述する通り,グループ名とグループ説明を修正する場合は新規作成となるため,注意が必要になる.

$ piculet --apply --names dev-web
Apply `Groupfile` to SecurityGroup
No change

ルールを変更してみる

次に Groupfile を修正して 80443 にして反映してみた.

# -*- mode: ruby -*-
# vi: set ft=ruby :
ec2 "vpc-xxxxxxxx" do
  security_group "dev-web" do
    description "web for dev env"

    ingress do
      permission :tcp, 443..443 do
        ip_ranges(
          "1.2.3.4/32"
        )
      end
    end

    egress do
      permission :any do
        ip_ranges(
          "0.0.0.0/0"
        )
      end
    end
  end
end

既存ルール 80 は削除されて,新規ルール 443 が反映された.グループ ID は同じで,副作用なく反映できていた.

$ piculet --apply --dry-run --names dev-web
Apply `Groupfile` to SecurityGroup (dry-run)
Delete Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 80..80 (dry-run)
  revoke 1.2.3.4/32 (dry-run)
Create Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 443..443 (dry-run)
  authorize 1.2.3.4/32 (dry-run)
No change

$ piculet --apply --names dev-web
Apply `Groupfile` to SecurityGroup
Delete Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 80..80
  revoke 1.2.3.4/32
Create Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 443..443
  authorize 1.2.3.4/32

ingress にグループ名を指定してみる

次に groups を使ってグループ名を指定してみる.ちなみに default は存在するグループ名で,known_group は存在しないグループ名とした.

# -*- mode: ruby -*-
# vi: set ft=ruby :
ec2 "vpc-xxxxxxxx" do
  security_group "dev-web" do
    description "web for dev env"

    ingress do
      permission :tcp, 443..443 do
        groups(
          "default",
          "known_group"
        )
      end
    end

    egress do
      permission :any do
        ip_ranges(
          "0.0.0.0/0"
        )
      end
    end
  end
end

恐らく AWS SDK for Ruby の仕様(バグ?)だとは思うが,存在しないグループ名を指定しても --dry-run だと検知できなかった.よって,実際に反映する際にエラーになってしまった.--dry-run で検知できれば良いのに...!

あと1点気になったのは,複数のセキュリティグループを一括で反映するときに一部にエラーが発生しても,全体をロールバックする機能はないということ.ようするにエラーが発生するまでの反映はそのまま apply されてしまう.CloudFormation とは違うという認識を持っておく必要がありそう.

$ piculet --apply --dry-run --names dev-web
Apply `Groupfile` to SecurityGroup (dry-run)
Update Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 443..443 (dry-run)
  authorize default, known_group (dry-run)
  revoke 1.2.3.4/32 (dry-run)
No change

$ piculet --apply --names dev-web
Apply `Groupfile` to SecurityGroup
Update Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 443..443
  authorize default, known_group
[ERROR] Can't find SecurityGroup: 111111111111/known_group in vpc-xxxxxxxx

テンプレート機能

Piculet にはテンプレート機能があり,共通の DSL を定義しておいて,再利用することができる.管理コンソールでセキュリティグループを管理するときにツライと感じるのは IP アドレスに別名(コメント)を付与することができないところで,特に複数オフィスの IP アドレスを登録しているとパッと見で判断できなくなる.テンプレートにコメントを書いて GitHub で管理しておけば,運用的に助かるし便利機能だと思った.以下のように書ける.

# -*- mode: ruby -*-
# vi: set ft=ruby :
template "https_from_office" do
  permission :tcp, 443..443 do
    ip_ranges(
      "1.2.3.4/32"
    )
  end
end

ec2 "vpc-xxxxxxxx" do
  security_group "dev-web" do
    description "web for dev env"

    ingress do
      include_template "https_from_office"
    end

    egress do
      permission :any do
        ip_ranges(
          "0.0.0.0/0"
        )
      end
    end
  end
end

ただし,テンプレート機能を使う場合,Groupfile をマスタとして管理していく必要がある.もし一時的に管理コンソールで直接修正をして,もう一度 Piculet でエクスポートをするとテンプレートは消えてそのまま展開されてしまう.運用フロー次第かなと.

グループ名とグループ説明を変更してみる(冪等性が保証されない)

webweb2 に修正して反映してみる.

# -*- mode: ruby -*-
# vi: set ft=ruby :
template "https_from_office" do
  permission :tcp, 443..443 do
    ip_ranges(
      "1.2.3.4/32"
    )
  end
end

ec2 "vpc-xxxxxxxx" do
  security_group "dev-web2" do
    description "web2 for dev env"

    ingress do
      include_template "https_from_office"
    end

    egress do
      permission :any do
        ip_ranges(
          "0.0.0.0/0"
        )
      end
    end
  end
end

まず,既存の dev-web セキュリティグループを EC2 に紐付けた状態にした.

$ aws ec2 describe-instances --instance-ids i-xxxxxxxx | jq '.Reservations[].Instances[].SecurityGroups'
[
  {
    "GroupName": "dev-web",
    "GroupId": "sg-11111111"
  }
]

すると,ルールは全て削除されてしまった.ただし,セキュリティグループ自体は [ERROR] resource sg-11111111 has a dependent object のエラーが出て削除されなかった.インスタンスに紐付いている場合,セキュリティグループは残るものの,ルールは全て消えてしまうため,意図的に反映しないと大規模障害の引き金となってしまいそう.

$ piculet --apply --dry-run --names dev-web
Apply `Groupfile` to SecurityGroup (dry-run)
Delete Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 443..443 (dry-run)
  revoke 1.2.3.4/32 (dry-run)
Delete Permission: vpc-xxxxxxxx > dev-web(egress) > any  (dry-run)
  revoke 0.0.0.0/0 (dry-run)
Delete SecurityGroup: vpc-xxxxxxxx > dev-web (dry-run)
No change

$ piculet --apply --names dev-web
Apply `Groupfile` to SecurityGroup
Delete Permission: vpc-xxxxxxxx > dev-web(ingress) > tcp 443..443
  revoke 1.2.3.4/32
Delete Permission: vpc-xxxxxxxx > dev-web(egress) > any
  revoke 0.0.0.0/0
Delete SecurityGroup: vpc-xxxxxxxx > dev-web
[ERROR] resource sg-11111111 has a dependent object

またグループ説明だけを修正した場合はエラーになる.これは良かった.グループ説明だけを修正した場合に新規セキュリティグループが反映されたら障害になってしまうし.

$ piculet --apply --dry-run --names dev-web
Apply `Groupfile` to SecurityGroup (dry-run)
[WARN] `description` cannot be updated: vpc-xxxxxxxx > dev-web (dry-run)
No change

$ piculet --apply --names dev-web
Apply `Groupfile` to SecurityGroup
[WARN] `description` cannot be updated: vpc-xxxxxxxx > dev-web
No change

運用を考えた

Piculet を活用する運用フローを考えてみた.ただし EC2 や RDS の運用を自動化してるかどうかによっても大きく違うと思うので,あくまで参考まで.

案1. 管理コンソールでの反映を禁止し,全て Piculet を使う場合

基本的に運用メリットが大きいフローだと思う.ただし,既に本番稼働中のサービスに導入するには Piculet の仕様をちゃんと理解して,適切に活用する必要がある.

  • メリット
    • 全ての運用を自動化できる(CircleCI で master にマージされたら反映したり)
    • プルリクベースで反映内容を事前にレビューできる
    • Piculet のテンプレート機能が使える
    • 新規プロジェクトに導入し易い
      • 新規プロジェクトなら部分最適 < 全体最適 だから CloudFormation / Terraform の方が良いかも
  • デメリット
    • 一時的に管理コンソールで変更をしてしまうと簡単に Groupfile との差が生じてしまう
      • 変更を検知できる別の仕組みが必要そう

案2. 管理コンソールでの反映を許容し,部分的に Piculet で反映する場合

  • メリット
    • Piculet で反映する場合は --names オプションを指定して部分的に反映できる
    • 管理コンソールでの反映も許容することである程度自由度を残した運用が可能になる
      • 管理コンソールで反映した場合はすぐに Groupfile をエクスポートして GitHub を最新にする必要がある
      • 前回試した CircleCI で CI するフローを徹底する必要がある
    • 本番稼働中のサービスに導入し易い
  • デメリット
    • 反映を完全に自動化できないため,運用カバーな部分が残ってしまう
    • Groupfile の運用が暗黙知になってインフラ担当が権威的になってしまう可能性がありそう

案3. 全ての反映を管理コンソールで実施し,常に最新の Groupfile を GitHub でバージョン管理する

  • メリット
    • 管理コンソールを使っていた場合に,今までの運用を大きく変えずにセキュリティグループ設定をバージョン管理できる
    • 前回試した CircleCI で CI するフローを徹底することで差分を検知することができる
    • 本番稼働中のサービスに導入し易い
  • デメリット
    • 自動化とは程遠い運用になってしまう

まとめ

前回 と今回で Piculet を一通り試した.とにかく素晴らしい!ただ前回も書いた通り,もう少し README が充実してると導入し易いとは思う.特に反映時の冪等性のあたりなど,仕様を理解しておかないと不安な部分だしなーとは思った.

ちなみに僕が担当してる現場の話で言うと,現在はまだセキュリティグループの運用を全て管理コンソールで行っていて,管理面に課題を感じていた(だからこそ今回 Piculet を試していた).現実的に今回考えた「案2」or「案3」になりそうだけど,提案してみようと思う.

同時に Piculet を運用で使ってる人の話を聞いてみたいと思ってて,タイミング良く 12/08 に Codenize Meetup が開催される!絶対参加したいし抽選に当たることを祈ってる...!!!

codenize.connpass.com

New Relic の "Deployment Tracking" でデプロイ前後のパフォーマンスを比較すると便利

New Relic APM で PRO 契約をすると "Deployment Tracking" という機能が使えるようになる(最近契約した).

  • デプロイ情報(タイムスタンプ/変更点など)を New Relic に登録できる
  • デプロイ前後のパフォーマンス比較(Apdex Score/Responce Time など)を確認できる

アプリケーションのパフォーマンスが良くなったり,悪くなったりしたときに,判断基準として「どのデプロイがトリガーになったか?」をすぐに知ることができれば,デプロイ対象となったプルリクエストを中心に一次調査ができるし,運用上のメリットは大きいと思う.New Relic APM PRO を使ってるなら "Deployment Tracking" を使わないと損!

デプロイ一覧

検証用に3回登録してみた.

f:id:kakku22:20161123010354p:plain

パフォーマンス比較

グラフ上に縦棒(デプロイしたタイミング)がプロットされて前後の差を確認できる.

f:id:kakku22:20161123010813p:plain

(プライベート情報が多いため Deployments page | New Relic Documentation から引用した)

デプロイ情報の登録

大きく2種類の登録方法がある.

  • REST API
  • Agent

docs.newrelic.com

1. REST API

今回はお手軽に使える REST API を使うことにした.前提として "Admin's API key" を発行しておく必要がある."Admin's API key" と "REST API key" は異なるので注意すること.

docs.newrelic.com

エンドポイントは3種類ある.

  • GET : List
  • POST : Create
  • DELETE : Delete

以下のパラメータは変数のままにしてある.

  • {application_id}
  • {api_key}
  • {id}

1-1. List

$ curl -X GET 'https://api.newrelic.com/v2/applications/{application_id}/deployments.json' \
       -H 'X-Api-Key:{api_key}' -i

1-2. Create

送信するパラメータは全て任意の文字列を登録することができる.

revision は必須パラメータで,ドキュメントには "such as a git SHA" と書いてあってコミットハッシュを登録するようだった.その他の changelogdescriptionuser は必須ではなく,必要に応じて使うことになる.user はデフォルトでアカウント名(企業名)が入っていた.

$ curl -X POST 'https://api.newrelic.com/v2/applications/{application_id}/deployments.json' \
       -H 'X-Api-Key:{api_key}' -i \
       -H 'Content-Type: application/json' \
       -d \
'{
  "deployment": {
    "revision": "string",
    "changelog": "string",
    "description": "string",
    "user": "string"
  }
}' 

今回は revision にタイムスタンプを含めるようにした.コマンドは以下の通り.

$ curl -X POST 'https://api.newrelic.com/v2/applications/{application_id}/deployments.json' \
       -H 'X-Api-Key:{api_key}' -i \
       -H 'Content-Type: application/json' \
       -d @<(cat <<EOF
{
  "deployment": {
    "revision": "$(date +%Y%m%d_%H%M)"
  }
}
EOF
)

1-3. Delete

$ curl -X DELETE 'https://api.newrelic.com/v2/applications/{application_id}/deployments/{id}.json' \
       -H 'X-Api-Key:{api_key}' -i

2. Agent

今回は試していないけど,言語ごとの Agent に機能が含まれている.

Rails の場合は Capistrano の deploy.rb に以下のタスクを追加するだけで良く,より簡単に使えるようになっている.

after "deploy:updated", "newrelic:notice_deployment"

API Explore

REST API を試すときは API Explore を使うと便利だった.Swagger をベースに作られたドキュメントで,API 仕様を読みながら実際に API をコールすることができる.

f:id:kakku22:20161123012814p:plain

デプロイ情報は現在時刻で登録される点に注意

"Deployment Tracking" の制約として,デプロイ情報は現在時刻で登録されることになる.よって,過去のデプロイ情報をまとめて登録することはできなかった.

Note that the time of your deployment will be recorded as the current time in UTC.

CircleCI と Piculet でセキュリティグループを CI する

管理コンソールでセキュリティグループ設定を管理するのは限界があると思っていて,例えば以下のような状態になってしまうことがあると思う.

  • 必要に応じて日々増えていくルール設定(無意識にポチポチと追加してしまう)
  • 使って無さそうだから消したいけど影響が出たら嫌だから残しているルール設定(何のために追加したのか情報が無い)
  • 無駄に広く開放されてしまっているルール設定(動作確認のために取り急ぎ設定したまま残ってしまう)

そこで,セキュリティグループ設定もバージョン管理できれば良さそうだと考えて,AWS Community Hero の @sgwr_dts さんが開発した Piculet を検証している.Piculet 以外にも Codenize.tools に AWS のリソースを管理するツールが多く公開されていて,素晴らしすぎる!

github.com

インストール

簡単!

$ gem install piculet

エクスポート

簡単!

$ piculet --export --output Groupfile
Export SecurityGroup to `Groupfile`

追加で --split オプションを付けると VPC ごとに Groupfile を分割できる.もし複数 VPC を運用してる場合だと便利な機能だと思う.

$ piculet --export --output Groupfile --split
Export SecurityGroup
  write `./vpc-xxxxxxxx.group`
  write `Groupfile`
$ cat Groupfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'vpc-xxxxxxxx.group'

また --names オプションでグループ名を指定すると対象のセキュリティグループを固定することができる.以下は試しに default グループをエクスポートしてみた.

$ piculet --export --output Groupfile --names default
Export SecurityGroup to `Groupfile`
$ cat Groupfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
ec2 "vpc-xxxxxxxx" do
  security_group "default" do
    description "default VPC security group"

    ingress do
      permission :any do
        groups(
          "default"
        )
      end
    end

    egress do
      permission :any do
        ip_ranges(
          "0.0.0.0/0"
        )
      end
    end
  end
end

Groupfile

DSL で表現されているため,直感的に理解できて可読性が高い.

  • ingress : インバウンド
  • egress : アウトバウンド
  • permission : プロトコル&ポート
  • groups : 送信元(セキュリティグループ)
  • ip_ranges : 送信元(IP アドレス)

次に「1.2.3.4/32 から http & https」と「グループ名 "default" から ssh」を許可する設定をしてエクスポートしてみた.正しくエクスポートできている.

# -*- mode: ruby -*-
# vi: set ft=ruby :
ec2 "vpc-xxxxxxxx" do
  security_group "sample-web" do
    description "sample-web"

    ingress do
      permission :tcp, 22..22 do
        groups(
          "default"
        )
      end
      permission :tcp, 80..80 do
        ip_ranges(
          "1.2.3.4/32"
        )
      end
      permission :tcp, 443..443 do
        ip_ranges(
          "1.2.3.4/32"
        )
      end
    end

    egress do
      permission :any do
        ip_ranges(
          "0.0.0.0/0"
        )
      end
    end
  end
end

JSON サポート

--format=json を追加すると JSON 形式で Groupfile をエクスポートできる.Groupfile をツールで処理する場合などは DSL より JSON の方が良さそう.

$ piculet --export --output Groupfile --names default --format=json
Export SecurityGroup to `Groupfile`
{
  "vpc-xxxxxxxx": {
    "sg-11111111": {
      "name": "default",
      "description": "default VPC security group",
      "tags": {
      },
      "owner_id": "111111111111",
      "ingress": [
        {
          "protocol": "any",
          "port_range": null,
          "ip_ranges": [

          ],
          "groups": [
            {
              "id": "sg-11111111",
              "name": "default",
              "owner_id": "111111111111"
            }
          ]
        }
      ],
      "egress": [
        {
          "protocol": "any",
          "port_range": null,
          "ip_ranges": [
            "0.0.0.0/0"
          ],
          "groups": [

          ]
        }
      ]
    }
  }
}

強制上書き

--export の挙動は強制上書きとなっていて,例えば「管理コンソール上の設定」と「Groupfile」がコンフリクトしているような場合,強制的に「管理コンソール上の設定」がエクスポートされる.まぁ実際に稼働している設定だし,適切な挙動だと思う.

CircleCI で CI をできるように

生成した Groupfile を GitHub で管理すれば,セキュリティグループ設定をバージョン管理することができる.さらに Groupfile の更新が形骸化しないように CircleCI で CI できたら良いなと思って試した.

事前準備

以下の環境変数を CircleCI 側に登録しておく.

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_REGION

IAM は CircleCI 用に作成した.CI するだけなら以下で良さそう.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt0000000000000",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeTags",
                "iam:GetUser"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

circle.yml

Piculet に diff 系のオプションは無かったため,CircleCI 上で diff 結果を判定するようにした.

machine:
  timezone:
    Asia/Tokyo
  ruby:
    version: 2.3.1

dependencies:
  pre:
    - gem install piculet

test:
  post:
    - piculet --export --output Groupfile.circleci
    - diff Groupfile Groupfile.circleci

結果

良さ!

f:id:kakku22:20161120013448p:plain

気になったところ

IAM ポリシーで iam:GetUser を許可する場合と許可しない場合で DSL の groups のエクスポート結果が異なっていた.仕様だとは思うけど,実際にコードを読んで原因を探ってみたいなと思う.

iam:GetUser 許可

「許可」の場合は「グループ名」になった.

ingress do
  permission :any do
    groups(
      "default"
    )
  end
end

iam:GetUser 非許可

「非許可」の場合は「グループ ID」になった.

ingress do
  permission :any do
    groups(
      ["111111111111", "sg-11111111"]
    )
  end
end

まとめと次回

今回は Piculet を使って以下を試してみた.セキュリティグループ設定をバージョン管理できるの最高!ただ,もう少し README にオプションの説明など,詳細なドキュメントが書かれてたら良いのになーと思った.

  • セキュリティグループ設定をエクスポートした
  • CircleCI で CI をできるようにした

さらに Piculet は --apply オプションで実際に反映することもできる.次回は --apply を試す!

関連記事

Mackerel の監視ルールを CircleCI で CI する話を前に書いた.インフラ系の設定値をバージョン管理して,プルリクエストを通してレビューできる開発プロセスはとにかく便利!

kakakakakku.hatenablog.com