Go のクロスコンパイルやらリリースやらの面倒を見てくれる便利な GoReleaser ですが、Docker イメージのビルドにも対応しています。
この記事では、GoReleaser を使用してマルチプラットフォーム (マルチ CPU アーキテクチャ) 対応の Docker イメージを作成する方法について紹介します。
記事中で使用する Go のバージョンは 1.19.1、GoReleaser のバージョンは 1.11.4 です。
サンプルプロジェクト
次のようなサンプルプロジェクトを例として扱います。
$ tree
.
├── go.mod
└── main.go
module github.com/frozenbonito/helloworld
go 1.19
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
このサンプルプロジェクトから linux/amd64
, linux/arm64
両対応の Docker イメージを作成します。
Dockerfile を作る
まずは Dockerfile を作成します。
Go のビルド自体は GoReleaser がやってくれるので、ビルド後のバイナリを COPY する感じで書けば OK です。
今回のサンプルプロジェクトの場合、module 名が helloworld
なのでデフォルトでは helloworld
というバイナリが生成されるはずです。
そのため Dockerfile は次のようになります。
FROM gcr.io/distroless/static-debian11
COPY helloworld /
ENTRYPOINT [ "/helloworld" ]
重要なのは COPY
命令の部分だけで、他は自由にカスタマイズ可能です。
GoReleaser の設定ファイルを作る
次に GoReleaser の設定ファイル .goreleaser.yaml
を作成します。
今回書く設定は主に Go のビルドに関する設定、Docker イメージのビルドに関する設定、Docker マニフェスト (マルチプラットフォーム対応イメージを実現するもの) に関する設定の 3 つに分けられます。
project_name: helloworld
builds:
# Go のビルドに関する設定
# ...
dockers:
# Docker イメージのビルドに関する設定
# ...
docker_manifests:
# Docker マニフェストのビルドに関する設定
# ...
Go のビルドに関する設定
次のように linux/amd64
, linux/arm64
向けのビルドを行うように設定します。
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
Docker イメージのビルドに関する設定
linux/amd64
用と linux/arm64
用の 2 つの設定を用意します。
今回は DockerHub に push することを想定して、frozenbonito/helloworld
というイメージ名を使うことにします。
また、セマンティックバージョニングに従ってイメージにタグを付けることにします。
dockers:
- image_templates:
- frozenbonito/helloworld:latest-amd64
- frozenbonito/helloworld:{{ .Version }}-amd64
- frozenbonito/helloworld:{{ .Major }}-amd64
- frozenbonito/helloworld:{{ .Major }}.{{ .Minor }}-amd64
use: buildx
goos: linux
goarch: amd64
build_flag_templates:
- --platform=linux/amd64
- image_templates:
- frozenbonito/helloworld:latest-arm64
- frozenbonito/helloworld:{{ .Version }}-arm64
- frozenbonito/helloworld:{{ .Major }}-arm64
- frozenbonito/helloworld:{{ .Major }}.{{ .Minor }}-arm64
use: buildx
goos: linux
goarch: arm64
build_flag_templates:
- --platform=linux/arm64
この設定により、タグ v1.0.0
をリリースした場合に次のようなイメージが作成されます。
-
linux/amd64
向けイメージfrozenbonito/helloworld:latest-amd64
frozenbonito/helloworld:1.0.0-amd64
frozenbonito/helloworld:1-amd64
frozenbonito/helloworld:1.0-amd64
-
linux/arm64
向けイメージfrozenbonito/helloworld:latest-arm64
frozenbonito/helloworld:1.0.0-arm64
frozenbonito/helloworld:1-arm64
frozenbonito/helloworld:1.0-arm64
いくつかポイントがあります。
- Docker でクロスプラットフォームビルドを行うには実験的機能である
buildx
を使用する必要があるため、use: buildx
を指定する - CPU アーキテクチャ毎に適切な
goarch
を指定する - CPU アーキテクチャ毎に適切な
--platform
フラグを指定する - 各タグに
-amd64
,-arm64
などの分かりやすいサフィックスをつけておく
Docker マニフェストに関する設定
Docker イメージのビルドに関する設定 で linux/amd64
用と linux/arm64
用のイメージを作ることはできていますが、アーキテクチャごとに別のタグとなってしまっています。
これではマルチプラットフォーム対応イメージとは言えないので Docker マニフェストを作成する設定を追加します。
docker_manifests:
- name_template: frozenbonito/helloworld:latest
image_templates:
- frozenbonito/helloworld:latest-amd64
- frozenbonito/helloworld:latest-arm64
- name_template: frozenbonito/helloworld:{{ .Version }}
image_templates:
- frozenbonito/helloworld:{{ .Version }}-amd64
- frozenbonito/helloworld:{{ .Version }}-arm64
- name_template: frozenbonito/helloworld:{{ .Major }}
image_templates:
- frozenbonito/helloworld:{{ .Major }}-amd64
- frozenbonito/helloworld:{{ .Major }}-arm64
- name_template: frozenbonito/helloworld:{{ .Major }}.{{ .Minor }}
image_templates:
- frozenbonito/helloworld:{{ .Major }}.{{ .Minor }}-amd64
- frozenbonito/helloworld:{{ .Major }}.{{ .Minor }}-arm64
この設定により次のようなマニフェストが作成され、DockerHub からマルチプラットフォーム対応イメージとして利用することができるようになります。
frozenbonito/helloworld:latest
frozenbonito/helloworld:1.0.0
frozenbonito/helloworld:1
frozenbonito/helloworld:1.0
ローカルで動作を確認する
.goreleaser.yaml
が作成できたらローカルで動作を確認しておきます。
GoReleaser の CLI をインストールします。
$ go install github.com/goreleaser/goreleaser@latest
Docker の buildx
コマンドを使用するので、実験的機能を有効にしておく必要があります。
例えば Docker Desktop for Windows (v4.12.0) の場合は次のように右上の歯車アイコンから設定を開き、Docker Engine の experimental
を true
にして Apply & Restart します。
Docker イメージを push せずにビルドするには --snapshot
をつけて goreleaser release
を実行します。
$ goreleaser release --rm-dist --snapshot
想定通りのイメージが作成されているかを確認します。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
frozenbonito/helloworld 0-arm64 7a445338dc93 5 seconds ago 3.6MB
frozenbonito/helloworld 0.0-arm64 7a445338dc93 5 seconds ago 3.6MB
frozenbonito/helloworld 0.0.0-SNAPSHOT-none-arm64 7a445338dc93 5 seconds ago 3.6MB
frozenbonito/helloworld latest-arm64 7a445338dc93 5 seconds ago 3.6MB
frozenbonito/helloworld 0-amd64 8408192f6a68 5 seconds ago 3.57MB
frozenbonito/helloworld 0.0-amd64 8408192f6a68 5 seconds ago 3.57MB
frozenbonito/helloworld 0.0.0-SNAPSHOT-none-amd64 8408192f6a68 5 seconds ago 3.57MB
frozenbonito/helloworld latest-amd64 8408192f6a68 5 seconds ago 3.57MB
ただしこの時点では Docker マニフェストは作成されません。
これは docker manifest create の制限としてマニフェストに追加するイメージが push されている必要があるためです。
また、--snapshot
をつけて実行した場合スナップショット用のバージョンが振られます。
このバージョン名は snapshot 設定でカスタイマイズ可能です。
GitHub Actions で DockerHub に push する
ここまでの手順で、リリースを実行すればマルチプラットフォーム対応の Docker イメージが DockerHub に push されるようになっています。
リリースの実行方法は色々とありますが、ここでは GitHub Actions で自動化する方法を紹介します。
次のような workflow ファイルを作成します。
name: Release
on:
push:
tags:
- v**
permissions:
contents: write
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: "1.19"
cache: true
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v3
with:
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GoReleaser Action の一般的な使い方に従えば OK ですが、イメージを push するために事前に DockerHub にログインしておく必要があります。
これは Docker 公式の Login Action で簡単に実現可能です。
ログインに必要な情報は GitHub リポジトリの Secret に保存します。
-
DOCKERHUB_USERNAME
- DockerHub のユーザー名
-
DOCKERHUB_TOKEN
- DockerHub のアクセストークン
アクセストークンは次の手順に従って Read & Write
権限を持つものを作成します。
これで GitHub への tag push をトリガーに自動でリリースが実行され、マルチプラットフォーム対応の Docker イメージが作成されるようになるはずです。