K8S client-go Patch example
使用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) 直接代替上面示例中的patchData
JSONPatch
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
进行替换,而不是新增,一定要注意避免踩坑。
PS:如果不喜欢使用上面struct的方式组成数据,可以使用如下的方式 labelsPatch := fmt.Sprintf([{"op":"add","path":"/metadata/labels/%s","value":"%s" }]
, labelkey, labelvalue) 直接代替上面示例中的patchData
Op:remove
要删除一个标签的话,代码和增加区别不大,唯一的区别就是提交的数据要由键值对修改为提交一个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:如各位还有其他更好的方式,欢迎交流补充。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
企业官网怎么选择合适的阿里云服务器ECS(个人见解)
企业和个人最大的区别就是企业面向的用户众多,对访问速度和数据安全性要求非常高。试想下你的网站三天两头被黑客攻击,网站被黑了,数据没了,这样的网站怎么能吸引到用户呢。 对于个人站长,用户基数不是非常大,我认识的很多站长基本都是购买一台高配的ecs后,所有的东西都往ecs上安装,比如数据库,缓存服务等。这样做的缺陷是web服务和数据库存储没有进行分离。企业站点一般来说可以分为api和数据存储,api是面向外网用户的,为用户提供数据服务和展现ui。数据库则存储用户的重要数据,将存储和服务分开部署可以有效减小单台服务器的压力,第二则是数据安全更有保障。 所以对于企业站点来说,首先需要将api和数据库存储进行分开部署,以便分摊压力。在企业的前期,可能访问量很小,这时候所有的数据只需要直接访问数据库就可以了。随着访问量的增长,那么对于服务器的性能要求便会非常高,一般企业的场景是:读多于写。所以这时候可能需要引入额外的缓存服务器。 那么对于api和数据库的存储配置我们该如何选择呢?首先也是付费方式和地域选: 在这里我们同样选择包年包月的付费方式,地域选择也是跟着客户走的,哪个节点离你的客户最近,那么...
- 下一篇
Docker之10 Docker Compose
什么是Docker Compose Compose是一个用于定义和运行多容器Docker应用程序的工具。Docker Compose可以使用YAML文件来配置应用程序的服务,接着就可以从配置中创建并启动所有服务。 如何配置Docker Compose 很简单,将docker-compose在github下载下来赋予执行权限即可运行。 curl -L https://github.com/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose Docker Compose 管理命令 查看容器列表docker-compose ps查看服务日志docker-compose log打印绑定端口docker-compose port构建服务docker-compose build 启动已存在的容器服务docker-compose start 停止已运行的容器...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器