🪣

【Docker】キャッチアップ2024

2024/12/06に公開

はじめに

こんにちは。イオンネクスト SREチームの荒井(@arairyus)です。

本記事はAEON Advent Calendar 2024の6日目の記事です。

皆さんは自分が関わっている技術の最新状況を把握していますか?
私は全然できていません。

エンジニアたるもの少なくとも自社で使っている技術の最新情報をキャッチアップしたいものです。
ですが、日々忙しくなかなかキャッチアップが追いつきません。(言い訳)

↓参考: イオンネクストの技術スタックです。

先日Xでdocker buildx bakeに関する投稿を見ましたが、ナニソレ?状態でした。
当たり前に使っているDockerですが、全然理解せずに使っている機能があるなと思いました。
アドベントカレンダーを書く機会を使って、雰囲気でしか理解していないことや使ったことのない機能について調べてみました。
スクラップのような記事になってしまいましたが、放流します。

前提知識

Buildkit

https://github.com/moby/buildkit

Key features:

Automatic garbage collection
Extendable frontend formats
Concurrent dependency resolution
Efficient instruction caching
Build cache import/export
Nested build job invocations
Distributable workers
Multiple output formats
Pluggable architecture
Execution without root privileges

GitHubのREADMEにはこのような記載があります。
Buildkitの嬉しいポイントは色々ありますが、大きいところですと並列処理によるマルチステージビルドの効率化でしょうか。

ステージ1とステージ2に依存関係がなく、それぞれのステージがステージ3と依存関係がある場合

あとは、syntaxディレクティブによるDockerfile構文の拡張でしょうか。
以下のような記述をすればdocker/dockerfile:1.12.0イメージがBuildkitフロントエンドとして使用され、Dockerfile v1.12.0の文法で読み込みます。

# syntax=docker/dockerfile:1.12.0

FROM xxx

BuildxとBuildkitの関係

Docker CLI

https://docs.docker.com/reference/cli/docker/
久しぶりに見たら知らないサブコマンドが大量にありました。

docker build(legacy builder)

This page refers to the legacy implementation of docker build, using the legacy (pre-BuildKit) build backend. This configuration is only relevant if you're building Windows containers.

For information about the default docker build, using Buildx, see docker buildx build.

だそうです。

docker buildx

Docker Engine v23.0?以降を使っていればdocker buildでもデフォルトでbuildkitが使われます。
ただし、後方互換として残っているコマンドなので、docker buildx buildを使うようにした方が良さそう?です。

明示的にBuildkitを無効化したい場合は、DOCKER_BUILDKIT=0を設定しdocker buildでビルドできます。

docker buildx bake(Experimental)

bakeファイルを定義することで、コマンド一発で複数のimageのビルドができるオプションのようです。

https://docs.docker.com/build/bake/
公式Docsを参考に理解していこうと思います。

.
├── docker-bake.hcl           # 上記のBake設定ファイル
├── frontend/
│   ├── frontend.Dockerfile   # フロントエンドのDockerfile ARG: NODE_VERSION
│   ├── package.json
│   ├── node_modules/
│   └── src/
│       ├── index.js
│       └── ...
│
└── backend/
    ├── backend.Dockerfile    # バックエンドのDockerfile ARG: GO_VERSION
    ├── go.mod
    ├── go.sum
    └── src/
        ├── main.go
        └── ...

上記のようなディレクトリ構成でfrontend、backendのimageをビルドしたい場合は従来下記コマンドを実行すると思います。

# フロントエンドのビルド
docker build \
  -f frontend.Dockerfile \
  --build-arg NODE_VERSION=22 \
  -t myapp/frontend:latest \
  ./frontend

# バックエンドのビルド
docker build \
  -f backend.Dockerfile \
  --build-arg GO_VERSION=1.23 \
  -t myapp/backend:latest \
  ./backend

それに対し、BakeではこのようにBakeファイルを記述し、docker buildx bakeで両イメージをビルドできます。
HCL、YAML(Compose)、JSONで書けるようですが、公式Docsを見るとHCLがオススメのようです。

group "default" {
  targets = ["frontend", "backend"]
}

target "frontend" {
  context = "./frontend"
  dockerfile = "frontend.Dockerfile"
  args = {
    NODE_VERSION = "22"
  }
  tags = ["myapp/frontend:latest"]
}

target "backend" {
  context = "./backend"
  dockerfile = "backend.Dockerfile"
  args = {
    GO_VERSION = "1.23"
  }
  tags = ["myapp/backend:latest"]
}

