はじめに
Cloud BuildはGCP上でCI/CDを行うためのサービスです。
特にコンテナを扱ったソリューションと相性がよく、
テストやビルドはGCP上にプロビジョニングされたコンテナ内で行われ、
Cloud FunctionsやCloud Runといった様々なコンピューティングサービス上にデプロイすることができます。
本記事では、
- ソースコードの管理:Cloud Source Repositories
- テスト及びビルド:Cloud Build
- コンテナレジストリ:Container Registry
- デプロイ:Cloud Run
の全てGCP内で完結するリソースを使ってコンテナアプリケーションにおけるCI/CD環境を構築したいと思います!
本記事で使用したソースコードは、こちらにあります。
開発環境
- macOS Catalina 10.15.6
- Docker Desktop stable 2.3.0.4
- Google Cloud SDK 308.0
登場人物たち
Cloud Source Repositories
プライベートなGitリポジトリをホスティングできるサービスです。
https://cloud.google.com/source-repositories/docs?hl=ja
Container Registry
プライベートなDockerイメージをホスティングできるサービスです。
https://cloud.google.com/container-registry/docs?hl=ja
Cloud Run
マネージドなコンテナ実行プラットフォームです。
コンテナを手軽にサービスとしてデプロイ、公開可能です。
https://cloud.google.com/run/docs?hl=ja
Cloud Build
本記事の主役です。
冒頭にもあるように、マネージドなCI/CD環境を構築できます。
詳しくは後の項で説明しますが、ビルドコンテナ内でgcloudコマンド叩けたりと柔軟なシナリオが作成可能です。
https://cloud.google.com/cloud-build/docs?hl=ja
作ってみよう!
ローカルにGitリポジトリを作成
適当な空のディレクトリを作成し、git init
を行います。
$ mkdir cloudbuild-sample && cd cloudbuild-sample
$ git init
サンプルアプリケーションの作成
Cloud Runにデプロイするアプリケーションを作成します。
AとBの和を返す簡単なアプリを考えます。
$ mkdir workspace
module mymod
go 1.15
package main
import (
"encoding/json"
"fmt"
"log"
"mymod/utils"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
j := json.NewDecoder(r.Body)
var req struct {
A int `json:"a"`
B int `json:"b"`
}
j.Decode(&req)
fmt.Fprintf(w, "%d\n", utils.Add(req.A, req.B))
})
log.Println("start server...")
http.ListenAndServe(":8080", mux)
}
足し算の関数を定義するサブパッケージを作成します。
$ mkdir workspace/utils
package utils
// Add return a + b
func Add(a int, b int) int {
return a + b
}
package utils
import "testing"
func TestSuccessAdd(t *testing.T) {
expect := 3
result := Add(2, 1)
if expect != result {
t.Fatalf("result is expected %d, got %d ", expect, result)
}
}
func TestFailureAdd(t *testing.T) {
t.Fatal("FAILURE!!")
}
CI実行時にテストの失敗を考慮されるかを確認するため、意図的に失敗するテストコードを作成しました。
さらに、公開用のDockerイメージファイルも作成します。
FROM golang:1.15-alpine as builder
WORKDIR /opt/build
COPY go.* ./
RUN go mod download
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o app
FROM alpine:3.12
ENV PORT 8080
COPY --from=builder /opt/build/app /opt/app
WORKDIR /opt
CMD ["./app"]
お疲れ様です!
上記のアプリは、
$ curl -X POST -H "Content-Type: application/json" -d '{"a":41, "b":10}' http://localhost:8080
51
のようなリクエストに対して、aとbの和を返してくれます。
gcloudコマンドの下準備
前提として、既にGCPプロジェクトが作成済みでgcloudコマンドが使用できることを確認してください。
ここではパイプライン環境構築のためのGCPプロジェクト側での設定を行います。
まずは利用するサービスのAPIを有効化しましょう!
$ gcloud services enable sourcerepo.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
containerregistry.googleapis.com
次にSource Repositoriesにリポジトリを作成し、リモートにセットします。
$ gcloud source repos create cloudbuild-sample
$ git remote add google https://source.developers.google.com/p/{YOUR_PROJECT_ID}/r/cloudbuild-sample
さらにリポジトリに対し認証情報を付与します。
$ git config --local credential.https://source.developers.google.com.helper gcloud.sh
ここまでのコードをpushできるか確認してましょう。
$ git add .
$ git commit -m 'first commit'
$ git push google master
次にCloud Buildのサービスアカウントに対しCloud Runへデプロイするための役割を付与します。
$ gcloud projects add-iam-policy-binding {YOUR_PROJECT_ID} \
--member serviceAccount:{YOUR_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com \
--role roles/run.admin
$ gcloud projects add-iam-policy-binding {YOUR_PROJECT_ID} \
--member serviceAccount:{YOUR_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com \
--role roles/iam.serviceAccountUser
お疲れ様です!
これで下準備は完了です。
Cloud Buildの構成ファイルを作成する。
Cloud Buildではテストやビルドの手順をYAMLファイルとして定義します。
簡単な例として、前項で作成したサンプルアプリケーションのテストを実行し、
成功したらデプロイ用のイメージをビルドする手順書を作成してみます。
steps: # steps下にテスト〜デプロイまでの各手順を定義します。
- name: 'golang:1.15-alpine' # コマンドを実行するコンテナイメージ
id: 'do testing' # 各ステップを表す識別子
entrypoint: '/bin/sh' # コンテナが実行される際のエントリーポイント
env: # コンテナが実行される際に渡される環境変数
- 'CGO_ENABLED=0'
dir: 'workspace' # エントリーポイントが実行されるディレクトリ
args: # エントリーポイントに対する引数
- '-c'
- |
go mod download \
&& go test mymod/...
- name: 'gcr.io/cloud-builders/docker' # 注1
id: 'do building'
args: # エントリーポイントは docker です。
- 'build'
- '-t'
- 'gcr.io/$PROJECT_ID/sample_app:$SHORT_SHA' # 注2
- '-f'
- './Dockerfile'
- './workspace'
設定しているオプションをよく見てみると、
普段のdocker run
コマンドを構成するオプションと似ていることが確認できるかと思います。
注1
nameオプションに設定できるコンテナイメージですが、
クラウドビルダーと呼ばれるGCP側で用意された特別なコンテナイメージも利用可能です。
クラウドビルダーコンテナにはIAM等が適切に割り当てられた環境でgcloud
コマンド等が実行できるため、
柔軟なパイプラインを作成することが可能になります。
利用可能なクラウドビルダーについては
https://cloud.google.com/cloud-build/docs/cloud-builders?hl=ja
を確認してみてください。
注2
$PROJECT_ID
や$SHORT_SHA
といった、いくつかの変数はCloud Build側で適切な値に自動で置換されます。
今回の例で使用した
$PROJECT_ID
は、GCPプロジェクトのID
$SHORT_SHA
は、ビルドをトリガーしたリポジトリへのコミットID
です。
変数値の置換については、
https://cloud.google.com/cloud-build/docs/configuring-builds/substitute-variable-values?hl=ja
を確認してみてください。
それでは完全なCloud Buildの構成ファイルを作成してみましょう!
下記の構成ファイルでは、
-
go test
を実行しテストが全てパスされるかを確認 - デプロイ用のコンテナイメージをビルド
- ビルドされたコンテナイメージをGCRへpush
- GCRへプッシュされたイメージを使い、Cloud Runへサービスをデプロイ
を行います。
steps:
- name: 'golang:1.15-alpine'
id: 'do testing'
entrypoint: '/bin/sh'
env:
- 'CGO_ENABLED=0'
dir: 'workspace'
args:
- '-c'
- |
go mod download \
&& go test mymod/...
- name: 'gcr.io/cloud-builders/docker'
id: 'do building'
args:
- 'build'
- '-t'
- 'gcr.io/$PROJECT_ID/sample_app:$SHORT_SHA'
- '-f'
- './Dockerfile'
- './workspace'
- name: 'gcr.io/cloud-builders/docker'
id: 'do pushing image'
args:
- 'push'
- 'gcr.io/$PROJECT_ID/sample_app:$SHORT_SHA'
- name: 'gcr.io/cloud-builders/gcloud'
id: 'do deploying'
args:
- 'run'
- 'deploy'
- 'sample-app'
- '--image=gcr.io/$PROJECT_ID/sample_app:$SHORT_SHA'
- '--region=asia-northeast1'
- '--platform=managed'
- '--allow-unauthenticated'
お疲れ様です!
パプリックなコンテナイメージとクラウドビルダーを使い分け様々なシナリオに対応できることが実感できたかと思います。
構成ファイルに関する正確な情報はこちらをご確認ください。
https://cloud.google.com/cloud-build/docs/build-config?hl=ja
Source Repositoriesへのpushでビルドをトリガーする。
最後に、Source Repositoriesへコードがプッシュされたときに自動的にCloud Buildが走るようトリガーを設定します。
$ gcloud beta builds triggers create cloud-source-repositories \
--repo cloudbuild-sample \
--branch-pattern="master" \
--build-config cloudbuild.yaml
cloudbuild-sampleリポジトリのmasterブランチにコードがpushされた際に、
cloudbuild.yamlに定義された内容のパイプラインが実行されます。
お疲れ様です!
作業は以上となります。
それではビルドの様子を確認していきたいと思います。
ビルドを実行してみよう!
これまでの内容をpushし、Cloud Buildが実行されるか確認してみます。
$ git add .
$ git commit -m 'second commit'
$ git push google master
下記のURLからビルドの履歴を確認できます。
https://console.cloud.google.com/cloud-build/builds?hl=ja
今回のビルドではgo test
に失敗し、ビルドが途中で中断されていることが確認できるかと思います。
テストコードを修正し、もう一度ビルドを実行しましょう。
package utils
import "testing"
func TestSuccessAdd(t *testing.T) {
expect := 3
result := Add(2, 1)
if expect != result {
t.Fatalf("result is expected %d, got %d ", expect, result)
}
}
func TestFailureAdd(t *testing.T) {
// t.Fatal("FAILURE!!") # コメントアウトしました。
}
再びpushします。
$ git add .
$ git commit -m 'fix test code'
$ git push google master
ビルドが正常に完了したことを確認します。
https://console.cloud.google.com/cloud-build/builds?hl=ja
さらに、正常にCloud Runへデプロイされたかを確認しましょう!
https://console.cloud.google.com/run?hl=ja
サービスのURLを確認し、curlを叩いてみます。
$ curl -X POST -H "Content-Type: application/json" -d '{"a":41, "b":10}' YOUR_CLOUD_RUN_URL
51
お疲れ様でした!
終わりに
本記事ではCloud Buildを使ってGo言語アプリケーションのCI/CDパイプラインを構築しました。
テストやビルドには様々なコンテナイメージを使えるためGo言語に限らず、柔軟なパイプラインを構築できるかと思います!
ありがとうございました!