
Our seeking to stable k8s deployment.
Posted on
1. Preface
こんにちは、新卒エンジニア[1]の鈴木(@GentleClarinet)です。ここ一年間を含め、僕は基盤チーム[2]で、monolithic だったシステムを micro service architecture[3] を採用した新しいシステムに移行することをメインでやっていました[4]。無事、数ヶ月前に移行は終わりまして、今はシステムの安定化に取り組んでいます[5]。本稿では、僕たちが開発しているプロダクトである KARTE[6] の新しいシステムでのデプロイに関する話をしていければなと思います。
2. Introduction
この一年間では、monolithic に肥大化したシステムを micro services に分割していました。もちろん、monolithic で密結合の多いシステムを疎結合にしていく、ないしは、適切な粒度で疎結合な部分を切り離していくのはとても困難なタスクです。ですので、魅力的で理想的な micro services とは程遠いとはいえ、ある程度の分割が進んでいますので、開発効率やデプロイスピードは非常に上がりました。
ただ、最近、不意なバグが入った version をデプロイしてしまうなどの問題がありました。その時直ぐにシステムを安定的なバージョンにロールバック[7]することができれば良かったのですが、当時のデプロイシステムだとシステムのロールバックはとても煩雑なフローを必要とするものとなっていました。
その時の反省を踏まえ、新しいシステム基盤をより安定的にするため、この一ヶ月間で、デプロイシステムを新しくしました。その結果、デプロイスピードが向上し、ロールバックが簡単になったことによる安定化が実現できました。副次的な恩恵として、ロールバックが簡単になったことによる、エンジニアのデプロイ時の心理的安全性が高くなるということもありました。
3. 今までのデプロイシステム[8]
今まではアプリケーションとデプロイシステムは mono repo で管理していました。擬似的なディレクトリ構造は以下のようになっていました。
- k8s manifests
- services
僕たちは、まず本番環境(production)にアプリケーションをデプロイする前に、確認のために、検証環境(evaluation)にデプロイします。要するに、複数環境のデプロイフローとなっています。それを一つのレポジトリで運用するとなると、シンプルなデプロイは次のようなフローになっていました。
- merge to master branch
- build and push docker images (with COMMIT_HASH image tag)
- deploy to evaluation (k8s apply)
- cutting release tag
- deploy to production (k8s apply)
micro services へ移行をしているとはいえ、分割したそれぞれの services が完全に疎結合になっているということはありません。ですので、基本的にデプロイ時は全ての services を一度にデプロイすることになっていました。また、master branch の COMMIT_HASH を使い、image を build したり、manifest を生成したりしていました。例えば、k8s manifest や cloud build trigger は下記のような感じです。
manifest.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment
spec:
# ...
template:
spec:
containers:
- image: gcr.io/$PROJECT_ID/imagename:$COMMIT_HASH
cloudbuild.yml
steps:
# ...
- id: 'build image'
name: cloud-builder docker image
entrypoint: 'bash'
args:
- '-c'
- |
docker build ... -t gcr.io/$PROJECT_ID/imagename:$COMMIT_HASH
このような構成になっていたので、master branch が更新されたら、そのまま直ぐに自動デプロイになっていました。これはこれで楽で良かったのですが、それぞれの service を個別でデプロイするのは難しいものとなっていました。もちろん、ロールバックをするには単純に git revert すれば良いというわけでもありませんでした。他にも様々な複合的な要因も含め問題が起きやすくなっていました。
4. 新しいデプロイシステム
流石に問題があると思うと、デプロイシステムを新しくすることにしました。新しくする上でのポイントは、以下に設定して開発をすることにしました。
- 直ぐにロールバックできること
- できるだけシンプルに保つこと
直ぐにロールバックできて、シンプルで分かりやすく、操作がしやすいという条件に良く当てはまったのは、gitops[9] でした。gitops とは ops[10] を git で管理することの名称です。Kubernetes[11] (k8s) が declarative [12] であるということもあり、僕たちが開発しているシステムは gitops と相性が良いです。
今まで declarative な k8s の manifest[13] を扱っては居たのですが、環境変数と kustomize[14] を用いていて、動的に manifest を生成していたので、結局、declarative ではない状態でした。ですので、新しいデプロイシステムでは k8s の manifest はきちんと生成された結果を commit するというアプローチを取りました。
新しいシステムの構成は次のとおりになりました。
- generated/evaluation
- generated/production
- manifests/template
manifests/tamplate には kustomize と環境変数を用いて作られた manifest 生成用の template file を置きます。template file から、generated/evaluation または generated/production に manifest を生成することで、複数環境のデプロイを実現します。
generated/evaluation または generated/production に変更が走った場合は、その環境に応じた trigger が走り、デプロイするというフローになっています。この構成はまさに、single source of truth [15] を体現していますね。しかも、git revert するだけで簡単にロールバックを実現[16]することもできます。もちろん、個別 service のデプロイもできます。しかも、デプロイ自体は僅か3分で終わります!素晴らしいですね。
5. Gitops 時に注意しておくポイント
もちろん、gitops を実現する上で注意しておく点はいくつかあります。新しいデプロイシステムを開発する上で多くの議論を交わしました。
5-1. Argo CD, Flux CD系のツール
CD系のツールを使うという選択はありました。gitops について調べると、このようなツールに関する情報にはしばしばお目にかかかることが多いですね。僕たちは Argo CD を試しに使用してみました。所感としては、Argo CD で使うための yaml file の管理をしなくていけないし、デプロイ自体、単純な shell script でも充分なので、それらに依存するよりは使わずにシンプルに保つ方が好ましいなという気持ちです。シンプルに保てれば、いつでも壊したり、変更したり、それこそ、新しいツールを入れるなんてこともできますしね。
5-2. Env branching or Generation commiting?
複数環境のデプロイを実現するのにはどうしたらいいのか?ということで、2つのアプローチがありました。それは、env branching と generation commiting [17]です。
env branching[18] はその名の通り、branch = 環境として、複数の branch 運用でデプロイをしていくやり方です。一方で、generation commiting は僕たちが採用したやり方で、生成した manifest を commit することです。
env branching は2つの branch が乖離してしまう可能性が考慮でき、generation commiting は生成物が commit されてしまうという煩わしさがありました。両者とも pros & cons があり、とても議論を交しました。結局、煩わしさはあるが運用がしやすい、generation commiting を採用することにしました。
5-3. Secret の管理
5-3-1. Kubesec
今までは secret を git で管理するために、 kubesec[19] を用いていました。kubesec は secret を暗号化できるものです。kubesec を扱うと、kubectl apply
[20] する前に、まず、暗号化された secret を復号化する必要が出てきます。つまり、デプロイ時に secret を復号化してから、kustomize を使う必要がありますので、k8s manifest の生成物の commit をすることはできなくなります。
5-3-2. Sealed Secret
secret を git で扱えるようにするためには、他にも良いツールがあります。それは、sealed secret[21] です。sealed secret は k8s 上に SealedSecret Resource Type を復号化し Secret を生成してくれる Controller を立ち上げることで、secret の git 管理を実現したものです。僕たちは sealed secret を用いることにしました。
5-3-3. Secret の変更時の、そのリソースに依存したデプロイメントのリロードできない問題
sealed secret を使うとはいえ、問題がありました。
今までは kustomzie の secret generator を用いて secret を管理していたので、secret の中身が変更されたら、Secret Resource の metadata.name
を変更することで、そのリソースに依存しているデプロイメントの再起動が走っていました。
しかし、kustomzie は SealedSecret Resource の metadata.name
の変更には対応していません。そのため、僕たちは、SealedSecret Resource の metadata.name
を変更する kustomize plugin を開発しました。
GitHub - plaidev/kustomize-plugins: kustomzie plugins
ただ、metadata.name
を変更することにも問題がありました。それは、sealed secret は default で、metadata.name
と namespace
を使って暗号化するので、metadata.name
を変えてしまうと復号化できないという点です。もちろん、暗号化の制約を緩めることはできますが、それは避けたいですよね。
5-3-4. Reloader を用いたリソースのリロード
これは、Sealed Secret の中の人[22]に色々と教えてもらったのですが、どうやら、secret の中身が変更されたら、それを検知してリソースをリロードしてくれる Controller があるとのことでした。それは、Reloader[23] というものなのですが、secret の中身が変更されたら、関連リソースの metadata.annotation
を変更することで、リソースのリロードを実現しています。とても便利です。
5-3-5. Secret の編集
Sealed Secret の編集は面倒くさいです。まず、sealed secret を secret に復号し、secret の base64 を復号し、編集してから、暗号化し、さらに符号化します。とても面倒だったので、 secret [24]を編集できる cli tool を開発しました。
GitHub - plaidev/ksedit: cli tool to edit k8s secret like kubesec edit.
$ ksedit ./secret.yml > ./secret-new.yml
割と便利なので、良かったら使ってみてください。
6. 今後に向けて
他にもやりたいことはいくつかあります。Canary release なんかはその一部だったりします。今後の課題ですね。
まだまだ、未完成[25]で改善の余地があります。デプロイシステムは常にベータ版[26]です。ベータ版過ぎてドキュメントが整備されてなく[27]、周りから苦情[28]が相次ぐということもありました。デプロイ周辺は守るところではあるけども、僕たちはそれぐらい高速に大胆[29]かつ冷静[30]に開発しています。
こんな感じですが、もし興味があれば僕たちと一緒に開発しませんか?
CX(顧客体験)プラットフォーム「KARTE」を運営するプレイドでは、KARTEを使ってこんなアプリケーションが作りたい! KARTE自体の開発に興味がある!というエンジニア(インターンも!)を募集しています。
詳しくは弊社採用ページまたはWantedlyをご覧ください。 もしくはお気軽に、下記の「話を聞きに行きたい」ボタンを押してください!
エンジニアというと適切に何をやってるか伝えるのは難しいですが、インフラに近いところからフロントエンドまではもちろん当然のようにやっています。 ↩︎
新しいシステム基盤を開発していくチーム。チーム全体のパフォーマンスと、プロダクトのパフォーマンスの両面を上げていくことを目的としている。 ↩︎
社内では他の名称を好んで使っている人が一部居ますが、ここでは micro service achitecture と言っておきます。micro という形容詞がミスリーディングな気もするので、あまり micro という言葉を使うのは気が引けますが。 ↩︎
もちろん、他にも色々やりました。 ↩︎
他にもやってますが。 ↩︎
障害が起こったときに、その前の状態に戻ること ↩︎
Continuous delivery、Devops とでも言えばいいのでしょうか。ただ、デプロイシステムと言っても伝わると思うので敢えて。 ↩︎
gitops: git で ops を管理する。terraform とかと同じ。 ↩︎
ops: 運用のこと ↩︎
kubernetes, k8s - container orchestration tool. ↩︎
宣言的(declarative)という意味 ↩︎
manifest: k8s 内の状態を記述するもの。参考 -> Kubernetesの基礎 | Think IT(シンクイット) ↩︎
single source of truth: 一つのリソースが唯一の信頼できるリソースであるということ。master branch の最新の状態とk8sの状態が一致しているということ。完全一致というわけではありませんが。 ↩︎
デプロイとロールバックが必要とする時間は僅か3分です。今まで、ロールバックが出来なかったときのロールバックにかかる時間を∞とするならば、今回の新デプロイシステムでは、ロールバックにかかる時間は∞%改善されたと言えそうです。∞ですよ? ↩︎
何と言えばいいのかわからないので、とりあえず、かっこいい感じで名付けました。 ↩︎
ENV Branching with Git | Branching Models & Git-Flow Strategies ↩︎
GitHub - shyiko/kubesec: Secure Secret management for Kubernetes (with gpg, Google Cloud KMS and AWS KMS backends) ↩︎
k8s に manifest を適応するときに扱うコマンド ↩︎
GitHub - bitnami-labs/sealed-secrets: A Kubernetes controller and tool for one-way encrypted Secrets ↩︎
色々教えてくれてありがとう ↩︎
GitHub - stakater/Reloader: A Kubernetes controller to watch changes in ConfigMap and Secrets and do rolling upgrades on Pods with their associated Deployment, StatefulSet, DaemonSet and DeploymentConfig – [✩Star] if you're using it! ↩︎
sealed secret ではなく、secret ↩︎
完全なものなど存在しないと、個人的には考えています。その前提の上で、既存のものを壊しつつ良いものにしていこうというスタイルです。 ↩︎
もはやベータですらないかもしれないけれど。 ↩︎
ごめんね。でも、永遠に過渡期だからさ! ↩︎
苦情とは少し言い過ぎかもしれません。正直に言うなれば、ただの誇張です。 ↩︎
大胆に変更していますが、なんと一度も問題が起きていません! ↩︎
僕たちは冷静であることと熱量があることは共存可能と考えています。今回の場合は、大きな変更による影響範囲の想定をした上で大胆に開発しています。 ↩︎