JenkinsでGitHubのプッシュ時に自動デプロイする

f:id:watass:20150111232633p:plain

Gitでバージョン管理をしていると、本番サーバにデプロイする際に、クライアントでpush、そして本番サーバにログインしてgit pull、ってやるのは面倒臭いですよね。そんな不毛な操作は自動化するのがプログラマとしては当然です。
GitHub上のリポジトリで、デプロイの自動化をやるにはWebhookやTravis CI、JenkinsなどのCIツールとの連携を考えます。選択肢は多々あり、それぞれにメリット、デメリットはありますが、今回は後々、FuelPHPユニットテスト自動化までを見据えて、Jenkinsによるデプロイ自動化を試してみようと思います。


サーバ構成イメージ

今回、Jenkinsを導入するにあたって、専用のEC2インスタンスを立ち上げます。このインスタンスをCIサーバとして利用していきます。
GitHubリポジトリへプッシュされたとき、GitHubはJenkinsサーバへ通知を送り、Jenkinsは本番サーバへログインして、git pullコマンドを実行するジョブを発行します。図にすると以下の感じです。

f:id:watass:20150112003127p:plain

上記の構成を踏まえると、JenkinsではGitHubのhookを受け取れるトリガの設定をし、ビルドではログイン、git pullを行うシェルを実行する、という具体的なイメージができてくると思います。それらを実現するために、Jenkinsの導入からデプロイテスト、Githubのhookをトリガにする設定を順に進めていきます。


開発環境について

今回はクライアントPCの操作はプッシュだけですので、Jenkinsサーバと本番サーバで用いるそれぞれのアプリケーションのバージョンについて明記しておきます。

・Jenkinsサーバ
OS : Amazon Linux AMI 2014.09
Java : version 1.7.0_71
Jenkins : version 1.596
Git : version 2.1.0

・本番サーバ
OS : Amazon Linux AMI 2014.09
Git : version 2.1.0


Jenkinsサーバを起動してインストール

AWSにログインし、早速Jenkinsサーバを立てていきます。EC2インスタンスを起動しましょう。現状ではJenkinsサーバにそれほどのスペックは必要ないので、無料枠レベルの最小スペックで問題ないはずです。なお、VPCはパブリックサブネットであればアベイラビリティゾーンはどちらでも大丈夫です。
注意するポイントはSecurityGroupの設定です。Jenkinsはデフォルトで8080ポートを使用しますので、8080ポートは開放しておきましょう。自分のIPのみを許可するのが理想的ですが、GitHubからJenkinsサーバへの通知を許可しないといけないので、すべてのトラフィックを許可するようにしました。もちろん、GitHubのIPを指定してもいいのですが、定期的に変更されることもあるようなので、その辺りも踏まえつつ考慮してみてください。
また、Jenkinsサーバから本番サーバへログインできる必要がありますので、本番サーバのSecurityGroupはJenkinsサーバからのSSHを許可するようにしてください。

インスタンスが起動したら、Jenkinsのインストールをします。Jenkinsの起動にはJavaが必要になるのですが、Amazon Linux 2014.09では既にJavaが入っていますので、確認しておきましょう。

$ java -version
java version "1.7.0_71"
OpenJDK Runtime Environment (amzn-2.5.3.1.49.amzn1-x86_64 u71-b14)
OpenJDK 64-Bit Server VM (build 24.65-b04, mixed mode)

問題なくインストールされていることが確認できたら、Jenkinsをyumリポジトリに追加して、yumからインストールします。以下のコマンドで実行できます。ついでにGitもインストールしておきましょう。

# wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
# rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
# yum install jenkins
# yum install git

インストールできたらJenkinsを起動しましょう。

# service jenkins start

これでhttp://[My IP]:8080へアクセスしてみると・・・

f:id:watass:20150111235514p:plain

おぉ!Jenkinsですね!
ちょっと感動。
これでインストール完了です。


Jenkinsの初期設定

インストールできたら、まずは初期設定を行います。現状では誰でもアクセスしてジョブを実行できる状態になっているので、セキュリティを有効化し、自分用の管理アカウントを作成します。その後、管理アカウントに管理者権限をすべて付与し、サインアップを無効化します。
具体的な流れについては以下のブログがとてもよくまとまっていてわかりやすいのでこちらを見ていただければと思います。


