宣言的Spinnaker 継続的デリバリーのさらに上にいく

## tl;dr

学部4年になり忙しくなるので、よりすべてを「自動化」しようと思いました。
記事が長くなってしまうので詳細は[ブログ](http://www.1915keke.com/entry/2018/05/19/124103)でご覧ください。

最近自分が考案した 宣言的継続的デリバリー(Declartive Continuous Delivery) を紹介させていただきます。
従来の継続的デリバリーにDeploy戦略も含めて宣言的に書くソフトウェア手法を考えました。

記事のざっくりとして内容は

- マイクロサービスを継続的デリバリーことができるspinnakerを宣言的に構築する
- まるでCircleCIの設定ファイルのような`config.yml`を配置するだけでspinnakerのパイプラインを走らせることができる。
  - 既存の環境への親和性が高い
  - 「それぞれの設定がそれぞれのアプリケーションへ帰属」というわかりやすさ
- templateは一括でrepoでモジュール化して管理しよう
  - 組織の財産となる
  - templateとアプリケーションを分離することで管理しやすい
  - 再利用性

です。設計したDCDフローは以下の通りです。
解説は記事を読んでください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/e44b2764-10d4-448c-7e1a-028a1e8f2d13.png)


設計思想や「宣言的とは何か」などもブログでは取り上げています。

------------------------

## 対象とする読者

- デプロイが大きな負担となっているひと
- 自動化が大好きな人
- Kubernetesなどでマイクロサービスを運用していて、さらにDeployサイクルをあげたい人
- カナリアデプロイ、ブルー・グリーンデプロイを自動的にやりたい人


## 解決する問題

1.kubernetesのDeploy問題
kubernetesは非常に機能が多く、マイクロサービスを導入しやすくして、よりアジャイルな開発へのパラダイムシフトを促したソフトです。

manifestファイルと呼ばれる、どのロードバランサーに何を繋げて、どのくらいのVMで、、、といったようなことがすべて宣言的に書けて、Deployするには`apply`コマンドを叩くだけです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/13021e4c-056c-79d8-03f0-ae35849697d7.png)


しかしながら、Deployが毎日数回など行われるときに`apply`コマンドを叩くのは面倒です。
現状として、Botを使ってSlack越しでDeployしたり、CIに任せて要るケースがほとんどです。

2.Deploy戦略の難しさ
Deployとは単純な作業ではなくて、実際にはデプロイ後に変な挙動をしていないか、他のマイクロサービスとうまく連携が取れて要るかなど、確認することが膨大にあります。

- Blue/Green Deployment
- Canary Deployment
- Rollbackが安全にできるか
- ビジネスモデルとの連携
- ...

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/b6d0a0f9-1e6b-8a5c-aad3-80a56f7eebf3.png)
https://sweetcode.io/13115-2/

これらをメンテしていくのは難しく、よくチームに「インフラエンジニア」や「SRE」などと肩書きがつくエンジニアが面倒見たりしています。

3.インフラエンジニアを導入する盲点
インフラに関するタスクがヘビーならインフラ専門のエンジニアをおけばいいじゃん！
って考えるかもしれません。
確かにインフラには専門的な知識や経験が必要で、膨大なドキュメントを読まないといけません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/f50bc9bc-14ac-f57f-ae9c-c3bdf2f6e8aa.png)
https://www.youtube.com/watch?v=LVgP63BkhKQ


確かにインフラにマジつよエンジニアがいればその人に任せることができます。
実際、どの会社もそのような形態を取っていることが多く、採用でもコースが分かれているほどです。

しかしながら、一人に任せるのにはデメリットがあり、

- インフラで起きることのフィードバックをその他のエンジニアに還元できない
- 責任がインフラエンジニアだけに生じる
- 本来の企業としての価値は生んでいない
- ...

のようなことが挙げられます。


## Spinnakerとは

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/6e29315f-9955-28bb-b608-e582083cf493.png)

Spinnakerとは継続的デリバリーを容易に導入しやくするソフトウェアで

- 高度なUIで設定、フィードバック
- 自動デプロイ機能を搭載している
- ヘキサゴナルアーキテクチャ（GCP,AWSなどに関わらす使える)
- Immutable Infrastructureを強制できる

のような特徴があり、現在のDeploy問題を解決します。
以下のようなメリットをもたらします。

- UIや通知でエンジニアにフィードバック
  - ソースを更新したエンジニアに直接還元
- PodやServiceなども監視することができる
- プロジェクト単位で管理できる

