前記事の続きです。
目次
はじめてのConcourse CI
はじめてのTask
まずはJobを作る前に、Task単体(One-Off Task)を試しましょう。JobはTaskとResourceから構成されますが、One-Off Taskの実行方法を覚えておくとJobの開発・デバッグに役立ちます。
hello.yml
を作成して、以下の内容を記述してください。
---
platform: linux
image_resource:
type: docker-image
source:
repository: alpine
run:
path: echo
args: ["Hello", "World"]
repositoryには極小サイズのDockerイメージであるalpine
を指定しました。
以下のコマンドを実行してください。
$ fly -t lite execute -c hello.yml
以下のような出力が得られるでしょう。
targeting http://192.168.100.4:8080
executing build 1
initializing
Pulling alpine@sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0...
sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0: Pulling from library/alpine
420890c9e918: Pulling fs layer
420890c9e918: Verifying Checksum
420890c9e918: Download complete
420890c9e918: Pull complete
420890c9e918: Pull complete
Digest: sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0
Status: Downloaded newer image for alpine@sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0
Successfully pulled alpine@sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0.
running echo Hello World
Hello World
succeeded
TaskはDockerコンテナ上で行われます。
初回はDockerイメージのプルが行われましたが、2回目以降はプル済みのイメージを使用します。
$ fly -t lite execute -c hello.yml
targeting http://192.168.100.4:8080
executing build 2
initializing
running echo Hello World
Hello World
succeeded
YAMLでインラインスクリプトを書く場合は、以下のようにsh -c
で文字列の複数行記法を使うのが好みです。
---
platform: linux
image_resource:
type: docker-image
source:
repository: alpine
run:
path: sh
args:
- -c
- |
echo "Hello World"
実行
$ fly -t lite execute -c hello.yml
targeting http://192.168.100.4:8080
executing build 3
initializing
running sh -c echo "Hello World"
Hello World
succeeded
タスクで何らかのコマンドやライブラリが必要な場合は、JenkinsのようにCIサーバーにそれをインストールするのではなく、必要なものが用意されたDockerイメージを使用すれば良いです。
例えば、前述のalpine
パッケージにはbash
もgit
もインストールされていません。
先ほどのhello.yml
のpath
をsh
からbash
に変えると次のエラーが発生するでしょう。
targeting http://192.168.100.4:8080
executing build 4
initializing
running bash -c echo "Hello World"
proc_starter: ExecAsUser: system: program 'bash' was not found in $PATH: exec: "bash": executable file not found in $PATH
failed
bash
やgit
を使うために、それらがインストール済みのイメージを指定すればよいです。ここではgetourneau/alpine-bash-git
を使用します。hello.yml
を次のように変更します。
---
platform: linux
image_resource:
type: docker-image
source:
repository: getourneau/alpine-bash-git
run:
path: bash
args:
- -c
- |
echo "Hello World"
実行
$ fly -t lite execute -c hello.yml
targeting http://192.168.100.4:8080
executing build 5
initializing
Pulling getourneau/alpine-bash-git@sha256:5a8941b28a8dafb0a1bd43e4ba1a4ea6f9f0e186e326ee4378deabe83d263442...
sha256:5a8941b28a8dafb0a1bd43e4ba1a4ea6f9f0e186e326ee4378deabe83d263442: Pulling from getourneau/alpine-bash-git
4d06f2521e4f: Pulling fs layer
e2433d8ede7d: Pulling fs layer
061a2bf86483: Pulling fs layer
4d06f2521e4f: Verifying Checksum
4d06f2521e4f: Download complete
e2433d8ede7d: Verifying Checksum
e2433d8ede7d: Download complete
4d06f2521e4f: Pull complete
4d06f2521e4f: Pull complete
e2433d8ede7d: Pull complete
e2433d8ede7d: Pull complete
061a2bf86483: Verifying Checksum
061a2bf86483: Download complete
061a2bf86483: Pull complete
061a2bf86483: Pull complete
Digest: sha256:5a8941b28a8dafb0a1bd43e4ba1a4ea6f9f0e186e326ee4378deabe83d263442
Status: Downloaded newer image for getourneau/alpine-bash-git@sha256:5a8941b28a8dafb0a1bd43e4ba1a4ea6f9f0e186e326ee4378deabe83d263442
Successfully pulled getourneau/alpine-bash-git@sha256:5a8941b28a8dafb0a1bd43e4ba1a4ea6f9f0e186e326ee4378deabe83d263442.
running bash -c echo "Hello World"
Hello World
succeeded
新たなイメージがダウンロードされ、bashを実行することができました。
スクリプトはインラインだけでなく外部ファイルを指定することも可能です、
run:
path: ./hello/hello.sh
ここで、疑問が出ます。ファイルはどうやってコンテナに渡せば良いでしょうか?
Taskにはinputs
、outputs
属性で入出力フォルダを指定できます。
---
platform: linux
image_resource:
type: docker-image
source:
repository: getourneau/alpine-bash-git
inputs:
- name: hello
run:
path: ./hello/hello.sh
インラインスクリプトは同じフォルダのhello.sh
に移してください。
#!/bin/bash
echo "Hello World"
またchmod +x hello.sh
で実行権限をつけてください。
fly execute
を実装する際に-i
オプションでinputs
のマッピングを指定します。
$ fly -t lite execute -c hello.yml -i hello=.
targeting http://192.168.100.4:8080
executing build 10
initializing
running ./hello/hello.sh
Hello World
succeeded
-i
で指定したディレクトリがアップロードされてコンテナからアクセスできました。
hello.sh
の内容を以下に書き換えて再度実行してみましょう。
#!/bin/bash
find .
実行
$ fly -t lite execute -c hello.yml -i hello=.
targeting http://192.168.100.4:8080
executing build 11
initializing
running ./hello/hello.sh
.
./hello
./hello/hello.yml
./hello/hello.sh
succeeded
カレントディレクトリの内容がコンテナ内のhello
ディレクトリに配置されたことがわかります。
ただしTaskを実行するコンテナはステートレスで、ここでアップロードした内容は永続化されるわけではありません。
Taskで使用するファイル等を永続化したい場合はどうすれば良いでしょうか。ここで出てくるのがResourceです。
はじめてのJob
ここで使用したhello.sh
などはResourceに置くことで、Taskから常にアクセスできます。
このようにResourceとTaskを組み合わせたものがJobです。
実際にはResourceの定義はJobの定義ファイルに記述します。
ここではTaskで必要なファイルをGitHubに置き、Git Resourceを定義してTaskが使用するJobを作成しましょう。
まずは現状のカレントフォルダをGitHubにpushしましょう。
ここではmaking/hello-concourseを使用します。
$ git init
$ git add -A
$ git commit -m "first commit"
$ git remote add origin https://github.com/making/hello-concourse.git
$ git push -u origin master
次にJobの定義を行います。
同じフォルダにpipeline.yml
を作成してください。
---
# Resourceの定義
resources:
# Git Resourceの定義
- name: hello
type: git
source:
uri: https://github.com/making/hello-concourse.git
# Jobの定義
jobs:
- name: hello-job
public: true # UI上でJobの結果をログイン不要で公開するかどうか
plan:
- get: hello
trigger: true # Resourceに変更があれば自動でジョブを実行するかどうか
- task: run-hello
file: hello/hello.yml
jobs
内のplan
でResourceとTaskを組み合わせます。get
はResourceをプルして、put
はResourceをプッシュします。task
にはさきほど作成したYAMLのパスを指定します。これもResourceから取得できるので、Resource名を考慮したパスを指定します。Taskはインラインで記述することも可能です。
Taskの定義ファイル内で宣言したinputs
の名前を持つResourceがget
で定義されている必要があります。今回の場合はhello
です。
Jobは1つですが、これが最小のパイプラインになりますfly set-pipeline
でこのパイプラインをConcourse CIに設定します。-p
はパイプライン名です。
$ fly -t lite set-pipeline -p hello -c pipeline.yml
targeting http://192.168.100.4:8080
resources:
resource hello has been added:
name: hello
type: git
source:
uri: https://github.com/making/hello-concourse.git
jobs:
job job-hello has been added:
name: job-hello
public: true
plan:
- get: hello
trigger: true
- task: run-hello
file: hello/hello.yml
apply configuration? [yN]: y
pipeline created!
you can view your pipeline here: http://192.168.100.4:8080/pipelines/hello
the pipeline is currently paused. to unpause, either:
- run the unpause-pipeline command
- click play next to the pipeline in the web ui
これでパイプラインが作成されました。http://192.168.100.4:8080にアクセスするとパイプラインが表示されます。
fly set-pipline
は fly sp
と省略可能です。
この時点では"paused"と呼ばれる状態で、パイプラインは止まっておりResourceの変更をウォッチしません。UIのヘッダーが青色なのは"paused"な状態を示しています。
パイプラインを動作させるには"un-pause"します。
$ fly -t lite unpause-pipeline -p hello
targeting http://192.168.100.4:8080
unpaused 'hello'
fly unpause-pipeline
は fly up
と省略可能です。
http://192.168.100.4:8080をリロードするとヘッダーの青色は消えます。そしてしばらくするとジョブが開始します。hello
Resourceの変更(初回分)を検知したためです。
Jobが成功すればブロックが緑色になります。
Jobをクリックすると結果を確認することができます。
Taskによるfind
の結果に.git
の中身も含まれていることがわかります。
Jobの実行は、Jobのページ右上の(+)ボタンをクリックしても行えますし、fly trigger-job
でも実行可能です。
結果のプッシュ
次に、put
も使ってみましょう。hello.sh
の出力結果をGitにpushしてみます。(ただのデモ用です。実際にはこんなことはしないでしょう)
hello.sh
を以下のように修正してください。
#!/bin/bash
find . > out/result.log
out
ディレクトリはhello.yml
にoutputs
として登録します。
---
platform: linux
image_resource:
type: docker-image
source:
repository: getourneau/alpine-bash-git
inputs:
- name: hello
outputs:
- name: out
run:
path: ./hello/hello.sh
fly execute
で動作確認しましょう。(fly e
と省略できます)
$ fly -t lite e -c hello.yml -i hello=. -o out=/tmp/out
targeting http://192.168.100.4:8080
executing build 22
initializing
running ./hello/hello.sh
succeeded
/tmp/out/result.log
がダウンロードされていることを確認してください。
次にこの結果をGitにコミットするTaskを作成します。
以下のようなcommit-log.yml
を作成してください。
---
platform: linux
image_resource:
type: docker-image
source:
repository: getourneau/alpine-bash-git
inputs:
- name: hello # ソースコード
- name: result # clone用のGit
- name: out # 全タスクの出力を入力にする
outputs:
- name: updated-result # commit用のGitリポジトリ
run:
path: ./hello/commit-log.sh
Concourseでは入力を出力に使えないため、clone用のGitとcommit用のGitをinputs
とoutputs
でそれぞれ定義しています。
commit-logs.sh
は以下のようになります。
#!/bin/bash
# clone用のGitをcloneしてcommit用のGitリポジトリを作成する
git clone result updated-result
cd updated-result/
# 前Taskの出力結果をGitのcommit用のGit作業ディレクトリに移動する
mv -f ../out/* ./
git config --global user.email "makingx at gmail dot com"
git config --global user.name "Toshiaki Maki"
git add -A
git commit -m "Update result log"
ちょっと複雑になってきましたが、これもfly execute
で動作確認しておきましょう。
$ mkdir /tmp/git
$ pushd /tmp/git
$ git init # 動作確認用のダミーGitレポジトリ作成
$ popd
$ fly -t lite e -c commit-log.yml -i out=/tmp/out -i result=/tmp/git -i hello=. -o updated-result=/tmp/updated-result
targeting http://192.168.100.4:8080
executing build 26
initializing
running ./hello/commit-log.sh
Cloning into 'updated-result'...
warning: You appear to have cloned an empty repository.
done.
[master (root-commit) 86b381e] Update result log
1 file changed, 54 insertions(+)
create mode 100644 result.log
succeeded
outputs
であるupdated-result
を確認すると、コミットされていることがわかります。
$ cd /tmp/updated-result/
$ git log
commit 86b381ece045bbd8d8d94554ae885049974d66f6
Author: Toshiaki Maki <makingx at gmail dot com>
Date: Sun Apr 10 17:12:58 2016 +0000
Update result log
さて、ようやくJobの準備です。ここまでの内容をまとめるとjobs
のplan
は以下のようになります。
jobs:
- name: job-hello
public: true
plan:
- get: hello # パイプラインのGitをpull
trigger: true
- get: result # 出力結果のGitをpull (次に定義する)
- task: run-hello # findの結果をファイルに書き出すTask
file: hello/hello.yml
- task: commit-log # 出力結果ファイルをGitにコミットするTask
file: hello/commit-log.yml
- put: result # 出力結果のGitのpush
params:
repository: updated-result
今回は出力結果を格納するGit Resource(result
)としてGistを使います。
まずはresult.log
というファイルを持つ、Gistを作成してください。
Gistができたら、SSH用のURLをコピーしてください。
SSHのURLをresult
Resourceの定義に貼り付けます。
- name: result
type: git
source:
uri: git@gist.github.com:dbc56fbd08f415fa52c784f4cd2e4bd3.git
private_key: {{github-private-key}}
branch: master
GitHubにプッシュするためにはSSHのプライベートキーが必要です。この機密情報はソースコード管理対象外にするため、pipeline.yml
上は{{github-private-key}}
という形式でプレースホルダを利用できます。プレースホルダはパイプラインをConcourse CIに設定するタイミングで別ファイルから埋め込み可能です。
以上の内容をまとめるとpipeline.yml
は次のようになります。
---
resources:
- name: hello
type: git
source:
uri: https://github.com/making/hello-concourse.git
- name: result
type: git
source:
uri: git@gist.github.com:dbc56fbd08f415fa52c784f4cd2e4bd3.git
private_key: {{github-private-key}}
branch: master
jobs:
- name: job-hello
public: true
plan:
- get: hello
trigger: true
- get: result
- task: run-hello
file: hello/hello.yml
- task: commit-log
file: hello/commit-log.yml
- put: result
params:
repository: updated-result
機密情報は~/.concourse/credentials.yml
に記述しましょう。
---
github-private-key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAuvUl9YU...
...
HBstYQubAQy4oAEHu8osRhH...
-----END RSA PRIVATE KEY-----
ようやくパイプラインの設定です。-l
で機密情報ファイルのパスを指定してください。
$ fly -t lite sp -p hello -c pipeline.yml -l ~/.concourse/credentials.yml
追加したパイプラインの設定ファイルをpushしてください。
パイプライン上はTaskのスクリプトファイルをGit Resourceから取っているため、パイプラインで使用するTaskを更新するにはGitHubにpushする必要がある点に注意が必要です。
$ git add -A
$ git commit -m "update pipeline" -a
$ git push origin master
UI上は以下のように表示されます。
Gitの更新が検知されると、job-hello
Jobが開始します。成功すると次のような出力が得られます。
Gistの方も更新されていることがわかります。
はじめてのJob連携
次にジョブとジョブをつなげて、少しだけパイプラインっぽくします。
最初のジョブの出力結果をGit、次のジョブで出力してみます。
まずは後半の結果出力のためのTaskから作成します。
次の内容のshow-result.sh
を作成します。
#!/bin/bash
echo "==== Result ===="
cat result/result.log
次の内容のshow-result.yml
を作成します。
---
platform: linux
image_resource:
type: docker-image
source:
repository: getourneau/alpine-bash-git
inputs:
- name: hello
- name: result
run:
path: ./hello/show-result.sh
input
にresult
を追加しました。このresult
の下に前Taskの出力結果であるresult.log
が作成されることとします。
まずはfly execute
でOne-Off Taskを試します。テスト用にresult.log
を/tmp/result
に作成して、-i
でresult=/tmp/result
を指定します。
$ mkdir /tmp/result
$ echo This is test > /tmp/result/result.log
$ fly -t lite e -c show-result.yml -i hello=. -i result=/tmp/result
targeting http://192.168.100.4:8080
executing build 18
initializing
running ./hello/show-result.sh
==== Result ====
This is test
succeeded
これで後半のTaskが出来ました。
pipeline.yml
にこのJobを追加しましょう。
---
resources:
- name: hello
type: git
source:
uri: https://github.com/making/hello-concourse.git
- name: result
type: git
source:
uri: git@gist.github.com:bd8d06457628f94c7a8abc06925fd052.git
private_key: {{github-private-key}}
branch: master
jobs:
- name: job-hello
public: true
plan:
- get: hello
trigger: true
- get: result
- task: run-hello
file: hello/hello.yml
- task: commit-log
file: hello/commit-log.yml
- put: result
params:
repository: updated-result
- name: job-show-result # 新規ジョブ
public: true
plan:
- get: hello
- get: result
trigger: true
passed: [ job-hello ]
- task: show-result
file: hello/show-result.yml
passed
で前段のジョブを指定できます。これによりジョブとジョブがつながります。
Concourseに設定しましょう。
$ fly -t lite sp -p hello -c pipeline.yml -l ~/.concourse/credentials.yml -n
パイプラインは次のようになります。
追加したパイプライン設定ファイルをgit pushしてください。
$ git add -A
$ git commit -m "add new job"
$ git push origin master
しばらくするとjob-hello
が始まり、成功するとjob-show-result
が開始します。
簡単ですが、ジョブ連携を試すことができました。
注意点としては、Job内(Task間)でinputs
とoutputs
を使ってファイルの受け渡しできますが、Job間ではそれがResourceを経由する必要があります。つまりS3などを使ってアーティファクトを受け渡しします。慣れるまで違和感があるかもしれません。
より高度なパイプライン
より高度なパイプラインの説明はまた今度書きますが、
Concourse CIの良いところは先人が組んで行ったパイプラインを簡単に参照できる点です。
例えばJavaプロジェクトのリリースのサンプルであれば
https://github.com/Pivotal-Field-Engineering/PCF-demo/tree/master/ci
こういうのが参考になります。
自分の環境で実行することも可能です。
情報がまだ少ない技術ですが、他人のパイプラインを見て学習することが可能です。
Meetup情報
近日開催予定です。要チェック
http://www.meetup.com/ja-JP/Concourse-CI-Tokyo-Meetup/
その他の資料
- 公式チュートリアル https://concourse.ci/tutorials.html
fly
コマンドマニュアル https://concourse.ci/fly-cli.html- 利用可能なResource一覧 http://concourse.ci/resource-types.html
- 有益なチュートリアル https://github.com/starkandwayne/concourse-tutorial
- 開発者による講演動画 https://www.youtube.com/watch?v=mYTn3qBxPhQ