monorepoと振り返る 2021
UbieではマイクロサービスのようにGKE上に複数のサービスを展開していますが、manifestはmonorepoにまとめています。monorepoが誕生してから2年ほど立ったのでその間の遷移をまとめてみました。年末だし。
medium脱却…といいつつもめんどくさくてブログを作っていない@sakajunquality です。Ubie DiscoveryでSRE+Infra+Securityみたいなことをやってます。
Ubieとアーキテクチャについて
UbieではユビーAI受診相談とユビーAI問診のいわゆるB2C/B2Bのプロダクトを展開しています。2021年は日本だけではなくシンガポールにも進出しました (展開する国は今後増やしていく予定です)。
クラウドとしてはGoogle Cloudを利用していますが、その中でもアプリケーションはサービスメッシュ(Istio)を入れたGKE上にマイクロサービスとして展開しています。
ざっくりとこんなイメージ(もっとたくさんのサービスが実際にはあります)
そしてすべてのサービスは一つのmonorepoに集約しています。
※VPCやデータベースなどの各種クラウドリソースはterraformで管理しており、それは別のmonorepoで管理しています。今回は完全にKubernetesの中のみを管理しているmonorepoの話です。
Ubieのmonorepoについて
このmonorepoには ConfigMap
や Deployment
といった標準的なアプリケーションのリソースだけではなく、Istio(トラフィック制御や認可)、Prometheus(モニタリング)のリソースなどクラスターに含まれるものはすべて含まれています。
// ディレクトリ構成
.
├── .github // GitHub Actions
├── clusters // クラスター全体のリソース
├── docs
├── namespaces // namesapceやsa
├── policy // Istio AuthorizationPolicyなど
├── scripts
├── services
│ ├── service_a // サービスごとにディレクトリ
│ │ ├── app
│ │ │ ├── base // kustomize使ってます
│ │ │ └── overlays
│ │ │ ├── production
│ │ │ ├── qa
│ │ │ └── staging
│ │ ├── config
│ │ ├── jobs
│ │ └── workflows
│ ├── service_b
│ ... // 結構いっぱいある
│ └── service_zzz
└── traffic // Istio VirtualService/DestinationRule/ServiceEntryなど
これは少し余談ですが、GKE(Kubernetes)のマニフェストだけではなくCloud Runのデプロイもここにまとめています。技術標準化の観点でGKE または Cloud Runに集約しています。Cloud Runも利用している理由としては、スピード重視でCloud Runでとりあえず動かしつつ、あとから連携のためGKEに引っ越しをすることがよくあるからです。
monorepoの具体的なオペレーション
そもそもこのリポジトリがどのように使われてるのか簡単にまとめておきます。
リリースフロー
簡単に言うとアプリケーションでコンテナイメージが作られると、そのイメージをデプロイするためのプルリクエストがこのmonorepoに作られます。そのプルリクエストをマージするとデプロイされます。デプロイはpull型ではなくpush型で行っています(この話は長くなりそうなのでまた別の機会に)。
すこしアーキテクチャは変ったものの、この仕組自体は3年ほど前からあって(実はKubeCon NA 2018でこの話をしました)、パブリックに公開(sakajunquality/flow)しています。
ときどき同僚がコントリビュートしてくれて改善しています。
なぜmonorepoにしたか
もともとはpolyrepoでスタイルで複数レポに分かれていましたが、2020年の4月くらいにmonorepoに集結させました。Polyrepoにした理由はCloud Buildがそのほうが扱いやすかったとか(少なくとも当時は)、SREの手の行き届かないところで増殖してたとかいろいろあります。これは失敗だったw
しかし下記のようなモチベーションによりmonorepoに集約しました。
- GitHub Actions 移行によりmonorepoが扱いやすくなったこと
- Resiliencyを重視しておりクラスターが壊れた際の復旧を容易にしたい
- リポジトリが分散していているとアップデートが大変
- 監査を一元的に行いたい
CIはpull型にすればいいだけなのでさておき、APIバージョンの追従やCRDの導入など分散しているとアップデートが大変でした。
また、障害が起きてもクラスターごと作り直せばすぐに復旧できる体制をとっていたのですべてのマニフェストがmonorepo1箇所に集約されてるほうがかなりメリットがありました。
ちなみにmonorepo自体については@b4b4r07さんの#k8sjpでの発表を聞いてポジティブになりました。
またmonorepoとは直接関係ないですが、サービスが増えていく中でmonorepoを通じてどういうふうにoperationを変えていくか定期的に思いを馳せたりしてます。個人的にはKubeConNA2018でLyftのセッションのあとにスピーカーのLitaと立ち話をしLyftでのproductionのoperationの話を聞いてかなり学びがありました。(そういうのアウトプットしろよと@kamina_zzzに怒られましたw)
monorepoの遷移: GitHubのInsightsを見てみる
リポジトリ自体は2019年4月から存在していて、クラスター全体で使うようなものがコミットされていました。そこから1年後にmonorepoとして集合し、コミットがどんどん増えているのがわかります。
qaやstagingが含まれていたり、サービス数自体も増えているので、全てが本番機能リリースというわけではないですがリリース回数がどんどん増えているのがわかります。このBot分人間が手動でコミットしてたらやってられないので作ってよかったなーと思いましたw
2021年のmonorepoの進化
結果的にですが、今年2021年はこのmonorepoがかなり進化しました。主要なものを書いてみます。
Policyディレクトリの追加
マイクロサービス間の通信が以前は比較的自由にできていましたが、PIIを扱うようなサービスが生まれたので、IstioのAuthorizationPolicyを導入しました。またSREの知らないところでサービス間通信が広がらないようにという意図もあります。
結構サービス数が増えてからやったのでけっこう大変でした…できることなら最初から入れておけばよかったです。
AuthorizationPolicy以外にも導入したいものがいくつかあります。セキュリティ向上のためにもこのディレクトリを盛り上げて行きたいところです。
Argo Rolloutsの全展開
2019年頃から一部で入れていたものの中途半端になってました。標準化観点で全サービスに展開していくことにしました。Progressive Deliveryをちゃんとしていきたいところ(このためにVirtualServiceのリファクタリングをしたけどRolloutsが複数VSサポートが入ったw)。リポジトリ内では apps/v1
の Deployment
がapp、argoproj.io/v1alpha1
Rollout
がappV2とというディレクトリになっています。appがかなり減ってきました。
(のこり数サービスですが年内には間に合わなそう…)
Argo Workflows/Events
別リポジトリであった開発者が自由にジョブを実行するスクリプトをセキュリティ観点でなくしたかったのでArgo EventsとArgo Workflowsを組み合わせた環境を整えました。社内のCLIでキックしpub/sub経由でジョブを実行できるようになっています。これらのジョブもすべてmonorepoに含まれているので、マージ前にレビューがちゃんと通るようになりました。
開発者もワークフローを書いてくれるようになったものの、書くのもレビューするのも難しかったりするので来年はテコ入れしたい。
CronJob廃止
またArgo Workflowsをちゃんと運用し始めたので、あまり監視してなかったCronJobを廃止して、Argoの CronWorkflow
に置き換えました。これも余談ですが@kameneko1004 に手伝ってもらいWorkflowsもちゃんとPrometheusで監視するようになりました。
GitHub Actionsの整理
前述の通りpush型でのクラスターへの反映を行っています。サービス、環境ごとに微妙に差異があったりなかったりしたので、Env×Serviceの数だけ .github/workflows にCIのファイルがありました。サービス数が少なかった頃はメンテ可能だったものの徐々に職人芸のような感じになってました。そこで次の3点を徐々に実施してワークフローをメンテ可能な状態に戻しました。
それぞれ、共通の処理をまとめたり(Composite Run)、マルチクラスターのクラスター情報をAPI経由で取得しデプロイ先を動的に制御したり(Dynamic Matrix)、全体でWorkflowを共通化(Reusable Workflows)しています。
細々としたのはたくさんあるのですが大きくはこんなところです。
2021 年末負債解消
2020年に引き続き、会社的には年末年始休暇に入ってから有志のメンバーで集まって負債の解消を行いました。今年はこのmonorepoの改善をテーマにしました。(makocchiさん/gosukenatorさんは業務委託としてUbieのプラットフォームを支えてくれています)
実は朝からとあるインシデントが起きていて午前中はその対応をしていました。実質14時〜19時しか時間が取れなかったものの集中して進捗出せてよかったです。
年末ギリギリで参加できないメンバーもいたので来年はもっと早く日常の仕事を納めて大掃除をしたいです。集まって一つのことに取り組むと進捗が一気に出ますね。
2チームに分かれて下記2つのテーマに取り組みました。僕はあまったので両方を行き来してました。
Secret管理の進化
KubernetesにはSecretsというリソースがあると思いますが、base64の値を直接リポジトリにコミットするのはためらわれます。そこで今までは Cloud KMS を使用して暗号化したyamlをコミットしていました。Cloud KMS自体はIAMでちゃんと管理し鍵も定期的にローテートしているものの、暗号化されたyamlはもはやdiffが取れずレビュー不可能であり、applyに失敗したらログに残ってしまうなど生産性・セキュリティ双方の観点で問題がありました。
そこで思い切ってCloud Secret Managerへの移行を決め、ファーストステップとしてExternal Secretsの導入を行いました。この辺は@itkqがやり切り次第ブログにしてくれることでしょう。
一旦全体でExternal Secretsに移行後、berglas(Goだとめっちゃ便利)やspring-cloud-gcp-starter-secretmanager(弊社はSpringBootのサービスが多い)などに最適化していこうと思います。クレデンシャルの扱いにはかなり気を使っていますが、アプリケーションの脆弱性などで流出するリスクを最小限に抑えたいというモチベーションがあります。
Kubernetes Validation with OPA
弊社の@m_mizutaniがOPA / Rego Advent Calendar 2021を一人で完走したばかりですが、社内ではOPAサーバーがデプロイされ、いろいろなところでRegoが導入されようとしています。
もともとの課題感としては、diffの確認やyamlのvalidateなど安全のためのCIが増えているものの、そのサービスがProduction Readyかどうか・セキュリティ上社内の基準を満たしているかなどは人間によるレビューで行っていたため完璧ではありませんでした。
Regoが社内で普及していることを受け、PullRequestでK8sのyamlのvalidationもOPAを使いたいと言うのがこのテーマです。
全部が全部機械的にチェックできないかもしれませんが、下記のようなことはポリシーで簡単にチェックできると思っています。
- 決められたlabelが付与されているか
- Probeが適切に設定されているか
- 適切なServiceAccountが指定されているか
- rootで動いていないか/昇格できないようになっているか
- etc…
OPAサーバー自体はあったので、ポリシーの追加と、差分のあったディレクトリの内容をこのサーバーに投げるGitHub Actionを作りました。
// サンプルのポリシー
package ubie.policy.examplefail[msg] {
some x
input.data[x].kind == "Rollout"
input.data[x].spec.replicas < 3 msg := sprintf("%s %s has replicas < 3", [input.data[x].kind, input.data[x].metadata.name])
}
ここ割愛しますがテストコードも書くことができます。
このポリシーを適用したので replicas: 1
にしたところ無事に落ちました!
これについてはOSS化予定です。またそれに合わせて解説記事を@m_mizutaniが執筆予定です。期待案件。
今後の方向性について
まだまだこのリポジトリは改善の余地があり来年も時間を見つけて手を手を入れていきたいと思っています。
Credential-less
OIDC + Workload Identity Federation で GitHub Actionsをクレデンシャルレスにするというものです。こんなことをつぶやいたものの完全にはできなかったです。(terraformとかもっと強い権限のあるところから対応してた)
vvakameさんが日本語でまとめてくれていますが、GitHub UniverseでGAして公式ドキュメントになりました。論理できてあとはやるだけ。
Policy as a Code + Enforcement
Policy as a Code という観点で仕組みができたので、まずはRegoでPolicyをどんどん増やしていきたいです。セキュリティ向上だけでなく、人間によるレビューの負荷を下げ、標準化による運用負荷低減も期待しています。
またPullRequestでのチェックだけでなく、同じポリシーをGatekeeperのように実体でEnforceされているかもしっかり担保していきたいです。
Security Enhancement
PodSecurityがそろそろ来るので対応を…というのとDataplane V2に切り替えたのでもっと活用していきたい
More Declarative
(これはおまけです) Cloud RunへのデプロイもGKE同様宣言的に行えるといいなーと思っています。
まとめ
2021年を振り返ったような振り返っていないような感じですが、弊社のK8sのmonorepoを紹介してみました。
来年はYAMLとHCLを書いた分だけRegoも書いていこうと思います。
それではまた来年〜良いお年を〜
おまけ
もっと詳しい話を聞いてみたいという方はMeetyやってるのでぜひ。60mとなってますが30mでも大丈夫です。