GitHubでPullRequestが出ると、Jenkinsでテストした後でEC2に自動デプロイする設定を行った

AWS, EC2, Git, Jenkins

この記事は最終更新日から1年以上が経過しています。

近頃は実装の仕事よりもその周りの援護的な仕事が多い江藤です。その中の一環で行った CI 環境の整理について、今回は記事にします。

WIZY のこれまで

WIZY では Jenkins で自動テストを走らせていました。
流れを図にするとこんな感じです。

ci_before.png

  1. 開発者は開発が終わると Pull Request を 出す
  2. マージ権限のある人がPRをレビューをして、問題があれば修正
  3. 問題がなければ権限のある人がマージを行う
  4. マージされると、自動テストが走り出す(単体テストの後で結合テスト)、同時にマージを行った人がEC2のサーバに入り手動デプロイを行う
  5. 自動テストで問題があれば、再修正を行い最初からやり直す
  6. 全てのテストがパスしていれば、次の開発に入る

このフローにはいくつか問題がありました。

  • テストコードが走る前に検証環境へデプロイを行っている
    マージをトリガーにして自動テストを走らせていたので、検証環境へデプロイをしている裏側でテストが走ることになっていました。
    たまに Typo などのミスでレビューでは見逃された構文エラーがあったりします。(ちゃんとスモークテストとかしていれば防げるハズなのですが…)
    これが原因で、検証環境にデプロイしたところ、検証の WIZY が止まってしまうなんてこともありました。
    先に単体テストを行っていれば、これは防ぐことができます。
    つまり、本来はPRが出た段階でテストコードは実行しなければいけないのです。

  • 検証環境へ手動でデプロイを行っている
    検証環境へのデプロイはマージを行った人が手作業で行っていました。
    SSHでサーバへ入って、ソースコードを最新にして、サービスを再起動させて、と一回一回は大した作業ではないのですが、レイアウトの微修正など細かいデプロイが重なった時など、予想以上の時間をデプロイに持っていかれていた気がします。
    それ以外にも、複数のブランチに依存するモジュール(例えば、WebとAPIの仲介をSwaggerで行っていますが、これはWeb画面のサーバとAPIのサーバのどちらにもデプロイが必要です)で片方だけデプロイを忘れていて動かなくなるということもありました。
    いずれにせよ、デプロイ時に叩くコマンドは決まっているので人力で行う必要はなく、これも自動化が出来ます。
    ということで、これもJenkinsで行わせます。

WIZY の現状

ということで、この問題を解決すべくフローを再考・再構成しました。
今はこのようなフローで行っています。

ci_after.png

  1. 開発者は開発が終わると Pull Request を 出す
  2. PRが出ると自動で単体テストが走り出す。これがパスしなければマージできない
  3. マージ権限のある人がPRをレビューをして、問題があれば修正
  4. テストをパスしていて、かつ問題がなければ権限のある人がマージを行う
  5. マージされると、検証用の EC2 環境に自動デプロイ
  6. マージされると、結合テストが走り出す
  7. 結合テストでデグレが発覚すれば、再修正を行い最初からやり直す
  8. 全てのテストがパスしていれば、次の開発に入る

これにより、上記のように動かないコードが検証環境にデプロイされてしまうことを防げるようになりました。
また、手動でデプロイの必要がなくなったので、マージする側の人の手間も減り、手作業で発生していたようなミスもなくなりました。
なお、結合テストがマージの後にある理由ですが、現状テストサーバのスペックがそこまで高くないため、通しで平均1時間半ほどかかっており、これをマージの度に待つのは現実的ではないという理由でマージ後に行っています。
今後、サーバの増強やテストを高速化するブレイクスルーがあれば、単体・結合テスト完了までマージできないようにしたい所です。

技術的解説

現状のフローを実現するためにどうやっているのかを簡単にですが解説しておきます。

開発者は開発が終わると Pull Request を出す

WIZY の開発は GitHub を使っています。
git フロー(完全にではありませんが)で開発しており、本番で動いている master ブランチと、検証環境で動く develop ブランチの2種類 が主に使われています。
開発する人達は実装が終わったら develop ブランチに対して Pull Request を出します。
これが全ての起点になります。

PRが出ると自動で単体テストが走り出す。これがパスしなければマージできない

Jenkins の設定

ここで Jenkins でやるべきことは2つです

  • PullRequest が来たらそれをビルドしてテストを行う
  • 成功・失敗の結果を GitHub に知らせる

これらをどちらも実現する GitHub Pull Request Builder というものがあります。
まず、それをインストールします。

Jenkins の管理 –> プラグインの管理 へ進み、GitHub Pull Request Builder をインストールします。


設定すべき事がいくつかあるのですが、まずは Jenkinsの管理 –> システムの設定 にある GitHub Pull Request Builder の設定を行います。
たくさんフォームがありますが、PRをビルドしてWebhookを飛ばすだけであれば、

  • GitHub Server API URL
  • Shared Secret
  • Auto-manage webhooks にチェック
    だけで大丈夫です。

