SREチームの @siroken3 です。
以前、メルカリでリリース手段としてChatOpsを採用していることを本ブログで紹介しました。今回は内部で使っている技術の一部を紹介したいと思います。
tl;dr
- メルカリではデプロイにAnsible使ってる
- 毎日デプロイしつつサーバが増加/入れ替え激しいと心が削れる
- MackerelのAPIとAnsibleを組み合わせたらハッピーになった
Insideデプロイ
メルカリではデプロイ用のサーバでSlack Botが働いており、デプロイの事前要件を満たしているか確認した後、大まかには以下の処理を実行しています。
- GitHubからデプロイ対象ソースの取得
- composer install / gulp などのビルド処理
- 対象サーバにrsyncでデプロイ
これらの処理は構成管理ツールであるAnsibleを使用しています。デプロイのためのSlack Bot *1はこのAnsibleを内部で呼び出しており、後述するplaybookの単位でデプロイを実行しています。
今回はデプロイ処理のうち対象サーバにrsyncでデプロイにフォーカスし、当時の課題とMackerel+スクリプトで解決した話の紹介になります。なお残念ながらAnsible自身については分量の関係上詳細には立ち入れませんのでここでは簡単な説明にとどめたいと思います。
Ansible playbook
Ansibleではいくつかのタスクをまとめて複数のサーバに対して実行するためのスクリプトを YAML 形式で記述します。これをplaybookと呼んでいます。下記は playbook の例です。
- hosts: role-application tasks: - name: deploy synchronize: src: "/var/www/vhosts/api/current/" dest: "/var/www/vhosts/api/current/"
上記は role-application
で定義としてされたサーバ群を対象にして Ansibleを実行しているデプロイ用サーバと /var/www/vhosts/api/current ディレクトリのファイルを同期することを意味しています。
Ansibleではサーバ群のことをInventoryと呼んでおり、多様な形式で定義することができます。下記はINI形式でrole-applicationというサーバ群にapp[1-2]が含まれていることを定義している例です。
[role-application] app1 app2
デプロイ用のサーバで以下のコマンドを実行することでplaybook.ymlに記述されたタスクが実行されます。
$ ansible-playbook -i ./hosts ./playbook.yml
上記は INI形式のInventoryを hosts というファイル名で保存、playbookを playbook.yml で作成した例です。Botは上記のようなスクリプトを子プロセスとして実行しています。
当時の課題
当初Inventory管理は、日本のメルカリではINI形式のファイル、USAのMercariでは Ansibleに同梱されている ec2.py というAWSのEC2インスタンスのタグから動的にInventory情報を取得することができるDynamic Inventory スクリプト*2を使っていました。
しかし、従来の方法だとサーバの増設、入れ替え毎にINI形式のファイルを更新したり、ec2のタグを設定しなおしたりする必要がありました。加えてサーバが増えたり入れ替わってる最中もBotのGoBoldさんはデプロイを繰り返しています。
結果「セットアップしたサーバにデプロイされない」逆に「まだセットアップ中のサーバにデプロイされてAnsible playbook実行失敗した」問題が発生することがありました。
これはAnsible playbook実行失敗して無邪気に叫ぶ GoBoldさん。人間はBotに気を使ってすり減る日々。
そこで以前から弊社でサーバの管理や監視に利用しているMackerelのAPIを使ってデプロイ対象サーバの管理を自動化しました。
Mackerel
Mackerel とは株式会社はてなで開発されているサーバ管理・監視サービスです。
MackerelはGo製のmackerel-agentを監視対象サーバにインストールするだけで監視対象になるので導入が容易です。弊社でもサーバのセットアップ時*3にmackerel-agentのインストールを実施しています。
さらにMackerelはAPIが用意されており、監視対象サーバの一覧を取得できます。またサーバに対してRoleでグループ化できます。
ec2.py もそうですがAnsibleはInventoryとして静的なファイル以外にもJSON形式でインベントリ情報を標準出力するプログラムであればなんでもインベントリファイルとして指定できます。Mackerelが監視しているサーバ一覧をAPIで取得するDynamic Inventoryスクリプトを自作すればよさそうです。
Dynamic Inventoryスクリプト自作
AnsibleがInventoryとして認識するスクリプトについて少し解説します。前述のINIファイルと同じ情報を表現しているのは以下のJSONになります。スクリプトはこのJSONを標準出力に出力すればよいのです。
{ "_meta": { "hostvars": { "app1": { "ansible_host": "10.0.0.10", "name": "app1" }, "app2": { "ansible_host": "10.0.0.11", "name": "app2" } } }, "role-application": [ "app1", "app2" ] }
また、Mackerelのホスト一覧を取得するAPIの実行結果として以下のように1サーバあたり1つのJSONオブジェクトとして情報が得られます。(もっとたくさんの情報が得られますが、ここではかなり省略しています)
{ name: 'app1', meta: { 'agent-name': 'mackerel-agent/0.35.0 (Revision 7e8ca7b)', 'agent-revision': '7e8ca7b', 'agent-version': '0.35.0', block_device: { ... }, cpu: [ ... ], filesystem: { '/dev/sda1': [Object], '/dev/sda3': [Object], tmpfs: [Object] }, kernel: { ... }, memory: { ... }}, type: 'unknown', status: 'working', memo: '', isRetired: false, id: 'xxxxxxxxxxx', createdAt: 1474589195, displayName: null, roles: { mercari: [ 'role-application' ] }, interfaces: [ { macAddress: 'xx:xx:xx:xx:xx:xx', name: 'eth1', ipv4Addresses: [Object], ipAddress: '10.0.0.10' } ] } { name: 'app2', meta: { ...
Mackerelからのレスポンスから name
, interfaces[].ipAddress
を拾ってroles
でグループ化するスクリプトが自作したDynamic Inventoryになります。
メルカリではこのスクリプトをNode.jsで作成しましたが、開発言語は問わないのでPerlやPythonを使って作成することもできます。*4
まとめ
Mackerelを利用したDynamic Inventoryを導入した結果、サーバのセットアップが終われば自動的に監視対象かつデプロイ対象にさせることができました。また、Mackerelで該当サーバをMackerelのコントロールパネルからmaintenenceモードにすることでサーバ一覧に入らないようにする工夫もでき(Mackerelレスポンスのstatus
で判定)、デプロイさせたくないサーバがあっても自然に対応できます。最後に喜びの声をお伝えしたいと思います。
以上、監視対象とデプロイ対象のサーバを揃えるとQoLが向上するお話でした。