Open Policy Agentとは

Open Policy Agentについて軽く説明したいと思います。Open Policy AgentはOSSの汎用ポリシーエンジンです。Regoというポリシー記述言語によってポリシーを定義して入力データを検証します。よくあるユースケースとしては、Deploymentに特定のLabelがあることを強制したり、DeploymentやJobなどのDockerイメージのTagにlatestが使われていないことを強制したりするユースケースが挙げられます。

それらのポリシーというのはコードで管理されていて、Policy as Codeという画面でも管理されています。ポリシーを基にKubernetes Manifestなどの入力データを検証することになります。メリットとしては、そのポリシーをコードにして明文化できたりとか、ポリシーをコードとして運用してそのあと開発していける。誰が見ても理解できるかたちであって、誰もが検証できるようなメリットがあります。

このようなポリシーエンジンを自分たちメルカリのCI/CDの中にどのように取り入れて行けばよいかを考えました。簡単に説明するとこのようなCI/CDになっていて、Open Policy Agentのプロダクトの中でもConftestというプロダクトとGatekeeperというプロダクトを導入しました。ConftestはCIに対して、GatekeeperはKubernetesに実際にデプロイしています。その理由はあとから説明します。

Conftest

まずConftestとは入力データをRegoで書いたポリシーで、検証するためのCLIツールになっています。もともとCI上で使われることを想定されています。Apply前にマニフェストを実際に検証してみることによって、そのマニフェストがルールに違反しているかしていないかなどのフィードバックを得られます。

フィードバックのレベルも3つに分けられていて、違反していなかったらSuccess、違反していたらViolation、そして警告メッセージとしてWarningを出せます。Conftestは、もともとはOPAのRegoを用いたサードパーティツールでしかなかったんですけど、最近になってOPAコミュニティの傘下に入って正式にCLIツールとして地位を築きつつあるプロダクトです。

CIの中でどのように使えるか見てみます。先ほど紹介したKubernetesリポジトリのCIに対して、Conftestで検証するようなJobを追加して検証します。例えばServiceCがルールに違反しているYAMLをプッシュしていると、CIの中でConftestが落ちて結果的にマージできない。そのフィードバックがデベロッパーに返るような仕組みになっています。

Success、Violationだけでなくて例えばWarningも出せて、今回YAMLをパスするもののベストプラクティスはこのような感じです。「こんな感じで実装をしてください」などの警告を出せて、徐々に組織全体のポリシーをかけていくということもできます。そしてCLIツールなので、実際にプッシュをしなくてもデベロッパーのローカル開発環境で検証してみてフィードバックを得られることもメリットの1つです。

Gatekeeper

次にGatekeeperの説明をします。GatekeeperはOPAをKubernetesのAdmission Controllerとして使えるようにしたものです。現在はバージョン3です。Gatekeeperの主要な機能としてkubectlのApplyされた入力と既にクラスタ上にデプロイされている既存のリソースを同時に参照しながらの検証ができるSync機能だったりとか、定期的な既存リソースの検証ができるAudit機能などがあります。

GatekeeperのポリシーはConstraintTemplateとConstraintの2つのCRDによって定義します。

ConstraintTemplateというのはConstraintを生成するための雛形になるもので、こちらは実際にRegoを用いてポリシーを記述します。そしてConstraintというのはConstraintTemplateから作られるCRDのことで、実際にどのポリシーをどのリソース、例えばDeployment、Ingressに適用するかや細かいパラメータを指定することになります。

ConstraintTemplateの実際の例はこちらです。こちらのConstraintTemplateはLabelをバリデーションするものです。targets以下で見られるようにここでポリシーを記述することになります。

次にConstraintです。ConstraintはConstraintTemplateを指定して、またそのポリシーを書けるためのリソースを指定して作ります。ここで実際にポリシーを書けます。

Gatekeeperというのはアドミッションコントローラーなので、例えば今回SpinnakerでApplyされるリソースだけではなくて、直接kubectlをクライアントがApplyされるものもあればCircle CIにApplyされるものも検証できます。また、ApplyだけではなくてCRDとか抽象化したリソースの中でもそのポリシーに違反するApplyがないかというのを検証できて、Kubernetesクラスタの最後の砦のような役割をします。