しかしGUIでぽちぽちするのでMutableなのです。
新米エンジニアが設定するときにうたた寝をしてしまい、レプリカ数を1000にしてしまうかもしれません。

つまり宣言的にしないといけないのです。

--------------------------

## 宣言的Spinnakerの導入
### コンセプト
重要なコンテプトとして以下のようなことを設計思想にいれました。

- 一つのアプリにはひとつの設定を
- 初期設定以外は何も意識しなくていい世界
- 一般的なCI、CDをいじらなくて済むように
- 直感的にわかりやすいもの

### 設計したフロー

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/985b881b-745a-c68e-2f4a-8af51ff28dc7.png)

簡単に説明すると

- アプリ固有のパイプライン設定ファイルを配置
- mergeされるとパイプライン設定ファイルと、使用するテンプレートを参照しspinnakerにデプロイ
- GCRにアプリのコンテナpush
- パイプラインがトリガーされて、パイプラインが走る
- カナリアデプロイができて、デプロイ環境は設定ファイルから読み取る

メリット
- アプリに書いた設定(例：`.spinnaker/config.yml`)が宣言的であり、それ以外はいじれない
- 宣言的継続的デリバリーの中では直感的にわかりやすい
- 従来のプロジェクトとの親和性
  - CIはそのまま、パイプラインデプロイ(curl経由で可能)、GCRプッシュくらい
    - もう取り入れて要る可能性が高い
  - それ以外はいじらなくていい
### 今回作るもの
ngnixサーバーを更新すると、CircleCIがテストをして、カナリアデプロイして、SlackにWebhookを送るパイプライン

### 実際のプロジェクト
githubリポジトリをアプリケーションとテンプレートの二つに分けています。


1. テンプレート側
テンプレートを保存してあるリポジトリのファイル構成です。

```bash
$ tree keke-template/
.
└── templates
    └── AtomicCanaryWithoutWebhook
         └── template.yml
```

`template.yml`にSpinnakerのUIに1対1対応するように記述します。

2. アプリケーション側
実際のRailsやGolang Serverなどのアプリケーションのファイル構成です。
余計なもの(`README`や`gitignore`など)は省略させていただいてます。

```bash
$ tree keke-app/
.　
├── .circleci
│   └── config.yml
├── .spinnaker
│   └── value.yml
├── app
    ├── Dockerfile
    └── default.conf
```

今回、Spinnakerの設定ファイルは`.spinnaker/value.yml`に定義し、以下のようになっています。

```bash
schema: "1"
pipeline:
  application: Demo
  name: Atomic Canary Deployment
  template:
    source: [YOUR_TEMPLATE_PATH]
  variables:
    replicas: 2
    loadbalancer: nginx-keke
    slackWebhookURL: [YOUR_SLACK_URL]
```

## Demo
### 1. アプリケーションを更新したとき

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/985b881b-745a-c68e-2f4a-8af51ff28dc7.png)

nginxを更新するとCircleCIが走ります。
Jobはこのようになっています。

- test
- pipelineをデプロイ
- `nginx/Dockerfile`でイメージを作ってGCRにpushする

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/e4d7397d-9e5f-0ca0-b157-8e68a33e3343.png)

二番目の手順であるpipelineのデプロイはSpinnakerで確認できます。
三番目のジョブはGCRにプッシュすることです。


pipelineはできていました。
GCRにpushされるとパイプラインが実行されてカナリアデプロイの完了です。

これを実際にやってみます。
まず、アプリケーションは作ったものの、何もpipelineが定義されていない状態からスタートです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/64e9f8e9-d9ed-7d85-7b1d-bb5b52c5da50.png)

コードを更新して、githubにpushします

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/01071ec1-ac62-6209-a29f-383293899952.png)

そしてPull requestを作成してmergeをするとします。
するとCircleCIでworkflowが走ります。

`test`ジョブが走りましたが、何も変化がありません。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/2f14626a-bba2-7b7b-8192-d48c25fda9a0.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/5756f80c-4588-2585-b5ca-1af33bed7510.png)

`test`が無事に終わって、Spinnakerにpipelineをデプロイさせるジョブが走っています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/c2ba56fe-057f-8a8f-db3b-11f54d158a82.png)

完了しました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/8aeaf5ba-11b3-ff53-0e8d-4261a9b27f61.png)

成功しました。するとSpinnaker上では

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/ad88340e-4b84-cbdb-c83a-ad5d9bcc4cd0.png)

