CLI で自在に操作可能な CI ツール Concourse を使う

CI ツールシリーズ第3弾。Concourse は Pivotal が開発した CI ツールです。概要についてはこのスライドが参考になります。

backpaper0.github.io

チュートリアルも日本語に翻訳されています。

concoursetutorial-ja.cfapps.io

macOS / Windows では Docker Desktop で簡単に導入できます*1。GitHub から concourse-docker をクローンまたはダウンロードして、docker-compose up -d するだけ。

github.com

EC2 などのサーバで試すには、docker-compose.yml の 環境変数 CONCOURSE_EXTERNAL_URL を環境変数から取得するようにしておくとよいでしょう。

  concourse-web:
    image: concourse/concourse
       :
    environment:
       :
    - CONCOURSE_EXTERNAL_URL=${CONCOURSE_EXTERNAL_URL}

起動前に環境変数を設定しておけば、ローカルのブラウザからパイプラインの状況を監視できます。

$  export CONCOURSE_EXTERNAL_URL=http://$(curl -s ifconfig.me):8080

Concourse の特徴である fly コマンドは Go で書かれたクロスプラットフォームな CLI ツールで*2、セットアップされたサイトのトップページからダウンロードできます。/usr/local/bin など PATH の通ったディレクトリに配置して起動できるようにします。

sudo mkdir -p /usr/local/bin
sudo mv ~/Downloads/fly /usr/local/bin
sudo chmod 0755 /usr/local/bin/fly

fly コマンドで作業するには Concourse API を叩くためのトークンを得るためにログインが必要です。

$ fly --target tutorial login --concourse-url http://127.0.0.1:8080

ここでは、tutorial という名前でセッションを作っています。ログイン用の URL が表示されますので、ブラウザで開いてログインを完了させます。Concourse を localhost で起動している場合は URL を叩くだけで完了します。リモートの場合は、画面に表示されるコピーボタンを押してクリップボードにトークンをコピーし、ターミナルのプロンプトに貼り付けます。

f:id:kondoumh:20190424232805p:plain

今回も Spring Boot サンプルアプリをターゲットにパイプラインを作ってみます。

github.com

Concourse では Git のリポジトリや Docker レジストリなどを外部のリソースとして使用することができます。今回作成するパイプラインは、ビルド対象のプロダクトとは別の Git リポジトリで管理するようにしました。こうすることでパイプラインの変更とビルド対象のコードの変更を分離できます。

前回の GitLab CI はプロダクトのリポジトリに CI の定義を配置するため、パイプラインの修正のたびにコミットが発生します。一発で成功することが分かっている場合を除き、フィーチャーブランチで作業して成功したら MR を作成するという開発スタイルになるでしょう。Concourse CI ではパイプライン専用のリポジトリを作ることができます。

今回作成したパイプラインのディレクトリ構造は以下のようにしています。

└── workspace
    ├── pipeline.yml
    └── tasks
        ├── build.sh
        ├── build.yml
        ├── deploy.sh
        └── deploy.yml

pypeline.yml がリソース・ジョブのオーケストレーションを記述するメインのファイルです。

今回定義した pipeline.yml は次のようになっています。ジョブは build-sb-sample という名前の1つだけで、ジョブ内でビルドとデプロイは別タスクに分けています。デプロイではコンテナをビルドして Docker in Docker 構成で起動するため privileged: true を指定しています。

resources:
  # プロダクトのリポジトリ
  - name: sb-sample-service
    type: git
    source:
      uri: https://github.com/kondoumh/sb-sample-service.git
      branch: master
  # パイプラインのリポジトリ
  - name: pipelines
    type: git
    source:
      uri: https://github.com/kondoumh/pipelines.git
      branch: master

jobs:
- name: build-sb-sample
  public: true
  plan:
  # 2つのリポジトリを取得
  - get: sb-sample-service
  - get: pipelines
  # プロダクトのビルドタスク
  - task: Build project
    file: pipelines/concourse/workspace/tasks/build.yml
  # プロダクトのデプロイタスク
  - task: Deploy service
    privileged: true
    file: pipelines/concourse/workspace/tasks/deploy.yml

タスクの定義は専用の YAML に切り出しています。

build.yml の定義。Java:8 のコンテナを使って、ビルド用のシェルを実行します。inputs としてパイプラインのプロダクトの両リポジトリを使うため2つとも指定。プロダクトのディレクトリ内(の target ディレクトリ) にビルド成果物である JAR ファイルを出力するため、outputs にもプロダクトのリポジトリを指定しています。Maven Central リポジトリから多くの依存ライブラリをダウンロードするため、Maven のローカルリポジトリ (.m2) をキャッシュに指定しました。

