GKEでGoアプリを動かすためのCI/CDパイプラインを構築する
Kubernetes入門がてらGKEでGoのアプリを動かすためのCI/CDパイプラインを組んでみたのでその作業ログ。これはあくまで入門記事なので本番で運用するレベルではない。
GCPの設定
まずはGCPの設定から。準備としてやることは大体こんな感じ。
- GCPプロジェクトの作成 - 使用するAPIの有効化 - CI用のサービスアカウント作成、権限付与
※以下gcloudコマンドではターミナルのクライアントにて$ gcloud auth login
で認証済みであることを前提とする
GCPプロジェクト作成
コマンドでさくっと作る
$ gcloud projects create <project_name> --set-as-default
<project_name>
は任意のプロジェクト名に置き換える。グローバル?で一意な名前じゃないとプロジェクトIDが少し変わるので面倒。できれば一意なプロジェクト名にしたい。
--set-as-default
を指定することでこれ以降叩くコマンドにいちいちプロジェクトを指定する必要がなくなる。
設定を確認
$ gcloud config list
あと念のため^で作ったプロジェクトが請求先アカウントと接続済みであることを確認しておく
gcr apiを有効にする
CircleCIでビルドしたコンテナイメージをGCRにpushする必要があるので、gcr apiを有効にする
$ gcloud services enable containerregistry.googleapis.com
有効になったか確認
$ gcloud services list
サービスアカウントを作成する
CircleCIからGCRへコンテナイメージをpushするためのサービスアカウントを用意
$ gcloud iam service-accounts create <service_account_name> --display-name "Account for upload docker image from circleci"
<service_account_name>
はそれっぽいアカウント名に置き換える。circleci
とかで一旦はよさそう
サービスアカウントに権限を付与する
$ gcloud projects add-iam-policy-binding <project_id> --member serviceAccount:<service_account_name>@gke-go-sample.iam.gserviceaccount.com --role roles/storage.admin
<project_id>
は先ほど作成したプロジェクトのプロジェクトIDに置き換える。プロジェクトIDはGCPコンソール画面で確認できる
Goアプリケーション
単純にHello, worldを返すだけのAPIを用意する
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, world!") }) log.Fatal(http.ListenAndServe(":8080", nil)) }
Dockerfileとかは以下 github.com
CircleCI
CIrcleCIではlintとtest, イメージのbuildとpushを行う
以下.circleci/config.yml
version: 2.1 executors: default: docker: - image: circleci/golang:1.13 working_directory: /go/src/github.com/saekis/gke-go-sample environment: - GO111MODULE: "on" gcloud: docker: - image: google/cloud-sdk working_directory: /go/src/github.com/saekis/gke-go-sample environment: - GO111MODULE: "on" commands: restore_module: steps: - restore_cache: name: Restore go modules cache key: go-mod-{{ .Branch }}-{{ checksum "go.mod" }} save_module: steps: - save_cache: name: Save go modules cache key: go-mod-{{ .Branch }}-{{ checksum "go.mod" }} paths: - /go/pkg/mod/cache jobs: setup: executor: name: default steps: - checkout - restore_module - run: go mod download - save_module lint: executor: name: default steps: - checkout - restore_module - run: name: Download lint tool command: go get -u golang.org/x/lint/golint - run: name: golint command: golint -set_exit_status -min_confidence=1.1 ./... - run: name: go vet command: go vet ./... test: executor: name: default steps: - checkout - restore_module - run: name: test command: go test -v ./... build: parameters: env: type: string executor: name: gcloud steps: - checkout - setup_remote_docker - run: name: Authenticate gcloud service account command: | echo $GCP_SERVICE_KEY > gcloud-service-key.json gcloud auth activate-service-account --key-file gcloud-service-key.json gcloud auth configure-docker --quiet - run: name: Build golang app image command: | docker build -t asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_BUILD_NUM} -f docker/golang/Dockerfile . docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:latest if [ -n "${CIRCLE_TAG}" ]; then docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app:${CIRCLE_TAG} fi - run: name: Build nginx image command: | docker build -t asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_BUILD_NUM} -f docker/nginx/Dockerfile . docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:latest if [ -n "${CIRCLE_TAG}" ]; then docker tag asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_BUILD_NUM} asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx:${CIRCLE_TAG} fi - run: name: Push golang image to GCR command: docker push asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-go-app - run: name: Push nginx image to GCR command: docker push asia.gcr.io/${GCP_PROJECT_NAME}/<< parameters.env >>-nginx workflows: build_and_deploy: jobs: - setup - lint: requires: - setup - test: requires: - setup - build: name: build_dev env: dev requires: - lint - test filters: branches: ignore: - stage - master - build: name: build_stg env: stg requires: - lint - test filters: branches: only: stage - build: name: build_prod env: prod requires: - lint - test filters: branches: only: master
プロジェクト名とサービスアカウントのアクセスキーはCircleCIの環境変数にセットする
一応ここまででGitHubにpushされるとCircleCIが動いてGCRにコンテナイメージがpushされるようになる。
GKEの設定
クラスタ作成
$ gcloud container clusters create <cluster_name>
<cluster_name>
は任意のクラスタ名を指定する
クラスタ作成は結構時間かかるので少し待つ
確認
$ kubectl config get-contexts
マニフェスト作成
deplayment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: gke-go-sample-backend labels: app: gke-go-sample-backend spec: replicas: 1 selector: matchLabels: app: gke-go-sample-backend template: metadata: labels: app: gke-go-sample-backend spec: containers: - name: nginx image: asia.gcr.io/gke-go-sample/prod-nginx:latest imagePullPolicy: Always ports: - containerPort: 80 - name: golang image: asia.gcr.io/gke-go-sample/prod-app:latest imagePullPolicy: Always ports: - containerPort: 8080
nginxをリバースプロキシに設定してるので、nginxのportを80、コンテナ間通信するためにgolangのコンテナのポートを8080にする
またコンテナを公開するのでingressも設定する ingress.yaml
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: gke-go-sample-backend-ingress spec: rules: - http: paths: - path: /* backend: serviceName: gke-go-sample-backend servicePort: 80
全てのマニフェストファイルは以下
GKEへデプロイ
マニフェストファイルが用意できたらGKEにデプロイする
$ kubectl apply -f ./k8s
これでマニフェストファイルで設定した項目がGKEに反映される。 コンソールでingressのexternal ipにアクセスしてHello, worldが表示されれば成功。