GCF Goを利用する上でCI/CDを考えてみた
ざっくり要件
- 自動テスト
- 自動デプロイ
一旦上記部分を中心に考えてみました。
自動テスト
この辺はとりあえず必ず実行したいところ。
- go test
- lint, race detector (今回は割愛)
実行タイミングとしては、
- PR作成時
- PRにcommit追加時
- ブランチにpush/merge時
あたりに自動実行されることを想定。
実行するのはJenkinsやCircleCIなどのCIツール上。
ただし、本番・開発環境と合わせるために、CI上でコンテナを起動してその環境下で実行します。
まとめると、
- PR作成などをhookにしてCIが動く
- CI上でコンテナを立ち上げる
- コンテナ上でテストを実行する
を考えました。
以下、実現するための環境構築やらコマンドです。
※サンプルではCIにCircleCIを選択
Dockerfile
FROM golang:1.11-alpine
RUN apk add --update alpine-sdk --no-cache --virtual .build-deps
ENV GO111MODULE=on
COPY . /go/src/github.com/{YOUR_ACCOUNT}/{YOUR_REPOSITORY}
go testコマンド
$ go test ./... -v -cover
Circleciのconfig
version: '2'
jobs:
build:
machine: true
steps:
- checkout
- run:
name: Create docker image
command: |
docker build -t ${YOUR_IMAGE_NAME} .
- run:
name: Run container
command: |
docker run -it -d --name ${YOUR_CONTAINER_NAME} ${YOUR_IMAGE_NAME} sh
- run:
name: Testing
command: |
docker exec -it ${YOUR_CONTAINER_NAME} sh -c "cd /go/src/${YOUR_ACCOUNT}/${YOUR_REPOSITORY} && go test ./... -v -cover"
workflows:
version: 2
build:
jobs:
- build
上記を準備した上で
- circleciのGUIからgithubと連携
- githubのリポジトリセッティングからwebhookのトリガーとなるイベントを設定
します。
実際に動作させてみた訳ではないですが、こんな感じで実行できるはずです。
※コマンドミス等ありましたらご指摘ください。
要件によるかと思いますが、テスト実行が完了したらコンテナを削除した方がいいかもしれないです。
自動デプロイ
以下をトリガーにして自動で実行させる。
- 特定ブランチにpush/merge
- tag作成
今回はCI上からGCFにデプロイをすることを想定します。
早速デプロイの設定やらコマンドやらに行きたいのですが、手動でデプロイを試している段階で問題が発生しました。以下で簡単に触れます。
※この辺は切り出して記事にしたい
デプロイする上で直面したimport path問題
Goの1.11を利用していると、go.modファイルが作られます。そして、ローカル上で開発する際はgo.modファイル内でrequire, replaceディレクティブを使ってパッケージをimportする際の実態をコントールすることもあると思います。ローカルで開発している分には問題ないのですが、GCFにデプロイする際にパッケージが見つからないというエラーが返却されます。
issueなど諸々みてみたのですが、ローカルと本番環境や開発環境をうまく区別させる方法を見つけられませんでした。
※もしうまいやり方などあれば教えてください・・・!
import path問題を解決した方法
考えました。
ただ、これが正しいという訳ではないので参考として読んでください。
結論、 vendorディレクトリ
を使います。
せっかくModulesでvendorディレクトリとはさよならできたのにまた出てきたよ・・・とお思いの方もいるかと思います。すみません。
ただ、開発環境ではvendoringなど瑣末なことに気を取られたくないのですが、実行環境においてはvendorディレクトリがあってもいいしそれでもいいんじゃないかと考えました。
これでうまくデプロイすることができました。
なのでここからはデプロイ時にはvendorディレクトリで依存関係を管理すること前提で進めていきます。
ちょっと気をつけないといけない点などもあります。
vendorディレクトリで依存環境を管理する上での注意点
実際に試してみたところ以下の点に注意が必要でした。
- Goがインストールされている環境が必要
- go.modをデプロイ対象から外す
それでは、これらを踏まえてcircleciの設定やらその他諸々がどうなったか書いていきます。
手順
- circleci上で動かすデプロイ用のコンテナイメージを作成
- Goがインストールされている
- google cloud sdkがインストールされている
- .circleci/config.ymlにデプロイ用のjobを追加
- circleciのGUIからEnvironment Variablesからgcp連携用のキーをセット(base64でエンコードしたもの)
- circleciのGUIからEnvironment Variablesからimage取得用のパスワードをセット
- .gcloudignoreにgo.modを追加
デプロイ用のコンテナ作成
FROM google/cloud-sdk:alpine
RUN apk add --update alpine-sdk --no-cache
RUN curl -Lso go.tar.gz "https://dl.google.com/go/go1.11.linux-amd64.tar.gz" \
&& tar -C /usr/local -xzf go.tar.gz \
&& rm go.tar.gz
ENV PATH /usr/local/go/bin:$PATH
このファイルを元にイメージ作成し、GCPのコンテナレジストリなどにアップしておき、circleciのjobで使用します。
※cloud-sdkが入っているイメージをベースにそこにGoをインストールしています。
.circleci/config.yml
deploy:
docker:
- image: {YOUR_GCP_REGION}/{YOUR_PROJECT_ID}/{YOUR_IMAGE_NAME}:{TAG_VERSION}
auth:
username: _json_key
password: $GCLOUD_SERVICE_KEY_PLAIN
steps:
- checkout
- run:
name: Auth gcp
command: |
echo $GCLOUD_SERVICE_KEY | base64 -d > ${HOME}/gcloud-service-key.json
gcloud auth activate-service-account --key-file ${HOME}/gcloud-service-key.json
- run:
name: Setting gcp
command: |
gcloud --quiet config set project {YOUR_PROJECT_ID}
gcloud --quiet config set compute/zone {YOUR_PROJECT_ZONE}
- run:
name: Make vendor
command: |
go mod vendor
- run:
name: Deploy
command: |
gcloud functions deploy helloWorld --entry-point HelloWorld --runtime go111 --trigger-http
workflows:
version: 2
build-and-deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only:
- develop
ポイントは
- imageをpullする際のパスワードの設定
- go mod vendorでvendorディレクトリで依存関係を管理
- workflowsの中でdeployはbuildが成功してからとすること
- developブランチにpush/mergeされた時のみとすること(要件によります)
です。
.gcloudignore
go.mod, go.sumを追加します。
デプロイまとめ
これで一通り必要なところは書きました。
ちょっとトリッキーかもしれませんが、ローカル場ではimportパス問題など発生せずにデプロイできたので一旦良しとします。
まとめ
GCF GoのCI/CDを考えてきましたが、一筋縄ではいきませんでしたが、一応構築できる目処がたったので良しとします。サクッとできてしまうエキスパートな方を尊敬せずにはいられない・・・
今回circleciのjobは具体的に試していないので今後どこかの環境で試してみる予定です。
もしこういうやり方もあるよですとか、もっといいやり方あるよなどありましたら是非コメント頂きたいです。
番外編
今回はCI上からGCFにデプロイをすることを想定してまとめてみましたが、
後から調べてみると、GCPのCloud Source RepositoriesとCloud Buildを利用してのソリューションもあります。
個人的には、Cloud build用のymlファイルに分かりやすくかけますし、
デプロイコマンドのみをバージョン管理できそうなのでこっちの方がありなんじゃないかと思いました。
公式のサンプルから抜粋
https://cloud.google.com/cloud-build/docs/configuring-builds/build-test-deploy-artifacts
steps:
- name: 'gcr.io/cloud-builders/gcloud'
args:
- functions
- deploy
- [FUNCTION_NAME]
- --source=.
- --trigger-http
こういった形でデプロイコマンドのみにフォーカスしたファイルをgithub上で管理することになるので、保守上良いかなと思っています。