Concourseで変革するポンチ絵と曼荼羅の世界

この記事は [NTTコミュニケーションズ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/nttcom) の11日目です。

## 3行で要約

- パワーポイント曼荼羅＜＜＜＜越えられない壁＜＜＜＜開発フローに組み込めるパイプライン
- リソースのフロー、コンポーネントの整合性・網羅性の設計は、プロジェクトを通じてチームで継続的な改善が必要
- ネタ記事

## ポンチ絵描いてますか？

*伝説には斯くある──その者ポンチ絵[^1]を纏い、मण्डल《マンダーラ》[^2]をかざし道を闢く。*

よーし、いい子だ。今すぐマウスを置け。逆線表とかいう根拠不明なパズルを解く作業をやめて、俺の話を聞け。いいな？　おい、待て。その後ろに隠した謎の**概念**はなんだ？　Googleの画像検索[^3]で拾ってきたOSSのロゴを矢印でつなぎ合わせて一体何を召喚するつもりだ？　……OSSは無料？　おい、やめろ。とにかくやめろ。期末の終わりに、部長に見せてしまった謎のお絵かきを笑顔で持ってくるな？　いいな、すでに後戻りはできないんだ。そんな目をしてもダメだ。経済新聞で聞きかじったバズワードをそのまま書くなぁ──ッ！！！

なぜかって──？　その**概念** は俺に効く。

( ͡° ͜ʖ ͡°) < ﾏｼﾞﾃﾞｷｸ



### パワーポイントお絵かきの問題点

> *なるほど完璧な作戦っスね──っ 不可能だという点*に目を*つぶればよぉ*〰〰！
>
> ジョジョの奇妙な冒険　第4部　「ダイヤモンドは砕けない」

屏風の虎を出せないことは、一休さんの時代から知られている。しかし、歴史は繰り返されるのだ。

ボスに施策を説明するためにポンチ絵が必要？　コンポーネントに分けないと線表が不安？　いいや、俺が問題だと言っているのはそこじゃない。

オーケー、そろそろ気づいてくれ。**お前が上司に見せたポンチ絵は、サービス開発に関わる一番重要なコンポーネント[^4]**だってことに。ろくすっぽ検証もしてないくせに、俺たちの開発を振り回しているってことに。

チェックリスト？──いいや、違う。ダブルチェック？──いいや、違う。俺が言いたいことはそういうことじゃない。もう2017年も終わるんだ。ポンチ絵の質を人間の努力でカバーする時代は終わらせてくれ。それはもう**技術で解決できるエンジニアリングの領域**なんだ。



2018年のスマートなポンチ絵ってやーつ、そろそろ見せてくれないか？？？



## Concourseでポンチ絵を書く

以上、フィクションです。

こんにちは、@nitkyです。初めましての人は初めまして。普段は脆弱性をペシペシしたり、マルウェアを愛でたり、ポンチ絵に巣食う魔を祓うお仕事をしています。~~あまりに強い除霊を行うと、施策そのものが成仏してしまうケースが稀によくあるとのこと。破ぁッ！！~~

ポンチ絵という用語に馴染みのない方もいると思うので補足しますが、プロジェクトに忖度が必要な謎パワポお絵描きが存在しているのであれば、間違いなくそれがポンチ絵です。


### この記事の背景

ポンチ絵・曼荼羅と呼ばれる超概念図ですが、「◯◯パイプライン」に限りなく近い何か、またはソフトウェア等のコンポーネントを謎のドメインで分割・結合した名状しがたい何か、が含まれていることが有識者より報告されています。[要出典]


**「パワポで矢印引きまくるくらいなら、最初からCI/CDツールでパイプライン書けばよくね？？？」**


という若手社員（僕）の純粋な疑問をもとにこの記事は構成されています。邪念はありません。

……曼荼羅人材は徳が高いのです。（ここで筆が途切れている）



### この記事の目的

