宣言的Spinnaker設計 継続的デリバリーのさらに上にいく
tl;dr
学部4年になり忙しくなるので、よりすべてを「自動化」しようと思いました。
最近自分が考案した 宣言的継続的デリバリー(Declartive Continuous Delivery) を紹介させていただきます。
まず、宣言的継続的デリバリーを定義させていただきます。
宣言的継続的デリバリーとは、カナリアデプロイなどの知的なデプロイを、宣言的に継続的デリバリーする手法
記事のざっくりとして内容は
- マイクロサービスを継続的デリバリーことができるspinnakerを宣言的に構築する
- まるでCircleCIの設定ファイルのような
config.yml
を配置するだけでspinnakerのパイプラインを走らせることができる。- 既存の環境への親和性が高い
- 「それぞれの設定がそれぞれのアプリケーションへ帰属」というわかりやすさ
- templateは一括でrepoでモジュール化して管理しよう
- 組織の財産となる
- templateとアプリケーションを分離することで管理しやすい
- 再利用性
です。設計したDCDフローは以下の通りです。 解説は記事を読んでください。
対象とする読者
- デプロイが大きな負担となっているひと
- 自動化が大好きな人
- Kubernetesなどでマイクロサービスを運用していて、さらにDeployサイクルをあげたい人
- カナリアデプロイ、ブルー・グリーンデプロイを自動的にやりたい人
解決する問題
1.kubernetesのDeploy問題 kubernetesは非常に機能が多く、マイクロサービスを導入しやすくして、よりアジャイルな開発へのパラダイムシフトを促したソフトです。
manifestファイルと呼ばれる、どのロードバランサーに何を繋げて、どのくらいのVMで、、、といったようなことがすべて宣言的に書けて、Deployするにはapply
コマンドを叩くだけです。
しかしながら、Deployが毎日数回など行われるときにapply
コマンドを叩くのは面倒です。
現状として、Botを使ってSlack越しでDeployしたり、CIに任せて要るケースがほとんどです。
2.Deploy戦略の難しさ Deployとは単純な作業ではなくて、実際にはデプロイ後に変な挙動をしていないか、他のマイクロサービスとうまく連携が取れて要るかなど、確認することが膨大にあります。
- Blue/Green Deployment
- Canary Deployment
- Rollbackが安全にできるか
- ビジネスモデルとの連携
- ...
これらをメンテしていくのは難しく、よくチームに「インフラエンジニア」や「SRE」などと肩書きがつくエンジニアが面倒見たりしています。
3.インフラエンジニアを導入する盲点 インフラに関するタスクがヘビーならインフラ専門のエンジニアをおけばいいじゃん! って考えるかもしれません。 確かにインフラには専門的な知識や経験が必要で、膨大なドキュメントを読まないといけません。
https://www.youtube.com/watch?v=LVgP63BkhKQ
確かにインフラにマジつよエンジニアがいればその人に任せることができます。 実際、どの会社もそのような形態を取っていることが多く、採用でもコースが分かれているほどです。
今年、夏休み1週間くらいインターン行こうかなって思って募集を見ていたらサイボウズSummerInternshipがこのようになっていて、やはりサーバーサイドとインフラの乖離はどこでもあるのだなと痛感しました。
しかしながら、一人に任せるのにはデメリットがあり、
- インフラで起きることのフィードバックをその他のエンジニアに還元できない
- 責任がインフラエンジニアだけに生じる
- 本来の企業としての価値は生んでいない
- ...
のようなことが挙げられます。
Spinnakerとは
Spinnakerとは継続的デリバリーを容易に導入しやくするソフトウェアで
- 高度なUIで設定、フィードバック
- 自動デプロイ機能を搭載している
- ヘキサゴナルアーキテクチャ(GCP,AWSなどに関わらす使える)
- Immutable Infrastructureを強制できる
のような特徴があり、現在のDeploy問題を解決します。 以下のようなメリットをもたらします。
- UIや通知でエンジニアにフィードバック
- ソースを更新したエンジニアに直接還元
- PodやServiceなども監視することができる
- プロジェクト単位で管理できる
しかしGUIでぽちぽちするのでMutableなのです。 新米エンジニアが設定するときにうたた寝をしてしまい、レプリカ数を1000にしてしまうかもしれません。
つまり宣言的にしないといけないのです。
なぜ宣言的がいいのか
そもそも宣言的とは
設定ファイルを書いて、あとは何もいじれない状態です。 そのような面でCircleCIはテストを宣言的に書いているといえます。
まずconfig.yml
などに設定を描き、それを反映させます。
すると、それ以降は誰もいじれません。
このようにすることで
- 誰でも
config.yml
などがあれば同じ環境を作れる。 - 複製が容易になり、マイクロサービスに使えやすい
- Deploy戦略を構築しやすい
- スノーフレークサーバーを生み出さない
などなどメリットがたくさんあります。 これをSpinnakerに適用すると、すぐあとで説明する「知的デプロイ」という、デプロイ戦略やフィードバックを含むデプロイまでを宣言的に書くことが宣言的Spinnakerの目的です。
宣言的の現状
現状では、Deployは継続デリバリーで実現しているところはありますが、Deploy戦略までは宣言的に実現している組織は見当たりませんでした。
僕がドキュメントにContributeさせてもらった商用SpinnakerであるArmoryでは以下のようにソフトウェアデリバリー(CD)のレベルを定義しています。 最も高いのは知的デプロイ(ID)です。
Stages of Software Delivery Evolution Infographic
大まかに説明すると従来(低いCD)は
- Mutableのものは「デプロイはイベントごと」くらいひやひやする
- 開発遅い
- 危ない
宣言的のメリット
最もレベルの高い知的デプロイは
- 自動カナリアデプロイ
- 自動ロールバック
- カオスエンジニアリング
などの特徴がありデプロイもものすごい数が行われます。 マイクロサービスに分けてサービスごとのチーム・組織を組み、開発サイクルを高速に回すことが可能となります。
このようなことから、エンジニアやデザイナーが創出した価値がユーザーにすぐに届けられるわけです。 宣言的のよさがわかったところで、Spinnakerの詳細に入りましょう。
Spinnakerでの概念
Spinnakerにはいくつか重要な概念があるので説明します。
application
これは特定のリポジトリで使われることが多いです。 レポジトリごとにアプリケーションを作るのが良いのではないかと思います。
applicationを作成する
pipeline
テストして、デプロイして、監視して、通知してなどの流れを定義する水道管のようなものです。
実際に簡単なものを作るとこんな感じです。
pipeline-template
これは可視化をするのが難しいのですが
pipelineの虫食い版のようなもの でtemplateに「レプリカ数はその時に決めるよ」のように書くと、pipelineを使うときに変数定義ができます。
またpipeline-templateを用いるとImmutable Pipelineとなり、あとから設定を消すことはできません。
Pipelineを作るときにTemplateを指定します。
すると変数を定義する画面に遷移するので変数を定義します。
するとImmutable Pipelineが完成します。
ここで注目していただきたいのが「 Manual edits are not allowed on templated pipelines 」と後から変更ができないようになっていることです。 これによって誰が作っても同じ構成で、変更すべて変数だけ作成時に指定することになります。
宣言的Spinnakerの導入
コンセプト
重要なコンテプトとして以下のようなことを設計思想にいれました。
- 一つのアプリにはひとつの設定を
- 初期設定以外は何も意識しなくていい世界
- 一般的なCI、CDをいじらなくて済むように
- 直感的にわかりやすいもの
設計したフロー
簡単に説明すると
- アプリ固有のパイプライン設定ファイルを配置
- mergeされるとパイプライン設定ファイルと、使用するテンプレートを参照しspinnakerにデプロイ
- GCRにアプリのコンテナpush
- パイプラインがトリガーされて、パイプラインが走る
- カナリアデプロイができて、デプロイ環境は設定ファイルから読み取る
メリット
- アプリに書いた設定(例:.spinnaker/config.yml
)が宣言的であり、それ以外はいじれない
- 宣言的継続的デリバリーの中では直感的にわかりやすい
- 従来のプロジェクトとの親和性
- CIはそのまま、パイプラインデプロイ(curl経由で可能)、GCRプッシュくらい
- もう取り入れて要る可能性が高い
- それ以外はいじらなくていい
今回作るもの
ngnixサーバーを更新すると、CircleCIがテストをして、カナリアデプロイして、SlackにWebhookを送るパイプライン
実際のプロジェクト
githubリポジトリをアプリケーションとテンプレートの二つに分けています。
- テンプレート側 テンプレートを保存してあるリポジトリのファイル構成です。
$ tree keke-template/ . └── templates └── AtomicCanaryWithoutWebhook └── template.yml
template.yml
にSpinnakerのUIに1対1対応するように記述します。
- アプリケーション側
実際のRailsやGolang Serverなどのアプリケーションのファイル構成です。
余計なもの(
README
やgitignore
など)は省略させていただいてます。
$ tree keke-app/ . ├── .circleci │ └── config.yml ├── .spinnaker │ └── value.yml ├── app ├── Dockerfile └── default.conf
今回、Spinnakerの設定ファイルは.spinnaker/value.yml
に定義し、以下のようになっています。
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. アプリケーションを更新したとき
nginxを更新するとCircleCIが走ります。 Jobはこのようになっています。
- test
- pipelineをデプロイ
nginx/Dockerfile
でイメージを作ってGCRにpushする
二番目の手順であるpipelineのデプロイはSpinnakerで確認できます。 三番目のジョブはGCRにプッシュすることです。
pipelineはできていました。 GCRにpushされるとパイプラインが実行されてカナリアデプロイの完了です。
これを実際にやってみます。 まず、アプリケーションは作ったものの、何もpipelineが定義されていない状態からスタートです。
コードを更新して、githubにpushします
そしてPull requestを作成してmergeをするとします。 するとCircleCIでworkflowが走ります。
test
ジョブが走りましたが、何も変化がありません。
test
が無事に終わって、Spinnakerにpipelineを走らせるジョブが走っています。
完了しました。
成功しました。するとSpinnaker上では
とpipelineができており、もちろん宣言的に書いたので一切いじれないパイプラインができています。
あとはGCRにイメージがpushされて、パイプラインが実行されるといういつも通りです。
1.1 まとめ
アプリケーション(Rails、Golang,Node.jsなど)を更新すると、.spinnaker/config.yml
を定義するだけで宣言的パイプラインで実行することができる。
(注)宣言的Spinnakerの設定ファイルである.spinnaker/config.yml
などのパスは任意です。
Notificationを設定しているので、以下のような通知が来るようになります。
2. tempateが更新されたとき
リポジトリを分けているからtemplateが更新されたときに、どうなるの? って思う方が要ると思いますが、こちらも対応しています。
具体的にはtemplateがmergeされるとpipelineそのものを新テンプレートで更新しなおす、ということをします。
試しに変数を追加してmergeします。
今回は以下のようなnewVariablesForDemo
という変数を追加しました。
CircleCI以下のようなWorkflowになっています。
最初にtest
ジョブが走って、これ自体はpipeline-template
自体をバリデーションしています。
テストが完了すると、publish
ジョブが実行されテンプレートが更新されます。
次に古いバージョンのテンプレートを使用しているものを新しく更新しないといけません。これを担うのはsync
です。
このジョブが終わるとCircleCI APIを通してアプリケーション側のWorkflowのSpinnakerのPipelineをビルドしなおし、新しいテンプレートを用いてパイプラインをデプロイしなおします。
完了しました。するとアプリケーション側でまたビルドが走っているのです。
これによって最新テンプレートがすぐ反映されることになります。
確認用にGUIからtemplateを利用してみると
と新しいtemplateができていることがわかります。
もちろん、これをしなくてもアプリケーション自体の更新があれば新しいテンプレートは反映されますが、template merge時とアプリで使っているテンプレートが異なるということは、カオスな環境をうむので即座に反映させようと思いました。
2.1 まとめ
別repositoryで管理しているtemplate自体もうまくすればアプリケーション側と連携を取ることができ、すぐ反映することができます。 これによってテンプレート自体も、エンジニアにフィードバックをもたらすことができます。
3. フィードバック
サーバーサイドエンジニアなどでも容易に「どのようなパイプラインでデプロイされたのか」や「カナリアデプロイがなんで失敗したか」などを知ることができます。
4. 追記
2018/08/22追記
Circle CIがWorkflowでAuto Cancelling Redundant Buildをサポートしました。
これによって、よりパイプラインや、DCDを含むアプリケーションがデプロイしやすくなります。
ついにWorkflowsでAuto Cancelling Redundant Buildサポートされました(まだPreviewですが)🥳同じブランチで複数のジョブが走った場合、古いジョブを自動でキャンセルしてくれる機能です。これを使うとコミットが多いプロジェクトでもビルド使用量を節約することができます。https://t.co/Kig2713huJ
— CircleCI Japan (@CircleCIJapan) 2018年8月20日
参考になるリンク
多くある問い合わせ
1. prod
、dev
クラスタなど複数のクラスタがあるときに使用できるの?
もちろん使用可能です。 想定としてはymlにて宣言的にデプロイ先のクラスタを定義することになります。
variables: devcluster: keke-dev prodcluster: keke-prod
これだけでは使うことができなく、あらかじめhalyard
にて設定を読み込む必要があります。
この部分が参考になるかもしれません。
2. Spinnaker内のapplication
のユースケースは?
Spinnakerにはapplication
だけではなくて、project
などの一見どのように使うのかわからない概念があります。
僕自身のチーム運用でのベストプラクティス以下の通りです。
project
: プロダクトを束ねる組織で分ける(例として、部署、小さい規模の会社など)。application
: プロダクトに紐づくように分ける。
しかしながら、これに対してはベストプラクティスがなく、チームのコンセンサスを取りながら進めていくのがいいのかもしれません。
最後に
これまでは宣言的継続的デリバリー(DCD)を実現すると理論的にはハッピーと周知されているものの、どのようすればよいのかが明白ではなかったです。
しかし、Spinnakerやkubernetesなどが登場し、継続的デリバリーが容易にできるようになった今、それを宣言的にするというパラダイムが到来することは自明でした。
今回挑戦してみて、CircleCIやTravis、JenkinsなどのCI手法を参考にしつつ、かなりよいDCDを考えつくことができたのではないかと思っています。
コメントやコメントなど質問・雑談・感想お待ちしています。 各種フォローよろしくお願いします!