摘要:所以使用記住要把原始的數據取出來和你要新增的數據合并后再提交,如單元測試如各位還有其他更好的方式,歡迎交流補充。
使用Patch方式更新K8S的 API Objects 一共有三種方式:strategic merge patch, json-patch,json merge patch。關于這三種方式的文字描述區別可看官方文檔update-api-object-kubectl-patch。
我在本文中主要會介紹使用client-go的Patch方式,主要包括strategic merge patch和json-patch。不介紹json merge patch的原因,是該方式使用場景比較少,因此不做介紹,如果有同學有興趣,可做補充。
StrategicMergePatch 新增Object值本次示例以給一個node新增一個labels為例,直接上代碼:
//根據Pod Sn 更新 pod func UpdatePodByPodSn(coreV1 v1.CoreV1Interface, podSn string, patchData map[string]interface{}) (*apiv1.Pod, error) { v1Pod, err := coreV1.Pods("").Get(podSn, metav1.GetOptions{}) if err != nil { logs.Error("[UpdatePodByPodSn] get pod %v fail %v", podSn, err) return nil, fmt.Errorf("[UpdatePodByPodSn] get pod %v fail %v", podSn, err) } namespace := v1Pod.Namespace podName := v1Pod.Name playLoadBytes, _ := json.Marshal(patchData) newV1Pod, err := coreV1.Pods(namespace).Patch(podName, types.StrategicMergePatchType, playLoadBytes) if err != nil { logs.Error("[UpdatePodByPodSn] %v pod Patch fail %v", podName, err) return nil, fmt.Errorf("[UpdatePodByPodSn] %v pod Patch fail %v", podName, err) } return newV1Pod, nil }
注意:上面的PatchData 必須是以 {"metadata":...}的go struct, 如:`map[string]interface{}{"metadata": map[string]map[string]string{"labels": {
"test2": "test2", }}}`
對應單元測試用例
func pod(podName string, nodeName string, labels map[string]string, annotations map[string]string) *v1.Pod { return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: podName, Labels: labels, Annotations: annotations}, Spec: v1.PodSpec{NodeName: nodeName}, Status: v1.PodStatus{}} } func TestUpdatePodByPodSn(t *testing.T) { var tests = []struct { expectedError interface{} expectedAnnotation string expectedLabel string podSn string patchData map[string]interface{} v1Pod []runtime.Object }{ {nil, "test2", "", "1.1.1.1", map[string]interface{}{"metadata": map[string]map[string]string{"annotations": { "test2": "test2", }}}, []runtime.Object{pod("1.1.1.1", "1.1.1.1", map[string]string{"test1": "test1"}, map[string]string{"test1": "test1"})}, }, {nil, "", "", "1.1.1.2", map[string]interface{}{"metadata": map[string]map[string]string{"labels": { "test2": "", }}}, []runtime.Object{pod("1.1.1.2", "1.1.1.1", map[string]string{"test1": "test1"}, map[string]string{"test1": "test1"})}, }, {nil, "", "test2", "1.1.1.3", map[string]interface{}{"metadata": map[string]map[string]string{"labels": { "test2": "test2", }}}, []runtime.Object{pod("1.1.1.3", "1.1.1.1", map[string]string{"test1": "test1"}, map[string]string{"test1": "test1"})}, }, } for _, test := range tests { client := fake.NewSimpleClientset(test.v1Pod...) v1Pod, err := UpdatePodByPodSn(client.CoreV1(), test.podSn, test.patchData) if err != nil { t.Errorf("expected error %s, got %s", test.expectedError, err) } assert.Equal(t, v1Pod.Annotations["test2"], test.expectedAnnotation) assert.Equal(t, v1Pod.Labels["test2"], test.expectedLabel) } }修改Object的值
修改Obejct的值使用方式如下,當使用strategic merge patch的時候,如果提交的數據中鍵已經存在,那就會使用新提交的值替換原先的數據。依舊以修改labels的值為例。
如新提交的數據為:
{ "metadata":{ "labels":{ "test2":"test3", }, } }
Node中已經存在的labels為:
{ "metadata":{ "labels":{ "test2":"test1", }, } }
最終Node中labels的key為test2的值會被替換成 test3。
刪除Object值當需要把某個Object的值刪除的時候,當使用strategic merge patch的時候,依舊是刪除labels為例提交方式是:
golang里面的表現形式是:
{ "metadata":{ "labels":{ "test2":nil }, } }
對應從瀏覽器提交的數據是:
{ "metadata":{ "labels":{ "test2":null }, } }
PS:如果不喜歡使用上面struct的方式組成數據,可以使用如下的方式 labelsPatch := fmt.Sprintf({"metadata":{"labels":{"%s":"%s"}}}, labelkey, labelvalue) 直接代替上面示例中的patchDataJSONPatch
JSONPatch的詳細說明請參考文檔:http://jsonpatch.com/。
JSONPatch 主要有三種操作方式:add,replace,remove。以下會以代碼示例說明這三種操作在Client-go對應的代碼示例來說明怎樣操作K8s 的資源。
使用JSONPatch,如果Patch中帶有斜杠“/”和 (~)這兩個字符,不能直接傳入這兩個字符,需要你輸入的時候就人工轉換下,/轉換成~1,~轉換成~0。以新增labels為例,如我要新增一個"test1/test2":"test3"的labels,可以把要傳入的數據修改為"test1~1test2":"test3"即可。Op:add
使用JSONPatch的方式新增一個標簽,其提交的數據格式必須是[{ "op": "replace", "path": "/baz", "value": "boo" }] 這樣的。代碼如下:
//patchStringValue specifies a patch operation for a string. type PatchStringValue struct { Op string `json:"op"` Path string `json:"path"` Value interface{} `json:"value"` } type PatchNodeParam struct { coreV1 v1.CoreV1Interface NodeSn string `json:"nodeSn"` OperatorType string `json:"operator_type"` OperatorPath string `json:"operator_path"` OperatorData map[string]interface{} `json:"operator_data"` } //patch node info, example label, annotation func patchNode(param PatchNodeParam) (*apiv1.Node, error) { coreV1 := param.coreV1 nodeSn := param.NodeSn node, err := coreV1.Nodes().Get(nodeSn, metav1.GetOptions{}) if err != nil { return nil, err } operatorData := param.OperatorData operatorType := param.OperatorType operatorPath := param.OperatorPath var payloads []interface{} for key, value := range operatorData { payload := PatchStringValue{ Op: operatorType, Path: operatorPath + key, Value: value, } payloads = append(payloads, payload) } payloadBytes, _ := json.Marshal(payloads) newNode, err := coreV1.Nodes().Patch(nodeSn, types.JSONPatchType, payloadBytes) if err != nil { return nil, err } return newNode, err }
單元測試:
func TestPatchNode(t *testing.T) { Convey("test patchNode", t, func() { Convey("Patch Node fail", func() { var tests = []struct { nodeSn string operatorType string operatorPath string operatorData map[string]interface{} expectedError interface{} expectedValue *v1.Node objs []runtime.Object }{ {"1.1.1.1", "add", "/metadata/labels/", map[string]interface{}{ "test1": "test1", "test2": "test2"}, "nodes "1.1.1.1" not found", nil, nil}, {"1.1.1.1", "aaa", "/metadata/labels/", map[string]interface{}{ "test1": "test1", "test2": "test2"}, "Unexpected kind: aaa", nil, []runtime.Object{node("1.1.1.1", nil, nil)}}, } for _, test := range tests { client := fake.NewSimpleClientset(test.objs...) param := PatchNodeParam{ coreV1: client.CoreV1(), NodeSn: test.nodeSn, OperatorType: test.operatorType, OperatorPath: test.operatorPath, OperatorData: test.operatorData, EmpId: test.empId, } output, err := patchNode(param) So(output, ShouldEqual, test.expectedValue) So(err.Error(), ShouldEqual, test.expectedError) } }) Convey("Patch Node success", func() { var tests = []struct { nodeSn string operatorType string operatorPath string operatorData map[string]interface{} expectedError interface{} expectedValue string objs []runtime.Object }{ {"1.1.1.1", "add", "/metadata/labels/", map[string]interface{}{ "test1": "test1", "test2": "test2"}, nil, "1.1.1.1", []runtime.Object{node("1.1.1.1", map[string]string{"test3": "test3"}, map[string]string{"test3": "test3"})}}, {"1.1.1.1", "add", "/metadata/labels/", map[string]interface{}{ "test1": "test1", "test2": "test2"}, nil, "1.1.1.1", []runtime.Object{node("1.1.1.1", map[string]string{"test1": "modifytest"}, map[string]string{"test1": "modifytest"})}}, } for _, test := range tests { client := fake.NewSimpleClientset(test.objs...) param := PatchNodeParam{ coreV1: client.CoreV1(), NodeSn: test.nodeSn, OperatorType: test.operatorType, OperatorPath: test.operatorPath, OperatorData: test.operatorData, } output, err := patchNode(param) So(output, ShouldNotBeNil) So(err, ShouldBeNil) So(output.Name, ShouldEqual, test.expectedValue) } }) }) }
使用add有個需要注意的地方就是,當你的Path是使用的/metadata/labels而不是/metadata/labels/labelkey的時候,那你這個add操作實際是對整個labels進行替換,而不是新增,一定要注意避免踩坑。Op:remove
PS:如果不喜歡使用上面struct的方式組成數據,可以使用如下的方式 labelsPatch := fmt.Sprintf([{"op":"add","path":"/metadata/labels/%s","value":"%s" }], labelkey, labelvalue) 直接代替上面示例中的patchData
要刪除一個標簽的話,代碼和增加區別不大,唯一的區別就是提交的數據要由鍵值對修改為提交一個string slice類型[]string,代碼如下:
type PatchNodeParam struct { coreV1 v1.CoreV1Interface NodeSn string `json:"nodeSn"` OperatorType string `json:"operator_type"` OperatorPath string `json:"operator_path"` OperatorData map[string]interface{} `json:"operator_data"` } //patchStringValue specifies a remove operation for a string. type RemoveStringValue struct { Op string `json:"op"` Path string `json:"path"` } //remove node info, example label, annotation func removeNodeInfo(param RemoveNodeInfoParam) (*apiv1.Node, error) { coreV1 := param.coreV1 nodeSn := param.NodeSn node, err := coreV1.Nodes().Get(nodeSn, metav1.GetOptions{}) if err != nil { return nil, err } operatorKey := param.OperatorKey operatorType := param.OperatorType operatorPath := param.OperatorPath var payloads []interface{} for key := range operatorKey { payload := RemoveStringValue{ Op: operatorType, Path: operatorPath + operatorKey[key], } payloads = append(payloads, payload) } payloadBytes, _ := json.Marshal(payloads) newNode, err := coreV1.Nodes().Patch(nodeSn, types.JSONPatchType, payloadBytes) if err != nil { return nil, err } return newNode, err }Op:replace
replace操作,會對整個的Object進行替換。所以使用replace記住要把原始的數據取出來和你要新增的數據合并后再提交,如:
type ReplaceNodeInfoParam struct { coreV1 v1.CoreV1Interface NodeSn string `json:"nodeSn"` OperatorType string `json:"operator_type"` OperatorPath string `json:"operator_path"` OperatorData map[string]interface{} `json:"operator_data"` DataType string `json:"data_type"` } //patchStringValue specifies a patch operation for a string. type PatchStringValue struct { Op string `json:"op"` Path string `json:"path"` Value interface{} `json:"value"` } func replaceNodeInfo(param ReplaceNodeInfoParam) (*apiv1.Node, error) { coreV1 := param.coreV1 nodeSn := param.NodeSn node, err := coreV1.Nodes().Get(nodeSn, metav1.GetOptions{}) if err != nil { return nil, err } var originOperatorData map[string]string dataType := param.DataType operatorData := param.OperatorData operatorType := param.OperatorType operatorPath := param.OperatorPath switch dataType { case "labels": originOperatorData = node.Labels case "annotations": originOperatorData = node.Annotations default: originOperatorData = nil } if originOperatorData == nil { return nil, fmt.Errorf("[replaceNodeInfo] fail, %v originOperatorData is nil", nodeSn) } for key, value := range originOperatorData { operatorData[key] = value } var payloads []interface{} payload := PatchStringValue{ Op: operatorType, Path: operatorPath, Value: operatorData, } payloads = append(payloads, payload) payloadBytes, _ := json.Marshal(payloads) newNode, err := coreV1.Nodes().Patch(nodeSn, types.JSONPatchType, payloadBytes) if err != nil { return nil, err } return newNode, err }
單元測試
func TestReplaceNodeInfo(t *testing.T) { Convey("test ReplaceNodeInfo", t, func() { Convey("Patch ReplaceNodeInfo fail", func() { var tests = []struct { nodeSn string operatorType string operatorPath string dataType string operatorData map[string]interface{} expectedError interface{} expectedValue *v1.Node objs []runtime.Object }{ {"1.1.1.1", "add", "/metadata/labels", "labels", map[string]interface{}{ "test1": "test1", "test2": "test2"}, "nodes "1.1.1.1" not found", nil, nil}, {"1.1.1.1", "aaa", "/metadata/annotations", "annotations", map[string]interface{}{ "test1": "test1", "test2": "test2"}, "[replaceNodeInfo] fail, 1.1.1.1 originOperatorData is nil", nil, []runtime.Object{node("1.1.1.1", nil, nil)}}, } for _, test := range tests { client := fake.NewSimpleClientset(test.objs...) param := ReplaceNodeInfoParam{ coreV1: client.CoreV1(), NodeSn: test.nodeSn, OperatorType: test.operatorType, OperatorPath: test.operatorPath, OperatorData: test.operatorData, DataType: test.dataType, } output, err := replaceNodeInfo(param) So(output, ShouldEqual, test.expectedValue) So(err.Error(), ShouldEqual, test.expectedError) } }) Convey("Patch Node success", func() { var tests = []struct { nodeSn string operatorType string operatorPath string dataType string operatorData map[string]interface{} expectedError interface{} expectedLabel string expectedAnnotation string objs []runtime.Object }{ {"1.1.1.1", "replace", "/metadata/labels", "labels", map[string]interface{}{ "test1": "test1", "test2": "test2"}, nil, "test3", "", []runtime.Object{node("1.1.1.1", map[string]string{"test3": "test3"}, map[string]string{"test3": "test3"})}}, {"1.1.1.1", "replace", "/metadata/annotations", "annotations", map[string]interface{}{ "test1": "test1", "test2": "test2"}, nil, "", "modifytest", []runtime.Object{node("1.1.1.1", map[string]string{"test1": "modifytest"}, map[string]string{"test1": "modifytest"})}}, } for _, test := range tests { client := fake.NewSimpleClientset(test.objs...) param := ReplaceNodeInfoParam{ coreV1: client.CoreV1(), NodeSn: test.nodeSn, OperatorType: test.operatorType, OperatorPath: test.operatorPath, OperatorData: test.operatorData, DataType: test.dataType, } output, err := replaceNodeInfo(param) So(output, ShouldNotBeNil) So(err, ShouldBeNil) So(output.Labels["test3"], ShouldEqual, test.expectedLabel) So(output.Annotations["test1"], ShouldEqual, test.expectedAnnotation) } }) }) }
PS:如各位還有其他更好的方式,歡迎交流補充。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/32987.html
摘要:生態周報內容主要包含我所接觸到的生態相關的每周值得推薦的一些信息。歡迎訂閱知乎專欄生態。版本發布這是修改發布周期后,第二個發布的版本,上個版本是。從發布周期來看,由原先的季度發布改成半年發布,也意味著的日漸成熟。 「K8S 生態周報」內容主要包含我所接觸到的 K8S 生態相關的每周值得推薦的一些信息。歡迎訂閱知乎專欄「k8s生態」。 Kubernetes client-go v11.0...
摘要:生態周報內容主要包含我所接觸到的生態相關的每周值得推薦的一些信息。歡迎訂閱知乎專欄生態。版本發布這是修改發布周期后,第二個發布的版本,上個版本是。從發布周期來看,由原先的季度發布改成半年發布,也意味著的日漸成熟。 「K8S 生態周報」內容主要包含我所接觸到的 K8S 生態相關的每周值得推薦的一些信息。歡迎訂閱知乎專欄「k8s生態」。 Kubernetes client-go v11.0...
摘要:如果上的資源耗盡,這類將無法成功調度。將這個資源及其對應的設備個數記錄到更新到。 extended-resources extended-resources在k8s1.9中是一個stable的特性。可以用一句話來概括這個特性: 通過向apiserver發送一個patch node 的請求,為這個node增加一個自定義的資源類型,用于以該資源的配額統計和相應的QoS的配置。 patch ...
摘要:功能提供的指標,按照階段分為三種類別實驗性質的中階段的或者的字段。穩定版本的中不向后兼容的主要版本的更新被廢棄的已經不在維護的。通過比較來保證的順序并不保證包含所有資源本文為容器監控實踐系列文章,完整內容見 概述 已經有了cadvisor、heapster、metric-server,幾乎容器運行的所有指標都能拿到,但是下面這種情況卻無能為力: 我調度了多少個replicas?現在可...
摘要:功能提供的指標,按照階段分為三種類別實驗性質的中階段的或者的字段。穩定版本的中不向后兼容的主要版本的更新被廢棄的已經不在維護的。通過比較來保證的順序并不保證包含所有資源本文為容器監控實踐系列文章,完整內容見 概述 已經有了cadvisor、heapster、metric-server,幾乎容器運行的所有指標都能拿到,但是下面這種情況卻無能為力: 我調度了多少個replicas?現在可...
閱讀 3499·2023-04-25 15:52
閱讀 581·2021-11-19 09:40
閱讀 2572·2021-09-26 09:47
閱讀 1023·2021-09-22 15:17
閱讀 3548·2021-08-13 13:25
閱讀 2200·2019-08-30 15:56
閱讀 3460·2019-08-30 13:56
閱讀 2095·2019-08-30 11:27