デプロイメントに求める速さ

これは KOBA789 日記 Advent Calendar 2021 - Adventar 4日目の記事です。

社内向けにデプロイについてエッセイを書くために先に日本語で書いたら想いが強すぎて思ったより長くなっちゃったので勿体ないし公開します。

あと、今回は k8s とか ArgoCD とかそういう特定ツールの名前は出さずに実現したい環境だけ書いてます。


デプロイメントに求める速さ

マーチンファウラーも「モノリスをマイクロサービスにする前にお前らやることちゃんとやってんのか?」の一つに Rapid application deployment をあげているようにデプロイの速さは大事です。速さは正義です。

ではデプロイの速度とはどこのことを言っているのでしょうか?

デプロイ速度

デプロイの速度を速くするとなった時にどの時間を参考にすればいいのでしょうか?例えばチャットボットにデプロイコマンドを話しかけた時から実際にサーバに反映されるまででしょうか? それとも kubectl rollout 実行してから反映されるまででしょうか? そういったシステムの話なのでしょうか? 僕はそうではないと思っています。わかりやすく説明するために極端な例を挙げてみます。例えば

チャットボットにデプロイコマンドを話しかけるのに上長含め10個のハンコを集めて承認しないとだめ

これは果たしてデプロイ速度が早いといえるでしょうか? アプリケーションのデプロイ速度を上げるというのは別にシステムに閉じたことだけを差しているわけではないのです。プロセスも含めてデプロイを速くすることを考えなくてはいけません。

ではどこから測るべきか。それはエンジニアが「この機能が完成した」と思ったタイミングだと思っています。これはちょっとプラットフォームエンジニア的な思想になってしまうかもしれませんが、アプリケーションエンジニアは常にユーザーにとって素晴らしい価値を開発してくれています。その価値をなるべく早く届けるべきなのです。完成した価値がデプロイされないままではユーザーに価値が届いていないので開発していないのと変わりません。

なので開発が完了したタイミングからユーザーが実際に経験できるまでの時間をデプロイ速度としたいと思います。

理想のデプロイ

開発が完了したタイミングからユーザーが実際に体験できるまでの時間というのをもう少し具体的にいうとプルリクのレビューが通ってmain相当のブランチにマージされたタイミングから、実際にサーバにデプロイ完了するところまでになります。なのでmainにマージした瞬間にユーザーがそれを経験できるようになっているのが理想です。

例えば週に一回デプロイとかある程度まとめてデプロイというのは現実解としては十分ありえる話です。iOS アプリなどは審査などがあるためどうしてもまとめてデプロイするものもあるでしょう。しかしあくまでも理想という話で言えばやはり開発が完了したものから順次ユーザーに届くのが理想だと思います。

また、個人的な今までの経験からも production で動いているコードと main ブランチのコードが離れていて良いことは何もないのでなるべく近づけるべきだと思っています。

理想実現のために

マージした瞬間にユーザーが経験できるようになるためには色々なやり方があると思います。今回は Webアプリケーションに限った話をしていこうと思います。

  1. main にマージされる
  2. container image をビルドする
  3. productionに Rollout する

今のところ僕が考えられる一番速いデプロイの実現はこの手段になります。とはいえこれだけだとただただ速いだけです。デプロイするためにはなるべく安全にデプロイしたいと思っています。そのためには次の三つが大事だと思っています。

  1. マージした後のコードでテストの実行
  2. staging で自分で気になったところの動作確認
  3. productionで障害が発生した時に即座に戻せる仕組み

これらを速度を犠牲にせずに実現するための方法を考えてみます

マージした後のコードでテストの実行

これは container image を作る前段階に CI でのテスト実行を持ってくれば良いのですがそれだと速度が犠牲になります。なので container image をビルドするのと並列でテストを走らせます。 image 作成とテスト実行がほぼ同じタイミングで終わっていることが理想です。テストに時間がかかる時は並列度を上げたり launchable を導入したり :) してテストの高速化を行います。*1 そしてテストが通った時だけデプロイするようにします。

staging で自分で気になったところの動作確認

これはいわゆる PR staging を作ることで解決します。pull request 毎にステージングを作成し、そこで動作確認できるようにします。また staging の動作確認をもっと確実にするためにproductionに近いデータを持つようにしたり、productionのシャドーリクエストを投げるようにしたりする仕組みが必要です。

productionで障害が発生した時に即座に戻せる仕組み

実はこれは本来二つの項目なのを一つにまとめてしまっています。監視とロールバックですね。そしてその二つを使って実現できるカナリアリリースが欲しいです。監視項目や閾値の話はまた深いのであまり触れませんが、少なくとも監視するのは500エラーなどのアプロケーションの実行エラーだけでなく、そのサービスとしての状態を監視できるようにします。そして異常な場合はロールアウトを止めロールバックし通知します。

