k8s webhook实例,java springboot程序实现 对Pod创建请求添加边车容器 ,模拟istio实现日志文件清理
大纲
- 背景与原理
- 实现流程
- 开发部署my-docker-demo-sp-user服务模拟业务项目
- 开发部署my-sidecar服务模拟边车程序
- 开发部署服务my-docker-demo-k8s-operator 提供webhook功能
- 创建MutatingWebhookConfiguration 动态准入配置
- 测试边车注入效果
背景与原理
背景:
程序容器运行期间会大量产生日志文件,久而久之会消耗大量的硬盘空间(除非程序重新部署)
如果手动的去删除比较麻烦,如果把程序文件夹挂载到主机需要手动去配置磁盘卷挂载比较麻烦
原理:
由于业务程序镜像统一把日志写入到/data/service/logs, 所以在开发一个程序专门定时清理/data/service/logs文件夹下的文件,利用k8s webhook 把这个定时程序作为边车挂载到业务程序中
利用边车的方式还可以实现很多对业务增强的功能
涉及项目
- my-docker-demo-sp-user 模拟业务项目
- my-sidecar 模拟边车程序
- my-docker-demo-k8s-operator 模拟webhook
实现流程
step1 开发部署my-docker-demo-sp-user服务模拟业务项目
my-docker-demo-sp-user是一个springboot项目主要代码如下
调用一个接口可以创建随机文件,模拟生成大量日志文件
my-docker-demo-sp-user 部署文件 deploy.yaml 如下
apiVersion: v1
kind: Namespace #类型 指定为Namespace
metadata:
name: my-sidecar-webhook #namespace的名称 注意只能是英文小写和数字
labels:
my-sidecar-inject: enabled #注意: 命名空间需要带有namespaceSelector中匹配的标签
---
apiVersion: v1
kind: Pod
metadata:
name: user-service-pod
namespace: my-sidecar-webhook
spec:
# 容器配置
containers:
- image: registry.cn-hangzhou.aliyuncs.com/jimliu/user-service:v5
imagePullPolicy: IfNotPresent #Always
name: user-service
ports:
- containerPort: 5588
name: http
protocol: TCP
可以看到这是一个极简的部署文件,创建一个命名空间和一个Pod, Pod只有一个容器user-service,执行部署文件deploy.yaml 可以看到只有一个容器在运行
测试发现功能正常并且Pod中只有一个容器
step2 开发部署my-sidecar服务模拟边车程序
my-sidecar (代码写在my-docker-demo-k8s-operator中打包的时候改了下jar包的名字)是一个springboot项目主要代码如下
有一个接口可以查看/data/service/logs文件夹下所有文件和一个接口和删除文件的接口
后续改造可以将删除功能做成定时任务执行
将my-sidecar服务打包制作成镜像推送到私库待用
step3 开发部署服务my-docker-demo-k8s-operator 提供webhook功能
本次实验webhook使用部署在k8s集群外部的方式 webhook可以参考(k8s Webhook 使用java springboot实现webhook 学习总结)
@RequestMapping("/mutate/v3")
public String mutateV2(HttpServletRequest req ,HttpServletResponse rep) throws Exception {
System.out.println("use mutate java k8s client lib [io.fabric8] @@@");
InputStream in = req.getInputStream();
/**
* io.fabric8库 内置AdmissionReview对象
* 使用 Serialization.unmarshal把输入流直接转化为对象
*/
AdmissionReview admissionReview = Serialization.unmarshal(in, AdmissionReview.class);
System.out.println(Serialization.asJson(admissionReview));
/**
* 需要给原始的Pod 添加一个边车容器用于管理原始容器中生成的文件
* Pod配置文件修改
* 1 添加一个空临时存储卷
* 2 添加一个边车容器并挂载存储卷
* 3 修改原始容器添加挂载存储卷
*
*/
//1 准备一个空临时存储卷对象
//Volume与EmptyDirVolumeSource对象是 io.fabric8库 内置对象
Volume v = new Volume();
v.setName("data");
EmptyDirVolumeSource emptyDir = new EmptyDirVolumeSource();
v.setEmptyDir(emptyDir);
//2 准备一个边车容器并挂载存储卷对象
//Container对象是 io.fabric8库 内置对象
Container sidecar = new Container();
sidecar.setImage("registry.cn-hangzhou.aliyuncs.com/jimliu/sidecar:v1"); //边车程序镜像
sidecar.setImagePullPolicy("IfNotPresent");
sidecar.setName("sidecar");
ContainerPort cp = new ContainerPort();
cp.setContainerPort(5533);
cp.setName("http");
cp.setProtocol("TCP");
sidecar.setPorts(Arrays.asList(cp));
//VolumeMount对象是 io.fabric8库 内置对象
VolumeMount vm = new VolumeMount();
vm.setMountPath("/data/service/logs"); //将容器内部的/data/service/logs 与 data 卷挂载
vm.setName("data");
sidecar.setVolumeMounts(Arrays.asList(vm));
//3 修改原始容器添加挂载存储卷
//Pod对象是 io.fabric8库 内置对象
Pod pod = (Pod) admissionReview.getRequest().getObject();
List<Container> containers = pod.getSpec().getContainers();
for(Container container : containers) {
VolumeMount vmt = new VolumeMount();
vmt.setMountPath("/data/service/logs");
vmt.setName("data");
container.getVolumeMounts().add(vmt);
}
//将边车容器加入
containers.add(sidecar);
/**
* 开始制作jsonpatch
* 对Pod的修改需要利用jsonpatch 提供的add 和 replace
* JsonPatchBean为自定义的一个对象
*/
List<JsonPatchBean> patchs = new ArrayList<>();
//添加卷
patchs.add(new JsonPatchBean("add","/spec/volumes",Arrays.asList(v)));
//替换容器加入边车容器
patchs.add(new JsonPatchBean("replace","/spec/containers",containers));
/**
* patchs转换为字符串
* 需要把字符串转换为base64编码
*/
String patchStr = JSON.toJSONString(patchs);
System.out.println(patchStr);
Base64 base64= new Base64();
String patch = new String(base64.encode(patchStr.getBytes()));
/**
* uid,从发送到 Webhook 的 request.uid 中复制而来
*/
String uid = admissionReview.getRequest().getUid();
/**
* io.fabric8库 内置AdmissionResponse对象
*/
AdmissionResponse response = new AdmissionResponse();
response.setAllowed(true);
response.setUid(uid);
response.setPatch(patch);
response.setPatchType("JSONPatch");
/**
* 填充Response
*/
admissionReview.setResponse(response);
return Serialization.asJson(admissionReview);
}
/**
* 封装一个JsonPatch 操作对象
* @author liuyijiang
*/
public static class JsonPatchBean {
private String op;
private String path;
private Object value;
public JsonPatchBean(String op,String path,Object value) {
this.op = op;
this.path = path;
this.value = value;
}
... get set 省略
}
这里 my-docker-demo-k8s-operator 项目需要使用https方式访问
如何配置证书与域名可参考 k8s Webhook 使用java springboot实现webhook 学习总结
这里默认相关的证书配置,k8s master节点的域名映射都完成,本地eclipse启动my-docker-demo-k8s-operator 成功
step4 创建MutatingWebhookConfiguration 动态准入配置
MutatingWebhookConfiguration 内容如下
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: my-test-webhook1 #创建MutatingWebhookConfiguration 的名称
webhooks:
- admissionReviewVersions: #指定可接受的 AdmissionReview 对象版本 这里支持v1beta1 v1
- v1beta1
- v1
clientConfig:
# CA根证书内容
caBundle: LS0tLS1CRUdJTiBDR...略
# 只支持https请求
url: "https://webhooktest.liuyijiang.com/mutate/v3" #webhook web服务访问的地址
failurePolicy: Fail
matchPolicy: Exact #精确匹配
name: webhooktest.liuyijiang.com #名称随意但是必须是域名格式
namespaceSelector:
matchLabels:
my-sidecar-inject: enabled #必须匹配标签为my-sidecar-inject=enabled的命名空间内的资源才会被拦截
rules:
- apiGroups:
- ""
apiVersions: #匹配的版本
- v1
operations: #拦截CREATE操作
- CREATE
resources: #拦截执行类型是pod
- pods
scope: '*' #所有命名空间
sideEffects: None #配置是否有副作用,None表示调用 Webhook 没有副作用
timeoutSeconds: 30 #请求超时时间
caBundle根证书内容生成 可参考 k8s Webhook 使用java springboot实现webhook 学习总结
部署MutatingWebhookConfiguration
step5 测试边车注入效果
再次部署my-docker-demo-sp-user项目 此时 我本机上已接收到k8s webhook请求
查看部署后的Pod 发现出现两个容器,边车容器与业务容器实现了共享存储空间,并可以清除日志文件,后续只需改造为定时任务即可
kubectl -n my-sidecar-webhook get pods -o json