tech.guitarrapc.cóm

Technical updates

Pulumi DockerBuildを使ったマルチプラットフォームDocker Image作成

Pulumiでマルチプラットフォーム対応のDocker Imageを作成する時はdocker-buildパッケージを使うのがオススメです。ただ注意点として、1年余りプレビュー状態が続いており、これまでのdockerパッケージから見ると挙動の違いに注意が必要です。

今回はPulumiでマルチプラットフォームなDocker Imageを作成する方法を紹介します。

PulumiでマルチプラットフォームDocker Imageを作成したい

Docker Imageを作成するにあたって長年課題だったのが、マルチプラットフォームなDocker Image作成ができないことでした。Docker BuildKitをベースにマルチプラットフォームイメージを簡単に扱えるようになっても、PulumiのDockerイメージ作成は長年ツギハギ対応で使いたくありませんでした。しかし、Docker Build Providerの登場でマルチプラットフォームイメージを安定して作成できるようになっています。

どうやるのか説明の前に、Dockerイメージ作成~利用を軽くイメージしてみましょう。

AWSを使っているならECRにイメージをプッシュすることでAWSサービスからIAM認証でプライベートなイメージを安全に取得できます。この挙動はサービスの安定的なコンテナイメージ取得において欠かせないもので、AzureやGoogle Cloudも同様の仕組みがあります。 例えば、PulumiでアプリケーションのイメージからECSでのタスク実行までやろうと思った場合、次のような流れになります。流れ自体は、amd64(x86_64)向けやarm64など単独プラットフォームなイメージであっても、マルチプラットフォームなイメージでも同じです。

  1. ローカル・CIでイメージをビルド&ECRにプッシュ
  2. TaskDefinitionでプッシュしたECRイメージを参照
  3. ECS Serviceで更新したTask Definitionを用いる

さて、この例でマルチプラットフォームの需要の鍵はECSです。

2024年12月にFargateがARM64 Spotに対応したこともあり、ECSをamd64で動かしていたがarm64にしたい。あるいは両アーキテクチャで動かしたい、というケースが現実を帯びるようになりました。こういった時、まずマルチプラットフォームなイメージに対応しておくことで、「Task Definitionのイメージタグ構成はかえず」に「ECS Service Providerをamd64/arm64で調整」するだけでスムーズなアーキテクチャ変更ができます。

ECSに限らずコンテナアプリケーションにおいて、マルチプラットフォーム対応したコンテナイメージは柔軟な運用を支えるため、マルチプラットフォームイメージは重要だと考えています。

マルチプラットフォームイメージとは

マルチプラットフォームなイメージ作成の基礎は、Dockerが出しているMulti-platform buildsがよくまとまっているので詳細はこちらに譲ります。ここではマルチプラットフォームイメージの概要だけかいつまみましょう。

単独プラットフォームイメージ

単独のマニフェストを持ち、それがコンフィグとレイヤーセットへのポインターとなっています。いわゆるDockerマニフェストという理解そのままではないでしょうか。

単独のマニフェストとはイメージのアーキテクチャやOSを指すため、amd64向けのイメージタグを取得したら中身はamd64向けのイメージだけです。 amd64とarm64の両方をサポートする単独タグは作れないので、「2つのイメージ」か「それぞれのタグ」を用意する必要があります。マルチプラットフォーム対応には、開発者・利用者共にイメージやタグを使い分ける必要があります。

alt text

引用: https://docs.docker.com/build/building/multi-platform/

マルチプラットフォームイメージ

マニフェストリストと複数のマニフェストを持ちます。マニフェストリストはOS・アーキテクチャに合わせて適切なマニフェストを指し示す役割を持っています。

マルチプラットフォームイメージをレジストリからダウンロードすると、まずマニフェストリストを取得し適切なアーキテクチャのイメージを選択、続いて対象アーキテクチャのマニフェストを取得します。 つまり、amd64とarm64の両方をサポートする「単独のイメージ・タグ」を作れます。マルチプラットフォーム対応には、開発者は「アーキテクチャごとにイメージビルド」「マニフェストリストの作成」を行う必要がありますが、利用者はamd64・arm64に関わらず同じイメージタグを利用できます。

alt text

引用: https://docs.docker.com/build/building/multi-platform/

まとめると次のようになります。複数アーキテクチャを単一イメージでシームレスに使ってほしい、という動機があるならマルチプラットフォームイメージを使うのがオススメです。

対象 単独プラットフォームイメージ マルチプラットフォームイメージ 備考
単独アーキテクチャ (開発者) × マルチプラットフォームに対応できない
単独アーキテクチャ (利用者) × マルチプラットフォームに対応できない
複数アーキテクチャサポート (開発者) 開発者はマニフェストリストの作成が必要
利用者は単一イメージタグでプラットフォーム問わず利用できる
複数アーキテクチャサポート (利用者) 開発者はマニフェストリストの作成が必要
利用者は単一イメージタグでプラットフォーム問わず利用できる

PulumiでDockerイメージを作る

ECRにDockerイメージをプッシュする流れをPulumiで書いてみましょう。今回は3つの例を紹介します。

  1. 単独プラットフォームイメージを作る (Pulumi.Docker)
  2. 単独プラットフォームイメージを作る (Pulumi.DockerBuild)
  3. マルチプラットフォームイメージを作る (Pulumi.DockerBuild)

事前作業

マルチプラットフォームに対応している適当なイメージでDockefileを用意しておきます。今回はdocker/Dockerfileに配置します。

FROM alpine:latest

単独プラットフォームイメージを作る (Pulumi.Docker)

以前から存在しているPulumi.Dockerパッケージで、単独プラットフォームイメージを作ってみましょう。

まずはパッケージを導入します。