理想への道

全ての環境が整ったら main にマージしたタイミングで上記デプロイプロセスが自動で動くのが理想です。しかし環境が整っていないのに自動でデプロイプロセスが動くは危険なので手動でchatbotや何かしらのデプロイ作業をトリガーにしてデプロイプロセスが動くようにします。いきなり理想に辿り着くのは大変なので中間地点を置いてまずはそこを目指すようにします。

まず大変なので PR staging とカナリアリリースは後回しにします。中間地点として目指すのは staging が一つありそれは常にmainの最新コードが動いている。何かしらのデプロイトリガーでstagingにある image がproductionにデプロイされるという流れです。

  1. main にマージされる
  2. CI が回る
  3. container image をビルドする
  4. staging に Rollout する
  5. 手動で production へのデプロイトリガーを実行する(Chatbot など)
  6. Staging に上がっている image をproductionへデプロイする

とういう手順になります。

ここでのキモはアプリケーション開発時に The Twelve-Factor App の提唱しているように設定を全て外出しすることです。 そうすることによってmainにマージされるたびに image を作り、それを staging とproduction両方にデプロイ出来るようになります。production用のimageを再ビルドするのは避けましょう。それは理想の環境から少し離れてしまいます。 main のコードがimage化されproductionにデプロイされるという状態はデプロイ後の結果は理想の状態と同じになります。これは理想への中間地点としてデプロイのプロセスが違うだけで結果は理想と同じになるというところを目指しています。

この状態でも安心のために必要としていた下記は実現できています。

  1. マージした後のコードでテストの実行
  2. staging で自分で気になったところの動作確認
  3. productionで障害が発生した時に即座に戻せる仕組み

また、production へのデプロイもすでにテストが通って作成されている image があるのでそれを rollout するだけなのでスグに行えます。production用のimageを再ビルドするのを避けるのはここの速度のためでもあります。

まずはこの状態を作り、カナリアリリースに向け監視を充実させ、 pr staging を作り staging のデータなどを充実させて行って理想へ辿り着きたいと思います。

デプロイトリガーをどうするか

上記説明でデプロイトリガーをどうするかをあまり詳しく書きませんでした。これは僕の中でも決定打というほどのものがなく、なんとなくこうした方が良いのではないかなというのがあるので chat bot と書いたのですが、なぜ Chat bot が今のところ有効だと思っているかを軽く書こうと思います。 ちなみに理想の世界が出来ても手動でデプロイやロールバックを行うことはあると思うのでここにコストをかけるのは良い判断だと思います。 デプロイトリガーを考えるときに僕が大事だと思っているのは下記になります。

  1. デプロイするということをみんなに知らせられる事
  2. 誰がいつデプロイしたか後で探せること
  3. 手順が他の人にもわかりやすい事

例えば何も通知などがない場合、チャットなどで「今からデプロイします」と一言言ってから行いましょうという運用に最初はなると思います。チャットで一言言ってから行うくらいなら一言言ったらデプロイされてば手順をひとつ省けます。また、すごい雑ですがチャットの検索でもある程度誰がデプロイしたかスグに見つけることができます(もっと詳細に必要であれば chatbot 側で保存しておくべきです) そして 3 が実は大事だと思っています。例えばシェルでコマンド実行したらデプロイされ、その時にチャットにも「xxx さんがデプロイしました」と通知されるという仕組みにしたとしましょう。これも良いのですが、新しく入社したエンジニアなどはその通知を見ただけではどうやってその通知を出しているのかわかりません。僕が今のとこと一番良いと思っているのはチャットボットを使ったデプロイです。slack の slash コマンドで実現する場合はコマンドが見えるように “response_type”: “in_channel” にするのがみんなにも見えて良いと感じます。 これなら新しく入社したエンジニアもスグにデプロイの仕方を理解できますしね。

まとめ

まずは一番大事な速度の定義を行いました。今回僕は開発者が完成させた価値をユーザーに届けるまでの時間をデプロイ速度としてみるようにしました。そしてそれを最速にするための理想の環境を考えました。そして理想は理想なんだけどちょっと遠いのでそこに至るまでの中間地点を定義しました。まずは中間地点を目指したいと思っています。

理想は「完成した価値をすぐにユーザーに届けること」です。それを違う言葉で表すと 「main にマージされた機能がスグにデプロイされること」になります。

本題ですが最近のおすすめの漫画は宙に参るです。

www.amazon.co.jp

*1:テストの高速化については別の話題なのでここでは触れません。