Kekeの日記

エンジニア、読書なんでも

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

tl;dr

学部4年になり忙しくなるので、よりすべてを「自動化」しようと思いました。

最近自分が考案した 宣言的継続的デリバリー(Declartive Continuous Delivery) を紹介させていただきます。

まず、宣言的継続的デリバリーを定義させていただきます。

宣言的継続的デリバリーとは、カナリアデプロイなどの知的なデプロイを、宣言的に継続的デリバリーする手法

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

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

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

image.png


対象とする読者

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

解決する問題

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

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

image.png

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

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

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

image.png https://sweetcode.io/13115-2/

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

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

image.png https://www.youtube.com/watch?v=LVgP63BkhKQ

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

今年、夏休み1週間くらいインターン行こうかなって思って募集を見ていたらサイボウズSummerInternshipがこのようになっていて、やはりサーバーサイドとインフラの乖離はどこでもあるのだなと痛感しました。

image.png

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

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

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

Spinnakerとは

image.png

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

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

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

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

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

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

なぜ宣言的がいいのか

そもそも宣言的とは

設定ファイルを書いて、あとは何もいじれない状態です。 そのような面でCircleCIはテストを宣言的に書いているといえます。

まずconfig.ymlなどに設定を描き、それを反映させます。

image.png

すると、それ以降は誰もいじれません。

image.png

このようにすることで

  • 誰でもconfig.ymlなどがあれば同じ環境を作れる。
  • 複製が容易になり、マイクロサービスに使えやすい
  • Deploy戦略を構築しやすい
  • スノーフレークサーバーを生み出さない

などなどメリットがたくさんあります。 これをSpinnakerに適用すると、すぐあとで説明する「知的デプロイ」という、デプロイ戦略やフィードバックを含むデプロイまでを宣言的に書くことが宣言的Spinnakerの目的です。

image.png

宣言的の現状

現状では、Deployは継続デリバリーで実現しているところはありますが、Deploy戦略までは宣言的に実現している組織は見当たりませんでした。

僕がドキュメントにContributeさせてもらった商用SpinnakerであるArmoryでは以下のようにソフトウェアデリバリー(CD)のレベルを定義しています。 最も高いのは知的デプロイ(ID)です。

image.png

Stages of Software Delivery Evolution Infographic

大まかに説明すると従来(低いCD)は

  • Mutableのものは「デプロイはイベントごと」くらいひやひやする
  • 開発遅い
  • 危ない

宣言的のメリット

最もレベルの高い知的デプロイは

  • 自動カナリアデプロイ
  • 自動ロールバック
  • カオスエンジニアリング

などの特徴がありデプロイもものすごい数が行われます。 マイクロサービスに分けてサービスごとのチーム・組織を組み、開発サイクルを高速に回すことが可能となります。

このようなことから、エンジニアやデザイナーが創出した価値がユーザーにすぐに届けられるわけです。 宣言的のよさがわかったところで、Spinnakerの詳細に入りましょう。

Spinnakerでの概念

Spinnakerにはいくつか重要な概念があるので説明します。

application

これは特定のリポジトリで使われることが多いです。 レポジトリごとにアプリケーションを作るのが良いのではないかと思います。

applicationを作成する image.png

pipeline

テストして、デプロイして、監視して、通知してなどの流れを定義する水道管のようなものです。

実際に簡単なものを作るとこんな感じです。

image.png

pipeline-template

これは可視化をするのが難しいのですが

pipelineの虫食い版のようなもの でtemplateに「レプリカ数はその時に決めるよ」のように書くと、pipelineを使うときに変数定義ができます。

またpipeline-templateを用いるとImmutable Pipelineとなり、あとから設定を消すことはできません。

Pipelineを作るときにTemplateを指定します。 image.png

すると変数を定義する画面に遷移するので変数を定義します。 image.png

するとImmutable Pipelineが完成します。 スクリーンショット 2018-05-19 2.42.51.png

ここで注目していただきたいのが「 Manual edits are not allowed on templated pipelines 」と後から変更ができないようになっていることです。 これによって誰が作っても同じ構成で、変更すべて変数だけ作成時に指定することになります。


宣言的Spinnakerの導入

コンセプト

重要なコンテプトとして以下のようなことを設計思想にいれました。

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

設計したフロー

image.png

簡単に説明すると

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

メリット - アプリに書いた設定(例:.spinnaker/config.yml)が宣言的であり、それ以外はいじれない - 宣言的継続的デリバリーの中では直感的にわかりやすい - 従来のプロジェクトとの親和性 - CIはそのまま、パイプラインデプロイ(curl経由で可能)、GCRプッシュくらい - もう取り入れて要る可能性が高い - それ以外はいじらなくていい

今回作るもの

ngnixサーバーを更新すると、CircleCIがテストをして、カナリアデプロイして、SlackにWebhookを送るパイプライン

実際のプロジェクト

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

  1. テンプレート側 テンプレートを保存してあるリポジトリのファイル構成です。
$ tree keke-template/
.
└── templates
    └── AtomicCanaryWithoutWebhook
         └── template.yml

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

  1. アプリケーション側 実際のRailsやGolang Serverなどのアプリケーションのファイル構成です。 余計なもの(READMEgitignoreなど)は省略させていただいてます。
$ 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. アプリケーションを更新したとき

image.png

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

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

image.png

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

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

これを実際にやってみます。 まず、アプリケーションは作ったものの、何もpipelineが定義されていない状態からスタートです。 image.png

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

image.png

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

testジョブが走りましたが、何も変化がありません。 image.png image.png

testが無事に終わって、Spinnakerにpipelineを走らせるジョブが走っています。 image.png

完了しました。

image.png

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

image.png

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

image.png

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

image.png

1.1 まとめ

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

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

Notificationを設定しているので、以下のような通知が来るようになります。 image.png

2. tempateが更新されたとき

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

image.png

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

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

image.png

CircleCI以下のようなWorkflowになっています。 image.png

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

image.png

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

image.png

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

image.png

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

スクリーンショット 2018-05-19 3.11.13.png

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

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

image.png

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

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

2.1 まとめ

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

3. フィードバック

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

スクリーンショット 2018-05-19 12.10.06.png

4. 追記

2018/08/22追記

Circle CIがWorkflowでAuto Cancelling Redundant Buildをサポートしました。

これによって、よりパイプラインや、DCDを含むアプリケーションがデプロイしやすくなります。

参考になるリンク

多くある問い合わせ

1. proddevクラスタなど複数のクラスタがあるときに使用できるの?

もちろん使用可能です。 想定としてはymlにて宣言的にデプロイ先のクラスタを定義することになります。

 variables:
    devcluster: keke-dev
    prodcluster: keke-prod

これだけでは使うことができなく、あらかじめhalyardにて設定を読み込む必要があります。 この部分が参考になるかもしれません。

www.spinnaker.io

2. Spinnaker内のapplicationのユースケースは?

Spinnakerにはapplicationだけではなくて、projectなどの一見どのように使うのかわからない概念があります。

僕自身のチーム運用でのベストプラクティス以下の通りです。

  • project: プロダクトを束ねる組織で分ける(例として、部署、小さい規模の会社など)。
  • application: プロダクトに紐づくように分ける。

しかしながら、これに対してはベストプラクティスがなく、チームのコンセンサスを取りながら進めていくのがいいのかもしれません。

最後に

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

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

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

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

Github: https://github.com/KeisukeYamashita