Claude Media
JenkinsfileをGitHub Actionsに移行 — Claude Codeで並走検証する手順

JenkinsfileをGitHub Actionsに移行 — Claude Codeで並走検証する手順

Jenkinsfileのstage・post・credentialsをActionsのjob・if・secretsに対応づけ、旧Jenkinsと同じコミットで並走して確かめる手順。CircleCI・GitLab CIの読み替え表つき。

JenkinsfileからGitHub Actionsへの移行は、宣言を1つずつ読み替えて、ワークフローのYAMLを作り直す作業です。GitHub Actions Importerという自動変換ツールがあるものの、手作業で残る範囲もはっきり決まっています。Claude Codeに任せるなら、変換を丸ごと頼むより、対応表の作成と並走での答え合わせを任せる進め方が安全です。

この記事では、Importerのdry-runで残りを測り、stage・post・credentialsをActionsのどこに置くかを決め、旧Jenkinsと新Actionsを同じコミットで走らせて結果を比べるところまでを順に書きます。CircleCIとGitLab CIも同じ型で進められるので、読み替え表を後半に置きました。

最初にImporterのdry-runで「手作業の残り」を測る

Claude Codeに触らせる前に、Importerで自動変換がどこまで届くかを見ます。残りの大きさが分かると、並走期間の長さも読めます。

ImporterはGitHub CLIの拡張として入ります。実行にはDockerとGitHub CLIが必要で、workflowスコープを持つclassicの個人用アクセストークンと、JenkinsのAPIトークンを用意します。

gh extension install github/gh-actions-importer
gh actions-importer configure   # 対話でトークンとJenkinsのURLを入力
gh actions-importer update      # コンテナイメージを最新にする

使えるサブコマンドはupdate・configure・audit・forecast・dry-run・migrateの6つです。移行の前に使うのはauditとdry-runで、migrateは変換結果をプルリクエストとして開くので最後に回します。

# Jenkins全体の変換率を見る
gh actions-importer audit jenkins --output-dir tmp/audit
 
# 手元のJenkinsfileで変換だけ試す(PRは開かない)
gh actions-importer dry-run jenkins \
  --output-dir tmp/dry-run \
  --source-file-path ./Jenkinsfile \
  --source-url http://jenkins.example.com/job/app

auditはaudit_summary.mdを出力します。パイプラインは「Successful(完全に変換)」「Partially successful(一部の項目が未変換)」「Unsupported」「Failed」に分類されます。ビルドステップは「known」と「unknown」に分かれ、手作業の項目には作成が必要なsecretとself-hostedランナーが並びます。

Importerが手作業に回す対象は、公式の制限事項に次の5つが挙がっています。

  • 必須のビルドツール
  • Scripted Pipeline
  • secrets
  • self-hosted runners
  • 未知のプラグイン

Declarative Pipelineの変換範囲は、Jenkinsfile側の構文ごとに表で示されています。

JenkinsGitHub Actions状態
docker・stageGitHub Actionsjobs.<id>.container・jobs.<id>状態対応
agent・environment・stages・steps・triggers・whenGitHub Actionsrunners・env・jobs・steps・on・if状態一部対応
inputs・matrix・options・parametersGitHub Actionsinputs・strategy.matrix・strategy状態未対応

matrixやparametersを使うJenkinsfileは、dry-runの出力にその部分が残らない前提で見積もります。この「未対応」の行が、次のClaude Codeに任せる仕事の中心になります。

stage・post・credentialsはActionsのどこに当たるか

読み替えの軸は、JenkinsのstageがActionsのjobに対応するという1点です。Jenkinsはstagesの中にstageを並べ、ActionsはYAMLのjobsの下にjobを並べます。

