はじめに
- Google Cloud Build は GCP のビルドサービスでリポジトリへの push などをきっかけにコードのビルドやデプロイなどを自動で行うことができる。
- GCP のサービスだけあって Go のビルド環境は標準で用意されているが、若干クセがあったのでビルド方法をまとめる。
ビルドと Docker イメージの作成
-
go install .
だけでビルド可能なソースコードを対象とする - Go バイナリの作成を行い、作成したバイナリを実行するだけの Docker イメージをビルドし、イメージを Google Container Registry へアップロードするところまでを行う
ビルド対象のソースコード
main.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
Cloud Build 設定ファイルの作成
- Cloud Build によるビルド処理内容の指定は
cloudbuild.yaml
で行う。 -
steps
には実行する処理を順に記載する。 -
steps
の個々の処理は Docker コンテナ上にて実行される。- ステップごとに指定した Docker コンテナが立ち上がり、ソースコードをマウントして、指定した引数で処理を実行する。
- git, docker, kubectl, gcloud, gsutils, go などよく使われるコマンドを実行するためのコンテナは標準で用意されている。
- 自分でビルドして GCR に配置すれば任意のコマンドを実行するコンテナも利用することができる。
-
images
に指定された名前の Docker イメージが steps の中でビルドされると、ビルド処理終了時に GCR へ自動で push される
cloudbuild.yaml
steps:
# 標準の go コンテナでビルドを行う. 詳細は後述する.
- name: 'gcr.io/cloud-builders/go:alpine'
args: ['install', '.']
env: ['PROJECT_ROOT=myapp']
# 標準の docker コンテナで Docker イメージのビルドを行う. Docker ファイルは後述する.
- name: gcr.io/cloud-builders/docker
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp', '.']
# ビルドした Docker イメージを終了時に GCR に push する
images: ['gcr.io/$PROJECT_ID/myapp']
- Docker イメージのビルド時に利用される Dockerfile はこちら
FROM alpine:latest
WORKDIR /usr/local/bin/
# ビルドされたバイナリが gopath/bin/myapp に配置されているのでコピーする
COPY gopath/bin/myapp .
ENTRYPOINT ["/usr/local/bin/myapp"]
go コンテナの解説
- 標準の go コンテナ
gcr.io/cloud-builders/go
は中で何をやっているのかを理解していないと使いこなすのが難しいので解説していく
GOPATH 配下のソースコードをビルドしたい場合
- ソースコードが $GOPATH/src/github.com/foo/bar など GOPATH 配下に置いてある場合、環境変数 PROJECT_ROOT を使ってビルドを行う
cloudbuild.yaml
- name: 'gcr.io/cloud-builders/go:alpine'
args: ['install', '.']
env: ['PROJECT_ROOT=myapp']
- 上記の設定で
gcr.io/cloud-builders/go
が実行されると下記のような処理が走る
1. ソースコードを /workspace へマウント
2. /workspace のシンボリックリンクとして /workspace/gopath/src/$PROJECT_ROOT を作成
3. GOPATH を /workspace/gopath に設定
4. cd /workspace/gopath/src/$PROJECT_ROOT
- 結果的に実行時のコンテナ内のファイル構成は以下のようになる
/
workspace <- ソースコードが自動でマウントされるディレクトリ
main.go <- ソースコードの main.go
gopath <- 初期化時にここが GOPATH に指定される
src
myapp <- 初期化時に /workspace のシンボリックリンクとして gopath/src/$PROJECT_ROOT が作成される
main.go <- /workspace/main.go と同じファイル
- このようにコンテナの初期化処理でソースコードのコピーと GOPATH の指定をすることで、各種 go tool でソースコードを操作しやすいようにお膳立てしてくれる
- 初期化処理後のワーキングディレクトリは
/workspace/gopath/src/myapp
となるのでgo install .
でビルドが実行され、GOPATH で指定された/workspace/gopath/bin/myapp
にビルドしたバイナリが配置される - 実際の初期化処理はコンテナのコードから確認できる
GOPATH を含むソースコードをビルドしたい場合
- リポジトリの内部に GOPATH を内包している場合は下記のように設定する
cloudbuild.yaml
- name: 'gcr.io/cloud-builders/go:alpine'
args: ['install', '.']
env: ['GOPATH=path/to/gopath']
- この場合は PROJECT_ROOT 指定時のような各種初期化処理は実行されず、そのまま GOPATH が指定され、ワーキングディレクトリは
/workspace
のままである。
内部のパッケージを参照しているコードのビルド
- 同一リポジトリ内部のパッケージを参照しているコードをビルドする場合は PROJECT_ROOT でパッケージが解決できるように指定する必要がある
ビルド対象のソースコード
main.go
package main
import (
"fmt"
"github.com/nirasan/myapp/hello"
)
func main() {
fmt.Println(hello.Hello())
}
hello/hello.go
package hello
func Hello() string {
return "hello world from subdir"
}
Cloud Build 設定
- ソースコードは
/workspace/gopath/src/$PROJECT_ROOT
に配置され、GOPATH=/workspace/gopath
になるのでimport
が解決できるように PROJECT_ROOT を指定する
cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/go:alpine'
args: ['install', '.']
env: ['PROJECT_ROOT=github.com/nirasan/myapp']
- name: gcr.io/cloud-builders/docker
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp', '.']
images: ['gcr.io/$PROJECT_ID/myapp']
dep で依存関係を管理しているコードをビルドする
- dep コマンド用のコンテナは標準で用意されていないためちょっと工夫が必要
- cloudbuild.yaml の設定では entrypoint オプションを指定することで任意のコマンドを実行することができる
- これを利用して dep のインストールと dep ensure の実行を行う
- entrypoint を変更して実行した場合、通常の entrypoint 使用時には自動で実行される処理(設定ファイルの prepare_workspace の部分)を手動で実行する必要があるので注意が必要
cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/go:alpine'
entrypoint: sh
args:
- '-c'
- |
# /workspace/gopath/src/$PROJECT_ROOT の作成と GOPATH の指定をするコマンドを直接実行する
. /builder/prepare_workspace.inc
prepare_workspace
# dep のインストールと実行
go get github.com/golang/dep/cmd/dep
./gopath/bin/dep ensure
# インストールの実行
go install .
env: ['PROJECT_ROOT=myapp']
- name: gcr.io/cloud-builders/docker
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp', '.']
images: ['gcr.io/$PROJECT_ID/myapp']
参考URL
- https://cloud.google.com/cloud-build/docs/api/custom-build-steps?hl=ja#entrypoint
- https://www.slideshare.net/lestrrat/google-container-builder-87244724
dep で github.com のプライベートリポジトリに依存したコードをビルドする
- dep で github.com のプライベートリポジトリを参照する際には dep 公式 FAQ の通りに github.com のアクセストークンを発行して .netrc を用意する
- アクセストークンの発行手順はこちら
cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/go:alpine'
entrypoint: sh
args:
- '-c'
- |
echo === CREATE GOPATH ===
. /builder/prepare_workspace.inc
prepare_workspace
echo === DEP ===
cat <<EOF >~/.netrc
machine github.com
login $_GITHUB_USERNAME
password $_GITHUB_ACCESS_TOKEN
EOF
go get github.com/golang/dep/cmd/dep
./gopath/bin/dep ensure
echo === INSTALL ===
go install .
env: ['PROJECT_ROOT=myapp']
- name: gcr.io/cloud-builders/docker
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp', '.']
images: ['gcr.io/$PROJECT_ID/myapp']
-
$_GITHUB_USERNAME
と$_GITHUB_ACCESS_TOKEN
はトリガーの変数で指定したり gcloud コマンドのオプションで置換する - 変数置換を含めた gcloud コマンドはこちら
gcloud builds submit --config=cloudbuild.yaml \
--substitutions=_GITHUB_USERNAME="username",_GITHUB_ACCESS_TOKEN="token" .
dep 用の独自コンテナを作って実行する
- entrypoint オプションを駆使するのが独自若干複雑なのでコンテナを作成して dep を実行する
- github.com のプライベートリポジトリにも対応する
cloudbuild.yaml
steps:
# dep コンテナをビルドする. Dockerfile は後述.
- name: gcr.io/cloud-builders/docker
args: ['build', '-f', 'Dockerfile.dep', '-t', 'gcr.io/$PROJECT_ID/dep', '.']
# ビルドした dep コンテナを使って dep ensure を実行する
# ビルドしたイメージは Cloud Build の Docker デーモン中に保存されているので、前のステップで作成したコンテナがそのまま利用できる
# もちろん先に dep コンテナをビルドして GCR に push しておき、このステップから実行することも可能
- name: gcr.io/$PROJECT_ID/dep
args: ['ensure']
env:
- "PROJECT_ROOT=myapp"
- "_GITHUB_USERNAME=$_GITHUB_USERNAME"
- "_GITHUB_ACCESS_TOKEN=$_GITHUB_ACCESS_TOKEN"
# go のビルド
- name: gcr.io/cloud-builders/go
args: ['install', '.']
env: ['PROJECT_ROOT=myapp']
# Docker イメージのビルド
- name: gcr.io/cloud-builders/docker
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp', '.']
# Docker イメージの push
images:
- 'gcr.io/$PROJECT_ID/myapp'
- dep コンテナ用の Dockerfile はこちら
- エントリーポイントには単純に dep コマンドを指定するのではなく、go コンテナと同様の初期化処理を実行するスクリプトにする
Docker.dep
FROM gcr.io/cloud-builders/go:alpine
RUN go get github.com/golang/dep/cmd/dep
COPY dep.ash /builder/bin
RUN chmod +x /builder/bin/dep.ash
ENTRYPOINT ["/builder/bin/dep.ash"]
- go コンテナと同様の初期化処理を行うエントリーポイントスクリプトがこちら
- github.com のプライベートリポジトリ用に .netrc の用意もやっている
dep.ash
#!/bin/ash
. /builder/prepare_workspace.inc
prepare_workspace || exit
if [[ "$_GITHUB_USERNAME" != "" ]]; then
cat <<EOF >~/.netrc
machine github.com
login $_GITHUB_USERNAME
password $_GITHUB_ACCESS_TOKEN
EOF
fi
/root/go/bin/dep "$@"
おわりに
-
gcr.io/cloud-builders/go
はクセがあって慣れるまでは面倒 - だが、自分で汎用的な go のビルドコンテナを作ろうとすると結局同じような感じにはなりそうな気がするので結果的に標準で用意してくれてありがとうという感じになってきた