Kubernetes官方java客户端之七:patch操作

准备工作

准备工作包括创建工程、编写辅助功能代码、初始化代码等:

打开《Kubernetes官方java客户端之一:准备 》一文创建的项目kubernetesclient,新增名为patch的子工程,pom.xml内容如下:

4.0.0com.bolingcavalrykubernetesclient1.0-SNAPSHOT../pom.xmlcom.bolingcavalrypatch0.0.1-SNAPSHOTpatchpatch demojarorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-jsonorg.springframework.bootspring-boot-starter-actuatororg.projectlomboklomboktrueio.kubernetesclient-javaorg.springframework.bootspring-boot-maven-plugin2.3.0.RELEASEtrue

编写一个辅助类ClassPathResourceReader.java,作用是读取json文件的内容作为字符串返回:

packagecom.bolingcavalry.patch;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.util.stream.Collectors;importorg.springframework.core.io.ClassPathResource;publicclassClassPathResourceReader{privatefinalString path;privateString content;publicClassPathResourceReader(String path){this.path = path;    }publicStringgetContent(){if(content ==null) {try{                ClassPathResource resource =newClassPathResource(path);                BufferedReader reader =newBufferedReader(newInputStreamReader(resource.getInputStream()));                content = reader.lines().collect(Collectors.joining("\n"));                reader.close();            }catch(IOException ex) {thrownewRuntimeException(ex);            }        }returncontent;    }}

接下来新建本篇文章的核心类PatchExample.java,首先这个类中有main方法,整个应用从这里启动:

publicstaticvoidmain(String[] args){        SpringApplication.run(PatchExample.class, args);    }

接下来有两个常量定义,分别是kubernetes环境里用来测试的deployment名称,以及namespace名称:

staticString DEPLOYMENT_NAME ="test123";staticString NAMESPACE ="default";

然后定义几个字符串变量,执行patch操作时用到的json内容都保存到这些字符串变量中:

static String deployStr, jsonStr, mergeStr, strategicStr, applyYamlStr;

在resources文件夹中放入json文件,稍后的初始化代码会将这些文件读取到字符串变量中,如下图,这些json文件的内容稍后会详细说明:

编写初始化代码(通过PostConstruct注解实现),主要是客户端配置,还有将json文件的内容读出来,保存到刚刚准备的字符串变量中:

