AWS CodeBuildでGoプロジェクトをビルドする
AWS TokyoリージョンにもCodeBuild等のCodeシリーズ(?)一式揃い、使ってみようということで、まずはCodeBuildを試してみました。
公式にGoのサンプルはあるんですが、他のライブラリ使ってないhello worldなので、CodeBuildの説明だけという感じでした。vendoringが試したいので、今回は昔コンセプト実装したrnbin*1のビルドをCodeBuildに任せる形で試してみました。
つまづきポイントとしては、glideでvendoringしても、cannot find package
になってしまうという点。回避策を書いてくれている方がいましたが、それでも解決せず、詳しく見たらそうかーという感じでした。
全体的なCodeBuild設定の流れ
- ビルドソースを選ぶ(S3, CodeCommit, github) *2
- ビルド方法を指定する(buildspec.ymlを書く or 直接ビルドコードをアップロード)
- ビルド生成物の保存先を指定(S3のバケット+Path)
- ビルド実行!
今回はgithubのpublic repository (rnbin)に、CodeBuild用のbuildspec.ymlを置いています。buildspec.ymlは、まだサンプルなので、featureブランチにpushして、master(default)のブランチにはマージしてない状態です。 このプルリク(色々試したので、コミットがめちゃくちゃ)が対象ブランチ。buildspec.ymlについては、後で説明します。
CodeBuildでBuild Projectを作成する
AWSコンソールから、CodeBuildを開いて、[Get Start] or [Create Project]とするとBuild Projectの作成画面になります。
Project nameは任意のものを。今回はsample-build-rnbin
にしました。Descriptionは特にないので省略。
次にSourceを選択します。今回はSource Providerにgithubを使います。githubを選択すると、[Connect to Github]と出て、GithubへのアクセストークンをOAuthで求められます。認証し、public repositoryを選んで、https://github.com/reiki4040/rnbin
を入れます。
次に、ビルドに使うイメージを選択します。
今回は、Codebuildに用意されているGolangの1.7.3を使いました。Use an image managed by AWS CodeBuild
を選び、Operating systemはUbuntu
(しかない)にします。Go最新のバージョンだと、docker hubなどから持ってきて使う必要がありますね。
build specificationはリポジトリのbuildspec.ymlを使うので、そのままUse the buildspec.yml in the source code root directory
を。
最後に、ビルド後のファイルを置くS3バケットとパスを指定します。
Amazon S3
、artifacts nameは、S3のディレクトリになるので、今回はrnbin
にします。Bucket nameは任意のS3バケットを選択します。
Service Roleはデフォルトで勝手に作ってくれるので、それに任せます。*3
[continue]を押すと、確認プレビューが出ます。Advancedの所は、ビルドマシンのスペックなど選択できますが、今回は省略したのでデフォルトのスペックで動くようになっています。
ビルドする
[Save and Build]を押すと、Build Projectが保存され、Buildが始まり、対象のハッシュを聞かれます。
省略するとデフォルトブランチの最新状態で実行されますが、今回はfeatureブランチの最新のコミットハッシュをコピーしてきて入れます。(画像のハッシュは途中のものなので、最後のものではないです)そして、[Start Build]を押すと開始されます。
あとは、ビルドのフェーズが着々と進み、In ProgressからSucceededやFailedなどやビルドのログが表示されます。ビルドのログは CloudWatch logsに記録されます。何か問題がある場合はそこを見て判断します。
最後まで成功すると、指定したS3のバケット/rnbin/にrnbinというバイナリが置かれています。*4
buildspec.yml概要と、vendoring回避策
リポジトリのbuildspec.ymlは以下。debug用のpwd
とecho $GOPATH
も入ってます。
version: 0.1 phases: install: commands: - go get github.com/Masterminds/glide pre_build: commands: # debug - pwd - echo $GOPATH # install libraries - glide up # workaround for vendoring - mkdir -p /go/src/github.com/reiki4040/rnbin - mv * /go/src/github.com/reiki4040/rnbin/ - mv .git /go/src/github.com/reiki4040/rnbin/ build: commands: # workaround for vendoring - cd /go/src/github.com/reiki4040/rnbin && ./build.sh post_build: commands: # workaround for vendoring - cp /go/src/github.com/reiki4040/rnbin/rnbin . artifacts: files: - rnbin
buildspec.yml概要
buildspec.ymlに書く内容は、主に4つのビルドフェーズと、ビルド生成物の出力定義(artifacts)です。 ビルドフェーズは以下4つです。
- install
- pre_build
- build
- post_build
必要なツールを入れる、依存解決など、ビルド自体、ビルド後の後始末など、といった感じで、それぞれ複数のコマンドを書けます。
rnbinには、build.shというスクリプトを使って、go buildの流れを実行できるようにしてあります。主にはgitからハッシュやversion、加えてgo versionなどを持ってきてgo buildするという処理が入っています。これをCodeBuildで実行させます。
今回は、glideをインストールして、ライブラリインストール、後述のvendoring回避策をやって、build.shを呼び出し、バイナリを移動させて、S3に保存する。となっています。
vendoringでハマる (回避はしているが、きれいでない)
glideでインストールしたライブラリですが、./vendor/
ができているにもかかわらず、cannot find package
と言われてハマりました。使っている環境はgo1.7.3で、go1.5からvendoring ができますが、この機能は$GOPATH以下の場合のみ動作し、それ以外では動作しないとのこと。
試しにpwd
とecho $GOPATH
で、パスを出してみると、GOPATHには含まれている。
コマンド | 結果 ※Xの部分はビルドごとに数字変わります。 |
---|---|
pwd |
/codebuild/output/srcXXXXXXXX/src |
echo $GOPATH |
/go:/codebuild/output/srcXXXXXXXX/src |
エラーを見ると、GOPATH以下のsrcを見ているようで、そもそもrnbin自体に含まれているs3backend packageを読み込めていない。
/usr/local/go/src/github.com/reiki4040/rnbin/s3backend (from $GOROOT) /go/src/github.com/reiki4040/rnbin/s3backend (from $GOPATH) /codebuild/output/srcXXXXXXXX/src/src/github.com/reiki4040/rnbin/s3backend
つまり、currentのパッケージも読めないと。vendorが読めないのは、GOPATH以下ですが、$GOPATH/src/github.com/reiki4040/rnbin
という形になっていないのが原因っぽい。そういうことか。
参考にしたページでは、$GOPATH
以下に全部コピーして、ビルドする回避策が提示されていましたが、これだと完全には解決しませんでした。
前述の通り、$GOPATH
には:
区切りで2つ入っているので、そのまま使うとうまく動作していない様子。それなら、/go
はまぁ入ってるでしょうと割り切り、$GOPATH
を使わず、直接/go/src/github.com/reiki4040/rnbin
といったようにコピーして実行していったらやっと通りました。
vendoring回避後のartifactsのファイル指定
生成したものをS3に保存するため、buildspec.ymlのartifactsの所にファイル名を書くんですが、goプロジェクト一式を/go
以下にコピーして、ビルドした関係上、そっちに生成されています。
ただ、以下のように指定すると、S3にもそのディレクトリ構造がそのまま残ってしまいました。
artifacts: files: - /go/src/github.com/reiki4040/rnbin/rnbin
これはpost_buildのcommandにcp /go/src/github.com/reiki4040/rnbin/rnbin .
とcurrentにバイナリをコピーしてくるコマンドを追加しました。commandは1行ずつ独立して実行されるので、.
で持って来れます。
これでartifactsの所にはrnbin (生成バイナリのファイル名)のみを指定して、S3にもそのバイナリファイルだけが保存されます。
別ブランチの指定
サンプルだったのでfeatureブランチを使いました。最初はpre_buildにgit checkout
を入れていましたが、start buildでfeatureブランチのハッシュを指定したらfeatureブランチ(というかハッシュのやつ)で落とされたので、不要でした。
まとめ
EC2立てずに、マネージドなサーバでビルドできるところがいいですね。githubじゃなくても、S3にファイルあげてもいいし、CodeCommitにしてもいいので、AWS使ってる人は手をつけやすいかも。vendoringハマりましたが、Jenkins用のインスタンス作って運用するのは手間なので、その点はマネージドの強み。前後の作業も、AWSマネージドでやれるなら便利そうですね。他のCIサービスと比べてどうかは、他やってないのでまだわからず。
glideなどでのvendoringやサブパッケージは、普通に使うと思うので、CodeBuild側でやらなくていいようにしてくれると嬉しいですね。