Featured image of post Kubernetesのsample-controllerをDeploymentとして動かす

Kubernetesのsample-controllerをDeploymentとして動かす

カスタムコントローラーをkindで作ったクラスタの中で動かした

やったことのまとめ

つかうもの

やったこと

コードの修正とimageのビルド

前回sample-controllerを動かしたときはコントローラー自体はクラスタ外で動かし,
Kubernetes APIへの接続にはローカルのkubeconfigを使用していた.

今回はコントローラー自体もPodとしてクラスタ内で動かすため,
Kubernetes APIに接続するための情報をPodに持たせる必要がある.

ここで使えるのがclient-gorest.InClusterConfig().
PodにマウントされたServiceAccountのトークンを使ってKubernetes APIへ接続する設定を作ってくれる1.

kubeconfigを使わないようにしたmain.go

rest.InClusterConfig()を使うようにコードを書き換えた状態でDocker imageをビルドする.

DockerfileGo用のものをいい感じに書いてみた.

# 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 imagekindで作ったクラスタで使えるように設定する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)にこのコントローラーで操作するDeploymentFoo(カスタムリソース)の一覧を取得する権限(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の作成

というわけで次はServiceAccountDeployemntFooのリソースを操作するための権限(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:
  - '*'

最後に, 作成したClusterRoleServiceAccountに紐付けるための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)にクラスタ内のDeploymentFoo(カスタムリソース)を操作する権限が与えられた.

再度動かしてみる

以上でコントローラーを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-controllerDocker image化して, Deploymentとして動かすことができた.

途中権限まわりでちょっとつまづいたけどなんとか乗り切れたので謎の達成感がある. うれしい.

次は自分でカスタムコントローラーを書いてみたいけど, だいぶめんどくさい…

おまけ

しあわせなねこ