Jenkinsによるデプロイテスト

初期設定を終えたら、早速Jenkinsでデプロイをするテストをしてみましょう。
新規ジョブの作成を行います。フリースタイルプロジェクトとして作成します。

f:id:watass:20150112000052p:plain

ビルドの項目で「ビルド手順の追加」から「シェルの実行」を選択します。すると、窓が現れますので、そこへビルドとして実行したいシェルスクリプトを記述します。
Jenkinsサーバから本番サーバへログインし、git pullを行うには以下のようなシェルスクリプトを記述します。

ssh -i /var/lib/jenkins/.ssh/[鍵名] ec2-user@[My IP] "cd /home/ec2-user/welcome/ ; git checkout develop ; git pull origin develop"

以上のみを記述して、ジョブを保存します。
以下のように、作成したジョブ一覧が表示されますね。

f:id:watass:20150112000247p:plain

ただ、これで完了とはいきません。そもそもログインできませんので、本番サーバへログインするための秘密鍵をJenkinsサーバに配置しなくてはいけません。ユーザ名jenkinsはインストール時に作成されますので、以下の手順で鍵を配置します。

[サーバへログイン]
# cd /var/lib/jenkins
# mkdir .ssh
# chown -R jenkins:jenkins .ssh
[クライアントへ戻る]
$ scp -i [Jenkinsサーバログイン用鍵名] [本番サーバログイン用鍵名] ec2-user@[My IP]:/home/ec2-user
[サーバへログイン]
# cd /home/ec2-user
# mv [鍵名] /var/lib/jenkins/.ssh
# cd /var/lib/jenkins/.ssh
# chown -R jenkins:jenkins [鍵名]

これで準備完了です。FuelPHPのViewをさくっと編集してGitHubへプッシュしましょう。今回はGitHubアカウントを追加します。

<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome! <?php echo $name; ?> !</h1>
<h3>My Blog</h3>
<a href="http://sil.hatenablog.com/">This is my blog.</a>
<h3>My Twitter</h3>
<a href="https://twitter.com/wata_sil/">This is my twitter.</a>
<h3>My Github</h3>
<a href="https://github.com/wata727/welcome/">This is my github.</a>
</body>
</html>

プッシュを完了したら、Jenkinsのジョブへアクセスし、ビルド実行をクリック。
正常にジョブが完了したら、本番サーバへアクセスしましょう。

f:id:watass:20150112000341p:plain

反映されていますね。これでJenkinsサーバによるデプロイテストは完了です。


Jenkins側でGit、GitHubの設定

Jenkinsサーバから本番サーバへのデプロイができることが確認できましたので、後はこの操作をGitHubへのプッシュをトリガにして実行するだけです。
この操作を実現するためには、JenkinsのGit関連プラグインを導入する必要があります。Jenkinsの管理→プラグインの設定→利用可能から以下のプラグインを導入しましょう。

なお、GIT pluginはそのままフィルタかけても何故か出てきませんでしたが、Git server pluginをインストールする際にセットでついてきました。GitHub pluginはフィルタ検索でそのまま出てきます。
これらを導入し、Jenkinsの再起動を行えば設定完了です。


ジョブにGitHubリポジトリを設定

先ほど作成したジョブでも、新しく作成したジョブでもいいですが、これらのジョブにGitHubリポジトリの設定を行うことで、プッシュタイミングをトリガにすることができます。

f:id:watass:20150112000456p:plain

GitHub projectではGithubのURL、https://github.com/wata727/welcome/を、ソースコード管理ではGitを選択し、RepositoryURLにはSSH用のパス、git@github.com:wata727/welcome.gitを指定します。ブランチはdevelopを設定しましょう。

ビルド・トリガでは「Build when a change is pushed to GitHub」をクリックします。
ビルド処理の内容は先ほどのデプロイコマンドと同様です。


GitHubでJenkinsサーバを設定

