やったことのまとめ
- sample-controllerを
Deployment
としてkindのクラスタ内で動かした
つかうもの
- macOS Big Sur 11.2.3
- Docker Desktop for Mac
- Version 3.4.0
- Docker Engine Version 20.10.7
- Docker Compose Version 1.29.2
- kind
- v0.11.0 go1.16.4 darwin/amd64
- Kubernetes
- v1.21.1
やったこと
コードの修正とimageのビルド
前回sample-controllerを動かしたときはコントローラー自体はクラスタ外で動かし,Kubernetes API
への接続にはローカルのkubeconfig
を使用していた.
今回はコントローラー自体もPod
としてクラスタ内で動かすため,Kubernetes API
に接続するための情報をPod
に持たせる必要がある.
ここで使えるのがclient-go
のrest.InClusterConfig()
.Pod
にマウントされたServiceAccount
のトークンを使ってKubernetes API
へ接続する設定を作ってくれる1.
rest.InClusterConfig()
を使うようにコードを書き換えた状態でDocker image
をビルドする.
DockerfileはGo
用のものをいい感じに書いてみた.
# Docker imageのビルド
$ ls Dockerfile
Dockerfile
$ docker image build -t sample-controller:develop .
# Dockerでこのまま実行しても必要な情報が無いので怒られる
$ docker container run sample-controller:develop
panic: unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined
goroutine 1 [running]:
main.main()
/sample-controller/main.go:56 +0x685
とりあえず動きそう.
kindでローカルimageを使う設定とDeploymentの作成
次にローカルでビルドしたカスタムコントローラーのDocker image
をkind
で作ったクラスタで使えるように設定する2.
# CRDが作成されていない場合は作成しておく
$ kubectl apply -f artifacts/examples/crd.yaml
customresourcedefinition.apiextensions.k8s.io/foos.samplecontroller.k8s.io created
# ローカルDockerのimageをkindのクラスタで使えるようにする
$ kind load docker-image sample-controller:develop
Image: "sample-controller:develop" with ID "sha256:dde0eac9c3c272e80dc1e98c705c34ddf35b7d5c585fc32364563d2d96db386d" not yet present on node "kind-control-plane", loading...
このimage
を使ったDeployment
を作成する.
# Deploymentの作成
$ kubectl create deployment sample-controller --image=sample-controller:develop
deployment.apps/sample-controller created
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
sample-controller 1/1 1 1 2m11s
$ kubectl get pods -l app=sample-controller
NAME READY STATUS RESTARTS AGE
sample-controller-f48d54cf4-drqwl 1/1 Running 0 2m31s
# PodにデフォルトのServiceAccount(default:default)が設定されている
# Tokenなどの情報が/var/run/secrets/kubernetes.io/serviceaccountにマウントされている(rest.InClusterConfig()はこれを参照している)
$ kubectl get pod sample-controller-f48d54cf4-drqwl -o yaml
apiVersion: v1
kind: Pod
metadata:
...
spec:
containers:
- image: sample-controller:develop
...
name: sample-controller
...
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-vx85f
readOnly: true
...
serviceAccount: default
serviceAccountName: default
...
volumes:
- name: kube-api-access-vx85f
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
すると今度はDocker
で動かしたときと異なり,ServiceAccount
の情報が取得できるのでコントローラーが動いた!
…が, 次はPod
に自動で紐づくデフォルトのServiceAccount
(default:default)にこのコントローラーで操作するDeployment
とFoo(カスタムリソース)の一覧を取得する権限(list)がないためエラーになってしまった.
# 今度はconfigが作成できて動作を開始している
# ...が, DeploymentとFoo(カスタムリソース)を参照する権限がないためうまく動いていない
$ kubectl logs sample-controller-f48d54cf4-drqwl
I0707 09:01:00.813715 1 controller.go:115] Setting up event handlers
I0707 09:01:00.813806 1 controller.go:156] Starting Foo controller
I0707 09:01:00.813810 1 controller.go:159] Waiting for informer caches to sync
E0707 09:01:00.826284 1 reflector.go:138] pkg/mod/k8s.io/client-go@v0.0.0-20210701054555-843bb800b12a/tools/cache/reflector.go:167: Failed to watch *v1alpha1.Foo: failed to list *v1alpha1.Foo: foos.samplecontroller.k8s.io is forbidden: User "system:serviceaccount:default:default" cannot list resource "foos" in API group "samplecontroller.k8s.io" at the cluster scope
E0707 09:01:00.826385 1 reflector.go:138] pkg/mod/k8s.io/client-go@v0.0.0-20210701054555-843bb800b12a/tools/cache/reflector.go:167: Failed to watch *v1.Deployment: failed to list *v1.Deployment: deployments.apps is forbidden: User "system:serviceaccount:default:default" cannot list resource "deployments" in API group "apps" at the cluster scope
...(以下繰り返し)
# いったん消しておく
$ kubectl delete deployment sample-controller
deployment.apps "sample-controller" deleted
ClusterRoleとClusterRoleBindingの作成
というわけで次はServiceAccount
でDeployemnt
とFooのリソースを操作するための権限(ClusterRole
)を作成する.
今回は使用するsample-controllerが全namespace
を対象に操作を行うため3,namespace
レベルの権限のRole
ではなくクラスタレベルのClusterRole
を使用した.
# ClusterRoleの作成
# DeploymentとFoo(カスタムリソース)とEventに対する全ての操作を許可する権限
$ kubectl create clusterrole foo-control --verb="*" --resource=foo,deployment,event
clusterrole.rbac.authorization.k8s.io/foo-control created
$ kubectl get clusterrole foo-control -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: "2021-07-12T14:04:28Z"
name: foo-control
resourceVersion: "4126888"
uid: 9b628c44-0320-48eb-8861-0e3c5d8f9ddf
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- '*'
- apiGroups:
- apps
resources:
- deployments
verbs:
- '*'
- apiGroups:
- samplecontroller.k8s.io
resources:
- foos
verbs:
- '*'
最後に, 作成したClusterRole
をServiceAccount
に紐付けるためのClusterRoleBinding
を作成する.
# ClusterRoleBindingの作成
# ClusterRole(foo-control)をServiceAccount(default:default)に紐付ける
$ kubectl create clusterrolebinding foo-control-binding --clusterrole=foo-control --serviceaccount=default:default
clusterrolebinding.rbac.authorization.k8s.io/foo-control-binding created
$ kubectl get clusterrolebinding foo-control-binding -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: "2021-07-12T14:06:04Z"
name: foo-control-binding
resourceVersion: "4127049"
uid: 09bde8fd-752a-433b-845a-5584aa1324a8
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: foo-control
subjects:
- kind: ServiceAccount
name: default
namespace: default
これでPod
にデフォルトで紐づくServiceAccount
(default:default)にクラスタ内のDeployment
とFoo(カスタムリソース)を操作する権限が与えられた.
再度動かしてみる
以上でコントローラーをDeployment
として動かすために必要な準備ができたはずなので,
もう一度挑戦してみる.
# Deploymentの作成
$ kubectl create deployment sample-controller --image=sample-controller:develop
deployment.apps/sample-controller created
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
sample-controller 1/1 1 1 8s
$ kubectl get pods -l app=sample-controller
NAME READY STATUS RESTARTS AGE
sample-controller-f48d54cf4-jgv4w 1/1 Running 0 12s
# 動いた...!
$ kubectl logs sample-controller-f48d54cf4-jgv4w
I0712 14:07:14.403608 1 controller.go:115] Setting up event handlers
I0712 14:07:14.404169 1 controller.go:156] Starting Foo controller
I0712 14:07:14.404319 1 controller.go:159] Waiting for informer caches to sync
I0712 14:07:14.705485 1 controller.go:164] Starting workers
I0712 14:07:14.706297 1 controller.go:170] Started workers
動いた…!Pod
のログの内容も前回と同じで, 問題なく動いているように見える.
最後にFooリソースを作成してみる.
# Fooを作成
$ cat << EOF | kubectl apply -f -
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
name: my-foo
spec:
deploymentName: my-foo
replicas: 10
EOF
foo.samplecontroller.k8s.io/my-foo created
# Fooとそれによって管理されるDeploymentが作成されている
$ kubectl get foo
NAME AGE
my-foo 40s
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
my-foo 10/10 10 10 27s
sample-controller 1/1 1 1 5m15s
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
my-foo-65f55f8578-2j2g6 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-44tbj 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-5n58m 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-bdtwp 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-gq7hh 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-jb258 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-kr6mt 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-mmtlk 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-pk847 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
my-foo-65f55f8578-vpvf6 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578
sample-controller-f48d54cf4-jgv4w 1/1 Running 0 5m35s app=sample-controller,pod-template-hash=f48d54cf4
# Fooで管理されているのでDeploymentを手で消してもすぐ復活する
$ kubectl delete deployment my-foo
deployment.apps "my-foo" deleted
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
my-foo 0/10 10 0 17s
sample-controller 1/1 1 1 6m58s
# Statusも正常に更新されている
$ kubectl get foo my-foo -o yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"samplecontroller.k8s.io/v1alpha1","kind":"Foo","metadata":{"annotations":{},"name":"my-foo","namespace":"default"},"spec":{"deploymentName":"my-foo","replicas":10}}
creationTimestamp: "2021-07-12T14:11:58Z"
generation: 23
name: my-foo
namespace: default
resourceVersion: "4128510"
uid: a38bc894-769c-4dd0-9d2b-8b3be3ec864b
spec:
deploymentName: my-foo
replicas: 10
status:
availableReplicas: 10
# ログを見る限り正常にEventも吐かれている
$ kubectl logs -f sample-controller-f48d54cf4-jgv4w
I0712 14:07:14.403608 1 controller.go:115] Setting up event handlers
I0712 14:07:14.404169 1 controller.go:156] Starting Foo controller
I0712 14:07:14.404319 1 controller.go:159] Waiting for informer caches to sync
I0712 14:07:14.705485 1 controller.go:164] Starting workers
I0712 14:07:14.706297 1 controller.go:170] Started workers
I0712 14:11:58.920953 1 controller.go:228] Successfully synced 'default/my-foo'
I0712 14:11:58.948805 1 event.go:291] "Event occurred" object="default/my-foo" kind="Foo" apiVersion="samplecontroller.k8s.io/v1alpha1" type="Normal" reason="Synced" message="Foo synced successfully"
...(以下繰り返し)
Deployment
として動かしたコントローラーでもちゃんとカスタムリソース(Foo)の管理ができた.
やったぜ.
おわり
sample-controllerをDocker image
化して, Deployment
として動かすことができた.
途中権限まわりでちょっとつまづいたけどなんとか乗り切れたので謎の達成感がある. うれしい.
次は自分でカスタムコントローラーを書いてみたいけど, だいぶめんどくさい…
おまけ
https://github.com/kubernetes/client-go/tree/master/examples/in-cluster-client-configuration ↩︎
https://kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster ↩︎
https://github.com/kubernetes/sample-controller/blob/b8d9e8c247129e53962d0dcfc08a4e8b47477318/pkg/generated/informers/externalversions/factory.go#L91-L108 ↩︎