consul + stertcherを使ったpull型デプロイを試している話
About
デプロイ改善したいなーと思っていて、前からやってみたかったpull型デプロイを試している話です。まだVMです。Railsアプリを想定しています。
push型のデプロイ(Capistrano)
詳しくはわからないですが、RailsアプリのデプロイはCapistranoが行う事が多かったのではないでしょうか。ざっくりとした流れは下記のようになると思います。
ワークフロー
- ローカルマシンからタスク実行
- 各サーバにログインし、
git clone
bundle install
する - もろもろした後にunicornとかを起動させる
- サーバ台数分行う
問題点
- ローカルマシンから各サーバにSSHログインできないといけない。台数が増えてきたら大変
- 各サーバで
bundle install
している。ネットワークリソースの無駄だし、bundle install
時に必要なパッケージをサーバに入れないといけない。 - 時間かかる。
pull型デプロイ(stretcher + consul)
次に今回試すpull型のデプロイです。
ワークフロー
- ローカルマシンからbuildサーバに対し命令する。
- buildサーバで
git clone
してきて、bundle install
する。 - その後インストールされたgem毎tarball化する
- stretcherで利用するmanifestファイルを作成する
- 出来たtarballとmanifestファイルをS3にアップロードする
- アップロード後、どこかのサーバ1台に
consul event
を発火する - アプリサーバで動いているconsulがeventを検知し、S3からmanifestファイルを取得する
- その設定を読み込み指定されたtarballをS3から取得後、アプリの展開・初期処理を実行する
ステップだけ見ると大変そうに思いますが、後述のcapistranoのタスクを実行するだけです。並列実行もできたり、bundle install
を各サーバで実行しなくて良いので、その分速いと思います。
stretcherを利用する上で必要なサーバの設定
今回cookbook化するに当たって、下記が必要でした。
- consulをデーモン化する
consul watch
をデーモン化する(role毎に必要)- unicornをデーモン化する
- 各サーバで
aws cli
が利用できる
デーモン化はCentOS7系を利用したのでsystemdに任せました。unicornはデーモン化しておいた方が取り扱いがしやすいと思いましたので合わせてやっています。リポジトリは下記です。
consul watch/eventの関係性
consulを使う上でこのあたりがわかりにくかったので整理しておきます。consul watchは状態を監視する仕組みです。変更を検知すると特定の処理を実行できます。今回は/usr/local/bin/consul watch -type event -name <%= @role %> stretcher
の用にtypeにeventを指定しているので、consul event
を監視していることになります。consul watchでAというeventを監視しておき、consul event -name A
とすることでeventを発行することで、特定の処理を実施させることができます。
実際に体験してみよう
わかりにくいので、実際に試せる環境を作りました。
構成
- app1 + buildサーバ
- app2
- app3
準備
- リポジトリをcloneする
- 各リポジトリでbundle installしておく
- credentialsを発行する(s3に読み書きできる権限があればよいです)
- s3にバケットを用意する
- 作成したバケット名をdeploy.rbに書く。
サーバを作成する
サーバ側リポジトリで下記コマンドを実施します。
$ vagrant up $ bundle exec knife solo bootstrap vagrant@192.168.34.42 -i ~/.vagrant.d/insecure_private_key $ bundle exec knife solo bootstrap vagrant@192.168.34.43 -i ~/.vagrant.d/insecure_private_key $ bundle exec knife solo bootstrap vagrant@192.168.34.44 -i ~/.vagrant.d/insecure_private_key
これでサーバができました。
consul clusterを作成する
すでにconsulは起動していますが、この時点ではclusterになっていません。clusterを作成します。
$ v ssh app1 Last login: Wed Jan 20 12:24:28 2016 from 192.168.34.1 [vagrant@localhost ~]$ consul members Node Address Status Type Build Protocol DC 192.168.34.42 192.168.34.42:8301 alive server 0.6.0 2 dc1 [vagrant@localhost ~]$ consul join 192.168.34.43 Successfully joined cluster by contacting 1 nodes. [vagrant@localhost ~]$ consul members Node Address Status Type Build Protocol DC 192.168.34.42 192.168.34.42:8301 alive server 0.6.0 2 dc1 192.168.34.43 192.168.34.43:8301 alive server 0.6.0 2 dc1 [vagrant@localhost ~]$ consul join 192.168.34.44 Successfully joined cluster by contacting 1 nodes. [vagrant@localhost ~]$ consul members Node Address Status Type Build Protocol DC 192.168.34.42 192.168.34.42:8301 alive server 0.6.0 2 dc1 192.168.34.43 192.168.34.43:8301 alive server 0.6.0 2 dc1 192.168.34.44 192.168.34.44:8301 alive server 0.6.0 2 dc1
consul join
でサーバをクラスタに参加させます。どちらのサーバで行ってもよいです。今の設定は3台サーバがないとclusterが組めない設定になっているので3台必要です。
この時点でclusterになっているので試しにconsul exec
を発行します
[vagrant@localhost ~]$ consul exec hostname 192.168.34.42: localhost.localdomain 192.168.34.42: ==> 192.168.34.42: finished with exit code 0 192.168.34.44: localhost.localdomain 192.168.34.44: ==> 192.168.34.44: finished with exit code 0 192.168.34.43: localhost.localdomain 192.168.34.43: ==> 192.168.34.43: finished with exit code 0 3 / 3 node(s) completed / acknowledged
全てのサーバで実行した結果を取得できました。
awsの設定をする
s3にファイルをアップロードするために設定が必要です。
- /root/.aws/config
[default] region=northeast-1
- /root/.aws/credentials
[default] aws_access_key_id = aws_secret_access_key =
VMなのでこの作業が発生していますが、AWS上で動かす場合はIAMロールを割り当てることで不要になります。
アプリケーションをtarballで固める
$ bundle exec cap development stretcher:archive_project
これはgemを使っています
pepabo/capistrano-stretcher - Ruby - GitHub
buildサーバの/tmp以下でアプリをcloneしてきて、bundle install
した後にtarballにしてアップロードします。その語、manifestファイルをerbを利用して作成します。
default: &default pre: - success: - failure: - cat >> /tmp/failure web: <<: *default post: - ln -nfs <%= fetch(:deploy_to) %>/shared/data <%= fetch(:deploy_to) %>/current/data - chown app. <%= fetch(:deploy_to) %>/current/ -R - systemctl reload unicorn || systemctl start unicorn - bundle exec rake db:create - bundle exec rake db:migrate
実際に出来たmanifestは下記のようになっています。
src: s3://tjinjin-backend-stretcher/assets/rails-application-20160123090336.tgz checksum: dc2ef7595691ef6b8a654eaaf1964038cc99223395b79bc7b16c4a3dcddf7aec dest: /var/www/stretcher_app/releases/20160123090336 commands: pre: - post: - ln -nfs /var/www/stretcher_app/releases/20160123090336 /var/www/stretcher_app/current - rm -rf /var/www/stretcher_app/current/log - ln -nfs /var/www/stretcher_app/shared/log /var/www/stretcher_app/current/log - mkdir -p /var/www/stretcher_app/current/tmp - ln -nfs /var/www/stretcher_app/shared/pids /var/www/stretcher_app/current/tmp/pids - ln -nfs /var/www/stretcher_app/shared/data /var/www/stretcher_app/current/data - chown app. /var/www/stretcher_app/current/ -R - systemctl reload unicorn || systemctl start unicorn - bundle exec rake db:create - bundle exec rake db:migrate success: - failure: - cat >> /tmp/failure excludes: - "*.pid" - "*.socket"
src:
に書いてあるtarballがデプロイされるアプリです。ここのpathを変更してできればアプリのロールバックも出来そうです。あとはpreやpostなどでデプロイ前後の処理を指定できます。successでデプロイ完了後にslack通知とかしてあげるとよさそうです。
post部分でchownしていたり無駄な処理入っている気がしますが、もう少し精査していけばキレイになると思います。unicornの部分については、最初にサーバを作成辞退ではアプリがなくunicornが起動に失敗してしまうため、reloadを失敗したときのみstartするようにしています。
アプリケーションをデプロイする
$ bundle exec cap development stretcher:kick_stretcher
capistranoがbuildサーバに対してeventを発行しています。発行されると各サーバにeventが伝播され、consul watch
が検知してeventがそれぞれのサーバで実行されます。体感としてはすぐに処理が行われました。
今後の構想
- AWSで動かす
- rollbackの仕組みを考える
- 運用考えた仕組みを整える。通知なり、監視なり。
- 何らかの影響で仕組みがうまく動かなかった時の代替手段の検討。
- 細かい部分の調整
参考
いろいろ参考にしました!