ConftestとGatekeeperを導入すると、こうなる

ConftestとGatekeeperを導入したあとのCI/CDはこのような構造です。結果的にこのような2つのプロダクトを導入するようになったんですけどそれには理由があって、例えばGatekeeperだけになるとメリットとしてはどのようなKubernetesリソースのApplyに対してもポリシーを書けるのですが、実際にApplyをしてみないと結果がわからないというデメリットがあります。

とくにマイクロサービスのデプロイはCI/CDを通してリポジトリにプッシュしてレビューもらって、それをCIで回してSpinnakerでデプロイするという流れがある中で、実際にデプロイしないとわからないというのはかなりフィードバックが遅くて開発体験が低いものです。

そのために今回Conftestという、デベロッパーのローカル開発環境でも検証できる、あとはCIの中でも検証できる比較的フィードバックの早いプロダクトも同時に導入しています。このようなものを組み合わせることによって開発体験を損なうことなく組織にポリシーを適用できています。

ConftestとGatekeeperの機能比較

ConftestとGatekeeperの機能を比較して表にしてみました。先ほども紹介した通りどちらもポリシーを基に検証を行うツールなんですけど、立ち位置が異なるために排他的なものではありません。

とくに注目していただきたいのが、そのバリデーションのフィードバックの速さです。ConftestはローカルやCIで行うためフィードバックは早いです。Gatekeeperは実際にApplyしてからでないとわからないので、CI/CDを敷いているところだとかなり遅いフィードバックになってしまいます。そのために検証の役割というのも違っていて、ConftestはメルカリではApply前に素早くフィードバックを得るために行うような検証に使われています。

またここでWarningなどの警告を用いてベストプラクティスをデベロッパーに提案しています。GatekeeperはKubernetesクラスタの最後の砦として、実際にこのポリシーだけは守りたいみたいな、確実に課したいポリシーに対してGatekeeperでポリシーをかけてブロックしているような流れです。

CI/CDフローの整理

全体のCI/CDのフローを整理すると、このような感じになっています。マイクロサービスをリポジトリにプッシュしてCIが走って、そこでConftestのバリデーションが走ります。そこを通ってもSpinnakerがデプロイしたときにGatekeeperのバリデーションがあります。

このようなかたちで組織的なルールをポリシーとして定義してConftestでCIでのチェック、Gatekeeperでの実際にApplyしてからのチェックを行うことによって、マイクロサービスのすべてに対してポリシーを課せます。

ポリシー自体のCI/CD

ポリシー自体もCI/CDを行っています。それを少し紹介したいと思います。Conftestにはユニットテストをする仕組みがあるので、ポリシー自体もテストできます。また先ほども紹介した通り、ConftestはRegoというポリシーファイルを用意して、GatekeeperはCRDを用意しなければならないので互換性がありません。

そこで実際にポリシーを開発するときに、ポリシーをプッシュするとそのポリシーをCRDに変換してGatekeeperのCRDとしてデプロイしています。このような仕組みを用意しているので、ルールがConftestのルールとGatekeeperルールで統一されているのがメリットです。どちらかのポリシーを通って、どちらのポリシーに通らないというケースはなくなります。

なのでOPAを導入することによって、各マイクロサービスのCI/CDの開発サイクルの中でポリシーを課しています。

Spinnaker Managed Pipelineの導入

次の問題点として、Spinnaker PipelineというのはデベロッパーがGUIで設定するので、設定ミスをしやすかったりデプロイ手法もサービスによって質が異なることが挙げられます。

その解決策として、デベロッパーはパイプラインを簡単な設定で作れる仕組みだったりとか、パイプラインを作成するとあとから変更できないイミュータブルな仕組み作り。そしてプラットフォームチームが各マイクロサービスのデプロイ手法を管理して、デプロイ手法の標準化だったりベストプラクティスを提供し続けられる仕組み作りが必要でした。そこでSpinnaker Managed Pipelineを導入することになりました。

ここでSpinnaker Managed Pipelineについて説明したいと思います。Spinnaker Pipelineは最初に説明したように、デベロッパーが作って設定して、それ基にGKEクラスタにマイクロサービスをデプロイしていました。

