🐕

Config Controller でポリシー制御をしながら Google Cloud のリソースを管理する

2022/12/18に公開

3-shake Advent Calendar 2022 の18日目の記事です。

Google Cloud の Config Controller について少し調べてみたので、この記事では Config Controller の概要や、実際に Config Controller でポリシー制御をしながら Google Cloud のリソースを管理をする方法を紹介していきます。

Config Controller の概要

Config Controller は Config Connector と Config Sync と Policy Controller をまとめたサービスで、これらが有効化されたマネージドな GKE クラスタを提供してくれます。

Config Connector とは

Config Connector は Kubernetes のアドオンで、Config Connector を用いることで Kubernetes 経由で Google Cloud のリソースを管理することができます。

具体的には Config Connector は CRD と controller を提供してくれます。CRD は Google Cloud のリソースを表現するためのもので、Google Cloud のリソースを作成したい場合はそのリソースに対応する Custom Resource をクラスタに apply します。すると、cnrm-system ネームスペースに存在する controller が先ほど apply した Custom Resource を元に実際のリソースを作成してくれます。

Config Connector を用いることで、インフラも Kubernetes 内のアプリケーションと同様に YAML で管理することができ、また Kubernetes に apply することでデプロイするという統一されたフローで両方管理することができるので、管理や運用の複雑さを削減できるというのが狙いの一つのようです。

Config Connector がサポートしている Google Cloud リソースはこちらで確認できます。
https://cloud.google.com/config-connector/docs/reference/overview

Config Sync とは

Config Sync は GitOps を実現できるサービスで、Kubernetes クラスタに GitHub や GitLab などのリポジトリを登録しておくことで、定期的にリポジトリを確認し、差分があればクラスタに apply してくれます。Git だけでなく、コンテナイメージや Helm のリポジトリを登録することもできます。

sync するリポジトリの設定は RootSync や RepoSync といった Custom Resource を用いて設定します。

なお、Config Controller で作成される Kubernetes クラスタはプライベートなクラスタであるため、インターネット経由でリポジトリと通信する場合には、Cloud NAT を用意するなどして外に出られるようにする必要があります。

Policy Controller とは

Policy Controller は Gatekeeper ベースのサービスで、ポリシーを設定してポリシーに違反する API リクエストを監査したり、ブロックしたりすることができます。

ポリシーを作成する際は、ConstraintTemplate でポリシーの雛形を用意し、Constraint で実際にポリシーを作成します。

# 例. Cloud Storage のバケットの location を日本リージョンのみに制限
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: gcpstoragelocationconstraintv1
spec:
  crd:
    spec:
      names:
        kind: GCPStorageLocationConstraintV1
      validation:
        legacySchema: true
        openAPIV3Schema:
          properties:
            exemptions:
              description: A list of bucket names that are exempt from this constraint.
              items:
                type: string
              type: array
            locations:
              description: A list of locations that a bucket is permitted to have.
              items:
                type: string
              type: array
  targets:
    - rego: |
        package gcpstoragelocationconstraintv1

        allowedLocation(reviewLocation) {
          locations := input.parameters.locations
          satisfied := [good |
            location = locations[_]
            good = lower(location) == lower(reviewLocation)
          ]

          any(satisfied)
        }

        exempt(reviewName) {
          input.parameters.exemptions[_] == reviewName
        }

        violation[{"msg": msg}] {
          bucketName := input.review.object.metadata.name
          not input.review.object.spec.location
          msg := sprintf("Cloud Storage bucket <%v> must include a location", [bucketName])
        }

        violation[{"msg": msg}] {
          bucketName := input.review.object.metadata.name
          bucketLocation := input.review.object.spec.location
          not allowedLocation(bucketLocation)
          not exempt(bucketName)
          msg := sprintf("Cloud Storage bucket <%v> uses a disallowed location <%v>, allowed locations are %v", [bucketName, bucketLocation, input.parameters.locations])
        }

        violation[{"msg": msg}] {
          not input.parameters.locations
          bucketName := input.review.object.metadata.name
          msg := sprintf("No permitted locations for Cloud Storage bucket <%v>", [bucketName])
        }
      target: admission.k8s.gatekeeper.sh

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GCPStorageLocationConstraintV1
metadata:
  name: only-japan
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [storage.cnrm.cloud.google.com]
      kinds: [StorageBucket]
  parameters:
    locations:
    - asia-northeast1
    - asia-norteast2

