4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

controller-runtime で Server Side Apply

Last updated at Posted at 2020-09-18

TL;DR

  • controller-runtime を利用して server-side apply してみたよ。
  • Unit Test で fake client を使ってたらテストできないよ!(envtestを使おう)
  • client-go で SSA したかったら別の記事を読んでね!

環境

go.mod
module github.com/yuanying/test-controller-runtime

go 1.13

require (
        k8s.io/api v0.18.6
        k8s.io/apimachinery v0.18.6
        k8s.io/client-go v0.18.6
        sigs.k8s.io/controller-runtime v0.6.2
)

Let's SSA

例えば Namespace のラベルをいい感じに controller が付与したラベルとkubectlでユーザが付与したラベルをマージしたかったとする。

controller-runtime を使うと以下ようなコードで server-side apply を実現できる。

package main

import (
        "context"
        "os"

        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        _ "k8s.io/client-go/plugin/pkg/client/auth"
        ctrl "sigs.k8s.io/controller-runtime"
        "sigs.k8s.io/controller-runtime/pkg/client"
        "sigs.k8s.io/controller-runtime/pkg/client/config"
        "sigs.k8s.io/controller-runtime/pkg/log/zap"
)

func main() {
        ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
        log := ctrl.Log.WithName("test")
        cfg := config.GetConfigOrDie()
        ctx := context.Background()

        cli, err := client.New(cfg, client.Options{})
        if err != nil {
                log.Error(err, "Can't load client")
                os.Exit(1)
        }
        ns := &corev1.Namespace{
                TypeMeta: metav1.TypeMeta{
                        Kind:       "Namespace",
                        APIVersion: corev1.SchemeGroupVersion.String(),
                },
        }
        ns.Name = "new-test"
        ns.Labels = map[string]string{
                "by-ssa": "v1",
        }
        // client.ForceOwnerShip を指定することで、conflict が発生した場合強制的にownerを変更して上書きする。
        // 指定しない場合は conflict でエラーが発生する。
        opts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("test")}
        if err := cli.Patch(ctx, ns, client.Apply, opts...); err != nil {
                log.Error(err, "Couldn't patch")
                os.Exit(1)
        }
}

実行してできた Namespace を確認してみる。

test-controller-runtime on  master [?] at ☸️  fraction (minecraft)
✦ ➜ go run main.go --kubeconfig=~/.kube/config

test-controller-runtime on  master [?] at ☸️  fraction (minecraft)  took 3s
✦ ➜ k get ns new-test -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2020-09-17T23:14:52Z"
  labels:
    by-ssa: "v1"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:by-ssa: {}
    manager: test
    operation: Apply
    time: "2020-09-17T23:16:35Z"
  name: new-test
  resourceVersion: "254973205"
  selfLink: /api/v1/namespaces/new-test
  uid: 37a946e4-e345-4823-b784-6a23e67d5c38
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

by-ssa というラベルがついた Namespace ができてることが確認できる。

それでは、手動で manual というラベルをつけて、確認してみる。

test-controller-runtime on  master [?] at ☸️  fraction (minecraft)
➜ cat <<EOF | k apply -f - --server-side
➜ apiVersion: v1
kind: Namespace
metadata:
  labels:
    manual: v1
  name: new-test
➜ EOF
namespace/new-test serverside-applied

test-controller-runtime on  master [?] at ☸️  fraction (minecraft)
➜ k get ns new-test -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2020-09-17T23:29:50Z"
  labels:
    by-ssa: v1
    manual: v1
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:by-ssa: {}
    manager: test
    operation: Apply
    time: "2020-09-17T23:29:50Z"
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:manual: {}
    manager: kubectl
    operation: Apply
    time: "2020-09-17T23:31:13Z"
  name: new-test
  resourceVersion: "254979686"
  selfLink: /api/v1/namespaces/new-test
  uid: d721a8cf-3dfa-4c96-9466-897157b335dc
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

field owner (manager) が kubectl になったラベル manual が追加されているのがわかる。それではコードを変更して by-ssa2 というラベルをコードから追加してみよう。

test-controller-runtime on  master [!] at ☸️  fraction (minecraft)  took 16s
➜ git diff
diff --git a/main.go b/main.go
index 6ec4745..3bc494f 100644
--- a/main.go
+++ b/main.go
@@ -32,7 +32,8 @@ func main() {
        }
        ns.Name = "new-test"
        ns.Labels = map[string]string{
-               "by-ssa": "v1",
+               "by-ssa":  "v2",
+               "by-ssa2": "v1",
        }
        opts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("test")}
        // opts := []client.PatchOption{client.FieldOwner("test")}

ついでに by-ssa ラベルも "v2" という値に変更してみた。

test-controller-runtime on  master [!?] at ☸️  fraction (minecraft)  took 15s
➜ go run main.go --kubeconfig=~/.kube/config

test-controller-runtime on  master [!?] at ☸️  fraction (minecraft)  took 3s
➜ k get ns new-test -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2020-09-17T23:29:50Z"
  labels:
    by-ssa: v2
    by-ssa2: v1
    manual: v1
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:manual: {}
    manager: kubectl
    operation: Apply
    time: "2020-09-17T23:31:13Z"
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:by-ssa: {}
          f:by-ssa2: {}
    manager: test
    operation: Apply
    time: "2020-09-17T23:32:43Z"
  name: new-test
  resourceVersion: "254980338"
  selfLink: /api/v1/namespaces/new-test
  uid: d721a8cf-3dfa-4c96-9466-897157b335dc
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

by-ssa ラベルの値が変更され、by-ssa2 ラベルが追加された。また、manual ラベルが保持されていることがわかる。

もちろん、ラベルを削除することもできる。以下のようにコード変更してby-ssa2ラベルのみ削除する。

test-controller-runtime on  master [!?] at ☸️  fraction (minecraft)  took 5s
➜ git diff
diff --git a/main.go b/main.go
index 3bc494f..ba46ec8 100644
--- a/main.go
+++ b/main.go
@@ -32,8 +32,7 @@ func main() {
        }
        ns.Name = "new-test"
        ns.Labels = map[string]string{
-               "by-ssa":  "v2",
-               "by-ssa2": "v1",
+               "by-ssa": "v2",
        }
        opts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("test")}
        // opts := []client.PatchOption{client.FieldOwner("test")}

実行して確認すると、by-ssa2ラベルのみが削除されていることが確認できる。

test-controller-runtime on  master [!?] at ☸️  fraction (minecraft)
➜ go run main.go --kubeconfig=~/.kube/config

test-controller-runtime on  master [!?] at ☸️  fraction (minecraft)  took 3s
➜ k get ns new-test -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2020-09-17T23:29:50Z"
  labels:
    by-ssa: v2
    manual: v1
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:manual: {}
    manager: kubectl
    operation: Apply
    time: "2020-09-17T23:31:13Z"
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:by-ssa: {}
    manager: test
    operation: Apply
    time: "2020-09-17T23:35:41Z"
  name: new-test
  resourceVersion: "254981630"
  selfLink: /api/v1/namespaces/new-test
  uid: d721a8cf-3dfa-4c96-9466-897157b335dc
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

コード側でラベルを付与している際に、動的にラベルの付与、削除を行おうとすると、どのラベルがコード側から付与して、どのラベルがユーザが付与したのか判断つかず、ラベルの削除 をコード側からできないという問題が、これで簡単に解決できる。

ただ、問題は、、利用しているプロジェクトにもよるが、もし、単体テストに fakeclient を利用していた場合、fakeclient が ApplyPatchType に対応していないためテストが書けない。(実戦投入していません。)

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?