tjinjin's blog

インフラ要素多めの個人メモ

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はデーモン化しておいた方が取り扱いがしやすいと思いましたので合わせてやっています。リポジトリは下記です。

github.com

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

準備

サーバを作成する

サーバ側リポジトリで下記コマンドを実施します。

$ 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の仕組みを考える
  • 運用考えた仕組みを整える。通知なり、監視なり。
  • 何らかの影響で仕組みがうまく動かなかった時の代替手段の検討。
  • 細かい部分の調整

参考

いろいろ参考にしました!