dotnet add package Pulumi.Docker --version 4.5.5

続けてPulumi C#で、ECR作成&Dockerイメージを作成しつつプッシュするコードです。

var ecr = new Pulumi.Aws.Ecr.Repository("demo-image", new ()
{
    Name = "demo-image",
    ForceDelete = true,
}, new CustomResourceOptions());
var credential = ecr.RegistryId.Apply(x => Pulumi.Aws.Ecr.GetAuthorizationToken.InvokeAsync(new()
{
   RegistryId = x,
}));
var demoImage = new Pulumi.Docker.Image("demo-image", new()
{
    Build = new Pulumi.Docker.Inputs.DockerBuildArgs()
    {
        Context = ".",
        Dockerfile = "docker/Dockerfile",
        Platform = "linux/amd64",
    },
    ImageName = ecr.RepositoryUrl.Apply(x => $"{x}:tag1"),
    Registry = new Pulumi.Docker.Inputs.RegistryArgs
    {
        Server = ecr.RepositoryUrl,
        Username = credential.Apply(x => x.UserName),
        Password = credential.Apply(x => x.Password),
    },
    SkipPush = false,
});

適用してみましょう。

$ pulumi up

 +   ├─ aws:ecr:Repository                   demo-image                                create
 +   └─ docker:index:Image                   demo-image                                create

Resources:
    + 2 created

できていますね。

alt text

APIを見てわかる通り、Pulumi.Docker.Imageは単独プラットフォームイメージのみサポートしています。マルチプラットフォームイメージ対応するために新設されたのが、Pulumi.DockerBuildです。

単独プラットフォームイメージを作る (Pulumi.DockerBuild)

続けて、本記事で紹介したいPulumi.DockerBuildパッケージを使って、単独プラットフォームイメージを作ってみましょう。先ほどのECRを一度消してゼロベースで実行していると考えてください。1

まずはパッケージを導入します。

dotnet add package Pulumi.DockerBuild --version 0.0.9

Pulumi.DockerとはAPの差異はあるものの、おおむね同じようなコードです。

var demoImage = new Pulumi.DockerBuild.Image("demo-image", new()
{
    BuildOnPreview = true,
    Context = new Pulumi.DockerBuild.Inputs.BuildContextArgs
    {
        Location = "docker/"
    },
    Platforms = [Pulumi.DockerBuild.Platform.Linux_amd64],
    Tags = [
        ecr.RepositoryUrl.Apply(x => $"{x}:tag1"),
    ],
    Registries = new Pulumi.DockerBuild.Inputs.RegistryArgs
    {
        Address = ecr.RepositoryUrl,
        Username = credential.Apply(x => x.UserName),
        Password = credential.Apply(x => x.Password),
    },
    Push = !Pulumi.Deployment.Instance.IsDryRun, // previewとpushでboolが切り替わる
});

適用してみましょう。

$ pulumi up

 +   ├─ aws:ecr:Repository                   demo-image                                created (0.55s)
 +   └─ docker-build:index:Image             demo-image                                created (5s)

Resources:
    + 2 created

できていますね。

alt text

DockerBuildパッケージの注意点

先ほどのコードは、PushによってPulumi.Dockerと挙動が異なっています。

Pulumi.DockerBuildは、Push = trueにしているとECRの存在いかんにかかわらずpushするように変更されました。 このため、ECRリポジトリがない状態でPush = trueかつpulumi upを実行すると次のエラーを出力します。

  docker-build:index:Image (demo-image):
    error: docker-build:index:Image resource 'demo-image': property exports[0] value {<nil>} has a problem: at least one tag or export name is needed when pushing to a registry

Push = trueの代わりにPulumi.Deployment.Instance.IsDryRunを使ってpreview時はプッシュしないように制御する必要があります。

マルチプラットフォームイメージを作る (Pulumi.DockerBuild)

APIを見てわかる通り、Pulumi.DockerBuild.Imageはマルチプラットフォームイメージをサポートしています。次のコードはPlatformslinux/arm64を追加しています。

    var demoImage = new Pulumi.DockerBuild.Image("demo-image", new()
    {
        BuildOnPreview = true,
        Context = new Pulumi.DockerBuild.Inputs.BuildContextArgs
        {
            Location = "docker/"
        },
        Platforms = [Pulumi.DockerBuild.Platform.Linux_amd64, Pulumi.DockerBuild.Platform.Linux_arm64],
        Tags = [
            ecr.RepositoryUrl.Apply(x => $"{x}:tag1"),
        ],
        Registries = new Pulumi.DockerBuild.Inputs.RegistryArgs
        {
            Address = ecr.RepositoryUrl,
            Username = credential.Apply(x => x.UserName),
            Password = credential.Apply(x => x.Password),
        },
        Push = !Pulumi.Deployment.Instance.IsDryRun,
    });

適用してみましょう。

$ pulumi up

 ~   └─ docker-build:index:Image             demo-image                                updated (5s)     [diff: ~platfor

Resources:
    ~ 1 updated

ECRを見るとマルチプラットフォームなイメージ一覧に代わっています。特に、先ほどまではArtifact typeがImageでしたが、Image Indexへ変わったことに注目してください。

alt text

まとめ

Pulumiでマルチプラットフォームなイメージ作成をするには、Pulumi.DockerBuildパッケージを使うのがオススメです。

実運用を考えるとアプリケーションはPulumiとは独立してイメージビルド・プッシュすることが多いでしょう。 しかし、簡易なアプリケーション構成ではIaCでデプロイまでまとめてやって手間を極小にするケースがあります。本記事がそういったユースケースで参考になれば幸いです。

参考


  1. ECRをかえずイメージ部分だけマイグレートもできます