Spinnaker Managed PipelineというのはManaged Pipeline Template、MPTと言われる雛形になるようなものに各デベロッパーが設定情報を渡してできるパイプラインのことです。これらはすべてコードで定義されています。

Spinnaker Managed Pipelineの主な機能として、Sync機能、Immutable機能、Configuration機能があります。Sync機能はMPTを更新するとそのMPTから生成されたすべてのパイプラインも同時に変更できるという機能です。想定されるユースケースとしては、プラットフォームチームが各マイクロサービスのデプロイ手法を一括で変えたいなどのケースです。

次にImmutable機能は、Managed Pipelineを直接変更できない機能です。これによって意図しない変更を防げます。

Configuration機能とは、必要な部分だけMPTの一部を変数化して、Managed Pipelineの生成時に設定できるような機能です。この変数はイミュータブルではなくて、いつでも変更できます。

Managed Pipelineを使えばMTPを管理するだけで横断的に全マイクロサービスのパイプラインを管理できます。

Sync・ Immutable・ Immutable機能の例

図にして説明してみます。Sync機能は各マイクロサービスがPipeline ConfigurationsとMPTを基にManaged Pipelineを生成します。例えばプラットフォームチームがデプロイ先を変更したい、クラスタを作り変えたいといったときに、プラットフォームチームはMPTを変更するだけで、各マイクロサービスのパイプラインを変更できます。

それによってデベロッパーは何もしなくてもデプロイ先のクラスタを変更することが可能です。また、デプロイ先に限らずプラットフォームチームがカナリアデプロイとかデプロイのベストプラクティスを提供したいとなったときに、MPTさえ変えられればManaged Pipeline自体もそのベストプラクティスを実装することになって、各マイクロサービスのパイプラインがより質の高いものになります。

次にImmutable機能です。Managed Pipelineはあとから変更できません。なのでこのようなパイプラインを1回作ったあとにパイプラインを改変したいとか、デベロッパーが誤って設定し直してしまうことを防げます。

Configuration機能はマイクロサービスに依存する設定値だけパイプライン生成時に設定できるような機能です。この設定値だけはあとからデベロッパーが変更できます。しかし基本的なパイプライン自体はイミュータブルになっていて、あとから変更できません。

Spinnaker Managed Pipelineのメリット

このSpinnaker Managed Pipelineのメリットを整理します。

Sync機能についてはプラットフォームチームがMPTを管理するだけで、全マイクロサービスのデプロイを管理できます。

Immutable機能についてはパイプラインをあとからデベロッパーに変更されて何か破壊的なことが起きてしまうことを防げます。

Configuration機能は必要な部分だけをデベロッパーに設定してもらう機能です。このような機能を使うことによって、マイクロサービスに必要な最低限の機能だけをデベロッパーに公開して、あとから変更されることもなく、マイクロサービス全体のデプロイの標準化と管理ができます。

このようなパイプライン自体もコードを管理していて、プラットフォームチームはMPTを保守・開発することによって全マイクロサービスのパイプラインを管理できます。なのでそのテンプレートだけの面倒を見ればいいわけです。

全体のデプロイ手法を管理するだけではなくて、クラスタマイグレーションだったり全マイクロサービスのパイプラインの変更を要するときも1つのMPTを変更するだけで済みます。この際にデベロッパーは何もする必要がないため、リスクを回避できます。

Spinnaker Managed Pipelineを導入することによって、プラットフォームチームが各マイクロサービスのデプロイを統括的に管理して質を向上できるようになりました。

より安全な継続的デリバリーを実現

最後にこれらのまとめをして終わりたいと思います。

まず前提としてマイクロサービスアーキテクチャの継続的デリバリーにおいてサービス全体の、全マイクロサービスの質を担保してすべてを安全にデプロイしていくことは一般的に難しいです。そのような中で安全な継続的デリバリーを行うためにも、ぞれぞれのマイクロサービスの開発の中でルール・ポリシーを課したりとか、デプロイをする仕組みをプラットフォームチームが提供するべきです。

今回のメルカリの例では、Open Policy AgentとSpinnakerのManaged Pipelineの機能を使うことによって、すべてのマイクロサービスのKubernetes Manifestへポリシーを課したりとデプロイを管理できて、より安全な継続的デリバリーを実現できました。

以上で終わりたいと思います。ありがとうございました。