ConstraintTemplate はデフォルトでもいくつか用意されているので、こちらを活用することもできます。

$ kubectl get constrainttemplates -l "configmanagement.gke.io/configmanagement=config-management"
NAME                                      AGE
allowedserviceportname                    4d2h
asmauthzpolicydisallowedprefix            4d2h
asmauthzpolicyenforcesourceprincipals     4d2h
asmauthzpolicynormalization               4d2h
asmauthzpolicysafepattern                 4d2h
asmingressgatewaylabel                    4d2h
asmpeerauthnstrictmtls                    4d2h
asmrequestauthnprohibitedoutputheaders    4d2h
asmsidecarinjection                       4d2h
destinationruletlsenabled                 4d2h
disallowedauthzprefix                     4d2h
gcpstoragelocationconstraintv1            4d2h
k8sallowedrepos                           4d2h
k8sblockendpointeditdefaultrole           4d2h
k8sblockloadbalancer                      4d2h
k8sblocknodeport                          4d2h
k8sblockprocessnamespacesharing           4d2h
k8sblockwildcardingress                   4d2h
k8scontainerlimits                        4d2h
k8scontainerratios                        4d2h
k8scontainerrequests                      4d2h
k8sdisallowanonymous                      4d2h
k8sdisallowedrolebindingsubjects          4d2h
k8sdisallowedtags                         4d2h
k8semptydirhassizelimit                   4d2h
k8sexternalips                            4d2h
k8shttpsonly                              4d2h
k8simagedigests                           4d2h
k8slocalstoragerequiresafetoevict         4d2h
k8smemoryrequestequalslimit               4d2h
k8snoenvvarsecrets                        4d2h
k8snoexternalservices                     4d2h
k8spodsrequiresecuritycontext             4d2h
k8sprohibitrolewildcardaccess             4d2h
k8spspallowedusers                        4d2h
k8spspallowprivilegeescalationcontainer   4d2h
k8spspapparmor                            4d2h
k8spspautomountserviceaccounttokenpod     4d2h
k8spspcapabilities                        4d2h
k8spspflexvolumes                         4d2h
k8spspforbiddensysctls                    4d2h
k8spspfsgroup                             4d2h
k8spsphostfilesystem                      4d2h
k8spsphostnamespace                       4d2h
k8spsphostnetworkingports                 4d2h
k8spspprivilegedcontainer                 4d2h
k8spspprocmount                           4d2h
k8spspreadonlyrootfilesystem              4d2h
k8spspseccomp                             4d2h
k8spspselinuxv2                           4d2h
k8spspvolumetypes                         4d2h
k8sreplicalimits                          4d2h
k8srequirecosnodeimage                    4d2h
k8srequiredannotations                    4d2h
k8srequiredlabels                         4d2h
k8srequiredprobes                         4d2h
k8srequiredresources                      4d2h
k8srestrictlabels                         4d2h
k8srestrictnamespaces                     4d2h
k8srestrictrolebindings                   4d2h
noupdateserviceaccount                    4d2h
policystrictonly                          4d2h
restrictnetworkexclusions                 4d2h
sourcenotallauthz                         4d2h

Policy Controller で Config Connector の Custom Resource に対してポリシーを設定し、Google Cloud リソースの管理を Config Connector に寄せることで、ポリシー制御を行いながら Google Cloud のリソースを管理することが可能になります。

Google Cloud リソースに対するポリシーは Organization policies という別のサービスでも設定することができますが、こちらは適用できるポリシーの種類が限られています。対して、Policy Controller は自由にポリシーを作成することができるため、Organization policies では不十分といった場合に利用を検討すると良さそうです。

費用に関して

