使用`controller-runtime`进行服务器端应用(Server Side Apply)

太长不看

    • controller-runtime を利用して server-side apply してみたよ。

 

    • Unit Test で fake client を使ってたらテストできないよ!(envtestを使おう)

 

    client-go で SSA したかったら別の記事を読んでね!

环境

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
)

让我们 SSA

假设我们想要将控制器附加的标签和用户使用 kubectl 附加的标签合并,以便为 Namespace 添加一个漂亮的标签。

使用 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 的命名空间。

那么,我们可以手动为它添加一个名为”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

可以看到字段所有者(管理者)已经添加了标签 manual ,成为了 kubectl。现在,让我们修改代码,尝试从代码中添加标签 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")}

顺便说一下,我还试着将 “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,因此无法编写测试。(尚未实际投入使用。)

广告
将在 10 秒后关闭
bannerAds