とpipelineができており、もちろん宣言的に書いたので一切いじれないパイプラインができています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/a235081e-c5ad-08a9-9b0a-6fbe14f4ba77.png)

あとはGCRにイメージがpushされて、パイプラインが実行されるといういつも通りです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/f9c3f895-a4c3-9524-996b-32811bb728ef.png)

#### 1.1　まとめ

アプリケーション（Rails、Golang,Node.jsなど)を更新すると、`.spinnaker/config.yml`を定義するだけで宣言的パイプラインで実行することができる。

*(注)宣言的Spinnakerの設定ファイルである`.spinnaker/config.yml`などのパスは任意です。*

Webhookを設定しているので、Slackには以下のような通知が来ます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/6998d0fd-326b-610c-fda9-2e4ae81747b2.png)


### 2. tempateが更新されたとき

リポジトリを分けているからtemplateが更新されたときに、どうなるの？
って思う方が要ると思いますが、こちらも対応しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/d202c6c7-611e-5b84-65d1-97e81fcaef08.png)


具体的にはtemplateがmergeされるとpipelineそのものを新テンプレートで更新しなおす、ということをします。

試しに変数を追加してmergeします。
今回は以下のような`newVariablesForDemo`という変数を追加しました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/3dbbd73b-f876-5353-004d-c8ec6ca343f6.png)

CircleCI以下のようなWorkflowになっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/55ce38e4-62d2-81fe-c3a1-c69d8fadce3d.png)

最初に`test`ジョブが走って、これ自体は`pipeline-template`自体をバリデーションしています。
テストが完了すると、`publish`ジョブが実行されテンプレートが更新されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/3ae44927-4d6b-9d90-c58e-9e6300058663.png)

次に古いバージョンのテンプレートを使用しているものを新しく更新しないといけません。これを担うのは`sync`です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/25a5048a-913a-f656-19b1-3447a3127c7b.png)

このジョブが終わるとCircleCI APIを通してアプリケーション側のWorkflowのSpinnakerのPipelineをビルドしなおし、新しいテンプレートを用いてパイプラインをデプロイしなおします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/8ea3659e-1e4c-a94e-bb07-5d550925b185.png)

完了しました。するとアプリケーション側でまたビルドが走っているのです。

![スクリーンショット 2018-05-19 3.11.13.png](https://qiita-image-store.s3.amazonaws.com/0/153320/0319891f-71c1-6625-f63b-699101fb21b0.png)

これによって最新テンプレートがすぐ反映されることになります。

確認用にGUIからtemplateを利用してみると

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153320/e28159c4-ac3d-5890-d823-e4d1d14537f7.png)

と新しいtemplateができていることがわかります。

もちろん、これをしなくてもアプリケーション自体の更新があれば新しいテンプレートは反映されますが、template merge時とアプリで使っているテンプレートが異なるということは、カオスな環境をうむので即座に反映させようと思いました。

#### 2.1 まとめ

別repositoryで管理しているtemplate自体もうまくすればアプリケーション側と連携を取ることができ、すぐ反映することができます。
これによってテンプレート自体も、エンジニアにフィードバックをもたらすことができます。

### 3. フィードバックも豊富です
サーバーサイドエンジニアなどでも容易に「どのようなパイプラインでデプロイされたのか」や「カナリアデプロイがなんで失敗したか」などを知ることができます。

![スクリーンショット 2018-05-19 12.10.06.png](https://qiita-image-store.s3.amazonaws.com/0/153320/d22455be-49fd-2617-af7e-a06062b3be89.png)

## 追記

Circle CIがWorkflowでAuto Cancelling Redundant Buildをサポートしました。 これによって、よりパイプラインや、DCDを含むアプリケーションがデプロイしやすくなります。

![スクリーンショット 2018-08-22 2.02.07.png](https://qiita-image-store.s3.amazonaws.com/0/153320/18361b78-ba42-4412-5553-7f573e66ecde.png)

## 最後に

これまでは宣言的継続的デリバリー（DCD)を実現すると理論的にはハッピーと周知されているものの、どのようすればよいのかが明白ではなかったです。

しかし、Spinnakerやkubernetesなどが登場し、継続的デリバリーが容易にできるようになった今、それを宣言的にするというパラダイムが到来することは自明でした。

今回挑戦してみて、CircleCIやTravis、JenkinsなどのCI手法を参考にしつつ、かなりよいDCDを考えつくことができたのではないかと思っています。

コメントやコメントなど質問・雑談・感想お待ちしています。
各種フォローよろしくお願いします！

Github: https://github.com/KeisukeYamashita