📝 BLOG.IK.AM

@making's memo
(🗃 Categories 🏷 Tags)

はじめてのConcourse CI

🗃 {Dev/CI/ConcourseCI}

🏷 Concourse CI 🏷 Docker

🗓 Updated at 2017-06-04T05:46:12+09:00 by Toshiaki Maki  🗓 Created at 2016-04-10T13:29:35+09:00 by Toshiaki Maki  {✒️️ Edit  ⏰ History}


🚨 Warning: This content is old. Please don't trust too much.

前記事の続きです。

目次

はじめての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 [email protected]: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 [email protected]:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0

Successfully pulled [email protected]: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パッケージにはbashgitもインストールされていません。

先ほどのhello.ymlpathshから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

bashgitを使うために、それらがインストール済みのイメージを指定すればよいです。ここでは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/[email protected]: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/[email protected]:5a8941b28a8dafb0a1bd43e4ba1a4ea6f9f0e186e326ee4378deabe83d263442

Successfully pulled getourneau/[email protected]:5a8941b28a8dafb0a1bd43e4ba1a4ea6f9f0e186e326ee4378deabe83d263442.

running bash -c echo "Hello World"    
Hello World
succeeded

新たなイメージがダウンロードされ、bashを実行することができました。

スクリプトはインラインだけでなく外部ファイルを指定することも可能です、

run:
  path: ./hello/hello.sh

ここで、疑問が出ます。ファイルはどうやってコンテナに渡せば良いでしょうか?

Taskにはinputsoutputs属性で入出力フォルダを指定できます。

---
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にアクセスするとパイプラインが表示されます。

image

fly set-piplinefly 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-pipelinefly upと省略可能です。

http://192.168.100.4:8080をリロードするとヘッダーの青色は消えます。そしてしばらくするとジョブが開始します。hello Resourceの変更(初回分)を検知したためです。

image

Jobが成功すればブロックが緑色になります。

image

Jobをクリックすると結果を確認することができます。

image

Taskによるfindの結果に.gitの中身も含まれていることがわかります。

Jobの実行は、Jobのページ右上の(+)ボタンをクリックしても行えますし、fly trigger-jobでも実行可能です。

結果のプッシュ

次に、putも使ってみましょう。hello.shの出力結果をGitにpushしてみます。(ただのデモ用です。実際にはこんなことはしないでしょう)

hello.shを以下のように修正してください。

#!/bin/bash                                                                                                                                                                                                
find . > out/result.log

outディレクトリはhello.ymloutputsとして登録します。

---
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をinputsoutputsでそれぞれ定義しています。

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の準備です。ここまでの内容をまとめるとjobsplanは以下のようになります。

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を作成してください。

image

Gistができたら、SSH用のURLをコピーしてください。

image

SSHのURLをresult Resourceの定義に貼り付けます。

- name: result
  type: git
  source:
    uri: [email protected]: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: [email protected]: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上は以下のように表示されます。

image

Gitの更新が検知されると、job-hello Jobが開始します。成功すると次のような出力が得られます。

image

Gistの方も更新されていることがわかります。

image

はじめての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

inputresultを追加しました。このresultの下に前Taskの出力結果であるresult.logが作成されることとします。

まずはfly executeでOne-Off Taskを試します。テスト用にresult.log/tmp/resultに作成して、-iresult=/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: [email protected]: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

パイプラインは次のようになります。

image

追加したパイプライン設定ファイルをgit pushしてください。

$ git add -A
$ git commit -m "add new job"
$ git push origin master

しばらくするとjob-helloが始まり、成功するとjob-show-resultが開始します。

image

簡単ですが、ジョブ連携を試すことができました。

注意点としては、Job内(Task間)でinputsoutputsを使ってファイルの受け渡しできますが、Job間ではそれがResourceを経由する必要があります。つまりS3などを使ってアーティファクトを受け渡しします。慣れるまで違和感があるかもしれません。

より高度なパイプライン

より高度なパイプラインの説明はまた今度書きますが、
Concourse CIの良いところは先人が組んで行ったパイプラインを簡単に参照できる点です。

例えばJavaプロジェクトのリリースのサンプルであれば
https://github.com/Pivotal-Field-Engineering/PCF-demo/tree/master/ci
こういうのが参考になります。

自分の環境で実行することも可能です。
image

情報がまだ少ない技術ですが、他人のパイプラインを見て学習することが可能です。

Meetup情報

近日開催予定です。要チェック
http://www.meetup.com/ja-JP/Concourse-CI-Tokyo-Meetup/

その他の資料