Engineering
Glossvation Engineering
クラウド

Spring Boot × Concourse CI/CD 実装 — PR テスト + Docker Push パイプライン

Spring Boot アプリを Concourse でビルドする最小実装。Pull Request トリガーの Unit テストと、master へのマージで Docker Registry Push を行う YAML を丸ごと解説。

Glossvation ·

Spring Boot x Concourse パイプライン

本稿で作るもの

Spring Boot アプリ 1 本に対して、Concourse で以下 2 ジョブを組む最小パイプラインを作ります。

  1. Pull Request トリガーの Unit テスト (Continuous Integration)
  2. master マージで Docker Image を Build → Push (Continuous Delivery)

Concourse アーキテクチャ の基本知識があると読みやすい内容です。

Concourse の構成要素おさらい

要素役割
Resource外部システム (Git / Docker Registry / Slack) との連携定義
Resource Type標準にない Resource 実装 (コミュニティ Docker Image で拡張)
Job複数ステップから成るパイプライン単位
TaskJob 内の 1 ステップ (Docker コンテナ 1 回分の実行)

fly set-pipeline で YAML を反映すれば動きます。

pipeline.yml 全体

resources:
  - name: repository
    type: git
    source:
      branch: main
      uri: git@github.com:example/spring-boot-app.git
      private_key: ((github-deploy-key))

  - name: pull-request
    type: pull-request
    source:
      uri: git@github.com:example/spring-boot-app.git
      repo: example/spring-boot-app
      access_token: ((github-access-token))
      private_key: ((github-deploy-key))
      base: main

  - name: app-image
    type: docker-image
    source:
      repository: ((docker-hub-account))/spring-boot-app
      username: ((docker-hub-user))
      password: ((docker-hub-pass))

resource_types:
  - name: pull-request
    type: docker-image
    source:
      repository: teliaoss/github-pr-resource

  - name: slack-notification
    type: docker-image
    source:
      repository: cfcommunity/slack-notification-resource
      tag: latest

jobs:
  # 1) Pull Request が来たら Unit テスト
  - name: unit-test
    serial: true
    public: true
    plan:
      - get: pull-request
        trigger: true
        version: every
      - put: pull-request
        params: { path: pull-request, status: pending }
      - task: unit-test
        file: pull-request/concourse/tasks/unit-test.yml
        input_mapping: { repository: pull-request }
        on_success:
          put: pull-request
          params: { path: pull-request, status: success }
        on_failure:
          put: pull-request
          params: { path: pull-request, status: failure }

  # 2) main にマージされたら Docker Image を Push
  - name: deploy-image
    serial: true
    public: true
    plan:
      - get: repository
        trigger: true
      - task: unit-test
        file: repository/concourse/tasks/unit-test.yml
      - task: build-image
        file: repository/concourse/tasks/build-image.yml
      - put: app-image
        params:
          build: out
          tag_file: out/tag.txt

パラメータの (( )) について

(( )) は Concourse の Credential Manager プレースホルダ 。 fly set-pipeline 実行時に -l credentials.yml で値を注入するか、 Vault / AWS Secrets Manager 等の外部 Credential Manager から自動解決させます。

credentials.yml (ローカルの秘匿ファイル):

github-access-token: ghp_xxxxxxxxxxxx
github-deploy-key: |
  -----BEGIN OPENSSH PRIVATE KEY-----
  ...
  -----END OPENSSH PRIVATE KEY-----
docker-hub-account: glossvation
docker-hub-user: ci-robot
docker-hub-pass: ********

絶対に Git にコミットしない (.gitignore に入れておく)。本番は Vault 等を使うべき。

Task 定義ファイル

concourse/tasks/unit-test.yml

platform: linux
image_resource:
  type: docker-image
  source: { repository: eclipse-temurin, tag: 17-jdk }
inputs:
  - name: repository
run:
  path: /bin/bash
  args:
    - -lc
    - |
      cd repository
      ./gradlew --no-daemon clean test

concourse/tasks/build-image.yml

platform: linux
image_resource:
  type: docker-image
  source: { repository: eclipse-temurin, tag: 17-jdk }
inputs:
  - name: repository
outputs:
  - name: out
run:
  path: /bin/bash
  args:
    - -lc
    - |
      cd repository
      ./gradlew --no-daemon bootJar
      cp build/libs/*.jar ../out/app.jar
      cp Dockerfile ../out/
      echo "$(git rev-parse --short HEAD)" > ../out/tag.txt

Docker Image のビルド自体は put: app-image の時に docker-image resource が担当します (DinD 不要)。

リソースの中身

name役割
repositorymain ブランチの Git リポジトリ
pull-requestGitHub の Pull Request (teliaoss/github-pr-resource で管理)
app-imageビルド成果物を Push する Docker Registry パス

teliaoss/github-pr-resource は Concourse コミュニティ製の Resource Type。 2018 年当時の jtarchie/pr は現在非推奨で、teliaoss 版がメンテ継続中。

パイプラインの反映

fly -t my-ci set-pipeline \
  -p spring-boot-app \
  -c pipeline.yml \
  -l credentials.yml

fly -t my-ci unpause-pipeline -p spring-boot-app

-t my-ci は fly login -t my-ci -c https://ci.example.com で事前に設定したターゲット。

2024 時点の現実解

本稿は Concourse を対象にしていますが、同じ要件 (PR テスト + Docker Push) を GitHub Actions で書いた場合は以下の 1 ファイルで済みます:

name: CI
on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { java-version: '17', distribution: 'temurin' }
      - run: ./gradlew clean test

  build-and-push:
    if: github.ref == 'refs/heads/main'
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_PASS }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: glossvation/spring-boot-app:${{ github.sha }}
  • Credential Manager 不要 (GitHub Secrets で済む)
  • Resource Type の管理不要
  • Pull Request との統合も Actions ネイティブ

既存の Concourse 資産を活かす必要がない新規プロジェクトなら GitHub Actions が圧倒的に軽量 です。

落とし穴

1. serial: true を付けないと並行実行で壊れる

同じジョブが複数並行で動くと、Docker Push のタグ衝突や DB マイグレーションの競合が起きます。 テストジョブはシリアル実行で守る。

2. Private Key の改行がうまく渡らない

private_key 等のマルチライン値は YAML の | 記法でちゃんと渡す。 fly set-pipeline に渡すときの -l credentials.yml 側で正しく書く。

3. PR Resource の認証スコープ

teliaoss/github-pr-resource は GitHub の PR / Status API を叩くので、 repo スコープ付きの Personal Access Token が必要。期限切れ時のアラートを運用に組み込む。

4. ビルドキャッシュが効かない

毎回ゼロから Gradle 依存をダウンロードすると遅い。 .gradle を Baggageclaim Volume として共有、またはキャッシュ付き Docker Image で改善を。


まとめ: Concourse の Spring Boot パイプラインは、 Pull Request Resource + Docker Image Resource + 標準 Task で素直に書けます。 2024 時点は GitHub Actions の方がカロリー少なく同じことができるので、 既存の Concourse ユーザー向けの保守・移行検討資料 として参照するのが現実的です。

関連記事