CI環境を作るTweetこんにちは。Androidエンジニアのうめもりです。2018の1日目の記事です。アドベントカレンダーについてはQiitaでもAdventarで公開していますので、ほかの記事については是非そちらから見てください。2019楽しみですね。自分も3つほどセッションを投稿したのがめでたく1つ採択されまして、今からしっかり準備せねばと発表内容を作っている日々です。Vitals徹底活用」というタイトルで発表しますので、興味ある方いれば是非聞きに来てもらえるとうれしいです。今日は投稿したもので採用されなかったものうちの1つについて、せっかくですのでブログ記事としてまとめておこうと思います。提出したセッションの概要についてはこんな感じでした。AndroidでCIしてますか?2018年現在では活用できるCIサービスが様々存在することもあり、AndroidのCI環境を構築することは特に複数人が開発にかかわるような環境では一般的なことになっていることと思います。タスク数の制限が他サービスに比べてゆるい、など特徴的なCIサービスであることもあり、一度は導入を検討した方もいるのではないでしょうか。CodeBuildは特に他サービスとの連携周りの機能が貧弱なこともあり、様々な利点がありながらも導入されない、といったことが多いのではないかと思います。CodeBuild最大限に活用する方法について説明します。LambdaのコードはGolangを使用しています。Androidのカンファレンスですが、実際はあんまりAndroidの話はしてないですねwでは内容に入っていきたいと思います。delyのAndroid開発ではこんなことをCIbotにやってもらってます2018年現在Android開発においてはCIを導入していないということはそうそうないことだと思うのですが、delyのAndroid開発でももちろんCIを導入しています。Slack上のbot、そしてGitHubのWebHookからほとんどの機能を呼び出すように作っており、2018年12月1日現在ではこんなことができるようになっています。以上の機能がSlackやGitHubのWebHookごしに呼び出せるようになっています。これらの機能が自動で呼び出せる環境は、2018年現在においてもそこそこ頑張っているような気がしますが実際どうなんでしょう?(もしかして当たり前だったりしますか?)このすべてのやり方をここに書いてしまうととても長くなってしまうので、実際に肝になる部分の一部だけ今回は記事として共有しますが、また別途細かく説明する記事を書こうかと思っています。これらの機能をどのように実現しているかでは実際、これらの機能をどんなツールやサービスを使って実現しているか紹介します。CIをやるにはとりあえずビルド用の環境が必要なのですが、それは以下のものを使って実現しています。Hookはありますが、正直不足ですよね)あまり話題になっていない印象がありますが、delyのAndroid開発ではこちらをフル活用しています。ECR(Dockerのリポジトリサービス)なのですが、こちらにアップロードしたイメージをCodeBuildから使うと、ビルド用のDockerイメージのダウンロード速度が爆速になります。CircleCIを使っていた際もオリジナルのビルドイメージを使っていると5分~10分待たされたりすることがありましたが、こちらの環境に切り替えてからそういった待ち時間はほとんど発生しなくなりました。ECRにアップロードすることで、いきなりビルドやテストから走るようにして、ビルド時間を削減しています。SAMを活用しています。Gatewayで受けて、実際のCodeBuildの呼び出し等を行っています。全体の構成はこんな感じになっています。CodeBuild周りの仕組みについて、そちらを採用した理由について説明します。CodeBuildはシンプルにビルド時間が1分かかるごとに課金される課金体系になっています。大きいインスタンスサイズであるbuild.general1.large(15GBメモリ、8vCPU)でも、ビルド1分当たりの料金は0.020USDです。1回自動ビルドやテストを回すたびに数十円程度の料金体系なので、まず導入してみる、という時に非常に良いのではないでしょうか。CodeBuildの制限について書いてあるのですが、他のCIサービスに比べて圧倒的に同時実行ビルド数の制限が緩いです。導入直後は5個のビルドを同時実行できますが、通常は20個のビルドを同時実行できるようになっています。複数人でゴリゴリ開発しても、キューイングされたビルドを待つことはあまりないですね。ECRを使った際)前述したとおりですが、CodeBuildとECRを連携するとDockerイメージの取得が爆速になります。これだけでもCodeBuildを採用する価値はあるのではないでしょうか。CodeBuild上でのビルドの仕方は基本的な使い方をしているだけですが、Dockerイメージ構築の際に気を付けていることについて共有しておきます。(といってもDockerイメージを使ってビルドする際には当たり前の話ですが...)ビルド用Dockerイメージ構築の際に気を付けていること依存ライブラリもイメージに含めてしまうもちろんCodeBulidにもキャッシュ機能があるので依存ライブラリはそちらに入れてしまう、というのもありなのですが、キャッシュの展開にもそれなりの時間がかかります。先にGradleのビルドを一回走らせてしまうことで、ビルドに必要な依存ライブラリは全てビルドイメージ上に展開された状態にしています。buildでイメージサイズを圧縮する上記のことをやると、どうしても無駄はソースコード等がイメージに残りがちです。bulidの機能を使って、余計なファイルがイメージ上に残らないように工夫しています。ビルド用dockerfile以下が弊社で使っているビルド用のdockerfileです。dangerやマーケティング用のツール、jsonパース用のjqなどを使うため、それらの依存関係も含んでいます。いったものをイメージに含めておくこともできるのはDockerイメージを自前で管理していることの利点ですね。CodePipelineからの呼び出し、またはGitHub等と連携してPush時に自動でビルドを行うことが可能です。なんとなくCIを回してテストコードを走らせる、あるいはデプロイパイプラインとして使うにはこれだけでも十分なのですが、例えばPullRequestを作ったタイミングでビルドを回し、Danger等を使って自動でPullRequestのレビューを行うデバッグ用のビルドと本番用のビルドを同じブランチから分岐してビルドさせる通常と違うパターンのビルドに対応させるなどは以上の手法を使っていると困難です。CodeBuildのAPIには外からビルドタスクを任意の設定で走らせるものがあり、こちらを使うことで細かいビルドタスクに対応することが可能です。SDKから呼び出すhttps://docs.aws.amazon.com/codebuild/latest/APIReference/API_StartBuild.html実際にビルドタスクを呼び出すためのAPIはこちらです。弊社はLambda上のコードから呼び出していますが、GoのSDKでの呼び出し方はこんな感じになっています。},})基本的な設定はコンソールで行っておくとして、肝はSourceVersionとEnvironmentVariablesOverrideの部分です。SlackやGitHubからビルドタスクを呼び出す際は、SourceVersionにブランチ名などを指定し、EnvironmentVariablesOverrideにビルドごとに異なる環境変数を設定し、実際にCodeBulid上で走るシェルスクリプトでその環境変数を利用して処理内容を分岐しています。SAMを使ってSlackとGitHubからCodeBuildを動かす上記のAPIを使い、SlackやGitHubからCodeBuildのビルドタスクを走らせています。Formationのテンプレートを生成し、そこからLambdaの構成を作っていきます。生成されたyamlを使って実際にAWS上に環境を構築するコマンドはこんな感じです。CAPABILITY_IAM実際はこれをシェルスクリプトに記述してそれを実行するようにしています。Formationのテンプレートを共有しておきます。Gatewayの定義も一緒に書くことができるのが特徴です。botの構成をすべてここで表現できるので、メンテナンスもしやすくていいですね。postさて、それでは説明に入っていきます。SAMの中で、SlackのWebHook用のLambdaの記述です。Gateway用の定義になっています。Apiの指定、そしてPathとMethodをPropertiesで指定することです。Hookを受け取ることができるようになっています。lambda.Start(handleRequest)}こんな感じのコードで受け付けられます。シグネチャがシンプルでいいですね。Storeを使って環境変数の設定項目をリポジトリに入れなくて済むようにするCloudFormationのテンプレートファイルにAPIのトークンを入れてしまうのは、このテンプレートファイル自体をGitリポジトリ等で管理することを考えるとあまりよくはありません。Storeの機能を使うことで、デプロイ時にAPIのトークンを設定してデプロイするようにしています。詳しくはこちらに書いてありますので、こちらをご参照ください。GoogleApiCredentialsこのように環境変数を設定する側で、あらかじめ定義していたパラメータ名を参照するだけで出来るようになっています。botとなるとやはりSlack、GitHubなど、様々なところからのトリガーでビルドを走らせたくなりますが、それをすべて同一のLambdaのエンドポイントで処理するのは保守性の面から考えてもあまりよくありません。弊社はそのそれぞれを別のバイナリとして切り分けてビルドしています。main.goとそれぞれmainパッケージを作り、別々にデプロイパッケージを作っているだけです。https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-go-how-to-create-deployment-package.htmlこちらにGoを使った場合のデプロイパッケージの作り方が書いてあります。botを実装するうえでハマったポイントをご紹介しておきます。してもCI周りのタスクは即座にレスポンスを返せないタスクもあり、レスポンスだけ返して処理をするという方法も、例えば別のLambdaを呼ぶなどの方法で実現できますがどうしても煩雑になりがちなので、リトライリクエストを無視するという力技で対応しています。ここに詳しいので、実際に実装する場合はここをよく読んだうえで実装しましょう。CodeBuildでCI環境を作るうえでのポイントについていくつか書きました。PlayなどのAPIをゴリゴリ呼んで実装するだけです。CodeBuildで最高なCI環境を作りましょう。気軽に聞いてください。