使用`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,因此无法编写测试。(尚未实际投入使用。)