Config Controller を使用すると、Anthos Config Management と GKE クラスタの費用がかかります。

2022年12月時点だと、

  • Anthos Config Management : $0.10/h
  • GKE クラスタ : $0.10/h + ノードの費用

となります。

実際にポリシー制御を行いながら Google Cloud のリソースを管理してみる

ここからは、実際に Policy Controller でポリシーを設定し、Config Connector で Google Cloud のリソースを管理してみます。

今回は Cloud SQL に関して、location を日本リージョンのみに限定するポリシーを作成し、実際に Config Connector でインスタンスを作成してみます。

Config Controller の準備

まずは、各種 API を有効化しておきます。

$ gcloud services enable serviceusage.googleapis.com \
    krmapihosting.googleapis.com \
    container.googleapis.com \
    cloudresourcemanager.googleapis.com

次に、Config Controller のインスタンスを作成します。
東京リージョンで作成しようとするとエラーが出て作成できなかったので、今回は大阪リージョンで作成します。

$ gcloud anthos config controller create test --location asia-northeast2

Config Controller のインスタンス一覧や状態はこちらで確認できます。

$ gcloud anthos config controller list
NAME                                                              LOCATION         STATE
projects/<PROJECT_ID>/locations/asia-northeast2/krmApiHosts/test  asia-northeast2  RUNNING

作成された GKE クラスタの認証情報はこちらで取得できます。

$ gcloud anthos config controller get-credentials test --location asia-northeast2

作成された GKE クラスタの中身はこんな感じです。

$ kubectl get no
NAME                                                  STATUS   ROLES    AGE    VERSION
gke-krmapihost-test-krmapihost-test-p-0d142341-cdqp   Ready    <none>   4d2h   v1.24.5-gke.600
gke-krmapihost-test-krmapihost-test-p-3f89df88-x0x7   Ready    <none>   4d2h   v1.24.5-gke.600
gke-krmapihost-test-krmapihost-test-p-62a75b97-q2kr   Ready    <none>   4d2h   v1.24.5-gke.600