platform: linux

image_resource:
  type: docker-image
  source: {repository: java, tag: 8}

inputs:
  - name: pipelines
  - name: sb-sample-service

outputs:
  - name: sb-sample-service

caches:
  - path: .m2/

run:
  path: "pipelines/concourse/workspace/tasks/build.sh"

build.sh では、取得したリポジトリ内の mvnw コマンドでビルド・単体テスト・パッケージングを行います。

#!/bin/sh -xe

cd sb-sample-service
./mvnw package

deploy.yml の定義。inputs として、前のタスクの outputs である (JAR が生成された) プロダクトリポジトリとパイプラインリポジトリを指定しています。

platform: linux

image_resource:
  type: docker-image
  source: {repository: quay.io/cosee-concourse/dind, tag: latest}

inputs:
  - name: sb-sample-service
  - name: pipelines

run:
  path: "pipelines/concourse/workspace/tasks/deploy.sh"

デプロイで使用する docker イメージは Docker in Docker での docker-compose に対応した dind のイメージを使いました。

github.com

deploy.sh では、Docker サービスを起動して、プロダクトのリポジトリ内の docker-compose.yml を使ってコンテナをビルド・起動し、curl で API を叩いて動作確認しています。

#!/bin/sh -xe
source /docker-lib.sh
start_docker

cd sb-sample-service
docker-compose up -d
sleep 30
docker ps
curl -X POST "http://localhost:18888/api/user/" -H "accept: */*" -H "Content-Type: application/json" -d "{ \"id\": 1, \"name\": \"Mike\"}"
curl -X GET "http://localhost:18888/api/usr/1" -H "accept: */*"
docker-compose down

このようにして作成したパイプラインを fly コマンドを使って Concourse に設定 (set-pipeline)、ポーズ解除 (unpause-pipeline)、ジョブ起動 (trigger-job) します。

$ fly -t tutorial set-pipeline -c pipeline.yml -p build-sb-sample
$ fly -t tutorial unpause-pipeline -p build-sb-sample
$ fly -t tutorial trigger-job -j build-sb-sample/build-sb-service -w

set-pipelinespunpause-pipelineup のように短縮名もありますので打鍵量を減らすことができます。

trigger-job で watch オプション (-w) を付けることで、そのままターミナルに実行ログが流れていきます。

f:id:kondoumh:20190424224856p:plain

もちろん Web UI でもパイプラインを起動して実行の様子を眺めることができます。右側の build-sb-sample が実行中になっています。

f:id:kondoumh:20190424012624p:plain

実行されているジョブの様子。2つのリソースがジョブに繋がって実行中であることがわかります。

f:id:kondoumh:20190424012643p:plain

成功して完了した画面。

f:id:kondoumh:20190424012809p:plain

Web UI でも実行ログを確認できます。タスクごとにセクションが分かれています。

f:id:kondoumh:20190424012838p:plain

Elm で書かれたという UI はシンプルでリアクティブにアニメーションします。

fly watch でターミナルを見ているだけでも実行状態を監視できるので Web UI は触らなくても作業が進みます。チュートリアルにもこうあります。

fly watch コマンドは、ラップトップPCのバッテリーの節約になります。実は、Concourse Web UI で実行されているJobを見ていると、ターミナルでfly watchを実行するよりもバッテリー消費量が多いことが分かりました。あなたのPCでは、状態が異なる場合があるかもしれませんが。

実際、今回は外出先の待ち時間でパイプラインの動作確認をしていたのですが、EC2 インスタンスで起動した Concourse に fly コマンドを叩いて実行を確認、YAML ファイルとシェルを編集し Git リポジトリに push という作業フローだったので、Pixel 3 の Termux だけで完結してしまいました。

blog.kondoumh.com

以上のように Concourse はターミナル操作がメインになるので、マニアックというか地味ですが、Jenkins と比べると覚えることが少なくシンプルな CI ツールに仕上がっています。

Maven や NPM のような各プログラミング言語専用のビルドツールの枠を超えて、開発者の手元で簡単に流せる CI ツールとして採用するのもよいでしょう。

ジョブ、タスクのような単位でビルドを管理できるため、多数のモジュールの依存関係を管理する複雑で巨大なパイプラインにもスケールアウト可能になっていると思います。

*1:いつの間にか Docker for Mac / Windows から名前変わったんですね

*2:Concourse 本体も Go で開発されています。