JenkinsパイプラインのエージェントとしてECRのプリビルドイメージを使いたい。
動機はシンプルで、毎回 docker build を走らせるのをやめて、ビルド済みイメージをそのまま引っ張ってくることでパイプラインを速くしたかった。

で、Claude Codeにプランを立てさせたら、「こうするといいですよ」と出てきた案が思ったより複雑で。

リジェクトしました。

最初に提案されたプラン

Jenkinsfileは今こういう構造になっている。

pipeline {
    agent {
        dockerfile {
            label 'ec2-fleet-small'
            dir '.'
            filename 'Dockerfile'
            args "-v \${WORKSPACE}:/workspace -w /workspace"
        }
    }
    stages { ... }
    post { /* archiveArtifacts, cleanWs, webhook */ }
}

これをECRイメージに切り替えるにあたって、AIが提案してきたのはこういう構造だった。

pipeline {
    agent { label 'ec2-fleet-small' }  // まずベアホスト
    stages {
        stage('ECR Login') { ... }     // ここでECR認証
        stage('Run in Docker') {
            agent { docker { image ECRイメージ, reuseNode true } }
            stages { 既存の全ステージ }
            post { archiveArtifacts, クリーンアップ }
        }
    }
    post { cleanWs, webhook }
}

狙いはわかる。ECR Loginをベアホストで走らせて、その認証情報を使ってDockerエージェントを立ち上げる。reuseNode true で同じノードを再利用するから、ログイン済みの状態が引き継がれる。

post ブロックの分割理由も筋が通っていた。Dockerコンテナ内の /tmp/ にあるファイルにアクセスする必要があるステップは、コンテナが死ぬ前にDockerステージの post でやる必要がある。cleanWsとwebhookはパイプラインレベルの post に残す——という設計。

ただ、パイプライン全体の構造を変えないといけない。ネストされたステージ、postブロックの分割、スクリプトスコープの変数管理……。

やりたいことに対して、変更量が多すぎる気がした。

「もっと楽なやり方、あるんじゃない?」

リジェクトして聞いてみた。

すると二つの選択肢が出てきた。

案1: amazon-ecr-credential-helper(EC2 OSレベル)
案2: Jenkins の Amazon ECR プラグイン(Jenkins側)

二つのアプローチを比較した

どちらも「DockerがECRから自動でイメージをpullできるようにする」という目的は同じ。ただ、仕組みと設置場所が違う。

credential-helper Jenkins ECR プラグイン
設置場所 EC2インスタンス(OSレベル) Jenkins(プラグイン)
認証方式 IAMインスタンスプロファイル JenkinsのCredentials
Jenkinsfile変更 agent ブロックだけ registryUrl + credentialsId を追加
影響範囲 そのインスタンス上の全Docker 該当ジョブのみ
他ジョブへの展開 Jenkinsfile変更不要 各ファイルに記述が必要
EC2の変更 AMI/ユーザーデータに追加 不要

Jenkinsfileの変更量で言えば、credential-helperの方が断然少ない。

// ECR プラグインを使う場合
agent {
    docker {
        image '<ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/your-image:latest'
        registryUrl 'https://<ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/'
        registryCredentialsId 'ecr:ap-northeast-1:<Jenkins Credential ID>'
        label 'ec2-fleet-small'
    }
}

// credential-helper を使う場合
agent {
    docker {
        image '<ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/your-image:latest'
        label 'ec2-fleet-small'
        alwaysPull true
    }
}

credential-helperが入っていれば、Jenkinsfileには認証に関する記述が一切不要になる。EC2のIAMインスタンスプロファイルを使って、Dockerが自動でECR認証してくれるから。

credential-helper を選んだ理由

credential-helperに決めた。

理由は大きく二つ。

今後の展開を考えると、各Jenkinsfileに認証情報を書きたくない。
今回対象のジョブだけじゃなく、他のパイプラインも順次ECRイメージに切り替えていく予定がある。ECRプラグインだと、そのたびに registryUrlregistryCredentialsId をJenkinsfileに追記しないといけない。credential-helperが入っていれば、全ジョブで何も書かなくていい。

Docker Hub など他のレジストリへの影響がない。
これは最初に確認した。credential-helperは credHelpers という仕組みで動いていて、ドメイン単位で設定する形になっている。

{
  "credHelpers": {
    "<ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com": "ecr-login"
  }
}

ここに書かれていないレジストリには一切干渉しない。Docker Hubからのpullは今まで通り動く。

トレードオフとして、EC2のAMIかユーザーデータに手を入れる必要がある。インフラレイヤーの変更だから、Jenkinsfileの変更より影響範囲は広い。ただ「一度入れれば全部に効く」という性質上、許容できるコストだと判断した。

なので、まずインフラレイヤーから

ここで気づいたのが、順番の問題だった。

credential-helperがEC2インスタンスに入っていない状態で、JenkinsfileをECRイメージに切り替えても当然動かない。前提条件が満たされていない。

必要なのは:

  1. EC2フリートインスタンスに amazon-ecr-credential-helper をインストール
  2. Dockerを実行するユーザーの ~/.docker/config.jsoncredHelpers を設定
  3. EC2のIAMロールに必要なECR権限を確認(ecr:GetAuthorizationTokenecr:BatchGetImageecr:GetDownloadUrlForLayer

これはJenkinsfileではなく、インフラのコードを変更する話になる。

なのでJenkinsfileを先に触るのではなく、まずIaCのIssueを起票してインフラ側の更新を先に進めることにした。インフラが整ったら、Jenkinsfileの変更はagentブロックを数行書き換えるだけで済む。

現時点での状態

まだJenkinsfileの変更はしていない。

インフラ側のIssueを作って、そちらを先に対応する順番にした。変更が終わったら、改めてJenkinsfileのPRを出す予定。

途中までの判断をメモとして残しておく。

参考リソース

Jenkinsのパイプライン設計やAWSインフラの運用について、以下の書籍が参考になります。

[📦 商品リンク: moshimo-book-jenkins-jissen]

[📦 商品リンク: moshimo-book-jenkins-blue]

[📦 商品リンク: moshimo-book-aws-operations]