Jenkinsの宣言GitHub Actionsの置き場所
agentGitHub Actionsの置き場所jobs.<job_id>.runs-on、コンテナならjobs.<job_id>.container
stageGitHub Actionsの置き場所jobs.<job_id>(表示名はjobs.<job_id>.name)
stepsGitHub Actionsの置き場所jobs.<job_id>.steps
environmentGitHub Actionsの置き場所jobs.<job_id>.env、ステップ単位ならsteps[*].env
optionsGitHub Actionsの置き場所strategy・strategy.fail-fast・timeout-minutes
parameters・inputGitHub Actionsの置き場所inputs
triggersGitHub Actionsの置き場所on(push・pull_requestのbranches・pathsなど)
triggers { upstream }GitHub Actionsの置き場所jobs.<job_id>.needs
cron構文GitHub Actionsの置き場所on.schedule
toolsGitHub Actionsの置き場所GitHubがホストするランナーのプリインストール済みソフトウェア
whenGitHub Actionsの置き場所jobs.<job_id>.if
postGitHub Actionsの置き場所対応なし

最後のpostだけは、公式の対応表で「None」と書かれています。post { failure { … } }のような後処理は、Actions側で自分で組み立て直します。

組み立てには状態チェック関数を使います。failure()は、同じジョブの前のステップが失敗したとき、あるいは依存するジョブの祖先が失敗したときにtrueを返します。always()は取り消されたときもtrueを返すため、ソースの取得のような重要な処理には向きません。その場合は!cancelled()が推奨の代替として示されています。

credentialsについては、Jenkinsに保存した資格情報はそのまま移りません。Importerもsecretsを手作業の項目に挙げていて、変換後のプルリクエストには「Manual steps」として作成が必要なsecretが並びます。ワークフロー側は${{ secrets.NAME }}で参照します。

次の例は、3段のJenkinsfileと、それに対応するワークフローです。Jenkinsfile側は読み替えの勘所を見せるための例で、公式のサンプルをそのまま写したものではありません。

pipeline {
  agent any
  environment { DEPLOY_TOKEN = credentials('deploy-token') }
  stages {
    stage('Build')  { steps { sh 'make build' } }
    stage('Test')   { steps { sh 'make test' } }
    stage('Deploy') {
      when { branch 'main' }
      steps { sh 'make deploy' }
    }
  }
  post { failure { sh './notify.sh' } }
}
on:
  push:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: make build
  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: make test
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    env:
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
    steps:
      - uses: actions/checkout@v6
      - run: make deploy
  notify:
    needs: [build, test, deploy]
    if: failure()
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: ./notify.sh

postはnotifyジョブに分かれ、failure()が祖先のジョブの失敗を拾います。Actionsのジョブは別々のマシンで走りうるため、各ジョブにactions/checkoutを置いています。ビルド成果物をジョブ間で渡す場合は、アーティファクトの受け渡しを別途書きます。

Claude Codeには変換でなく「対応表の作成」から頼む

Jenkinsfileを渡して「Actionsにして」と頼むと、postのように対応のない宣言が黙って落ちたり、未対応のmatrixが推測で埋められたりする余地が残ります。読み替えの根拠が見える形にするために、頼む順番を3段に分けます。

手順

Claude Codeに頼む順番

  1. 1

    Importerのdry-run結果を渡す

    tmp/dry-runの出力とJenkinsfileを並べて読ませます。変換できた部分と、未変換として残った部分を区別させるのが目的です。

  2. 2

    宣言ごとの対応表を書かせる

    YAMLを書く前に、Jenkinsfileの宣言を1行ずつ拾い、Actions側の置き場所と「未対応・要判断」の印を付けた表を出させます。人が表を直してから先へ進みます。

  3. 3

    表に沿ってワークフローを書かせる

    表で合意した範囲だけをYAMLにします。表に無い変更は足さない、という制約を最初の指示に入れます。

指示を毎回書き直さないために、リポジトリのCLAUDE.mdに移行中だけの規約を置いておきます。次は書き方の例です。

## CI移行の作業ルール(移行期間中のみ)
- Jenkinsfileは変更しない。新しいワークフローは `.github/workflows/ci.yml` に置く
- Jenkinsfileの stage 名と同じ文字列を job の `name:` に使う
- 変換できない宣言(matrix / parameters / post など)は黙って落とさず、
  「未対応」として一覧にして報告する
