さて、今日はMakefileとCIについて見ていきたいと思います。
昨日の時点でMakefileは以下のような状態になりました。
SERVICE := qiita-advent-calendar-2019
GIT_HASH := $(shell git rev-parse HEAD)
LINKFLAGS := -X main.gitHash=$(GIT_HASH)
.PHONY: install
install:
go get -v ./...
$(SERVICE):
go build -ldflags '$(LINKFLAGS)' .
.PHONY: clean
clean:
@rm -f $(SERVICE)
lint
リンターは golangci/golangci-lint を使用します。
リンターのダウンロードとリンターを走らせるタスクをそれぞれ定義します。
LINTER_EXE := golangci-lint
LINTER := $(GOPATH)/bin/$(LINTER_EXE)
$(LINTER):
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
LINT_FLAGS :=--enable golint,unconvert,unparam,gofmt
.PHONY: lint
lint: $(LINTER)
$(LINTER) run $(LINT_FLAGS)
実行してみましょう。
$ make lint
/<GOPATH>/bin/golangci-lint run --enable golint,unconvert,unparam,gofmt
何も表示されなければオッケーです。
test
まだテストは書いていませんが、テストを走らせるタスクを定義しておきましょう。
TEST_FLAGS := -v -cover -timeout 30s
.PHONY: test
test:
go test $(TEST_FLAGS) ./...
$ make test
go test -v -cover -timeout 30s ./...
? github.com/KentaKudo/qiita-advent-calendar-2019 [no test files]
all
バイナリを作成する際に実行しておきたいタスクを簡単のためにallとしてまとめておきましょう。CIで実行する際に便利です。
.PHONY: build
build: $(SERVICE)
.PHONY: all
all: install lint test clean build
$ make all
go get -v ./...
/<GOPATH>/bin/golangci-lint run --enable golint,unconvert,unparam,gofmt
go test -v -cover -timeout 30s ./...
? github.com/KentaKudo/qiita-advent-calendar-2019 [no test files]
go build -ldflags '-X main.gitHash=61374325f9c4b3a6258bd480618ac295a6ebf55b' .
docker-image
昨日Dockerのイメージをビルドしましたが、これもMakefileに書いておきましょう。
DOCKER_REGISTRY=docker.io
DOCKER_REPOSITORY_NAMESPACE=kentakudo
DOCKER_REPOSITORY_IMAGE=$(SERVICE)
DOCKER_REPOSITORY=$(DOCKER_REGISTRY)/$(DOCKER_REPOSITORY_NAMESPACE)/$(DOCKER_REPOSITORY_IMAGE)
DOCKER_IMAGE_TAG=$(GIT_HASH)
.PHONY: docker-image
docker-image:
docker build -t $(DOCKER_REPOSITORY):$(DOCKER_IMAGE_TAG) . \
--build-arg SERVICE=$(SERVICE)
コマンドをみやすくするため変数をいくつか定義しています。
またタグとしてgithashを使いました。
docker-image-push
ビルドだけでなくできたイメージをレジストリにプッシュするタスクも追加しておきましょう。
CI上でも実行したいので、レジストリにログインするタスクも用意しておきます。
.PHONY: docker-auth
docker-auth:
@docker login -u $(DOCKER_ID) -p $(DOCKER_PASSWORD) $(DOCKER_REGISTRY)
.PHONY: docker-build
docker-build: docker-image docker-auth
docker tag $(DOCKER_REPOSITORY):$(DOCKER_IMAGE_TAG) $(DOCKER_REPOSITORY):latest
docker push $(DOCKER_REPOSITORY)
ログインに必要な情報はコマンドラインから与えます。
$ DOCKER_ID=kentakudo DOCKER_PASSWORD=<redacted> make docker-build
...
The push refers to repository [docker.io/kentakudo/qiita-advent-calendar-2019]
ddfebc023a12: Pushed
1b7f5ec5366c: Pushed
f1b5933fe4b5: Layer already exists
084b0a196fb9b3b96bb7b5b638b0087cfc4b2516: digest: sha256:1dd6b3aca580cd2cfa28d3ead40863d56494d4294275e9dafa866cbcf1e98592 size: 949
ddfebc023a12: Layer already exists
1b7f5ec5366c: Layer already exists
f1b5933fe4b5: Layer already exists
latest: digest: sha256:1dd6b3aca580cd2cfa28d3ead40863d56494d4294275e9dafa866cbcf1e98592 size: 949
CI
ここまでで必要なmakeタスクは揃いました。CIのおおまかな流れは以下の通りです。
- master以外のブランチ:
make all
を実行し、テストとリントが通過することをチェック。 - masterブランチ:再び
make all
を実行後、make docker-image-push
でコンテナイメージの作成とレジストリへのプッシュ。
今回はCircleCIで動作を確認してみましょう。
version: 2
jobs:
build:
working_directory: ~/qiita-advent-calendar-2019
docker:
- image: circleci/golang:1
steps:
- checkout
- run: make all
deploy:
working_directory: ~/qiita-advent-calendar-2019
docker:
- image: circleci/golang:1
steps:
- checkout
- run: make all
- setup_remote_docker:
- run: make docker-build
workflows:
version: 2
development:
jobs:
- build:
filters:
branches:
ignore: master
deployment:
jobs:
- deploy:
filters:
branches:
only: master
実行時に必要な変数(Dockerレジストリへのログイン情報)をコンソール上で設定しておいてください。
おまけ
masterブランチではさらにもう1つ、make kubernetes-push
というタスクを実行します。これは作成したイメージをもとに開発環境のk8sクラスタ上のアプリケーションに対してパッチHTTPリクエストを投げるタスクです。PRのマージ時に自動的に開発環境へのデプロイもしています。
今回はminikubeを使用しているため実行はしませんが以下のような雰囲気です。
K8S_NAMESPACE=qiita
K8S_DEPLOYMENT_NAME=$(SERVICE)
K8S_CONTAINER_NAME=$(SERVICE)
K8S_URL=https://<dev env>/apis/apps/v1/namespaces/$(K8S_NAMESPACE)/deployments/$(K8S_DEPLOYMENT_NAME)
K8S_PAYLOAD={"spec":{"template":{"spec":{"containers":[{"name":"$(K8S_CONTAINER_NAME)","image":"$(DOCKER_REPOSITORY):$(DOCKER_IMAGE_TAG)"}]}}}}
.PHONY: kubernetes-push
kubernetes-push:
test "$(shell curl -o /dev/null -w '%{http_code}' -s -X PATCH -k -d '$(K8S_PAYLOAD)' -H 'Content-Type: application/strategic-merge-patch+json' -H 'Authorization: Bearer $(K8S_AUTH_TOKEN)' '$(K8S_URL)')" -eq "200"
本番環境へのデプロイの自動化はまた違った方法をとっていますがそれに関してはまた後日触れられたらなと思います。
今日はこんなところで。
明日はヘルスチェックのエンドポイントを実装していきたいと思います。