この記事は Python Advent Calendar 2015 18 日目の記事です。
構成管理とデプロイツール
構成管理やアプリケーションのデプロイに使うツールは capistrano や fabric のようなsshによるシェルのリモート実行がベースのものと、 Chef や Ansible といったDSLベースでミドルウェアのビルド等ができたり何度繰り返し実行しても同じ状態になる(冪等性というらしいです)ことを目指したものがあります。
それぞれ特徴がありますが、わたしはPython使いなのでFabricかAnsibleが有力の候補です( capistrano や Chef は Ruby 製)。
Ansibleなの?Fabricなの?
Ansible、いろいろ良いです。良いのですが、DSLが好きでないのと、今日はCentOS・明日はUbuntu、のようにいろいろな環境にミドルウェアを入れることもないので、とくにそのあたりの抽象化を必要としていません。
Fabric、shellをリモートで実行しつつPythonの便利機能が使える。良いんです。ただ、徐々にスクリプトが大きくなっていきます。
実際の環境構築やデプロイはどうしてる?
イミュータブルインフラストラクチャー的考えでAWSにインスタンスを起動する場合、私は環境構築・デプロイスクリプトを次のようにフェーズに分けます。
Constructionフェーズ
OSのベースAMIからインスタンスを起動し、起動したインスタンスにプロジェクトで共通に使うミドルウェアなどをインストールし、AMIとしてフリーズします。 各種セキュリティフィックスやミドルウェアのバージョンアップ時にはConstructionフェーズからAMIを作り直します。
Deploymentフェーズ
Constructionで作成したAMIを用いて、各種ロールのソースコードなどを配備します。ステージング環境やプロダクション環境で違いのある設定ファイルなどはこの時点では配備しません。この段階でソースコードがAMIとしてフリーズされます。
Releaseフェーズ
Deployフェーズで作成したAMIを用いて、ステージング環境やプロダクション環境にインスタンスを起動します。環境に依存する設定ファイルや、環境によって違うファイルを配備します。重要なのは、ステージング環境を起動したAMIで必要な確認を行った後、同じAMIを用いてプロダクション環境を作ることです。ブルーグリーンデプロイメントを行います(不具合があったらできるだけ容易に前のバージョンのインスタンスに戻せるように進めます)。
staging.py、production.pyなどに同じ名前のタスクを定義して fabfile/__init__.py にモジュールを import して使うと差異を確認しやすいと思います。
Adhocフェーズ
リリースされている各環境に対して、Adhocに変更を加えます。本来はDeploymentフェーズから行うべきですが、Hotfix的なものに関してはAdhocに行いたい操作もあります。
各フェーズに関してfabfileを分けたいと思うのは、Fabricユーザとしては自然だと思います。しかし、Fabricでfabfileを分けると、共通モジュールの管理に困ります。別の独立したモジュールとしてPYTHONPATHに加え、都度更新がないかを確認しながら使うことになります。
Ansibleの再発明っぽいストラクチャーをFabricで使う
どのフェーズからでも共通で使えるcommonというフォルダを提供。
. ├── common │ ├── __init__.py │ ├── files │ ├── functions │ │ ├── common_multiple_fabric_environment.py │ ├── templates │ │ └── sample_a.txt │ └── vars.py ├── construction │ ├── fabfile │ │ ├── __init__.py │ ├── templates │ └── vars.py ├── deployment │ ├── fabfile │ │ ├── __init__.py │ ├── templates │ └── vars.py └── release ├── fabfile │ ├── __init__.py │ ├── staging.py │ ├── production.py ├── templates └── vars.py設定ファイルがyamlだとプログラマブルでないので、辞書ライクでありつつドットシンタックスでアクセスできる設定オブジェクトを提供します。設定オブジェクトはcommon/vars.pyをベースとして、各フェーズで追加・上書きできる仕組みにしています。
jinja2テンプレートをレンダリングしつつ各サーバへアップロードできる機能でも、指定のテンプレートが各フェーズのtemplatesディレクトリを起点に探して発見できない場合にはcommonのtemplatesディレクトリからも探す仕組みにしています。
フェーズを分けたけど串刺しで実施したい
fabfileが肥大化するので、フェーズを分けられるように構造を作りました。しかし、実際には複数のフェーズを一気に実行したいことがあります。毎回長大なfabコマンドを && でつないで実行したりするのも少し面倒です。
そこで、通常の1fabfile内複数タスクの実行をjobと呼ぶこととし、複数のフェーズにまたがるjobの連なりをjobsetと呼ぶこととしました。Oozappaでの実行単位はjobsetです(1jobのjobsetでも良い)。
記録をとりたい
いつどのjobsetを実施して、その際にはどういったリビジョンのファイルが使われ、何が行われたかはログを保存すればわかります。Oozappaは、ファイルへの出力とファイルパスや実施日時、結果をデータベースに保存します。
手離れしたい
デプロイには破壊的なものと破壊的で無いものがあります。例えば、ティザーサイトなどは多少の問題があったとしてもすぐに修正してやり直せばこと無きを得る場合が多く、デプロイ職人がスタンバッておく必要がないかもしれません。OozappaはWebUIを提供しますので、該当のjobsetを指定して誰でも簡単に実行できます。
ティザーサイトなどはmasterにマージしたら即配信とはいかないビジネス上の事情や、逆にチャンスが転がっている場合にはエンジニアがいなくても配信したいといった事情があるでしょう。そんな場合でもストレスなく配信を行え、エンジニアはいつでもWebUIでjobsetの実施ログが確認できるので、きっと便利です(実際1年以上運用している感じ、便利っぽい評価を受けています)。
ちょっとだれにでもは実行させたく無いけれどjobsetとしてまとめて実行したいし記録も取りたい、という要求にも答えます。jobsetをCLIからのみ実行できるように制限できます。いつ実施したかや、ログの確認はWebUIからできます。
動くの?
1年以上運用されていて、そこそこの頻度で使われているのできっと動きます。動かない場合にはPRお待ちしております。
お礼
退職に関するエントリでウィッシュリストを貼り付けたところ、本当に贈り物が届きました。届くと思っていなくて、普通に使っているウィッシュリストを貼ってしまいました :-)
ID書くといやらしいので書きません。みんなありがとーーーー!!!1
送りたいものが見当たらなくてGift Cardを送ってきてくれた若者までいて恐縮してます