SREの徳田です。
今回は先日リリースされたConfig Syncについて、内部のオブジェクトや挙動などについて調査してみました。
はじめに
Config Syncという機能がリリースされましたね。
Config Syncはざっくり言うとKubernetesクラスタにポリシー系統の設定をGitリポジトリと同期してくれるツールです。
要はポリシー用GitOpsツールというところでしょうか。一味違うのは、
- 抽象名前空間ディレクトリという考えがあり、設定を継承する仕組みがある
- ClusterSelectorやNamespaceSelectorという特定の条件によって設定を適用させる仕組みがある
などがあります。
詳しくはこの辺りを見てください。
Config Syncの概要
Config Syncのコンセプト
Config Syncのインストール
これらに目を通すなり実際に動かしてみると雰囲気はわかるかと思います。
注意:
この記事はTutorialなどをやり、Config Syncについての一定の知識・理解がある人向けです。
また、この記事での内容・挙動はDocumentationされている内容では ない ため、変更される可能性があります。
出てくる疑問
自分が上の文献を見つつ色々やってみたところ、いくつか疑問が出てきました。
- どのリソースがGitリポジトリからの同期対象なのか
- どのリソースが継承される対象なのか
- 内部でどのようなリソースが作成・管理されているのか
これらについていくつか検証・調査してみることにしました。
Config Syncインストール直後
まず、Config Syncをインストールした際にManifestを適用していますが、何を適用しているか見てみましょう。
$ grep '^kind' config-sync-operator.yaml
kind: CustomResourceDefinition
kind: ClusterRole
kind: ClusterRoleBinding
kind: ServiceAccount
kind: Deployment
kind: Namespace
これを見てざっとわかるのは
- ConfigManagementというCRD
- CRDで定義したリソースを処理するDeployment
- CRDのリソースに対して操作するためのClusterRole/ClusterRoleBinding/ServiceAccount
- Config Syncのコンポーネントが動作するNamespace
というところでしょうか。
実のところはConfig Syncを導入するためのOperatorです。
これ以上気になる方はインストール時のManifestを眺めてみてください。
ConfigManagementリソースを適用した後
さて、Tutorialに沿ってGitのリポジトリをForkしてConfigManagementリソースを作成すると、インストール時に作成したNamespaceに色々作成されます。
$ kubectl get all -n config-management-system
NAME READY STATUS RESTARTS AGE
pod/git-importer-5c9dd78497-2t676 2/2 Running 0 103s
pod/monitor-c46b96d69-f6c7d 1/1 Running 0 103s
pod/syncer-78fdcbbfbc-zzw8c 1/1 Running 0 103s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/git-importer ClusterIP 10.0.36.112 <none> 8675/TCP 103s
service/monitor ClusterIP 10.0.35.179 <none> 8675/TCP 103s
service/syncer ClusterIP 10.0.40.180 <none> 8675/TCP 102s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/git-importer 1/1 1 1 103s
deployment.apps/monitor 1/1 1 1 103s
deployment.apps/syncer 1/1 1 1 103s
NAME DESIRED CURRENT READY AGE
replicaset.apps/git-importer-5c9dd78497 1 1 1 103s
replicaset.apps/monitor-c46b96d69 1 1 1 103s
replicaset.apps/syncer-78fdcbbfbc 1 1 1 103s
パッと見て、
- git-importer: Gitリポジトリを取り込む
- monitor: リソースの変更をモニタする
- syncer: Configなどの同期をする
という感じでしょうか。
さて、ここでCRDを見てみます。
$ kubectl get crd
NAME CREATED AT
backendconfigs.cloud.google.com 2020-03-18T11:47:54Z
clusterconfigs.configmanagement.gke.io 2020-03-18T12:21:51Z
clusters.clusterregistry.k8s.io 2020-03-18T12:21:51Z
clusterselectors.configmanagement.gke.io 2020-03-18T12:21:51Z
configmanagements.configmanagement.gke.io 2020-03-18T12:09:40Z
fulfillmentcenters.foo-corp.com 2020-03-18T12:22:13Z
hierarchyconfigs.configmanagement.gke.io 2020-03-18T12:21:51Z
managedcertificates.networking.gke.io 2020-03-18T11:47:21Z
namespaceconfigs.configmanagement.gke.io 2020-03-18T12:21:51Z
namespaceselectors.configmanagement.gke.io 2020-03-18T12:21:52Z
repos.configmanagement.gke.io 2020-03-18T12:21:52Z
scalingpolicies.scalingpolicy.kope.io 2020-03-18T11:47:22Z
storagestates.migration.k8s.io 2020-03-18T11:47:20Z
storageversionmigrations.migration.k8s.io 2020-03-18T11:47:19Z
syncs.configmanagement.gke.io 2020-03-18T12:21:52Z
updateinfos.nodemanagement.gke.io 2020-03-18T11:47:21Z
結構色々と追加されてみます。ConfigManagementリソースを作成した後に作成されたCRDだけに絞ってみると以下の8個のCRDになります。
(実際は9個なのですが、そのうち1つは同期対象のリポジトリに定義されているCRDになります)
- clusterconfigs.configmanagement.gke.io
- clusters.clusterregistry.k8s.io
- clusterselectors.configmanagement.gke.io
- hierarchyconfigs.configmanagement.gke.io
- namespaceconfigs.configmanagement.gke.io
- namespaceselectors.configmanagement.gke.io
- repos.configmanagement.gke.io
- syncs.configmanagement.gke.io
$ kubectl get crd -o jsonpath='{.items[*].spec.names.kind}' \
clusterconfigs.configmanagement.gke.io \
clusters.clusterregistry.k8s.io \
clusterselectors.configmanagement.gke.io \
hierarchyconfigs.configmanagement.gke.io \
namespaceconfigs.configmanagement.gke.io \
namespaceselectors.configmanagement.gke.io \
repos.configmanagement.gke.io \
syncs.configmanagement.gke.io
ClusterConfig Cluster ClusterSelector HierarchyConfig NamespaceConfig NamespaceSelector Repo Sync
ということで以下のオブジェクトが作成されたようです。
- ClusterConfig
- Cluster
- ClusterSelector
- HierarchyConfig
- NamespaceConfig
- NamespaceSelector
- Repo
- Sync
この内、
- Cluster
- ClusterSelector
- NamespaceSelector
の3つについてはドキュメントに説明がありますし、それ以上のことがないので調査から外します。
よって
- ClusterConfig
- HierarchyConfig
- NamespaceConfig
- Repo
- Sync
についてどういうものなのか見ていきましょう!
Repo
実はこのRepoオブジェクトはチュートリアルで使用したRepoのsystemフォルダに入っているリソースです。
一応クラスタに保存されているRepoを取得してみましょう。
$ kubectl get repo
NAME AGE
repo 37m
GitリポジトリのConfigの通り、一つだけリソースがあるようです。中を見てみます。
$ kubectl get repo/repo -o yaml
apiVersion: configmanagement.gke.io/v1
kind: Repo
metadata:
creationTimestamp: "2020-03-18T12:22:08Z"
generation: 1
name: repo
resourceVersion: "18097"
selfLink: /apis/configmanagement.gke.io/v1/repos/repo
uid: 263d20fa-64a2-4b8f-9279-69c4f47970dc
spec:
version: 1.0.0
status:
import:
lastUpdate: "2020-03-18T12:22:19Z"
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
source:
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
sync:
lastUpdate: "2020-03-18T13:00:03Z"
latestToken: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
完全に内部の状態を管理するファイルですねwspec
には追跡するべきブランチの情報、 status
には各コンポーネントの情報が入っています。
ClusterConfig
clusterconfigを取得してみましょう。
$ kubectl get clusterconfig
NAME AGE
config-management-cluster-config 28m
config-management-crd-cluster-config 28m
どうやらCRDとそれ以外のconfigがあるみたいですね。
yamlで出してみましょう。
(長いですがご容赦ください🙇♂️)
$ kubectl get clusterconfig
apiVersion: v1
items:
- apiVersion: configmanagement.gke.io/v1
kind: ClusterConfig
metadata:
creationTimestamp: "2020-03-18T12:22:13Z"
generation: 1
name: config-management-cluster-config
resourceVersion: "9252"
selfLink: /apis/configmanagement.gke.io/v1/clusterconfigs/config-management-cluster-config
uid: df96ee68-eff8-4c98-bfc7-ed6236562544
spec:
importTime: "2020-03-18T12:22:13Z"
resources:
- group: rbac.authorization.k8s.io
kind: ClusterRole
versions:
- objects:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: cluster/namespace-reader-clusterrole.yaml
creationTimestamp: null
name: namespace-reader
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- watch
- list
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: cluster/pod-creator-clusterrole.yaml
creationTimestamp: null
name: pod-creator
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- '*'
version: v1
- group: rbac.authorization.k8s.io
kind: ClusterRoleBinding
versions:
- objects:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: cluster/namespace-reader-clusterrolebinding.yaml
creationTimestamp: null
name: namespace-readers
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: namespace-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: cheryl@foo-corp.com
version: v1
- group: policy
kind: PodSecurityPolicy
versions:
- objects:
- apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: cluster/pod-security-policy.yaml
creationTimestamp: null
name: psp
spec:
fsGroup:
rule: RunAsAny
runAsUser:
rule: RunAsAny
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
volumes:
- '*'
version: v1beta1
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
status:
syncState: synced
syncTime: "2020-03-18T12:22:18Z"
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
- apiVersion: configmanagement.gke.io/v1
kind: ClusterConfig
metadata:
creationTimestamp: "2020-03-18T12:22:13Z"
generation: 1
name: config-management-crd-cluster-config
resourceVersion: "9205"
selfLink: /apis/configmanagement.gke.io/v1/clusterconfigs/config-management-crd-cluster-config
uid: f88f3f07-158f-42b1-8711-6f24caf02d39
spec:
importTime: "2020-03-18T12:22:13Z"
resources:
- group: apiextensions.k8s.io
kind: CustomResourceDefinition
versions:
- objects:
- apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: cluster/fulfillmentcenter-crd.yaml
creationTimestamp: null
name: fulfillmentcenters.foo-corp.com
spec:
group: foo-corp.com
names:
kind: FulfillmentCenter
plural: fulfillmentcenters
singular: fulfillmentcenter
scope: Namespaced
validation:
openAPIV3Schema:
properties:
spec:
properties:
address:
type: string
required:
- address
type: object
versions:
- name: v1
served: true
storage: false
- name: v2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: null
storedVersions: null
version: v1beta1
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
status:
syncState: synced
syncTime: "2020-03-18T12:22:13Z"
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
kind: List
metadata:
resourceVersion: ""
selfLink: ""
このリソースを見たところ、
- Cluster-wideなオブジェクト毎にリソースをまとめている
- おそらくこれはこのClusterに適用されるべきConfig群
- CRDだけ別のManifestとして管理されている
という感じですね。
これらのリソースが実際にClusterに適用されており、その際CRDから適用が行われているのでしょう。
気が利いてますね!!
このリソースはconfig-management-system内部で利用されるリソースであって、我々が編集する余地はなさそうですね。
NamespaceConfig
ClusterConfigというオブジェクトがあったところみると、これはそのNamespace版、というところでしょうか。
見てみましょう!
$ kubectl get namespaceconfig
NAME AGE
audit 40m
shipping-dev 40m
shipping-prod 40m
shipping-staging 40m
GitリポジトリにConfigとしておいてあるNamespaceたちが表示されていますね。
代表して色々SelectorなどでConfigが適用されていた shipping-prod
のリソースを見てみましょう。
(長いですがご容赦ください🙇♂️)
$ kubectl get namespaceconfig shipping-prod -o yaml
apiVersion: configmanagement.gke.io/v1
kind: NamespaceConfig
metadata:
annotations:
audit: "true"
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: namespaces/online/shipping-app-backend/shipping-prod/namespace.yaml
creationTimestamp: "2020-03-18T12:22:13Z"
generation: 1
labels:
env: prod
name: shipping-prod
resourceVersion: "9272"
selfLink: /apis/configmanagement.gke.io/v1/namespaceconfigs/shipping-prod
uid: 252a5203-02e6-41ca-9fba-0ae934e16491
spec:
deleteSyncedTime: null
importTime: "2020-03-18T12:22:13Z"
resources:
- group: foo-corp.com
kind: FulfillmentCenter
versions:
- objects:
- apiVersion: foo-corp.com/v1
kind: FulfillmentCenter
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: namespaces/online/shipping-app-backend/shipping-prod/fulfillmentcenter.yaml
name: production
namespace: shipping-prod
spec:
address: 100 Industry St.
version: v1
- group: rbac.authorization.k8s.io
kind: RoleBinding
versions:
- objects:
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/namespace-selector: sre-supported
configmanagement.gke.io/source-path: namespaces/sre-rolebinding.yaml
creationTimestamp: null
name: sre-admin
namespace: shipping-prod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: sre@foo-corp.com
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: namespaces/viewers-rolebinding.yaml
creationTimestamp: null
name: viewers
namespace: shipping-prod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:serviceaccounts:audit
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: namespaces/online/shipping-app-backend/pod-creator-rolebinding.yaml
creationTimestamp: null
name: pod-creators
namespace: shipping-prod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: pod-creator
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: bob@foo-corp.com
version: v1
- kind: ResourceQuota
versions:
- objects:
- apiVersion: v1
kind: ResourceQuota
metadata:
annotations:
configmanagement.gke.io/cluster-name: my-cluster
configmanagement.gke.io/source-path: namespaces/online/shipping-app-backend/quota.yaml
creationTimestamp: null
name: backend-quota
namespace: shipping-prod
spec:
hard:
cpu: "1"
memory: 1Gi
pods: "3"
status: {}
version: v1
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
status:
syncState: synced
syncTime: "2020-03-18T12:22:18Z"
token: 0f8738755c64402c09a8f5e1936bd6f7c96ef536
ClusterConfigとデータの持ち方はほぼ同じで、 shipping-prod
のNamespaceに適用されていたリソース群がありますね!
また、NamespaceSelectorを使った適用をしていた sre-admin
というRoleBindingがあることも確認できます。
このことからNamespaceConfigはNamespaceに適用するリソース群が保持されているリソースということがわかりました。
ClusterConfig同様、内部で利用されるリソースみたいですね!
HierarchyConfig
こちらのオブジェクト、実は1つも作成されていないし、CRDを見てもスキーマが定義されていないため、クラスタのリソースを見ただけでは全くなにかわかりません。
しかし、こちらのドキュメントに簡単にですが例が解説されています。
Manifestを引用します。
# system/hierarchy-config.yaml
kind: HierarchyConfig
apiVersion: configmanagement.gke.io/v1
metadata:
name: rbac
spec:
resources:
# Configure Role to only be allowed in leaf namespaces.
- group: rbac.authorization.k8s.io
kinds: [ "RoleBinding" ]
hierarchyMode: none
どうやらHierarchyConfigは同期対象のリソースについての継承をするかしないかを設定できるようです。hierarchyMode
に none
を指定することで継承させないように設定できるようですね。
この hierarchyMode
に何が設定できるのだろうか?と思いましたが、リファレンスなども特にないため、 hoge
と入れて適用させてみたところ、 git-importer
の importer
コンテナでエラーが出ておりました。
[1] KNV1042: HierarchyMode "hoge" is not a valid value for the APIResource "RoleBinding.rbac.authorization.k8s.io". Allowed values are [none,inherit].
とのことで、 hierarchyMode
には none
/ inherit
が設定できるようですね。
まとめると、HierarchyConfigオブジェクトでは指定のオブジェクトの継承について設定することができ、Gitリポジトリの system/
ディレクトリに配置して設定する、ということですね。
Sync
最後にSyncです。
とりあえず取得してみましょう。
$ kubectl get sync
NAME AGE
clusterrole.rbac.authorization.k8s.io 90m
clusterrolebinding.rbac.authorization.k8s.io 90m
customresourcedefinition.apiextensions.k8s.io 90m
fulfillmentcenter.foo-corp.com 90m
podsecuritypolicy.policy 90m
resourcequota 90m
role.rbac.authorization.k8s.io 90m
rolebinding.rbac.authorization.k8s.io 90m
どうやらポリシー系統のオブジェクトとCRDに関する名前のリソースがあります。
一つピックアップして見てみましょう。
$ kubectl get sync resourcequota -o yaml
apiVersion: configmanagement.gke.io/v1
kind: Sync
metadata:
creationTimestamp: "2020-03-18T12:22:13Z"
finalizers:
- syncer.configmanagement.gke.io
generation: 1
name: resourcequota
resourceVersion: "9227"
selfLink: /apis/configmanagement.gke.io/v1/syncs/resourcequota
uid: 37706879-d091-4b6e-b40c-2987c5f47ab9
spec:
group: ""
kind: ResourceQuota
status:
status: syncing
spec
を見る限り、APIオブジェクトを指定しているみたいです。また、 status
に同期しているかの状態が保存されています。
これから推測するに、オブジェクトの同期対象をこのリソースによって設定しているようですね。
検証してみる
長々リソースなどを見てきて、いじって面白そうなものは以下の2つでしょう。
- HierarchyConfig
- Sync
この2つがうまく設定できれば任意のリソースを継承させたりしつつクラスタに同期させることができます。
それでは、
Deploymentを抽象名前空間に配置し、継承されつつ各Namespaceに同期されるか
これを確認してみましょう。
とりあえずHierarchyConfigだけ作ってみてCommitしてみましょう。
以下のManifestを system/deployment.yaml
に保存します。
apiVersion: configmanagement.gke.io/v1
kind: HierarchyConfig
metadata:
name: deployment
spec:
resources:
- group: apps
kinds: [ "Deployment" ]
hierarchyMode: inherit
そしてnginxを動かすDeploymentを namespaces/online/shipping-app-backend/nginx.yaml
として保存します。
対象のNamespaceにはResourceQuotaが作成されるのでCPUなどのPodのリソースについての指定をします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
resources:
limits:
cpu: 100m
memory: 8Mi
それではCommitしてにPushしてみます。
こちらのリポジトリで確認できます。
するとどうでしょう・・・
$ kubectl get deploy --all-namespaces | grep nginx
shipping-dev nginx 1/1 1 1 6m16s
shipping-prod nginx 1/1 1 1 6m17s
shipping-staging nginx 1/1 1 1 6m16s
各NamespaceにDeploymentが継承されてる・・・!
ちゃんと同期されて動いています!!
ここでSyncはどうなっているでしょうか?確認してみましょう!
$ kubectl get sync
NAME AGE
clusterrole.rbac.authorization.k8s.io 114m
clusterrolebinding.rbac.authorization.k8s.io 114m
customresourcedefinition.apiextensions.k8s.io 114m
deployment.apps 7m43s
fulfillmentcenter.foo-corp.com 114m
podsecuritypolicy.policy 114m
resourcequota 114m
role.rbac.authorization.k8s.io 114m
rolebinding.rbac.authorization.k8s.io 114m
deployment.apps
が追加されています!
どうやらHierarchyConfigの設定をもとに内部でSyncリソースを作成してくれているみたいです。
これによって任意のリソースを継承の設定をさせつつ同期させることができますね!!
最後に
Config Syncで使用されているオブジェクトについて確認し、そこから簡単な検証を行いました。
Config SyncにおいてHierarchyConfigが全てな感じがありますね!
ただ、何でもかんでもConfig Syncで同期させるのはあまり良くなさそうだと思っており、ポリシー系のオブジェクトのみにとどめておくべきだなと感じました。
通常のアプリケーションなどで利用するオブジェクトは継承の仕組みがあまり必要ないため、と個人的に思っています。それ以外の観点では基本Config Syncのコンセプトで解説されていますのでそちらをどうぞw
しかし、ポリシー系の拡張されたリソースなどはあるので、それらをConfig Syncのシステムで管理するのはとても良さそうだと思っています!
さておき、通常のアプリケーションはどう管理すればいいんだ!となるところですが、これまた最近Application Managerというアドオンがリリースされたようで、そちらで管理するのが良さそうです。
こちらのアドオンも近いうち試してみたいと思っています!
それでは!!🙋♂️