Jenkinsの自動デプロイにAnsibleを使ってみた
ありがたいことに、以前のJenkins自動デプロイ記事がそれなりに多くの反応をいただきまして、冷静に見直してみたのですが、ちょっとデプロイ処理が雑だなと。
最近ではデプロイをやるにも、Capistranoやfabricなどのツールがあり、多様化するデプロイ要件に柔軟に対応できるような工夫が施されています。これらのツールを用いることで、シェルスクリプトで記述されたぐちゃぐちゃなオレオレデプロイを回避できたり、デプロイ失敗時のロールバックなどがしやすくなるメリットがあります。
現在開発しているアプリでは、git pullだけでデプロイできるようにしてあるので、前回の記事のようにJenkinsのジョブにシェルスクリプト直書きでもいいのですが、
- デプロイ先の変更やスケールアウトに対応しにくい
- 認証鍵の場所をコマンドで直書きするのが、個人的にちょっと抵抗ある
- 書き方がスマートじゃない ←重要
という問題があり、後々のことも考えてデプロイツールの導入を決心しました。
今回はAnsibleを使い、GitBucketで管理されているリポジトリへのプッシュをトリガにして、Jenkinsで自動デプロイするまでの流れをまとめてみます。なお、前回のシェルスクリプトによる自動デプロイは以下の記事です。
Ansibleとは
Ansibleとは、ChefやPuppetと並ぶ構成管理ツールです。今話題の"Infrastructure as code"と絡めて話題にあがることが多く、仮想サーバのセッティングなどを自動的に、かつ再利用可能に実現できることが最大のメリットです。
最近はChefが主流になっている印象がありますが、やれることが幅広い分、学習コストが高いと言われるため、より簡素なAnsibleも根強い人気があります。個人的にはサーバにPythonさえ入っていれば、どのサーバにも利用できるのが嬉しいです。
Ansibleは構成管理ツールですので、デプロイツールとは目的が微妙に異なるのですが、上記に挙げているシェルスクリプトによるデプロイの問題を解決できるだけのスペックを秘めていることと、将来的にサーバの構成管理をやるとき、Ansibleの経験があると役立ちそう(結局ChefやOpsWorkを使うことになったとしても)だと思ったことから、今回の採用を決定しました。
サーバ構成
構成自体は前記事と変わらず。差分はJenkinsがAnsibleを使ってデプロイするくらいです。
ちなみに、SecurityGroupに注目して見ると、本番環境はELBからのHTTPとCIサーバからのSSHのみ許可すればよく、CIサーバは22,80,8080,8090番ポートを開放しなくてはいけないものの、すべてのポートはIP制限を掛けることができます。せいぜいGitBucketから本番環境へのプルを実現するために、SecutiryGroupを指定して8090番ポートを開けるだけなので、セキュアな環境が実現できているといえるでしょう。
使用しているバージョン
Ansibleの実行にはPythonが必要ですが、Amazon Linux AMI 2014.09には既にAnsibleを動作させるのに十分最新なPythonがインストールされているため、そのまま利用します。各サーバで用いるバージョンは以下の通りです。
CIサーバ
- Amazon Linux AMI 2014.09
- Python v2.6.9
- Ansible v1.8.2
- Git v2.1.0
- Java v1.7.0_71
- Jenkins v1.596
- Tomcat v7.0.57
- GitBucket v2.7
本番サーバ
Ansibleのインストール
さっそくAnsibleをインストールします。CIサーバにログインしてpipでインストール。
# easy_install pip # pip install ansible
無事インストールできたか、バージョン確認をしてみましょう。
$ ansible --version /usr/lib64/python2.6/site-packages/Crypto/Util/number.py:57: PowmInsecureWarning: Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability. _warn("Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability.", PowmInsecureWarning) ansible 1.8.2 configured module search path = None
む、Warningが出ていますね。
ちょっとググってみましたが、Pythonの内部パッケージが要求するライブラリのバージョンが古いことを指摘しているようです。動作自体は問題ないことと、現状Amazonから提供されているリポジトリには該当の最新バージョンがないことから、とりあえずこのまま進めていくことにしましょう。
参考:Using Ansible on AWS – EC2インスタンスを作成する | Developers.IO
configurationファイルの作成
まず、Ansibleの基本設定を行うために、configurationファイルを作成します。
configurationファイルは複数用意でき、カレントのconfigurationファイルが優先的に適用されるなどのルールがあるのですが、とりあえず全般的に適用される/etc/ansible/ansible.cfgを以下のように作成します。
[defaults] host_key_checking=False inventory=/etc/ansible/hosts private_key_file=/var/lib/jenkins/.ssh/mykey.pem
[defaults]でデフォルトの設定を記述できます。項目は以下の通りです。
host_key_checking
接続先の確認有無の設定です。Falseにすることでknown_hostsに追加されていない接続先への接続時にもエラーが発生しなくなりますが、セキュリティ的にはバットプラクティスです。今回は同じネットワーク内での接続に留めるため、Falseにすることを許しています。
inventory
後に作成するinventoryファイルのパスです。基本的には/etc/ansible/hostsを指定します。
inventoryファイルの作成
次に、Ansibleで接続する接続先を設定するinventoryファイルを作成します。
/etc/ansible/hostsを以下のように作成します。
[develop-server]
192.168.0.3
[developer-server]はグループ名の明示です。接続先をまとめてグループとして定義できます。今回は開発環境(兼本番環境)にssh接続するため、develop-serverと名前をつけました。
ここでポイントなのは接続先のインスタンスをプライベートIPで指定していることです。パブリックIPは再起動する度に変更されますが、プライベートIPは変更されません。加えて、プライベートIPでの通信は同一AZ内ならば通信料が無料なので、明示的にプライベートIPを指定したほうがお得です。
Playbookの作成
さて、これで設定関係は完了ですので、後は接続先に対する操作を記述するPlaybookを作成します。/var/lib/jenkins/git-deploy.ymlを以下のように作成します。
- hosts: develop-server user: ec2-user vars: app_dir: /home/ec2-user/apps branch: master tasks: - git: repo=http://ci.example.com:8090/gitbucket/git/watass/myapp.git dest={{ app_dir }} version={{ branch }} update=yes
Ansibleで使用するPlaybookはYAML形式で記述できます。上記はgitでデプロイするためのPlaybookで、詳細な項目は以下の通りです。
hosts
接続先のグループを指定します。inventoryファイルのdevelop-serverグループを指定します。
user
接続時に使用するユーザ名です。デフォルトのec2-userを使用します。
vars
Playbook内で使用する変数の定義です。Playbookが肥大化しても管理しやすくなります。
Ansibleによるデプロイテスト
さて、それでは作成したPlaybookを使用してコマンドベースでデプロイのテストをやってみましょう。設定情報はconfigurationファイルにすべて記載してありますので、ansible-playbookコマンドでPlaybookを指定するだけでOKです。
$ ansible-playbook /var/lib/jenkins/git-deploy.yml /usr/lib64/python2.6/site-packages/Crypto/Util/number.py:57: PowmInsecureWarning: Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability. _warn("Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability.", PowmInsecureWarning) PLAY [develop-server] ********************************************************* GATHERING FACTS *************************************************************** ok: [192.168.0.3] TASK: [git repo=http://ci.example.com:8090/gitbucket/git/watass/myapp.git dest={{ app_dir }} version={{ branch }} update=yes] *** changed: [192.168.0.3] PLAY RECAP ******************************************************************** 192.168.0.3 : ok=2 changed=1 unreachable=0 failed=0
okが2つ、changedが1つ発生しました。SSH接続とタスクの実行が成功したことと、タスクによって1つの変更が発生したことを示しています。実際にブラウザからアプリケーションにアクセスしてみると、最新のバージョンに更新されていることがわかりますね。
Jenkinsのジョブ設定
Ansibleのコマンドベースでデプロイができることが確認できましたので、後はJenkinsのジョブとして実行するだけです。フリースタイルプロジェクトのビルドを作成し、ビルド処理としてシェルの実行でansible-playbookコマンドを記述します。URLのリクエストベースでジョブを実行する予定なので、申し訳程度にトークンを指定しておくとよいでしょう。
ジョブを作成したら、手動でジョブを実行してデプロイできるか確認しておきましょう。
GitBucketのWebhook設定
最後に、リポジトリの更新をトリガにJenkinsのジョブを走らせるために、GitBucketのWebhookを設定します。リポジトリのページからSetting→Service HooksでJenkinsのジョブURLを追加しましょう。
今回は同一インスタンス上にJenkinsとGitBucketが乗っているので、ジョブURLにlocalhostが使用できます。Webhookはジョブを外部から叩けるようにしないといけないことがセキュリティ的にネックになりがちですが、GitBucketのようにローカルで展開できると、localhost指定で外に穴を開けなくて済むのでGoodです。
ジョブ名を"gitbucket-auto-deploy"に指定すると、以下のようなURLになります。トークンはジョブ作成時に設定したものです。
http://localhost:8080/job/gitbucket-auto-deploy/build?token=[トークン]&cause=git-push
ここで、URLからジョブが実行できるか確認しておきましょう。Jenkinsのグローバルセキュリティを厳しく設定していると、ビルドできないことがあります。匿名ユーザにビルド権限を与えるだけでなく、ジョブの閲覧権限なども設定しておかないとダメなようです。ログアウト時に、URLから実行できるか確認しておくといいでしょう。
匿名ユーザがログインなしでジョブを実行できることに不安はありますが、インスタンス自体にIP制限のSecurityGroopをかけていますし、ジョブの編集や削除などの権限は一切ないので、セキュリティ的にそこまでまずい、というわけではないと思います。
自動デプロイを試してみる
これで準備完了です。早速ローカルからリポジトリにプッシュして、自動デプロイできるか試してみましょう。Jenkinsのジョブの実行履歴を見てみると・・・
お、成功したようですね。
コンソール出力を見てみましょう。
Started by remote host 127.0.0.1 with note: git-push Building in workspace /var/lib/jenkins/workspace/gitbucket-auto-deploy [gitbucket-auto-deploy] $ /bin/sh -xe /tmp/hudson5176095097434713106.sh + ansible-playbook /var/lib/jenkins/git-deploy.yml /usr/lib64/python2.6/site-packages/Crypto/Util/number.py:57: PowmInsecureWarning: Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability. _warn("Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability.", PowmInsecureWarning) PLAY [develop-server] ********************************************************* GATHERING FACTS *************************************************************** ok: [192.168.0.3] TASK: [git repo=http://ci.example.com:8090/gitbucket/git/watass/myapp.git dest={{ app_dir }} version={{ branch }} update=yes] *** changed: [192.168.0.3] PLAY RECAP ******************************************************************** 192.168.0.3 : ok=2 changed=1 unreachable=0 failed=0 Finished: SUCCESS
成功していますね。ブラウザから確認しても最新版になっていると思います。
以上でAnsibleを使用したデプロイ自動化の完了です。
まとめ
おまけ:SourceTreeの認証を自動化する
さて、自動デプロイできるようになったので、どんどんコード書いてプッシュしましょう。
(#^ω^)ビキビキ
GitHubのアカウントがSourceTree登録することで認証自動化ができたので、同じ要領で、どっかで設定できるんじゃないかと考えたのですが、残念ながらダメでした。とはいえ、これはあまりにも面倒臭い。自動デプロイの恩恵が薄まってしまいます。
Mac OS XではKeychainを使うことでこの問題が解決できます。git本体にMacのkeychainを使用する機能がありますので、ターミナルから以下のコマンドを叩いてみましょう。
git credential-osxkeychain
git config --global credential.helper osxkeychain
これで一度認証を行うことで、認証をスキップできます。素晴らしい!助かりました。
さて、今回はAnsibleで一元管理できるようになりましたが、Auto Scalingのように新しくインスタンスが作られるようなパターンには対応できていません。
そのようなケースでは、きっとCodeDeployのようなAWSから提供されているデプロイサービスを利用するべきだと思いますが、AWSに依存しないプラクティスを理解することもきっと意義が深いと思います。でも、CodeDeployの東京リージョン開放は早くお願いします(切実)
参考
【AWS】ansibleでEC2インスタンスにプロビジョニングする【ansible】 - くどはむと猫の窓
# AWS上でのAnsible使用イメージがつかめました。ありがとうございます。
SourceTree + Git + Ansible + Jenkns で 継続的デリバリお試し環境をつくってみた(その2) | My diary for @halchiyo
# 構成が似ているため、全体像のイメージを考えるときに大変役立ちました。
はじめてAnsibleを使う人が知っておきたい7つのモジュール | 株式会社インフィニットループ技術ブログ
# Ansibleを利用するときによく使うモジュールが紹介されていて非常に有益です。
Ansibleのドキュメントを読んでみたメモ - Qiita
# Ansibleで色々できることが紹介されています。公式ドキュメント読みにくいですよね。
Ansible Documentation — Ansible Documentation
# やっぱり最後は公式ドキュメントですね。よく使うモジュール系は読んでおくといいと思います。