https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;
这是篇万字长文,所以一开始就要明确本文的核心内容:开发一个SpringBoot应用并部署在kubernetes环境,这个应用通过kubernetes的java客户端向API Server发请求,请求内容包括:创建名为test123的deployment、对这个deployment进行patch操作,如下图:
接下来先了解一些kubernetes的patch相关的基本知识;
kubernetes的patch一共有四种:
名称 | 链接 | 备注 |
---|---|---|
项目主页 | 该项目在GitHub上的主页 | |
git仓库地址(https) | 该项目源码的仓库地址,https协议 | |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
准备工作包括创建工程、编写辅助功能代码、初始化代码等:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>kubernetesclient</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>patch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>patch</name>
<description>patch demo</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
<!--该配置会在jar中增加layer描述文件,以及提取layer的工具-->
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>
</project>
package com.bolingcavalry.patch;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
import org.springframework.core.io.ClassPathResource;
public class ClassPathResourceReader {
private final String path;
private String content;
public ClassPathResourceReader(String path) {
this.path = path;
}
public String getContent() {
if (content == null) {
try {
ClassPathResource resource = new ClassPathResource(path);
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
content = reader.lines().collect(Collectors.joining("\n"));
reader.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return content;
}
}
public static void main(String[] args) {
SpringApplication.run(PatchExample.class, args);
}
static String DEPLOYMENT_NAME = "test123";
static String NAMESPACE = "default";
static String deployStr, jsonStr, mergeStr, strategicStr, applyYamlStr;
@PostConstruct
private void init() throws IOException {
// 设置api配置
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);
// 设置超时时间
Configuration.getDefaultApiClient().setConnectTimeout(30000);
// 部署用的JSON字符串
deployStr = new ClassPathResourceReader("deploy.json").getContent();
// json patch用的JSON字符串
jsonStr = new ClassPathResourceReader("json.json").getContent();
// merge patch用的JSON字符串,和部署的JSON相比:replicas从1变成2,增加一个名为from的label,值为merge
mergeStr = new ClassPathResourceReader("merge.json").getContent();
// strategic merge patch用的JSON字符串
strategicStr = new ClassPathResourceReader("strategic.json").getContent();
// server side apply用的JSON字符串
applyYamlStr = new ClassPathResourceReader("applyYaml.json").getContent();
}
/**
* 通用patch方法
* @param patchFormat patch类型,一共有四种
* @param deploymentName deployment的名称
* @param namespace namespace名称
* @param jsonStr patch的json内容
* @param fieldManager server side apply用到
* @param force server side apply要设置为true
* @return patch结果对象转成的字符串
* @throws Exception
*/
private String patch(String patchFormat, String deploymentName, String namespace, String jsonStr, String fieldManager, Boolean force) throws Exception {
// 创建api对象,指定格式是patchFormat
ApiClient patchClient = ClientBuilder
.standard()
.setOverridePatchFormat(patchFormat)
.build();
log.info("start deploy : " + patchFormat);
// 开启debug便于调试,生产环境慎用!!!
patchClient.setDebugging(true);
// 创建deployment
ExtensionsV1beta1Deployment deployment = new ExtensionsV1beta1Api(patchClient)
.patchNamespacedDeployment(
deploymentName,
namespace,
new V1Patch(jsonStr),
null,
null,
fieldManager,
force
);
log.info("end deploy : " + patchFormat);
return new GsonBuilder().setPrettyPrinting().create().toJson(deployment);
}
{
"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":{
}
}
/**
* 通用patch方法
* @param patchFormat patch类型,一共有四种
* @param deploymentName deployment的名称
* @param namespace namespace名称
* @param jsonStr patch的json内容
* @param fieldManager server side apply用到
* @param force server side apply要设置为true
* @return patch结果对象转成的字符串
* @throws Exception
*/
private String patch(String patchFormat, String deploymentName, String namespace, String jsonStr, String fieldManager, Boolean force) throws Exception {
// 创建api对象,指定格式是patchFormat
ApiClient patchClient = ClientBuilder
.standard()
.setOverridePatchFormat(patchFormat)
.build();
log.info("start deploy : " + patchFormat);
// 开启debug便于调试,生产环境慎用!!!
patchClient.setDebugging(true);
// 创建deployment
ExtensionsV1beta1Deployment deployment = new ExtensionsV1beta1Api(patchClient)
.patchNamespacedDeployment(
deploymentName,
namespace,
new V1Patch(jsonStr),
null,
null,
fieldManager,
force
);
log.info("end deploy : " + patchFormat);
return new GsonBuilder().setPrettyPrinting().create().toJson(deployment);
}
/**
* 通用patch方法,fieldManager和force都默认为空
* @param patchFormat patch类型,一共有四种
* @param jsonStr patch的json内容
* @return patch结果对象转成的字符串
* @throws Exception
*/
private String patch(String patchFormat, String jsonStr) throws Exception {
return patch(patchFormat, DEPLOYMENT_NAME, NAMESPACE, jsonStr, null, null);
}
[
{
"op":"replace",
"path":"/spec/template/spec/terminationGracePeriodSeconds",
"value":27
}
]
/**
* JSON patch格式的关系
*
* @return
* @throws Exception
*/
@RequestMapping(value = "/patch/json", method = RequestMethod.GET)
public String json() throws Exception {
return patch(V1Patch.PATCH_FORMAT_JSON_PATCH, jsonStr);
}
@RequestMapping(value = "/patch/fullmerge", method = RequestMethod.GET)
public String fullmerge() throws Exception {
return patch(V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH, mergeStr);
}
{
"spec":{
"template":{
"spec":{
"containers":[
{
"name":"test456",
"image":"tomcat:7.0.105-jdk8"
}
]
}
}
}
}
@RequestMapping(value = "/patch/partmerge", method = RequestMethod.GET)
public String partmerge() throws Exception {
return patch(V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH, strategicStr);
}
@RequestMapping(value = "/patch/strategic", method = RequestMethod.GET)
public String strategic() throws Exception {
return patch(V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH, strategicStr);
}
@RequestMapping(value = "/patch/apply", method = RequestMethod.GET)
public String apply() throws Exception {
return patch(V1Patch.PATCH_FORMAT_APPLY_YAML, DEPLOYMENT_NAME, NAMESPACE, applyYamlStr, "example-field-manager", true);
}
mvn clean package -U -DskipTests
# 指定基础镜像,这是分阶段构建的前期阶段
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-stretch
WORKDIR application
# 前一阶段从jar中提取除了多个文件,这里分别执行COPY命令复制到镜像空间中,每次COPY都是一个layer
COPY --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"]
docker build -t 192.168.50.43:5888/common/patch:1.0-SNAPSHOT .
docker push 192.168.50.43:5888/common/patch:1.0-SNAPSHOT
apiVersion: v1
kind: Service
metadata:
name: patch
namespace : kubernetesclient
spec:
type: NodePort
ports:
- port: 8080
nodePort: 30102
selector:
name: patch
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
namespace : kubernetesclient
name: patch
spec:
replicas: 1
template:
metadata:
labels:
name: patch
spec:
serviceAccountName: kubernates-client-service-account
containers:
- name: patch
image: 192.168.50.43:5888/common/patch:1.0-SNAPSHOT
tty: true
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 5
failureThreshold: 10
timeoutSeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 10
periodSeconds: 5
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "1000m"
kubectl apply -f patch.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
先说一下验证的步骤:
至此,通过kubernetes的java客户端执行patch操作的实战就全部完成了,从理论到环境准备,再到实际操作,涉及到太多内容,感谢您的耐心,希望本文能助您用好java客户端这个利器,更高效的操作kubernetes环境;