@PostConstructprivatevoidinit()throwsIOException{// 设置api配置ApiClient client = Config.defaultClient();        Configuration.setDefaultApiClient(client);// 设置超时时间Configuration.getDefaultApiClient().setConnectTimeout(30000);// 部署用的JSON字符串deployStr =newClassPathResourceReader("deploy.json").getContent();// json patch用的JSON字符串jsonStr =newClassPathResourceReader("json.json").getContent();// merge patch用的JSON字符串,和部署的JSON相比:replicas从1变成2,增加一个名为from的label,值为mergemergeStr =newClassPathResourceReader("merge.json").getContent();// strategic merge patch用的JSON字符串strategicStr =newClassPathResourceReader("strategic.json").getContent();// server side apply用的JSON字符串applyYamlStr =newClassPathResourceReader("applyYaml.json").getContent();    }

以上就是准备工作;

创建服务

首先要开发一个部署的接口,通过调用此接口可以在kubernetes环境部署一个deployment:

部署服务的path是/patch/deploy,代码如下,可见部署deployment的代码分为三步:创建api实例、用字符串创建body实例、把body传给api即可:

/**    * 通用patch方法    *@parampatchFormat patch类型,一共有四种    *@paramdeploymentName deployment的名称    *@paramnamespace namespace名称    *@paramjsonStr patch的json内容    *@paramfieldManager server side apply用到    *@paramforce server side apply要设置为true    *@returnpatch结果对象转成的字符串    *@throwsException    */privateStringpatch(String patchFormat, String deploymentName, String namespace, String jsonStr, String fieldManager, Boolean force)throwsException{// 创建api对象,指定格式是patchFormatApiClient patchClient = ClientBuilder                .standard()                .setOverridePatchFormat(patchFormat)                .build();        log.info("start deploy : "+ patchFormat);// 开启debug便于调试,生产环境慎用!!!patchClient.setDebugging(true);// 创建deploymentExtensionsV1beta1Deployment deployment =newExtensionsV1beta1Api(patchClient)                .patchNamespacedDeployment(                        deploymentName,                        namespace,newV1Patch(jsonStr),null,null,                        fieldManager,                        force                );        log.info("end deploy : "+ patchFormat);returnnewGsonBuilder().setPrettyPrinting().create().toJson(deployment);    }

body实例用到的json字符串来自deploy.json文件,内容如下,很简单,只有nginx的1.18.0版本的pod:

{"kind":"Deployment","apiVersion":"extensions/v1beta1","metadata":{"name":"test123","labels":{"run":"test123"}  },"spec":{"replicas":1,"selector":{"matchLabels":{"run":"test123"}    },"template":{"metadata":{"creationTimestamp":null,"labels":{"run":"test123"}      },"spec":{"terminationGracePeriodSeconds":30,"containers":[          {"name":"test123","image":"nginx:1.18.0","ports":[              {"containerPort":80}            ],"resources":{            }          }        ]      }    },"strategy":{    }  },"status":{  }}

如此一来,web浏览器只要访问/patch/deploy就能创建deployment了;

发起patch请求的通用方法

通过kubernetes的客户端发起不同的patch请求,其大致步骤都是相同的,只是参数有所不同,我这里做了个私有方法,发起几种patch请求的操作都调用此方法实现(只是入参不同而已),可见都是先建好ApiClient实例,将patch类型传入,再创建V1Patch实例,将patch字符串传入,最后执行ExtensionsV1beta1Api实例的patchNamespacedDeployment方法即可发送patch请求:

/**    * 通用patch方法    *@parampatchFormat patch类型,一共有四种    *@paramdeploymentName deployment的名称    *@paramnamespace namespace名称    *@paramjsonStr patch的json内容    *@paramfieldManager server side apply用到    *@paramforce server side apply要设置为true    *@returnpatch结果对象转成的字符串    *@throwsException    */privateStringpatch(String patchFormat, String deploymentName, String namespace, String jsonStr, String fieldManager, Boolean force)throwsException{// 创建api对象,指定格式是patchFormatApiClient patchClient = ClientBuilder                .standard()                .setOverridePatchFormat(patchFormat)                .build();        log.info("start deploy : "+ patchFormat);// 开启debug便于调试,生产环境慎用!!!patchClient.setDebugging(true);// 创建deploymentExtensionsV1beta1Deployment deployment =newExtensionsV1beta1Api(patchClient)                .patchNamespacedDeployment(                        deploymentName,                        namespace,newV1Patch(jsonStr),null,null,                        fieldManager,                        force                );        log.info("end deploy : "+ patchFormat);returnnewGsonBuilder().setPrettyPrinting().create().toJson(deployment);    }

上述代码中,有一行代码要格外重视,就是patchClient.setDebugging(true)这段,执行了这一行,在log日志中就会将http的请求和响应全部打印出来,是我们调试的利器,但是日志内容过多,生产环境请慎用;

上述patch方法有六个入参,其实除了patch类型和patch内容,其他参数都可以固定下来,于是再做个简化版的patch方法:

/**    * 通用patch方法,fieldManager和force都默认为空    *@parampatchFormat patch类型,一共有四种    *@paramjsonStr patch的json内容    *@returnpatch结果对象转成的字符串    *@throwsException    */privateStringpatch(String patchFormat, String jsonStr)throwsException{returnpatch(patchFormat,  DEPLOYMENT_NAME, NAMESPACE, jsonStr,null,null);    }

入参patchFormat的值是四种patch类型的定义,在V1Patch.java中,其值如下所示:

接下来可以轻松的开发各种类型patch的代码了;

执行json patch

首先来看json patch要提交的内容,即json.json文件的内容,这些内容在应用启动时被保存到变量jsonStr,如下所示,非常简单,修改了terminationGracePeriodSeconds属性的值,原来是30,这个属性在停止pod的时候用到,是等待pod的主进程的最长时间:

[  {"op":"replace","path":"/spec/template/spec/terminationGracePeriodSeconds","value":27}]

接下来就是web接口的代码,可见非常简单,仅需调用前面准备好的patch方法:

/**    * JSON patch格式的关系    *    *@return*@throwsException    */@RequestMapping(value = "/patch/json", method = RequestMethod.GET)publicStringjson()throwsException{returnpatch(V1Patch.PATCH_FORMAT_JSON_PATCH, jsonStr);    }

merge patch(全量)

先尝试全量的merge patch,也就是准备好完整的deployment内容,修改其中一部分后进行提交,下图是json文件merge.json的内容,其内容前面的deploy.json相比,仅增加了红框处的内容,即增加了一个label:

代码依然很简单:

@RequestMapping(value = "/patch/fullmerge", method = RequestMethod.GET)publicStringfullmerge()throwsException{returnpatch(V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH, mergeStr);    }

merge patch(增量)

前面曾提到merge patch和strategic merge patch的区别:merge patch提交一个container时做的是替换,而strategic merge patch提交一个container时做的是合并,为了展示这两种patch的不同,这里我们就用同一个json内容,分别执行merge patch和strategic merge patch,看看结果有什么区别,这是最直观的学习方法;

这个json对应的文件是strategic.json,内容如下:

{"spec":{"template":{"spec":{"containers":[          {"name":"test456","image":"tomcat:7.0.105-jdk8"}        ]      }    }  }}

增量merge的代码如下:

@RequestMapping(value = "/patch/partmerge", method = RequestMethod.GET)publicStringpartmerge()throwsException{returnpatch(V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH, strategicStr);    }

strategic merge patch

strategic merge patch用的json内容和前面的增量merge patch是同一个,代码如下:

@RequestMapping(value = "/patch/strategic", method = RequestMethod.GET)publicStringstrategic()throwsException{returnpatch(V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH, strategicStr);    }

apply yaml patch

apply yaml patch与其他patch略有不同,调用ExtensionsV1beta1Api的patchNamespacedDeployment方法发请求时,fieldManager和force字段不能像之前那样为空了:

@RequestMapping(value = "/patch/apply", method = RequestMethod.GET)publicStringapply()throwsException{returnpatch(V1Patch.PATCH_FORMAT_APPLY_YAML,  DEPLOYMENT_NAME, NAMESPACE, applyYamlStr,"example-field-manager",true);    }

上面的代码中,如果force字段不等于true,可能会导致patch失败,在官方文档也有说明,如下图红框:

apply yaml patch的json字符串来自文件applyYaml.json,其内容是从deploy.json直接复制的,然后改了下图两个红框中的内容,红框1修改了nginx的版本号,用来验证patch是否生效(原有版本是1.18),红框2是kubernetes1.16之前的一个问题,protocol字段必填,否则会报错,问题详情请参考:https://github.com/kubernetes-sigs/structured-merge-diff/issues/130

以上就是所有代码和patch的内容了,接下来部署到kubernetes环境实战吧

制作镜像并且部署

在patch工程目录下执行以下命令编译构建:

mvn clean package -U -DskipTests

在patch工程目录下创建Dockerfile文件,内容如下:

# 指定基础镜像,这是分阶段构建的前期阶段FROM openjdk:8u212-jdk-stretch as builder# 执行工作目录WORKDIR application# 配置参数ARG JAR_FILE=target/*.jar# 将编译构建得到的jar文件复制到镜像空间中COPY ${JAR_FILE} application.jar# 通过工具spring-boot-jarmode-layertools从application.jar中提取拆分后的构建结果RUN java -Djarmode=layertools -jar application.jar extract# 正式构建镜像FROM openjdk:8u212-jdk-stretchWORKDIR application# 前一阶段从jar中提取除了多个文件,这里分别执行COPY命令复制到镜像空间中,每次COPY都是一个layerCOPY --from=builder application/dependencies/ ./COPY --from=builder application/spring-boot-loader/ ./COPY --from=builder application/snapshot-dependencies/ ./COPY --from=builder application/application/ ./ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

在patch工程目录下执行以下命令创建docker镜像:

docker build -t 192.168.50.43:5888/common/patch:1.0-SNAPSHOT .

如果您已经配置了docker镜像仓库私服,建议将此镜像推送到私服上去,以便kubernetes上可以使用该镜像,我这边的推送命令如下,仅供参考(涉及到身份验证的话还请执行docker login登录):

docker push 192.168.50.43:5888/common/patch:1.0-SNAPSHOT

SSH登录kubernetes环境,新建patch.yaml文件,内容如下,image那里请按您的镜像情况自行调整:

apiVersion:v1kind:Servicemetadata:name:patchnamespace :kubernetesclientspec:type:NodePortports:-port:8080nodePort:30102selector:name:patch---apiVersion:extensions/v1beta1kind:Deploymentmetadata:namespace :kubernetesclientname:patchspec:replicas:1template:metadata:labels:name:patchspec:serviceAccountName:kubernates-client-service-accountcontainers:-name:patchimage:192.168.50.43:5888/common/patch:1.0-SNAPSHOTtty:truelivenessProbe:httpGet:path:/actuator/health/livenessport:8080initialDelaySeconds:5failureThreshold:10timeoutSeconds:10periodSeconds:5readinessProbe:httpGet:path:/actuator/health/readinessport:8080initialDelaySeconds:5timeoutSeconds:10periodSeconds:5ports:-containerPort:8080resources:requests:memory:"512Mi"cpu:"100m"limits:memory:"1Gi"cpu:"1000m"

执行以下命令即可完成部署:

kubectl apply -f patch.yaml

用于验证patch的deployment名为test123(浏览器访问/patch/deploy就会创建),这个deployment里面是个nginx容器,咱们要给它准备一个NodePort类型的service,以便验证的是否可以通过浏览器访问,该service对应的yaml文件是nginx-service.yaml,内容如下:

apiVersion: v1

kind: Service

metadata:

  name: test123

  namespace : default

spec:

  type: NodePort

  ports:

    - port: 80

      nodePort: 30101

  selector:

    run: test123

执行以下命令即可完成部署:

kubectl apply -f nginx-service.yaml

亚马逊测评 www.yisuping.cn

你可能感兴趣的:(Kubernetes官方java客户端之七:patch操作)