Codeシリーズで始めるはじめてのCI/CD (#higobashiaws で登壇しました)

こんにちは、大阪オフィスのかずえです。先日HIGOBASHI.AWSという弊社主催の勉強会にて登壇いたしました。お越し下さった方、ありがとうございます。

本エントリーは、先日のHIGOBASHI.AWSの登壇内容「Codeシリーズで始めるはじめてのCI/CD」をブログむけにアレンジしたものになります。長いですがご一読いただければ幸いです。

このセッションのゴール

CI/CDわからない、難しそうと感じてらっしゃる方、
Codeシリーズなんとなく知ってるけど使ったことない方に
Codeシリーズの概要を理解していただき、便利やん!やってみよ!と思っていただく、というところ目指しています。

このセッションでやること

上記ゴール達成のために、今回はあるサンプルの開発現場を想定して、そこにCodeシリーズを投入していってだんだんと便利にしていく、といったシナリオでやっていきます。

目次

用語の整理

CI/CDって何?

上記はアプリケーションの一般的なリリースプロセスを表したものです。

まず、私たちは何かアプリケーションを作ろうとした場合、コードを書きます。次にそのコードをビルド、つまり動く状態にし、テストを実施します。テストが通ってやっとデプロイを行ないます。

CI/CDというのは、このリリースまでの作業を自動化、省力化する試みのこと を言います。
(細かくいうともっと色々ありますが、とりあえずこの認識を持っていただければOKです)

何でCI/CDが必要なの?

最近このCI/CDという言葉、耳にする機会がある方も多いと思うのですが、なぜ注目されているのでしょうか。
それは一言で言うと、開発に求められるスピードのレベルが上がっているから、です。

これまでのアプリケーションの開発現場では、数ヶ月から数年かけて機能を練り上げていき、最後にまとめてリリース、といった手法が一般的でした。ですが近頃では、迅速にユーザーのニーズに応えることが要求される開発現場が増えて来ました。そのため細かく機能や部品ごとにリリースして、リリース後のユーザーの反応を見てさらに改修を行なっていく、と言う手法が主流になってきています。前者をウォーターフォール的な開発手法、後者をアジャイル的な開発手法ということもできると思います。

では、そうなった際に、一回リリースするのに準備で諸々1日潰れますみたいなアプリケーションと、コードをコミットすればあとは自動的にビルドやテストが走って問題なければすぐリリースされますよ、といったアプリケーションがあったとします。どちらの方が良い製品になりそうでしょうか?後者ですよね。故にCI/CDが注目されているわけです。

未リリース=在庫

別の角度からもう一度CI/CDの重要性を説明します。作ったけどまだリリースできていない機能というのは、小売業でいうところの在庫と一緒のようなものです。

小売業の方々は、商品をどこかから仕入れて、それ売るという商売をされています。たくさん商品を仕入れたとしても売れなければ商売あがったりです。

我々の開発現場も同じようなものです。我々がコードを書く際、その目的は何かしらの問題解決ではないかと思います。よりこの機能を便利にしたいとか、この機能を高速化したいとか。では、その問題解決がいつから効力を持つかというと?リリースされてからですよね。リリースしてユーザーに使ってもらって初めて、我々の書いたコードの価値が生まれます。

未リリースの機能を多く抱えているというのは、在庫を多く抱えているお店と同じようなものです。勿体無いですよね。さっさと売って、そのお金で新しい商品をまた仕入れて、さらに売る、という正のスパイラルを作りたいところですよね。このスパイラルを作るのに、CI/CDが役立つというわけです。

Codeシリーズって何?

セッションタイトルにあるCodeシリーズですが、こういったCI/CDを実現するのに役立つAWSのサービス群の総称のことをこう呼びます。その名の通り、どのサービスもその名前の頭にCodeが付きます。現在のところ、以下5サービスがリリースされています。

ここまでで用語の解説、終わりです。次に行きましょう。

今回扱う環境

さて、冒頭にお伝えした通り、今回はCodeシリーズを紹介するにあたって、とある開発環境を題材にし、Codeシリーズを使ってだんだん便利にしていく形式をとりたいと思っております。お題とする開発環境はこんな感じです。

  • Laravelで作られたアプリケーション
  • インフラ構成はALB 配下にEC2一台
  • Gitは使っている。レンタルサーバーにGitリポジトリ(Gitolite)を置いている
  • デプロイはファイルリスト作ってSFTPでアップロード
  • テストコードは特に書いていない

ではここから、この環境をちょっとずつ良くしていきましょう。

ステップ1:デプロイ作業を簡単にしよう

状況:インスタンスを増やすことになりました

ありがたいことにこのアプリケーションの人気が高まってきて、負荷が高まってレスポンスが悪くなることが増えてきました、とします。
そこで、これまで1台だったEC2インスタンスをスケールアウトさせて4台にすることにしました。狙い通り負荷を捌くことができるようになりました。

が、デプロイ作業がちょっと面倒になりました。これまではファイルアップ先はインスタンス1台だけだったのですが、今は4台です。4回ファイルアップする必要があります。めんどくさいです。

CodeDeploy

そこでCodeDeployの登場です。CodeDeployはその名の通りDeployを楽にするサービスです。Zip化したソースコードや、GitHub上のソースコードを、指定のEC2インスタンスなどに簡単にデプロイすることが可能になります。

CodeDeploy の仕組み

まず、デプロイ先のインスタンスにCodeDeploy Agentというものをインストールして、常駐させます。

これがCodeDeployに対してポーリングを行ないます。

そして、コードが更新されたらAgentがその情報を検知して、ファイルを取って来て、インスタンス上に配置します。加えてそのタイミングでスクリプトを実行することもできます。

CodeDeploy の設定

大別すると三つあります。デプロイグループ、デプロイ先のEC2、ソースコードです。

デプロイグループ

デプロイグループとはデプロイ先のインスタンスのグループのことです。これをCodeDeployのコンソールから設定します。

ポイントは、デプロイ先インスタンスを直接指定するのではなく、「このタグが付いているインスタンス」といった感じで間にタグをかます点です。こうすることによってより柔軟にインスタンスをデプロイ対象に加える・外すことが可能になります。

デプロイ先のEC2

まず、先ほど申し上げたように専用のAgentをインストールして常駐させる必要があります。

そして、このAgentがCodeDeployに対してポーリングを行ないます。その際のプロトコルはHTTPSですので、HTTPSアウトバウンドアクセスを可能にする必要があります。もしプライベートサブネットにインスタンスが置かれている場合、NAT ゲートウェイなどでインターネットに出れるようにする必要があります。

次に、先ほどデプロイグループで設定したタグを付与します。

最後に、S3バケットへのアクセス権限をIAMロールに設定します。これはなぜかというと、CodeDeployで配信されるソースコードは実はS3バケット上に存在しているからです。GetとListの権限が必要です。

ソースコード

ソースコードも1点修正が必要です。appspec.yml というYAMLファイルをソースコードのルートディレクトリに配置する必要があります。

このappspec.ymlはデプロイの指示書のような役割を果たします。中身をみていきましょう。 files以下でソースコード内の各ファイルやディレクトリをインスタンスのどの場所に配置するか指定します。
さらにhooks以下でデプロイ時に実行するスクリプトを指定することができます。スクリプト実行タイミングは複数あり、今回の場合だとAfterInstallということで、デプロイファイルがインスタンスに配置された直後にスクリプトが実行されます。実行するスクリプトはソースコード内に含めておく必要があり、locationでその場所を指定します。

ここまでが最初に設定する内容です。

デプロイ作業

毎度のデプロイ作業でやらないといけないことは以下の三つです。

  1. ソースコード全体をzipファイル化します。
  2. そのソースコードをS3バケットにアップします。
  3. CodeDeployコンソールからアップした場所など各種設定をして「デプロイの作成」ボタンを押します。

状況改善: デプロイ作業が簡単になりました

デプロイ先のインスタンスが何台に増えても、エンジニアの作業量は同じ、zip→S3にアップ→CodeDeployコンソールをポチポチ、だけです。

ステップ2:各環境へのデプロイを一本化しよう

状況: 「Stgと本番環境それぞれにCodeDeploy使うのめんどいわ」

本番環境しかない開発現場ってあまりないかと思います。前段にステージング環境があったり、さらには開発環境があったり。各環境ごとにデプロイ作業するの面倒くさいな、とエンジニアが思ったとします。

CodePipeline

そこでCodePipelineを使ってみましょう。

CodePipelineは色々なサービスを組み合わせてCI/CDのパイプラインを作る、ハブのようなサービスです。パイプラインというのは「一連の処理」みたいなイメージで思っていただければ結構です。
組み合わせることのできるサービスは、今回ご紹介しているCodeシリーズはもちろんのこと、そのほかのAWSの様々なサービス、さらにはAWS以外のサービスとも連携可能になっています。例えばJenkinsと組み合わせたりとか、サードパーティ製のテストツールを実行したり、負荷テストしたり、みたいなこともできます。

CodePipelineの概念と仕組み

パイプライン

CodePipelineの最上位概念です。パイプラインは、複数個のステージをトランジション、つまり遷移するといった構造になっています。リリースプロセス(CI/CDプロセス)全体をまとめた概念です。

ステージ

パイプラインの「一連のプロセス」の各プロセスを指します。ビルド、テスト、デプロイといったものがこれにあたります。

アクション

全てのステージには少なくとも一つのアクションが含まれます。具体的なタスクを表します。 S3にソースコードが置かれるだとか、CodeDeployでデプロイグループにデプロイするとかですね。

トランジション

前段のステージから次のステージに処理が移ることです。

アーティファクト

中間生成物。各ステージは、入力値となる入力アーティファクトを必要としたり、処理結果を格納した出力アーティファクトを出力したりします。
出力アーティファクトが、トランジション先の次のステージで入力アーティファクトとして使われたりします。

今回作成するパイプライン

4ステージのパイプラインを作成しました。

まず最初のステージ。左上のSourceです。S3にZIPファイルをアップするところですね。
次は左下です。ステージング環境へのデプロイです。
その次に続くのは右上のApproveというステージです。これは何なのかというと、会社のお偉いさんが、ステージングに反映された新機能なりを確認して本番リリースのGOサインを出すといったような手動承認のステージです。こういうステージを追加することもできます。そして承認がおりたら、右下の最後のステージで本番環境へのデプロイが行われます。

状況改善: Deploy作業がもう一つ簡単になりました

ステージングと本番環境のデプロイを一つのリリースプロセスとすることができました。

さらに良いことに、CodeDeployコンソールでデプロイ設定をする必要も無くなりました。

先ほどCodeDeployをご紹介した際に、毎度のDeploy作業のステップは3つ、zip→S3にアップ→CodeDeployコンソールをポチポチとお伝えしましが、最後のステップが不要になっています。実はCodePipelineのソースステージでS3バケットにzip化したソースコードを置く方法を選択した場合、裏で自動的にS3にファイルが置かれた(更新された)際に発火するCloudWatch Eventのトリガーも作成されます。これが自動でパイプラインをスタートさせます。

さらに、パイプラインを作ったことで、今どの段階まで作業が進んでいるのかというのがCodePipelineのコンソールで一発でわかるようになりました。ステージング環境へのデプロイでエラーが出て止まっているとか、お偉いさんの承認のところでずっと止まっているとかですね。

ステップ3:Gitリポジトリから直接デプロイしよう

状況: 「毎回zipファイル作ってS3に上げるのもめんどくさいわ」

先ほどお伝えしたように、現在のところのリリースプロセスは以下のようになっています。

S3にzipファイルをアップするところまでやれば、あとはAWSがよしなにやってくれるようになりました。(厳密にはお偉いさんの承認フェーズがありますが)
ただまだzipファイル化とS3へのアップロードはエンジニアの仕事です。どうせならこれも無くしてしまいたい。

CodeCommit or GitHubを使おう

こういった場合にはCodeCommitかGitHubを使うことを検討しましょう。
実はCodePipelineのソースステージには、S3を使う方法以外にもCodeCommitやGitHubを使う方法があります。

今回はCodeシリーズをご紹介するセッションですので、GitHubの説明は割愛させていただきます。

CodeCommit

CodeCommitはGitリポジトリのマネージドサービスです。AWS版のGitHubみたいなイメージで結構です。マネージドですから、インフラ管理が不要になります。

大きな特徴としては、IAMユーザー単位で認証認可できる、という点があります。ですのでGitHubなどの外部サービスを使うのに比べると、アカウント管理をシンプルにすることができます。

CodeCommit使い方

リポジトリをCodeCommitのコンソールで作って、接続できるようにし、既存のコードをプッシュします。またCodePipelineのソースステージをCodeCommitを使うように書き換えましょう。そして今回の場合、CodeDeployのappspec.ymlから実行されるスクリプトも要修正です。どういうことなのか説明します。

appspec.ymlから実行されるスクリプトの修正

これまでのzipファイルをS3にアップロードする方法だと、zipファイル内に含まれているものは、Gitで管理しているファイル+Composerでインストールする外部ライブラリでした。
ですがこれをCodeCommitに変更すると、もちろんCodeCommitから取れるファイルはGitで管理しているものだけになります。外部ライブラリは含まれません。そのままデプロイするとLaravelはエラーを吐きます。ですので代わりにappspec.ymlで定義するフックスクリプト内で composer installして外部ライブラリをインストールしてくる必要があります。

状況改善: リモートブランチに変更加えるだけで処理開始

というわけでリリースプロセスがより省力化されました。

これまでもコードを書いたらGitにコミット、プッシュするなどの作業を行なっていましたが、これはリリースプロセスとは直接紐づいていませんでした。zip化してS3にアップするという工程が必要でした。
ソースステージをCodeCommit(もしくはGitHub)に変更することで、Git操作から直接CodePipeline処理を開始させることができるようになりました。

加えて、CodeCommitはマネージドサービスですので、Gitリモートリポジトリを置いているサーバーの管理からも解放されました。
さらに、CodePipelineソースステージをCodeCommitに変更すると、パイプラインでデプロイされるソースコードの内容を、ソースステージからCodeCommitのコンソールに遷移することができるので簡単に確認できるようになりました。この点も少し便利になったと言えます。

ステップ4: テストを自動実行しよう

状況:「これからはちゃんとユニットテスト書いていこうな!」

プログラムミスで本番環境で大きめの障害を起こしてしまいました(とします)。開発チームは今後の予防策として、ちゃんとテストを書くことになりました。そこで、テストをパスしたコードだけがデプロイされるような仕組みを入れたいです。

CodeBuild

CodeBuildを使ってみましょう。このサービスは以下のようなことができるサービスです。

  • 指定のスペックのコンテナ上に
  • 指定のファイルを置いて
  • 任意のコマンドを実行して
  • 結果をS3にアウトプットする

サービス名に「ビルド」と入っていますが、何かしらのコマンドの結果が欲しい際に幅広く使えるサービスです。

CodeBuildでやること

というわけで今回はCodeBuildでPHPUnitを実行してみましょう。 そのためにCodeBuildで以下のことをします。

  • AmazonLinux2コンテナ上で
  • 必要なパッケージインストールして
  • Composerで外部ライブラリをインストール(PHPUnitもComposerで入るので)
  • PHPUnit実行

CodeBuildの設定 ソースコード

CodeBuildもCodeDeployと同様、YAMLファイルを追加する必要があります。buildspec.ymlというファイルをルートディレクトに置く必要があります。

buildspec.yml はCodeBuildの指示書のようなものです。ビルドにはいくつかのステップがあるのですが、各ステップでどのようなコマンドを実行するかというのをphases 以下に記載していきます。

そしてaritifactsには何をアウトプットとするか、つまりS3バケットにアップロードするかを定義します。今回 **/* としましたが、これは ビルド開始位置以下の全ディレクトリとファイルを対象とすることを意味します。

CodeBuildの設定 CodePipeline

パイプラインにこのCodeBuildの処理ステップを加えましょう。ソースとステージング環境へのデプロイステージの間に追加します。

CodeBuild処理中

設定終わったらパイプライン処理開始してCodeBuildの処理を見てみましょう。CodeBuildのコンソールにて処理結果を逐次確認することができます。

以下はcomposer install しているあたりです。(ルートで実行するなと怒られていますねw)

以下はPHPUnitの実行部分です。1件しかテストがありませんが、1/1でテスト成功していることがわかります。

テスト失敗したら

わざとテストを失敗させてみました。すると次ステージ(ステージング環境へのデプロイ)へのトランジションが発生しません。狙い通り、テストにパスしたコードしかデプロイされないようにすることができました。

状況改善: コードの質アップ

リモートブランチに変更が加わるたびに自動でテストが実行されるようになりました。加えてテストにパスしたコードしかデプロイされないようにすることができました。とはいえテストを自動的に書くサービスではないので、そこはエンジニアが頑張る必要があります。

番外編:1から作る場合はCodeStarを検討しよう

ここまで、既存の環境を改善していくシナリオでお話ししましたが、もし新しいプロジェクトを立ち上げる場合は、CodeStarを使うことを検討してみても良いかもしれません。CodeStarは、これまで紹介して来たような
CI/CDパイプライン付きの環境を
サクッと作ってくれるサービスになります。色んな環境のテンプレートがあるのでそれを選ぶだけです。今回取り上げたLaravelのテンプレートもありました。やってみましたが、以下のようなリソースが自動で作成されます。

まとめ

Codeシリーズをご紹介しました。各サービスどのようなメリットがあるか、どのように使うか、なんとなく掴めていただけましたでしょうか。
最初、各設定には正直手こずることがあるかもしれません、ですが一度やってしまえば、毎日の作業負荷を確実に低減できますので、やってみる価値はあると思います。まずはできるところからでもちょっとずつ試してみて、快適な開発ライフを送りましょう!

Q&A

CodeDeployをS3バケットにファイルアップロードする形で使う場合、圧縮ファイル化しないでも使うことはできない?

できません。S3を使う場合は圧縮ファイル化する必要があります。念の為CLIリファレンスも確認しましたが、圧縮ファイルを使う方法しかありませんでした。

蛇足ですが、圧縮方法としてはzip以外にもtgzとtarが使えます。

CodeDeployのappspec.ymlはZIPファイルの中に含めないとだめ?例えばCodeBuildはbuildspec.ymlを含めなくても外から設定できるじゃないですか。そんな感じでできない?

できません。CodeBuildのようにコンソールで設定を記述することはできません。

また、試しにappspec.ymlなしのzipファイルを使ってデプロイを試してみましたが、以下のように The CodeDeploy agent did not find an AppSpec file within the unpacked revision directory at revision-relative path "appspec.yml". とエラーになりました。

資料