jenkins_ghprb_1.png

Shared Secret は 高度な設定 の中にある Create Access Token から生成することができます。

jenkins_ghprb_2.png

GitHub から作成したい場合は、GitHub で自分のアイコン画像 –> Setting –> Personal access tokens –> Generate new token と進み、「 repo, repo:status」 にチェックを入れたアクセストークンを発行してください。

リポジトリに対してテストしてみて成功すれば大丈夫です。
なぜか私が試した Jenkins では入力した値が無視されてしまい、何度かプラグインをアンインストール・インストールを繰り返しているとその内ちゃんと適用されました。(途中までは、なぜか値を入れているのにフォームが空だと怒られたりしました)


次に、PRをビルドしたいリポジトリに対してジョブを作成しましょう。
新規ジョブの作成 から フリースタイル・プロジェクトのビルド を選択します。ジョブ名はなにか分かりやすいものを適当に付けましょう。
ここでは、以下の 3 つを設定します(テストを動かす設定については割愛します。WIZY の場合、シェルスクリプトで nosetests を動かして、カバレッジやテスト結果のXMLを出力させています)

  • GitHub Project
  • ソースコードの管理
  • ビルド・トリガ

まず、 GitHub Project にチェックを入れ、フォームにリポジトリのURLを入れましょう。一見すると必要なさそうですが、これが設定されていないとビルドがされません。

jenkins_test_job_1.png

