TL;DR
-
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
)
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 に対応していないためテストが書けない。(実戦投入していません。)