年中アイス

いろいろつらつら

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の作成画面になります。

f:id:reiki4040:20170625173506p:plain

Project nameは任意のものを。今回はsample-build-rnbinにしました。Descriptionは特にないので省略。

次にSourceを選択します。今回はSource Providerにgithubを使います。githubを選択すると、[Connect to Github]と出て、GithubへのアクセストークンをOAuthで求められます。認証し、public repositoryを選んで、https://github.com/reiki4040/rnbinを入れます。 次に、ビルドに使うイメージを選択します。

f:id:reiki4040:20170625173643p:plain

今回は、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バケットとパスを指定します。

f:id:reiki4040:20170625174259p:plain

Amazon S3、artifacts nameは、S3のディレクトリになるので、今回はrnbinにします。Bucket nameは任意のS3バケットを選択します。

Service Roleはデフォルトで勝手に作ってくれるので、それに任せます。*3

[continue]を押すと、確認プレビューが出ます。Advancedの所は、ビルドマシンのスペックなど選択できますが、今回は省略したのでデフォルトのスペックで動くようになっています。

f:id:reiki4040:20170625175015p:plain

ビルドする

[Save and Build]を押すと、Build Projectが保存され、Buildが始まり、対象のハッシュを聞かれます。

f:id:reiki4040:20170625175507p:plain

省略するとデフォルトブランチの最新状態で実行されますが、今回はfeatureブランチの最新のコミットハッシュをコピーしてきて入れます。(画像のハッシュは途中のものなので、最後のものではないです)そして、[Start Build]を押すと開始されます。

f:id:reiki4040:20170625175557p:plain

あとは、ビルドのフェーズが着々と進み、In ProgressからSucceededやFailedなどやビルドのログが表示されます。ビルドのログは CloudWatch logsに記録されます。何か問題がある場合はそこを見て判断します。

最後まで成功すると、指定したS3のバケット/rnbin/にrnbinというバイナリが置かれています。*4

buildspec.yml概要と、vendoring回避策

リポジトリのbuildspec.ymlは以下。debug用のpwdecho $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以下の場合のみ動作し、それ以外では動作しないとのこと。

試しにpwdecho $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側でやらなくていいようにしてくれると嬉しいですね。

参考

*1:複数のS3バケットを束ねて、リクエスト制限を分散できるようにするコンセプトのやつ

*2:githubは、OAuth通すので、プライベートリポジトリも行けそうな雰囲気(試してはいないです)

*3:ビルド中にAWSリソースにアクセスする場合などは、必要な権限を持つRoleを作っておいてそれを選択します。

*4:rnbinのbuild.shは未指定だとMac用のバイナリを作っています