- 神々の世界と人の世界の接続
- 検証可能なパイプラインとしての曼荼羅翻訳
- **パワポで１ドットの微調整に30分かけるくらいならパイプライン図かけ**  ​



## Concourseについて

[Concourse](https://concourse.ci/)はGo言語で書かれたパイプラインベースのCIシステムです。今回はこれを使ってポンチ絵を検証可能にすることを考えます。



### Concourseのインストール

[公式ドキュメントのインストール手順](https://concourse.ci/installing.html)を参考に環境を用意してください。お手軽に試すのであれば、Single VMまたはDockerがオススメです。



### Concourseのコンセプト

Concourseで扱う概念は大きく分けて三つあります。

- リソース:　実体（エンティティ）を指す。gitリポジトリなど。
- タスク:　リソースを利用可能。隔離された環境でリソースを利用してスクリプトを実行。
- ジョブ:　タスクを実行可能。依存したリソースをトリガとして実行。

![screencapture-ci-concourse-ci-teams-main-pipelines-main-1510590404093.png](https://qiita-image-store.s3.amazonaws.com/0/217024/f56539f4-1f74-d8ea-ece8-e017ea208c8c.png)
[図1: Concourseのパイプライン例]

なるほど分からん、という印象を受けたかもしれませんが安心してください。

要は**リソースという入力**を**ジョブという関数で変化させていく**一連のプロセスを**パイプライン**と呼んでいます。（図1上にあるパイプラインでは、ジョブによってタスクの存在がラッピングされ見えなくなっています）

これさえ理解できれば、あとはYAMLによるパイプラインの記述に慣れるだけです。例を通してYAMLによるパイプラインの記述を実践していきましょう。



## Hello, World!

それでは、曼荼羅人材の気分になって作図を開始していきましょう！　(ｽｩ…

まずは、[ここ](https://github.com/concourse/fly#installing-from-the-concourse-ui-for-project-development)を参考にConcourseのWebUIを表示して、**fly**をダウンロード&インストールします。

（Docker版であればデフォルトURLは http://localhost:8080/ ）

```bash
$ chmod +x ~/Downloads/fly
$ mv ~/Downloads/fly /usr/local/bin/fly
$ fly --version
3.6.0
```

バージョンが表示さればインストールは成功です。動作確認はMac OS Sierraで行いましたが、それ以外の方は各OSや環境に合わせて、パス等の修正をお願いします。



次に、ターゲット名（エイリアス）の指定と、そのログインを行います。今回は`http://localhost:8080 `のターゲット名として`lite`を指定します。

```bash
$ fly -t lite login -c http://localhost:8080
```

ユーザ名とパスワードを聞かれた場合は、インストール時に指定したものを入力してください。(デフォルト設定は `concourse: changeme`）

`fly targets`で、現在使用可能なターゲットの一覧を確認できます。

```bash
$ fly targets
lite  http://localhost:8080  main  Thu, 07 Dec 2017 00:58:17 UTC
```



それでは、以下のようなパイプラインのコンフィグを用意して`lite`にセットしてみましょう。

```yaml:hello.yml
jobs:
- name: hello-world
  plan:
  - task: say-hello
    config:
      platform: linux
      image_resource:
        type: docker-image
        source: {repository: ubuntu}
      run:
        path: echo
        args: ["Hello, world!"]
```

```bash
$ fly -t lite set-pipeline -p hello-world -c hello.yml
```



`hello.yml`を`hello-world`というパイプライン名でセットしたところ、図2のような画面が表示されたと思います。

![screencapture-localhost-8080-1512522763564.png](https://qiita-image-store.s3.amazonaws.com/0/217024/6854a969-e26b-68be-83f0-d8cbd861b8d7.png)
[図2-1: hello-world]


おっと、僕としたことが失礼しました。曼荼羅人材は単金が高いので、タスクなんて小さな粒度で物事を考えるのは効率[^5]が悪いことを失念していました。細かいタスクを並べ挙げるのは、僕のようなエクソシストエンジニアのお仕事です。

そう考えると、このパイプラインのコンフィグはさらにシンプルになります。

```yaml:hello.yml
jobs:
- name: hello-world
```

```bash
$ fly -t lite set-pipeline -p hello-world -c hello.yml
```

さっぱりしましたね。出てくる絵を見比べても違いはありません。

![screencapture-localhost-8080-1512689558824.png](https://qiita-image-store.s3.amazonaws.com/0/217024/a71aa8cb-f6e0-15d0-f2a4-371c72c26430.png)
[図2-2: 曼荼羅人材hello-world]


ジョブが用意出たので、次にリソースを用意します。「リソースという入力をジョブという関数で変化させていく一連のプロセスをパイプラインという」の原則に沿って、リソースとジョブをそれぞれ定義しましょう。

```yaml:hello.yml
resources:
- name: exorcist
  type: time
  source: {interval: 1m}
jobs:
- name: hello-world
```

Concourseはリソースを定義する際、その`type`を宣言する必要があります。一般的な場合、リソースのエンティティとしてgitリポジトリなどが入るのですが、**そういう細かい修正**はエクソシストエンジニアの仕事です。今、僕たちの気持ちは曼荼羅人材。迷える子羊を、ただひたすらに導いていく高単金マンです。ここは涙を飲んで、一番単純なリソースタイプである`time`[^6]を**仮入れ**しておきました。**完璧なポンチ絵を描けば、あとはエクソシストエンジニアがなんとかしてくれます。**~~だんだん僕も曼荼羅人材のお気持ちが高まってきたぞ〜！~~

```bash
$ fly -t lite set-pipeline -p hello-world -c hello.yml
```

……なんと、エラーが出ました。リソースを宣言したにも関わらず使っていないので、どうやらそれを怒られているようです。

```bash
$ fly -t lite set-pipeline -p hello-world -c hello.yml
invalid resources:
	resource 'exorcist' is not used
```

限られたリソースを無駄にしない。曼荼羅人材であれば当然のことです。さっさと追加したリソースをジョブに放り込みましょう。

```yaml:hello.yml
resources:
- name: exorcist
  type: time
  source: {interval: 1m}
jobs:
- name: hello-world
  plan:
  - get: exorcist
```

![screencapture-localhost-8080-1512690200130.png](https://qiita-image-store.s3.amazonaws.com/0/217024/ded3f122-2e32-f475-7fe6-2f4426ee40de.png)
[図2-3: ジョブがリソースを取得]

無事、コマンドを実行して~~生贄~~リソースを投入できました。(リソースとジョブの間が点線で接続されているのは`trigger`が`true`になっていないからですが、Concourseを用いた作図の趣旨から外れるので今回は割愛します。詳しくは[ドキュメントを参照](https://concourse.ci/build-plans.html)してください。)

リソースをジョブに放り込んだら、次は成果の提供ですね。リリースを定義して配置してみましょう。


```yaml:hello.yml
resources:
- name: exorcist
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: hello-world
  plan:
  - get: exorcist
  - put: release
```

```bash
$ fly -t lite set-pipeline -p hello-world -c hello.yml
```

![screencapture-localhost-8080-1512691034461.png](https://qiita-image-store.s3.amazonaws.com/0/217024/d72ab202-969a-855f-4feb-c2a779b5fbaa.png)
[図2-4: リソースの取得とリソースの設置]

exorcistをhello-worldに放り込んで、サービスをリリース……。若干helloというよりhellな感じがしなくもないですが、それは気のせいです。基本的なConcourseの使い方は以上となります。あとは人月の神話[^7]がプロジェクトの成功を約束します。



## Concourseによるお絵かき例

ここでは、特にConcourseを用いたパイプラインの作図に特化して例を紹介します。ConcourseのWeb画面にある左上のハンバーガーアイコンをクリックすることで、セットされたパイプラインの一覧を表示することができます。


### 1つのリソースが1つのジョブを通過してreleaseに配置

リソース

- resA
- release

ジョブ

- job01

```yaml:example1.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
  - put: release
```

```bash
$ fly -t lite set-pipeline -p example1 -c example1.yml
```

![screencapture-localhost-8080-teams-main-pipelines-example1-1512692699477.png](https://qiita-image-store.s3.amazonaws.com/0/217024/bb0a914c-2e90-5469-b853-2f32f6051fc8.png)
[図3-1: 1つのリソースが1つのジョブを通過してreleaseに配置]



### 2つのリソースが1つのジョブを通過してreleaseに配置

リソース

- resA
- resB
- release

ジョブ

- job01

```yaml：example2.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: resB
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
  - get: resB
  - put: release
```

```bash
$ fly -t lite set-pipeline -p example2 -c example2.yml
```

![screencapture-localhost-8080-teams-main-pipelines-example2-1512697952233.png](https://qiita-image-store.s3.amazonaws.com/0/217024/a24635c1-37b1-cea5-92e9-93007e1d841c.png)
[図3-2: 2つのリソースが1つのジョブを通過してreleaseに配置]



### 1つのリソースが2つのジョブを漏れなく通過してreleaseに配置

リソース

- resA
- release

ジョブ

- job01
- job02

```yaml:example3.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
  - put: release
- name: job02
  plan:
  - get: resA
  - put: release
```

```bash
$ fly -t lite set-pipeline -p example3 -c example3.yml
```

![screencapture-localhost-8080-teams-main-pipelines-example3-1512698696429.png](https://qiita-image-store.s3.amazonaws.com/0/217024/3e08c281-13e5-d926-f360-735f89665c69.png)
[図3-3: 1つのリソースが2つのジョブを漏れなく通過してreleaseに配置]



### 1つのリソースが2つのジョブを順に通過してreleaseに配置

リソース

- resA
- release

ジョブ

- job01
- job02

```yaml:example4.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
- name: job02
  plan:
  - get: resA
    passed: [job01]
  - put: release
```

```bash
$ fly -t lite set-pipeline -p example4 -c example4.yml
```

![screencapture-localhost-8080-teams-main-pipelines-example4-1512698759159.png](https://qiita-image-store.s3.amazonaws.com/0/217024/64db4204-50e2-53c8-9040-fa3d12d39994.png)
[図3-4: 1つのリソースが2つのジョブを順に通過してreleaseに配置]



### 2つのリソースが2つのジョブを漏れなく通過してreleaseに配置

```yaml:example5.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: resB
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
  - get: resB
  - put: release
- name: job02
  plan:
  - get: resA
  - get: resB
  - put: release
```

```bash
$ fly -t lite set-pipeline -p example5 -c example5.yml
```

![screencapture-localhost-8080-teams-main-pipelines-example5-1512699408426.png](https://qiita-image-store.s3.amazonaws.com/0/217024/f9d031af-e21e-6057-f4c3-ded5b17b6816.png)
[図3-5: 2つのリソースが2つのジョブを漏れなく通過してreleaseに配置]



### 2つのリソースが2つのジョブを順に通過してreleaseに配置

```yaml:example6.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: resB
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
  - get: resB
- name: job02
  plan:
  - get: resA
    passed: [job01]
  - get: resB
    passed: [job01]
  - put: release
```

```bash
$ fly -t lite set-pipeline -p example6 -c example6.yml
```

![screencapture-localhost-8080-teams-main-pipelines-example6-1512699647676.png](https://qiita-image-store.s3.amazonaws.com/0/217024/12946ebd-314f-ec73-6478-6c1f8d0f9b53.png)
[図3-6: 2つのリソースが2つのジョブを順に通過してreleaseに配置]



### 2つのリソースが2つのジョブを漏れなく通過して、1つのジョブに集約されてからreleaseに配置

```yaml:example7.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: resB
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
  - get: resB
- name: job02
  plan:
  - get: resA
  - get: resB
- name: job03
  plan:
  - get: resA
    passed: [job01, job02]
  - get: resB
    passed: [job01, job02]
  - put: release
```

```
$ fly -t lite set-pipeline -p example7 -c example7.yml
```

![screencapture-localhost-8080-teams-main-pipelines-example7-1512699920130.png](https://qiita-image-store.s3.amazonaws.com/0/217024/0c1b1f29-99f1-1988-4564-f4928d9d647f.png)
[図3-7: 2つのリソースが2つの並列なジョブを漏れなく通過して、1つのジョブに集約されてからreleaseに配置]



### さらに高度なパイプライン

3つのリソースが、4つのジョブを以下のように通過することを考える。

- resA, resB, resCが存在する
- resAはjob01とjob03とjob04をそれぞれ順に通過する
- resBはjob02とjob03を順に通過する
- resCはjob01とjob02を並列に通過し、さらにjob03とjob04を順に通過する

```yaml:advanced.yml
resources:
- name: resA
  type: time
  source: {interval: 1m}
- name: resB
  type: time
  source: {interval: 1m}
- name: resC
  type: time
  source: {interval: 1m}
- name: release
  type: time
  source: {interval: 1m}
jobs:
- name: job01
  plan:
  - get: resA
  - get: resB
  - get: resC
- name: job02
  plan:
  - get: resB
  - get: resC
- name: job03
  plan:
  - get: resA
    passed: [job01]
  - get: resB
    passed: [job02]
  - get: resC
    passed: [job01, job02]   
- name: job04
  plan:
  - get: resA
    passed: [job03]
  - get: resC
    passed: [job03]
  - put: release
```

```
$ fly -t lite set-pipeline -p advanced -c advanced.yml
```

![screencapture-localhost-8080-teams-main-pipelines-advanced-1512700351060.png](https://qiita-image-store.s3.amazonaws.com/0/217024/a007fc7c-b509-daed-b0d9-d3646d22d9f8.png)
[図3-8: 高度なパイプラインの例]



## パワーポイント図との機能比較

###利点

**開発フロー（CI/CD）に組み込むことで、図の正しさを継続的に検証可能**

-　パイプラインのフローでやりとりされるもの（リソース）の一貫した明記
-　各ステージでジョブが行うことの一貫した明記
-　適切なコンポーネント選択なのか検証が可能
-　適切なドメイン分割なのか検証が可能
-　（開発時に判明した）事実に沿った修正・変更が容易
-　開発初期段階から、E2Eテストを意識して開発を回せる
-　パイプラインの修正頻度が少ない＝設計時の見積もりが上手い
-　パイプラインの修正頻度が多い＝設計時の見積もりが甘い
-　パイプラインを動かすことができない＝論外

###欠点

**動くポンチ絵をちゃんと描ける人材なら、そいつの曼荼羅に困ってない**




## まとめ

「私……全てのポンチ絵を生まれる前に消し去りたい。全ての宇宙、過去と未来の全てのポンチ絵を、この手で」


## 参考URL
- https://concourse.ci/
- https://github.com/concourse/concourse
- https://github.com/concourse/pipelines


[^1]: 美術作品。実用性は乏しい。魔除けに使う細かい呪文がびっしりと書き込まれている。
[^2]: 上司の精神状態や残業によってポンチ絵の神聖が高まった状態。耐性のない人間が見ると発狂する。
[^3]: [gooにも画像検索がある](https://search.goo.ne.jp/?mode=7)こと、忘れないでください。
[^4]: テクニカルアーキテクトから見れば、アーキテクチャ設計やグランドデザインをコンポーネントといってよいか疑問だが、サービスリリースを成功に導く構成要素として、あえてコンポーネントという用語を使用した。
[^5]: エンジニアの間では、曼荼羅人材の単金で優秀なエンジニアを雇用することを指す。
[^6]: インターバル実行などに用いるエンティティ。
[^7]: 銀の弾丸などない。しかし、金の弾丸は社内政治力と深い関わりがある。