このブログ投稿シリーズでは、最適化されたコンテナ化されたGo開発環境を導入する方法を示します。 パート 1では、ローカルの Go 開発用にコンテナ化された開発環境を開始する方法を説明し、さまざまなプラットフォーム用のサンプル CLI ツールを構築しました。パート 2 では、Goの依存関係を追加する方法、より高速なビルドとユニットテストのためのキャッシング方法について説明しました。 この 3 番目と最後のパートでは、コード リンター、GitHub Action CI、および追加のビルド最適化を追加する方法を示します。
リンターの追加
優れたプログラミングプラクティスのチェックを可能な限り自動化したいので、セットアップにリンターを追加しましょう。 最初のステップは、ドッカーファイルを変更することです。
# syntax = docker/dockerfile:1-experimental
FROM --platform=${BUILDPLATFORM} golang:1.14.3-alpine AS base
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.* .
RUN go mod download
COPY . .
FROM base AS build
ARG TARGETOS
ARG TARGETARCH
RUN --mount=type=cache,target=/root/.cache/go-build \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/example .
FROM base AS unit-test
RUN --mount=type=cache,target=/root/.cache/go-build \
go test -v .
FROM golangci/golangci-lint:v1.27-alpine AS lint-base
FROM base AS lint
COPY --from=lint-base /usr/bin/golangci-lint /usr/bin/golangci-lint
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/root/.cache/golangci-lint \
golangci-lint run --timeout 10m0s ./...
FROM scratch AS bin-unix
COPY --from=build /out/example /
...
これで、使用したいリンターを含むゴランチリント画像のエイリアスであるリントベースステージができました。 次に、lintを実行するlintステージを用意し、キャッシュを正しい場所にマウントします。
単体テストに関しては、リンティング用のリントルールをMakefileに追加できます。 テストルールにエイリアスを付けて、リンターテストと単体テストを実行することもできます。
all: bin/example
test: lint unit-test
PLATFORM=local
.PHONY: bin/example
bin/example:
@docker build . --target bin \
--output bin/ \
--platform ${PLATFORM}
.PHONY: unit-test
unit-test:
@docker build . --target unit-test
.PHONY: lint
lint:
@docker build . --target lint
CI の追加
開発プラットフォームをコンテナ化したので、プロジェクトにCIを追加するのは本当に簡単です。 Dockerビルドを実行するか、CIスクリプトからコマンドを実行するだけで済みます。 これを実証するために、GitHub アクションを使用します。 これを設定するには、次の .github/workflows/ci.yaml
ファイルを使用できます。
name: Continuous Integration
on: [push]
jobs:
ci:
name: CI
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run linter
run: make lint
- name: Run unit tests
run: make unit-test
- name: Build Linux binary
run: make PLATFORM=linux/amd64
- name: Build Windows binary
run: make PLATFORM=windows/amd64
CIで実行するコマンドは、ローカルで使用するコマンドと同じであり、すべてがDockerfileですでに定義されているため、ツールチェーンの構成を行う必要がないことに注意してください。
最後の最適化
COPYを実行すると、コンテナイメージに追加のレイヤーが作成され、処理が遅くなり、余分なディスク容量が使用されます。 これは、ビルドコンテキスト、ステージ、またはイメージからマウントを使用して RUN --mount
バインドすることで回避できます。 このパターンを採用すると、結果の Dockerfile は次のようになります。
# syntax = docker/dockerfile:1-experimental
FROM --platform=${BUILDPLATFORM} golang:1.14.3-alpine AS base
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.* .
RUN go mod download
FROM base AS build
ARG TARGETOS
ARG TARGETARCH
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/example .
FROM base AS unit-test
RUN --mount=target=. \
--mount=type=cache,target=/root/.cache/go-build \
go test -v .
FROM golangci/golangci-lint:v1.27-alpine AS lint-base
FROM base AS lint
RUN--mount=target=. \
--mount=from=lint-base,src=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/root/.cache/golangci-lint \
golangci-lint run --timeout 10m0s ./...
FROM scratch AS bin-unix
COPY --from=build /out/example /
FROM bin-unix AS bin-linux
FROM bin-unix AS bin-darwin
FROM scratch AS bin-windows
COPY --from=build /out/example /example.exe
FROM bin-${TARGETOS} AS bin
デフォルトのマウントタイプは、コマンドで docker build
渡すコンテキストからの読み取り専用バインドマウントです。 これは、 COPY . .
と RUN --mount=target=.
コマンドを実行するためにコンテキストからのファイルが必要であるが、最終的なイメージに保持する必要がない場合。
Goモジュールのダウンロードを分離する代わりに、これを削除して、の /go/pkg/mod
キャッシュマウントを使用できます。
結論
この一連の投稿では、最適化されたコンテナ化された Go 開発環境を配置する方法と、この同じ環境を CI で使用する方法について説明しました。 このようなプロジェクトで開発したい人にとっての依存関係は、Dockerとmakeのみであり、後者はオプションで別のスクリプト言語に置き換えられます。
この例のソースは、私のGitHubにあります https://github.com/chris-crone/containerized-go-dev
実験的なDockerfile構文の詳細については、こちらをご覧ください。 https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
Docker でのビルドに興味がある場合は、Buildx リポジトリをご覧ください。 https://github.com/docker/buildx