コマンドが簡潔になることは理解できましたが、嬉しいポイントがしっくりきませんでした。
Claudeにbakeの利点を聞いてみました。

--回答--

bakeの主な利点を説明します:

  1. ビルドの並行実行
  • 複数のターゲットを自動的に並行ビルド
  • 依存関係を考慮しながら効率的にスケジューリング
  1. 設定の再利用と継承
# 共通設定を定義
target "common" {
  platforms = ["linux/amd64", "linux/arm64"]
  cache-from = ["type=registry,ref=cache"]
}

# 共通設定を継承
target "app" {
  inherits = ["common"]
  dockerfile = "Dockerfile"
}
  1. マトリックスビルド
target "build" {
  matrix = {
    version = ["1.0", "2.0"]
    platform = ["linux/amd64", "linux/arm64"]
  }
  tags = ["myapp:${version}"]
  platforms = [platform]
}
  1. 変数とテンプレート
variable "TAG" {
  default = "latest"
}

target "app" {
  tags = ["myapp:${TAG}"]
}
  1. キャッシュ戦略の詳細な制御
  • レジストリキャッシュの設定
  • インラインキャッシュの制御
  • キャッシュのインポート/エクスポート
  1. CI/CD との親和性
  • 設定ファイルによる宣言的な定義
  • 環境変数との連携
  • ビルド設定のバージョン管理

これらの機能により、特に複雑なビルド要件がある場合やCI/CDパイプラインでの利用時に威力を発揮します。

--回答以上--

複数イメージを並列処理できるのは確かに嬉しいです。CI/CDのメンテナンスも楽になり良さそうです。
CIは実行時間が短ければ短いほど嬉しいですしね。

GitHub Actionも公開されているので週明け試してみようと思います。
https://github.com/docker/bake-action

docker checkpoint

存在は知っていましたが、使ったことは一度もありません。
コンテナの状態(メモリ情報など)をダンプし、ディスク上に書き出すサブコマンドです。

docker stop&docker startではstop時に一度コンテナプロセスが落ちるので、メモリ情報などが飛んでしまいます。
docker checkpoint createでダンプし、docker start --checkpointで再開することでプロセスが生きた状態で再開できるのが嬉しいポイントのようです。

ローカルでモデルの学習やでかいバッチ処理をするときに使ったりするのでしょうか?
私は今のところ使う機会がない気がします。

docker compose

未だにdocker-composeコマンドを実行していませんか?
互換性はありますが、docker composecompose.yamlを使いましょう。

docker-compose.yamlとcompose.yamlがどちらもある場合、compose.yamlが優先されるらしいです。
https://docs.docker.com/compose/intro/compose-application-model/#the-compose-file

docker debug

使ったことないなと思ったら、サブスクリプションが必要なサブコマンドのようです。

Docker Debug requires a Pro, Team, or Business subcription. You must sign in to use this command.

コンテナサイズのスリム化のためにbashがないimageを作ることがありますが、そのようなimageでもshellでデバッグできるようです。

With docker debug you can get a debug shell into any container or image, even if they don't contain a shell.

docker init

このサブコマンドたまに使います。
下記のテンプレートを作成してくれるサブコマンドです。

  • ASP.NET Core: Suitable for an ASP.NET Core application.
  • Go: Suitable for a Go server application.
  • Java: suitable for a Java application that uses Maven and packages as an uber jar.
  • Node: Suitable for a Node server application.
  • PHP with Apache: Suitable for a PHP web application.
  • Python: Suitable for a Python server application.
  • Rust: Suitable for a Rust server application.
  • Other: General purpose starting point for containerizing your application.

生成されるファイルは下記の通りです。

  • .dockerignore
  • Dockerfile
  • compose.yaml
  • README.Docker.md

実際のGoのDockerfileをdocker initで生成してみます。

% docker init

Welcome to the Docker Init CLI!

This utility will walk you through creating the following files with sensible defaults for your project:
  - .dockerignore
  - Dockerfile
  - compose.yaml
  - README.Docker.md

Let's get started!

? What application platform does your project use? Go
? What version of Go do you want to use? 1.23
? What's the relative directory (with a leading .) of your main package? .
? What port does your server listen on? 3000

✔ Created → .dockerignore
✔ Created → Dockerfile
✔ Created → compose.yaml
✔ Created → README.Docker.md

