🐈
Cloud Run CI/CD完全ガイド
Cloud Run CI/CD完全ガイド
cloudbuild.yamlを使用して、Cloud RunでCI/CDを実装します。
- golang
- dockertest
- PostgreSQL
Flow
- GitHubリポジトリにpush
- イメージをbuild and test
- Artifact Registryにイメージをpush
- Cloud Runにdeploy
Buildトリガーを設定
ここで、リポジトリや環境変数を設定します。
↓ 例
環境変数名 | 意味 | 例 |
---|---|---|
_CLOUDSQL_NAME | CloudSQLの接続名 | myproject:us-central1:myinstance |
_IMAGE_NAME | Artifact Registryに登録するイメージの名前(URL) | us-central1-docker.pkg.dev/my-project/my-repo/my-app |
_SERVICE_NAME | Cloud Runのサービス名 | my-service |
_REGION | リージョン | us-central1 |
テストコードを作成
golangのdockertestを使用し、PostgreSQLのテストコードを作成します。
pathはcmd/server/main_test.goとしておきます。
package main
import (
"database/sql"
"fmt"
"os"
"testing"
"time"
_ "github.com/lib/pq"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
log "github.com/sirupsen/logrus"
)
var db *sql.DB
func TestMain(m *testing.M) {
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not construct pool: %s", err)
}
err = pool.Client.Ping()
if err != nil {
log.Fatalf("Could not connect to Docker: %s", err)
}
// pulls an image, creates a container based on it and runs it
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: "postgres",
Tag: "15",
Env: []string{
"POSTGRES_PASSWORD=secret",
"POSTGRES_USER=user_name",
"POSTGRES_DB=dbname",
"listen_addresses = '*'",
},
}, func(config *docker.HostConfig) {
// set AutoRemove to true so that stopped container goes away by itself
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
})
if err != nil {
log.Fatalf("Could not start resource: %s", err)
}
hostAndPort := resource.GetHostPort("5432/tcp")
databaseUrl := fmt.Sprintf("postgres://user_name:secret@%s/dbname?sslmode=disable", hostAndPort)
log.Println("Connecting to database on url: ", databaseUrl)
resource.Expire(120) // Tell docker to hard kill the container in 120 seconds
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
pool.MaxWait = 120 * time.Second
if err = pool.Retry(func() error {
db, err = sql.Open("postgres", databaseUrl)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
//Run tests
code := m.Run()
// You can't defer this because os.Exit doesn't care for defer
if err := pool.Purge(resource); err != nil {
log.Fatalf("Could not purge resource: %s", err)
}
os.Exit(code)
}
func TestRealbob(t *testing.T) {
// all tests
}
cloudbuild.yamlを作成
steps:
# Pull image from cache
- name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args: ['-c', 'docker pull gcr.io/$PROJECT_ID/${_IMAGE_NAME}:latest || exit 0']
# Build the container image using cache
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', '${_IMAGE_NAME}:latest', '--cache-from', 'gcr.io/$PROJECT_ID/[IMAGE_NAME]:latest', '.']
# Push the container image to Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', '${_IMAGE_NAME}:latest']
# Deploy container image to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- '${_SERVICE_NAME}'
- '--image'
- '${_IMAGE_NAME}:latest'
- '--execution-environment=gen2'
- '--set-cloudsql-instances'
- '${_CLOUDSQL_NAME}'
- '--region'
- '${_REGION}'
- '--allow-unauthenticated'
- '--set-env-vars'
- 'POSTGRES_USER=user_name'
- '--set-env-vars'
- 'POSTGRES_PASSWORD=secret'
- '--set-env-vars'
- 'POSTGRES_DB=dbname'
# Run tests and save to file
- name: golang:1.20
entrypoint: /bin/bash
args:
- -c
- |
go install github.com/jstemmer/go-junit-report/v2@latest
2>&1 go test -timeout 1m -v ./cmd/server/... | /go/bin/go-junit-report -set-exit-code -iocopy -out ${SHORT_SHA}_test_log.xml
waitFor: ['-']
substitutions:
_CLOUDSQL_NAME: ${_CLOUDSQL_NAME}
_IMAGE_NAME: ${_IMAGE_NAME}
_SERVICE_NAME: ${_SERVICE_NAME}
_REGION: ${_REGION}
images:
- '${_IMAGE_NAME}:latest'
options:
logging: CLOUD_LOGGING_ONLY
machineType: 'E2_HIGHCPU_32'
cloudbuild.yamlの解説
環境変数
substitutions:
_CLOUDSQL_NAME: ${_CLOUDSQL_NAME}
_IMAGE_NAME: ${_IMAGE_NAME}
_SERVICE_NAME: ${_SERVICE_NAME}
_REGION: ${_REGION}
トリガーで設定した環境変数をここで使えるように設定します。
テストの実行
# Run tests and save to file
- name: golang:1.20
entrypoint: /bin/bash
args:
- -c
- |
go install github.com/jstemmer/go-junit-report/v2@latest
2>&1 go test -timeout 1m -v ./cmd/server/... | /go/bin/go-junit-report -set-exit-code -iocopy -out ${SHORT_SHA}_test_log.xml
waitFor: ['-']
options:
logging: CLOUD_LOGGING_ONLY
machineType: 'E2_HIGHCPU_32'
テストを並列実行することで、ビルドの時間を短縮しています。
さらに、CPUの性能を上げることで、さらにビルドの時間を短縮します。そのお金がかかるので、注意が必要です。
Discussion