- deploy 系の job には `if: false` を付けておく(並走が終わるまで動かさない)
- secret の値は読まない。必要な secret の名前だけ列挙する

stage名とjobのnameをそろえておくと、後で結果を機械的に突き合わせられます。secretの値を読ませない制約は、Importerが「secretは手作業」と明記している点とも合います。

変換後のワークフローをプルリクエストで確認する段階では、Claude Code GitHub Actionを使う手もあります。@claudeメンションに反応するワークフローを入れておき、プルリクエストのコメントでpostに当たるnotifyジョブの条件を点検させます。ワークフローの入れ方はClaude CodeをGitHub Actionsに組み込む手順に、YAMLの書き方はGitHub Actionsのワークフロー作成の手順にまとめています。

旧Jenkinsと新Actionsを同じコミットで並走させる

対応表とYAMLができても、すぐJenkinsを止めません。同じコミットを両方のCIに走らせ、stageとjobの結果が一致するかを比べます。

並走させる準備は次の4点です。

  • 移行用のブランチを作り、Jenkinsfileとワークフローを同じコミットに入れる
  • そのブランチをJenkinsも拾うようにしておく
  • ワークフローのdeploy系jobはif: falseのままにして、本番への二重デプロイを防ぐ
  • notifyのような通知は、並走中だけ宛先をテスト用に向ける

結果の突き合わせは、GitHub側をghで、Jenkins側を保存したログで取ります。次はGitHub側の取り出し方です。

SHA=$(git rev-parse HEAD)
RUN=$(gh run list --commit "$SHA" --json databaseId --jq '.[0].databaseId')
gh run view "$RUN" --json jobs \
  --jq '.jobs[] | [.name, .conclusion] | @tsv'