→ Your Docker files are ready!
  Review your Docker files and tailor them to your application.
  Consult README.Docker.md for information about using the generated files.

生成されたDockerfileがこちらです。

# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/

# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7

################################################################################
# Create a stage for building the application.
ARG GO_VERSION=1.23
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build
WORKDIR /src

# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds.
# Leverage bind mounts to go.sum and go.mod to avoid having to copy them into
# the container.
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,source=go.mod,target=go.mod \
    go mod download -x

# This is the architecture you're building for, which is passed in by the builder.
# Placing it here allows the previous steps to be cached across architectures.
ARG TARGETARCH

# Build the application.
# Leverage a cache mount to /go/pkg/mod/ to speed up subsequent builds.
# Leverage a bind mount to the current directory to avoid having to copy the
# source code into the container.
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,target=. \
    CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin/server .

################################################################################
# Create a new stage for running the application that contains the minimal
# runtime dependencies for the application. This often uses a different base
# image from the build stage where the necessary files are copied from the build
# stage.
#
# The example below uses the alpine image as the foundation for running the app.
# By specifying the "latest" tag, it will also use whatever happens to be the
# most recent version of that image when you build your Dockerfile. If
# reproducibility is important, consider using a versioned tag
# (e.g., alpine:3.17.2) or SHA (e.g., alpine@sha256:c41ab5c992deb4fe7e5da09f67a8804a46bd0592bfdf0b1847dde0e0889d2bff).
FROM alpine:latest AS final

# Install any runtime dependencies that are needed to run your application.
# Leverage a cache mount to /var/cache/apk/ to speed up subsequent builds.
RUN --mount=type=cache,target=/var/cache/apk \
    apk --update add \
        ca-certificates \
        tzdata \
        && \
        update-ca-certificates

# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser
USER appuser

# Copy the executable from the "build" stage.
COPY --from=build /bin/server /bin/

# Expose the port that the application listens on.
EXPOSE 3000

# What the container should run when it is started.
ENTRYPOINT [ "/bin/server" ]

このようにマルチステージビルドのDockerfileが簡単に生成されます。これをベースに修正することでDockerfileを書くのに慣れていない方でもサクッと書けると思います。

docker inspect

コンテナの詳細情報をJSONで取得できるサブコマンドのようです。

IPアドレスを取得する場合

 docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $INSTANCE_ID

ユースケースが今のところ思いつかないですが、いつか使ってみようと思います。

その他

build checks(lint)

Dockerfileのlintツールと言えば、hadolintですが、docker自体にlintの機能提供がされています。

dockerコマンドでDockerfileがベストプラクティスに沿っているかチェックしてくれます。
docker build --checkと実行すればbuildをすることなく構文チェックをしてくれます。

Build checks are supported in:

  • Buildx version 0.15.0 and later
  • docker/build-push-action version 6.6.0 and later
    docker/bake-action version 5.6.0 and later

OpenTelemetry support

https://docs.docker.com/build/debug/opentelemetry/
buildxでOTELがサポートされたようです。

docker run -d --name jaeger -p "6831:6831/udp" -p "16686:16686" --restart unless-stopped jaegertracing/all-in-one
docker buildx create --use \
  --name mybuilder \
  --driver docker-container \
  --driver-opt "network=host" \
  --driver-opt "env.JAEGER_TRACE=localhost:6831"
docker buildx inspect --bootstrap

手順通りにコマンドを実行して、buildxでbuildしてみました。
buildの状況をJaegerで見れるようになりました。
確認してみましたが、特にbuild時のログ以上に詳しい情報が書かれている訳でもなくユースケースが分かりませんでした。

つぶやき

小一時間で調べて記事を書いてみましたが、知らないことが結構ありました。早速仕入れた情報をもとにCIの速度改善してみようと思います。
使っている全技術のアップデートを追いかけ続けることを半ば諦めていましたが、疲弊しない程度にキャッチアップを頑張ってみようと思いました。

イオンネクストではマルチクラウド&様々な言語・FW、アーキテクチャパターンのシステムが存在します。エンジニアとして技術的に飽きることのない環境です。
少しでも興味があればぜひカジュアル面談しましょう!

参考にしたサイト

イオングループで、一緒に働きませんか?

イオングループでは、エンジニアを積極採用中です。少しでもご興味もった方は、キャリア登録やカジュアル面談登録などもしていただけると嬉しいです。
皆さまとお話できるのを楽しみにしています!

AEON TECH HUB

Discussion