以上でJenkins側の設定は完了ですが、肝心のGitHubはどこへ通知を送ればいいのかわからないままです。なので、GitHub側でJenkinsサーバを指定します。リポジトリのSettingへアクセスし、Webhooks & Servicesをクリックします。

f:id:watass:20150112000558p:plain

Add serviceでJenkins(GitHub plugin)を指定し、JenkinsサーバのURLの末尾に/github-webhook/をつけて設定します。これでGitHubリポジトリへのプッシュがあったことをJenkinsサーバへ通知します。


GitHubにプッシュしてテスト

これで準備は完了ですので、早速GitHubへプッシュしましょう。
またGitHubのアカウントを削除したViewをプッシュします。

<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome! <?php echo $name; ?> !</h1>
<h3>My Blog</h3>
<a href="http://sil.hatenablog.com/">This is my Blog.</a>
<h3>My Twitter</h3>
<a href="https://twitter.com/wata_sil/">This is my Twitter.</a>
</body>
</html>

これでJenkinsサーバを見てみると、ジョブが発生して完了しているようですね。

f:id:watass:20150112000744p:plain

ではサーバにアクセスしてみてみましょう。

f:id:watass:20150112000828p:plain

よいですねー、これで自動デプロイの完了です。
今回はdevelopブランチだけですが、同様の手順でmasterブランチにも適用できます。


まとめ

  • developブランチへのプッシュをwebhookでJenkinsサーバに通知
  • JenkinsはGitHub pluginでプッシュ通知時にビルド実行
  • sshで本番サーバへログインしてgit pullコマンドを実行


CodeDeployやCodePipelineに移行したいです

これでデプロイの自動化ができたので、後はガリガリコードを書くだけ・・・と思いきや、今回のCIサーバ構成には結構問題があります。
まず、Jenkinsサーバは再起動の度にIPが変更されるので、GitHubで設定したJenkins hook URLを再起動の度に設定しなおさなくてはいけません。Elastic IPをJenkinsサーバに付与すればいいわけですが、無駄にコストがかかってしまいます。他にも、本番サーバもIPが再起動の度に変わるとJenkinsのジョブにおけるビルドシェルで、SSHによる接続先IPも変更しなくてはいけません。実はこれはプライベートIPを使えばいいわけなんですけどね。
とはいえ、今後EC2をAutoScalingなどで自動拡張したりするインフラを整備した場合、IPが定期的に変更されるので、その度に設定し直すのも余計な手間になってしまいます。その辺りを踏まえると、AWSのサービス上でこれらのCIシステムを構築できるのが一番ですよね。GitHubはCodeCommitに、JenkinsはCodePipelineに、デプロイ用のシェルはCodeDeployに移行できるように準備しておきたいところです。早く東京リージョン開放されないかなー。

ところで、GitHubリポジトリホストに採用しましたが、どうせCIサーバを立てるんなら、CIサーバをGitサーバと兼用すればよかったんじゃ・・・と今更になって感じています。もしかしたら修正するかもしれません。
とりあえず、次は現状の構成でJenkinsを用いたFuelPHPユニットテスト自動化をやってみたいと思います。そのためにはユニットテストから勉強しないとなぁ・・・


参考にした記事

GitHubとJenkins連動 自動デプロイ 開発環境設定編 at ITエンジニアmegadreamsの開発日記
# 一番イメージに近いJenkinsの自動デプロイ構成をやってくださっていたので大変参考になりました。
Jenkinsのインストールと初期設定 - hidemium's blog
# Jenkinsのインストールから初期設定まで大変わかりやすくまとまっています。
githubのwebhookを使って、push時に自動でサーバーを更新させる - MANA-DOT
# GitHubのwebhookを使って自動デプロイする簡単な例が参考になります。
GithubからJenkinsへのServer Hook - Qiita
# GitHubからJenkinsへserverhookするためのパターンが色々網羅されていて選択肢の参考になります。
kakakikikekeのブログ: GithubのWebHookを使ってJenkinsのビルドを実行する方法
# severhookの具体的な設定手法について大変参考になりました。
とらりもん - SSHを利用したリモートサーバに対するコマンド実行
# sshによるリモートサーバでコマンドを実行するシェルスクリプトの例として大変参考になりました。