出力はjobごとの名前<TAB>結論の一覧です。Jenkins側のstage名と結果は、ビルドのコンソールログから拾います。ログに[Pipeline] { (Build)の形でstage名が出る環境なら、grepで抜き出して同じ形のTSVにそろえられます。ログの書式は環境ごとに違うので、手元のログを見て調整してください。

両方のTSVができたら、Claude Codeに差分を読ませます。指示は「stage名とjob名で対応づけ、結果が食い違う行と、片方にしかない行を挙げる」で足ります。次の4つの軸で見ると、見落としが減ります。

比べる軸見るもの
結果見るもの同じコミットで、stageとjobの成功・失敗が一致するか
順序と依存見るものJenkinsのstageの並びと、needsの依存が同じ順になるか
成果物見るもの作られるファイルやイメージが同じか
所要時間見るもの実行時間と待ち時間がどれだけ違うか

最後の所要時間は、forecastコマンドでも見積もれます。Jenkinsの完了済みジョブから、実行時間・キュー待ち時間・同時実行ジョブ数を集計するコマンドで、既定では直近7日が対象です。実行にはJenkinsにpaginated-buildsプラグインを入れておく必要があります。ホステッドランナーの利用料の見当をつけたいときに向いています。

並走をいつ終えるかは、チームごとの取り決めです。たとえば、通常のコミットとリリースブランチの両方で一定回数連続して結果が一致したら、Jenkinsのstageを1つずつ止めていく、という進め方があります。最初に止めるのは、副作用の少ないテストstageからです。

CircleCIとGitLab CIで読み替えが変わる点

CircleCIやGitLab CIでも進め方は同じで、「対応表を作り、同じコミットで並走させる」を繰り返します。変わるのは読み替え表の中身です。GitHub Docsには、それぞれの手作業の移行ガイドがあります。

CircleCI

CircleCIGitHub Actions
config.yml内の複数のworkflowsGitHub Actionsワークフローごとに1ファイル。workflowsの宣言は不要
requiresGitHub Actionsneeds
orbGitHub Actionsaction(JavaScriptまたはDockerイメージで作る)
YAMLのアンカーとエイリアスGitHub Actionsそのまま使える。バリエーションにはmatrixも使える
テストの自動並列化(履歴からグループ分け)GitHub Actions組み込みでは提供されない

注意が要るのは最後の行です。CircleCIの自動テスト分割に頼っているジョブは、移行すると並列度が落ち、所要時間の軸で並走の差が開きます。また、CircleCIのビルド済みイメージはUSERがcircleciに設定されていて、Actions側と権限が衝突するとガイドに書かれています。DockerfileでUSER命令を使わないことが回避策の1つです。

GitLab CI/CD

GitLab CI/CDGitHub Actions
scriptGitHub Actionsrun(チェックアウトはactions/checkoutで明示)
tagsGitHub Actionsruns-on
imageGitHub Actionscontainer
rules: - if:GitHub Actionsjobs.<job_id>.if
stages(前のstageが終わると次へ)GitHub Actionsneedsで依存を明示
パイプラインスケジュール(UIで設定)GitHub Actionson.schedule
cache・artifactsGitHub Actionsactions/cache・actions/upload-artifact

GitLabのstagesは「同じstageのjobは並列、前のstageが全部終わってから次」という動きで、Actionsではneedsを張って再現します。Jenkinsのstageより暗黙の依存が多いので、needsの書き漏れが並走の食い違いとして出やすい箇所です。GitLab側のClaude Code連携を知りたい場合は、Claude CodeとGitLab CI/CDの連携も参考になります。

並走中に見つかりやすいずれ

落とし穴

postに当たる後処理が黙って消える

postに対応するActionsの宣言は公式の対応表で「None」とされ、変換の対象外です。変換結果からpostの中身が消えていても、気づきにくい箇所です。Claude Codeへの指示に「postはif: failure()かif: ${{ !cancelled() }}のjobとして残す。残せないなら一覧に挙げる」と入れておくと、見落としが出ません。

落とし穴

BUILD_NUMBERがrun_idに置き換わる

Importerは${BUILD_ID}と${BUILD_NUMBER}の両方を${{ github.run_id }}に変換します。${BUILD_TAG}は${{ github.workflow }}-${{ github.run_id }}、${JOB_NAME}は${{ github.workflow }}、${WORKSPACE}は${{ github.workspace }}です。ビルド番号を連番のタグやファイル名に使っているスクリプトは、置き換え後の値の見え方が変わっていないか確認します。

ほかにも、手作業になる項目があります。

  • matrix・parameters・optionsは未対応です。strategy.matrixやinputsで書き直します
  • Jenkinsのcron構文H/15 * * * 1-5は、公式の例では*/15 * * * 1-5に書き換わります
  • self-hostedランナーを指すagentのラベルは、Actions側でも同じラベルのランナーを用意するまで動きません
  • 未知のプラグインを使うstepは、Importerが変換せずに残します。代わりのactionを探すか、runで書き直します

どれも、対応表の「未対応・要判断」欄に載せておけば、並走の結果と照らして潰せます。

まとめ

Jenkinsfileの移行で難しいのは、構文の変換より「変換されずに消えるもの」の把握です。postとmatrix、parameters、secretsがその筆頭で、ここはImporterの出力にもClaude Codeの変換にも頼りきれません。

そこで、Claude Codeには表を先に書かせ、人が未対応の欄を決めてからYAMLに進みます。同じコミットで旧Jenkinsと新Actionsを走らせ、stage名とjob名をそろえておけば、結果の差分がそのまま残作業の一覧になります。CircleCIとGitLab CIでも、違うのは読み替え表の中身だけです。失敗を診断する側の話は、Jenkins MCP ServerでClaude Codeにビルド失敗を診断させる手順にあります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.github.com/en/actions/migrating-to-github-actions/automated-migration…
  2. docs.github.com/en/actions/tutorials/migrate-to-github-actions/manual-migr…
  3. docs.github.com/en/actions/tutorials/migrate-to-github-actions/manual-migr…
  4. docs.github.com/en/actions/tutorials/migrate-to-github-actions/manual-migr…
  5. docs.github.com/en/actions/reference/workflows-and-actions/expressions
  6. code.claude.com/docs/en/github-actions
この記事を共有:XはてブLinkedIn