1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

さて、今日は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"

本番環境へのデプロイの自動化はまた違った方法をとっていますがそれに関してはまた後日触れられたらなと思います。
今日はこんなところで。


明日はヘルスチェックのエンドポイントを実装していきたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?