$ kubectl get po -A
NAMESPACE                         NAME                                                             READY   STATUS    RESTARTS   AGE
cnrm-system                       cnrm-controller-manager-4s5hagzmxjow7rr4wtvq-0                   2/2     Running   0          4d2h
cnrm-system                       cnrm-deletiondefender-0                                          1/1     Running   0          4d2h
cnrm-system                       cnrm-resource-stats-recorder-5cf496b66-l7m96                     2/2     Running   0          4d2h
cnrm-system                       cnrm-unmanaged-detector-0                                        1/1     Running   0          4d2h
cnrm-system                       cnrm-webhook-manager-bc647699-6hvbv                              1/1     Running   0          4d2h
cnrm-system                       cnrm-webhook-manager-bc647699-pqnpq                              1/1     Running   0          4d2h
config-management-monitoring      otel-collector-5bc7d4bb6d-f5drx                                  1/1     Running   0          4d2h
config-management-system          config-management-operator-775dd7c54c-9j75j                      1/1     Running   0          4d2h
config-management-system          reconciler-manager-d67cf5469-4pmck                               2/2     Running   0          4d2h
configconnector-operator-system   configconnector-operator-0                                       1/1     Running   0          4d2h
gatekeeper-system                 gatekeeper-audit-d9d9b84b8-bs6hg                                 1/1     Running   0          4d2h
gatekeeper-system                 gatekeeper-controller-manager-5f4cc4475b-mcdrr                   1/1     Running   0          4d2h
krmapihosting-monitoring          krmapihosting-metrics-agent-26pbh                                1/1     Running   0          4d2h
krmapihosting-monitoring          krmapihosting-metrics-agent-dzzp4                                1/1     Running   0          4d2h
krmapihosting-monitoring          krmapihosting-metrics-agent-s5ln4                                1/1     Running   0          4d2h
krmapihosting-system              bootstrap-9d9db889f-99v9r                                        1/1     Running   0          2d
kube-system                       event-exporter-gke-857959888b-8xfmk                              2/2     Running   0          4d2h
kube-system                       fluentbit-gke-5wtpr                                              2/2     Running   0          4d2h
kube-system                       fluentbit-gke-gvm64                                              2/2     Running   0          4d2h
kube-system                       fluentbit-gke-wxx5f                                              2/2     Running   0          4d2h
kube-system                       gke-metadata-server-n76hd                                        1/1     Running   0          4d2h
kube-system                       gke-metadata-server-xnthv                                        1/1     Running   0          4d2h
kube-system                       gke-metadata-server-xsk24                                        1/1     Running   0          4d2h
kube-system                       gke-metrics-agent-9khmc                                          1/1     Running   0          4d2h
kube-system                       gke-metrics-agent-rfss8                                          1/1     Running   0          4d2h
kube-system                       gke-metrics-agent-vvxx8                                          1/1     Running   0          4d2h
kube-system                       kube-dns-55d79c844b-bqndc                                        4/4     Running   0          4d2h
kube-system                       kube-dns-55d79c844b-s5zjl                                        4/4     Running   0          4d2h
kube-system                       kube-dns-autoscaler-9f89698b6-4sqp8                              1/1     Running   0          4d2h
kube-system                       kube-proxy-gke-krmapihost-test-krmapihost-test-p-0d142341-cdqp   1/1     Running   0          4d2h
kube-system                       kube-proxy-gke-krmapihost-test-krmapihost-test-p-3f89df88-x0x7   1/1     Running   0          4d2h
kube-system                       kube-proxy-gke-krmapihost-test-krmapihost-test-p-62a75b97-q2kr   1/1     Running   0          4d2h
kube-system                       l7-default-backend-6dc845c45d-vmwcv                              1/1     Running   0          4d2h
kube-system                       metrics-server-v0.5.2-6bf845b67f-g7924                           2/2     Running   0          4d2h
kube-system                       netd-mg7s6                                                       1/1     Running   0          4d2h
kube-system                       netd-mrzhb                                                       1/1     Running   0          4d2h
kube-system                       netd-qjwpg                                                       1/1     Running   0          4d2h
kube-system                       pdcsi-node-68xtw                                                 2/2     Running   0          4d2h
kube-system                       pdcsi-node-bg9x7                                                 2/2     Running   0          4d2h
kube-system                       pdcsi-node-plm9h                                                 2/2     Running   0          4d2h
resource-group-system             resource-group-controller-manager-7f89854f9-q2crg                2/2     Running   0          4d2h

Config Connector では Workload Identity を使用しますが、大方の設定は Config Controller がよしなにやってくれています。あとは、Config Connector が使用する Google Cloud の Service Account に Google Cloud リソースを管理できる権限を渡す必要があるので、ここでは roles/editor の role を設定します。実際に使用する際には必要最低限の権限を渡すように設定してください。

$ kubectl -n config-control get ConfigConnectorContext -o jsonpath='{.items[0].spec.googleServiceAccount}'
service-<PROJECT_NUMBER>@gcp-sa-yakima.iam.gserviceaccount.com
$ gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member "serviceAccount:service-<PROJECT_NUMBER>@gcp-sa-yakima.iam.gserviceaccount.com" \
    --role "roles/editor" \
    --project "<PROJECT_ID>"

Policy Controller でポリシーの作成

Config Controller の用意ができたので、ここからは Cloud SQL のインスタンスの location を日本リージョンのみに制限するポリシーを作成します。

まずは、ConstraintTemplate を用意します。この ConstraintTemplate は locations というプロパティを持ち、こちらには Cloud SQL インスタンスの実行を許可する location を渡します。実際のインスタンスの location が locations に含まれていない場合に違反とみなすように Rego を記述します。