次に、 ソースコードの管理Git にチェックを入れ、以下のように設定します

  • リポジトリURL : git@[リポジトリURL].git
  • Refspec : +refs/pull/*:refs/remotes/origin/pr/*
  • ブランチ指定子 : {sha1}

jenkins_test_job_2.png

最後に ビルド・トリガ を設定します。

  • GitHub Pull Request Builder にチェック
  • Use github hooks for build triggering にチェック
    • 必須ではないですが、この方が素早くビルドを行うことが出来ます
  • Admin list に Pull Request を行った時にビルドを行うユーザ名を全て入力
    • Organization があれば、それを 高度な設定 –> List of organizations. Their members will be whitelisted. に書いた方が楽だと思います。

jenkins_test_job_3.png

これで、GitHub 上で PR が出されると自動的にビルドを行う設定ができました。

GitHub 側の設定

続いて、GitHub で設定をしていきます。GitHub で行いたいのは以下の2つです

  • PR が出たら Webhook を投げる
  • Jenkins のテストが通るまでは PR をマージさせない

PR が出たら Webhook を投げる
設定したいブランチで Setting –> Hooks & services –> Webhooks にある Add Webhook を選びます。
以下のように設定して保存します。

  • Payload URL : [Jenkins URL]/ghprbhook/
  • Content type : application/x-www-form-urlencoded
  • Which events would you like to trigger this webhook?
    • Issue Comment
    • Pull request

Jenkins のテストが通るまでは PR をマージさせない
GitHub には Protected branches という機能があります。
これを使うと、「特定の条件を満たさないとこのブランチにはマージさせない」ということができます(人の制限はチームを使えばできますが、それ以上に複雑なことがここで出来ます)
設定したいブランチで Setting –> Branches –> Protected branches にある Choose a branch から設定したいブランチ(今回の場合はdevelop) を選びます。
(スクショではすでに選択済みなので、選択肢として出ています)

github_branch_protection_1.png

Protect this branch にチェックを入れ、その下にある Require status checks to pass before merging にチェックを入れます。これにより、「特定の形の Webhook を受けないとマージボタンを押せない(Adminであれば無理矢理押せます)」という設定がされます。(Webhook の内容については、GitHub Pull Request Builder が勝手に送ってくれるので、あんまり気にしなくて大丈夫です)

github_branch_protection_2.png

一度ステータスチェックが走ると、次からここの選択肢に出て必須設定にすることができます。どうやら、GitHub Pull Request Builder は default という名前で登録されるみたいなので、初回テストが上手くいったら設定画面でチェックを入れておきましょう。

動作チェック

これで、どちらもちゃんと設定が出来ているハズなので、実際にPRを出してみましょう。

すると、このように「テストしているからマージできません」みたいな表示になると思います。

pr_test_testing.png

Jenkins側を見てみると、PRに対してテストジョブが動き出しています

job_start.png

テストが無事PASSすると、このようにマージが可能になります。

pr_test_success.png

マージ権限のある人がPRをレビューをして、問題があれば修正

これは、特にチームでどのようにするかなので、「自分以外の誰かに必ずチェックを受ける」なのか「全てのマージは開発リーダーがチェックする」なのか色々あると思いますが、各チームで決めた方法で行えば良いと思います。
ちなみに、「レビューの強制」は GitHub 上で設定することが可能です。また、「特定の人しかマージできない」という設定も可能です。

レビューの強制
先ほどの Protected branch の設定画面で Require pull request reviews before merging とすると、チームの誰かがチェックをして許可しないと、マージボタンが押せなくなります(これも、管理者ならば押すことが出来ますが)

特定の人しかマージできない
Protected Branch で Restrict who can push to this branch にチェックを入れてチームを選択すると、そのチームに属している人しかマージボタンが押せなくなります。対象外の人は そのブランチに Push などもできなくなります。私のように「ボーとっしていてうっかり master ブランチに直接 Pushする」ような問題人がいる場合、設定しておいた方が安全かと思います。

マージされると、結合テストが走り出す

ググってください

マージされると、検証用の EC2 環境に自動デプロイ

EC2 SSM とは

WIZY は 商用環境も検証環境も AWS の EC2(のAmazon Linux) を使ってサービス提供しています。
EC2 には Amazon EC2 Systems Manager (以下、SSM) という機能があります。
EC2 のメニューで SYSTEMS MANAGER SERVICES –> コマンドの実行 から使うことが出来、動いているEC2にログインすることなくコマンドを実行することが出来ます。
今までは手動でコマンドを叩いていたのですが、Jenkins から SSM 経由でコマンドを実行することで、自動デプロイを実現します。
これまでは、手動でSSMエージェントをインストールする必要があったのですが、 Amazon Linux 2017.09 からはデフォルトで SSM エージェントが動いているので、EC2 にロールを設定してあげれば動かすことが出来ます。
もし、古い Amazon Linux をお使いの場合は こちらを参考にエージェントのインストールを行いましょう。

SSM のロール設定

設定したい EC2 を選択 –> IAMロール –> ポリシーをアタッチ –> AmazonEC2RoleforSSM にチェックを入れて保存します
これで、SSM を受け付けることが出来るようになります。

ssm_ec2_policy.png

デプロイのコマンドを設定

SSM を使って送るコマンドを定義します。
EC2 のメニューで SYSTEMS MANAGER SERVICES –> コマンドの実行 –> コマンドを実行 で新規コマンドの定義を行いましょう。

今回はシェルスクリプトの実行なので、 コマンドのドキュメントAWS-RunShellScript を選択します。

次に ターゲットの選択 で 「インスタンスの選択」を押して、先ほど設定したEC2インスタンスが選択肢にあるかをチェックしておきましょう。ちゃんと SSM エージェントが設定されて入れば候補として表示されます。
ここで、直接インスタンスを指定しても良いですし、タグを使って汎用的に設定するのもいいと思います。

そして、 CommandsWorking Directory を設定しましょう。ここに、「どこで」「どんなコマンドを実行するか」を記述します。例えば、「ソースコードのあるディレクトリで」「GitHubから最新のソースをPullして、サーバを再起動する」などです。それぞれの環境よって大きく変わると思うので、詳細は割愛します。なお、このコマンドは root 権限で実行されるので、必要であれば sudo コマンドなどで実行ユーザを変更しましょう。

これで、一通りの準備は整ったと思うので、Run を押す前に AWS コマンドラインインターフェイスコマンド から CLI コマンド をコピーしておきましょう。一度実行すると、今の所再度表示する手段はありません。デバッグして再実行なんてさせてくれないワイルドなスタイルです。

実行すると、コマンド一覧にステータス(成功/失敗)が表示されます。失敗していた場合はログを確認してデバッグしましょう。コマンドを選択し、 出力 –> 出力の表示 からログを見ることが出来ます。

Jenkins から自動でコマンドを実行するようにする

先ほどの aws コマンドを Jenkins から実行すれば、自動でデプロイができるようになります。

ロールの設定
まずは、Jenkins のサーバに SSM を実行するロールを付与しましょう(Jenkins が EC2 で動いていない場合は、 aws configure コマンドで権限を持ったユーザを設定します)
Jenkins のサーバの IAM ロールを選択して、「ポリシーをアタッチ」から AmazonSSMFullAccess (本当はフルアクセスの必要はないと思うのですが、公式見てもフルアクセスを付与しているので、もうこれでいいかなと思います) を選択します。

ジョブの作成
次に、デプロイ用のジョブを作成します。
新規ジョブの作成 から フリースタイル・プロジェクトのビルド を選択します。
ジョブは以下のように設定します

  • GitHub Project
    • GitHub のリポジトリ URL
  • ソースコードの管理
    • Git
    • リポジトリURL : git@[リポジトリURL]
    • ブランチ指定子 : */[ブランチ名]
  • ビルド・トリガ
    • GitHub hook trigger for GITScm polling
  • ビルド
    • シェルの実行
    • ここに、先ほどコピーした aws コマンドをペースト

これでジョブの設定ができました。ジョブが正常終了して、「コマンドの実行」の箇所に履歴が出れば成功です。

Done

これで、最初に言った「PRが出ると自動テストが走り、テストがパスするまでマージできず、マージされると自動でデプロイされる」が実現できました。
あとは、Jenkins の Slack Notifications プラグインなどを使ってエラー時に通知が来るようにしておけば、より「放っておいても大丈夫な環境」になると思います。

参考

AWS, EC2, Git, Jenkins