template.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: cloudsqllocation
spec:
  crd:
    spec:
      names:
        kind: CloudSQLLocation
      validation:
        openAPIV3Schema:
          type: object
          properties:
            locations:
              description: A list of locations where Cloud SQL instances are allowed to run.
              items:
                type: string
              type: array
  targets:
  - rego: |
      package cloudsqllocation

      allowedLocation(reviewLocation) {
        locations := input.parameters.locations
        satisfied := [good |
          location = locations[_]
          good = lower(location) == lower(reviewLocation)
        ]

        any(satisfied)
      }

      violation[{"msg": msg}] {
        instanceName := input.review.object.metadata.name
        instanceLocation := input.review.object.spec.region
        not allowedLocation(instanceLocation)
        msg := sprintf("Cloud SQL instance <%v> runs in a disallowed location <%v>, allowed locations are %v", [instanceName, instanceLocation, input.parameters.locations])
      } 
    target: admission.k8s.gatekeeper.sh
$ kubectl apply -f template.yaml

そして、日本リージョンのみを許可するように Constraint を作成します。

constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: CloudSQLLocation
metadata:
  name: only-japan
spec:
  match:
    kinds:
    - apiGroups: ["sql.cnrm.cloud.google.com"]
      kinds: ["SQLInstance"]
  parameters:
    locations: ["asia-northeast1", "asia-northeast2"]
$ kubectl apply -f constraint.yaml

Config Connector で Cloud SQL インスタンスの作成

ポリシーを作成できたので、ここからは Config Connector で Cloud SQL インスタンスを作成していきます。

config-control ネームスペースに SQLInstance を作成すると、Config Connector が実際に Cloud SQL のインスタンスを作成してくれます。まずは試しに、us-central1 で作成してみます。

cloudsql.yaml
apiVersion: sql.cnrm.cloud.google.com/v1beta1
kind: SQLInstance
metadata:
  name: sample-mysql
  namespace: config-control
spec:
  databaseVersion: MYSQL_8_0
  region: us-central1
  settings:
    tier: db-f1-micro
$ kubectl apply -f cloudsql.yaml
Error from server (Forbidden): error when creating "cloudsql.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [only-japan] Cloud SQL instance <sample-mysql> runs in a disallowed location <us-central1>, allowed locations are ["asia-northeast1", "asia-northeast2"]

すると、us-central1 は許可されていない location だと怒られてしまったので、spec.regionasia-northeast2 に変更します。

cloudsql.yaml
apiVersion: sql.cnrm.cloud.google.com/v1beta1
kind: SQLInstance
metadata:
  name: sample-mysql
  namespace: config-control
spec:
  databaseVersion: MYSQL_8_0
  region: asia-northeast2
  settings:
    tier: db-f1-micro
$ kubectl apply -f cloudsql.yaml
sqlinstance.sql.cnrm.cloud.google.com/sample-mysql created

今度は正常に作成できました。

しばらくすると、インスタンスが立ち上がります。

$ gcloud sql instances list
AME   DATABASE_VERSION  LOCATION           TIER         PRIMARY_ADDRESS PRIVATE_ADDRESS  STATUS
mysql  MYSQL_8_0        asia-northeast2-b  db-f1-micro  <PUBLIC_IP>     -                RUNNABLE

以上が Policy Controller と Config Connector の使用例となります。
Config Sync については割愛してしまったので、気になる方は公式のチュートリアルをご覧ください。

まとめ

  • Config Controller は Config Connector と Config Sync と Policy Controller をまとめたサービスです
  • Config Connector を使うと Kubernetes 経由で Google Cloud のリソースを管理できます
  • Config Sync を使うと Git や OCI、Helm リポジトリと同期できます
  • Policy Controller を使うとポリシーを設定して、ポリシーに違反するリクエストを監査したり、ブロックしたりすることができます
  • Policy Controller で Config Connector のポリシーを用意しておき、Google Cloud リソースの管理を Config Connector に寄せることで、ポリシー制御を行いながら Google Cloud のリソースを管理することができます
  • Google Cloud リソースのポリシーは Organization policies でも設定できるので、まずはそちらを試してみて、それだけでは要件が満たせなければ、Config Controller の使用を検討すると良いかと思います

Config Connector については、秋季インターンでインターン生がまとめてくれている記事があるので、ご興味あればぜひそちらもご覧ください。

https://sreake.com/blog/config-connectortest/

Discussion