【Kubernetes】基于SpringBoot的Mutating Admission Webhook Server实现

  1. 背景
  2. 注意事项
  3. 思路
  4. 过程
  5. 2022/01/21 更新 toAdmissionReview方法

背景

公司内网的Kubernetes集群因为Istio sidecar的原因,经常会达到公司运维给我们组设置的资源上限。一个我们的业务服务通常由两部分容器组成,一个是我们真实对外服务的容器,另一个则是由Istio sidecar inject也就是Istio框架自动注入的Istio-sidecar。不提我们的业务容器,只针对Istio sidecar,他的resource/limit/cpu就被设置为了2,要知道我们组所属的namespace下,资源limit上限被设置为了100cpu,超过100之后服务就无法被部署成功。同时,作为一个内网的服务,他的上限也根本不用2,这一个较高的数字。

在Istio后续的1.4版本中,有一个专门为Pod.spec.resource.cpu.limit和memory设置的注解,但是在之前的版本中不提供这样的支持,所以只能通过我们自己来实现。最简单的方法自然是为每一个环境(内网或者外网)做一份不同的设置,考虑到我们服务过于多,一个一个添加不大科学,也不符合k8s不影响到业务集成过程的理念。综上所述,最终考虑到k8s原生支持的Mutating Admission Webhook来实现。

Mutating Admission Webhook对我而言,简而言之,就是Kube-api 在执行真正的部署命令之前,提前对你要部署的东西做一个修改。Istio的Istio sidecar injector也是通过这个来实现的。那对我来说,只要在Istio sidecar injector注入sider,也就是他对Pod做完修改之后,我在将他的istio proxy中的container.resource.cpu.limit修改为我要的值就好了。

在google和github上搜索一番,基本都是基于Go语言来实现Admission Webhook Server,没有发现有Java实现的,所以打算自己动手实现一个。

注意事项

  • 第一次搞Mutating Webhook Admission Webhook,可以将在Pod中注入一个新的Label作为成功标准
  • 接上条,可以把Webhook Admission Webhook Configuration中的failPolicy设置为Fail,即在Server端中有意外情况,算作失败,这样可以在Replicas看到部署失败的原因

思路

从官方文档上来说,比较关键的东西有三个

  1. Mutating Admission Webhook Configuration,一份Webhook的配置文件,他决定谁会被拦截,和将请求转发到那里去
  2. Mutating Admission Webhook Server,在本文中专指Springboot 实现的一个Server,他的作用是怎么修改即将要被部署的kubernetes 资源
  3. 请求,也就是第一点提供到谁会被拦截到的谁,一般来说是指某一个被打了特定label的namespace
    比较琐碎的细节,包括但不限于Kube-api 里面是否开启了admission webhook的限制,configuration和server的证书配置,server端要求返回的格式

过程

几个关键节点大概分为这几个历程

  1. Object生成请求拦截(这里就是Pod生成之前会给我们的server发消息)
  2. RequestBody 反序列化 (可以看到我们的server接收到k8s发来的消息是什么样子的)
  3. 尝试修改pod参数 (尝试修改一下pod的label)
  4. 尝试修改 istio-proxy resource参数

在第一个关键节点上我们理论上就应该完成大部分的工作,后面的过程是在做细节上的处理。

我的Configuration文件是这个样子的

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  name: mutateme
  labels:
    app: mutateme
webhooks:
###name 要求是以域名的形式出现 所以设置为 12Dong-qwer是不行的
  - name: mutateme.i18n-system.svc.cluster.local
    clientConfig:
     #这一张证书要求从k8s集群内部取 取法可以看这篇教程中 https://medium.com/ovni/writing-a-very-basic-kubernetes-mutating-admission-webhook-398dbbcb63ec 中的 kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' 这个命令
      caBundle: "12Dong*****"
      service:
        #请求转发到Server 也就是我们的Springboot Server
        name: auto-fill-annotation
        namespace: i18n-system
        #对应的路径名是一个Post请求
        path: "/mutate"  
    rules:
      #什么样的请求会被拦截到 我这里设置的是当有Pod生成时,部署好之后 可以用kubectl apply 命令生成一个即可测验
      - operations: ["CREATE"]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
    namespaceSelector:
      matchLabels:
        mutateme: enabled

比较需要注意的,以这样的格式默认生成的是一个FailPolicy是Ignore的Webhook Configuration就是指,如果Server出了问题,他会选择无视当作无事发生处理。建议第一次使用时,手动修改为Fail。这样可以在相应的位置看到报错
在这里插入图片描述
比如说这样 在Replicaset中我看到报错的原因
把他apply到我们的集群中,开始处理下一步Server端的代码

Server端的代码配置流程有三步
第一步,开启服务内部的Https 这个可以参照SpringBoot的官方文档来做
springboot服务开启https配置

第二部,将证书的相关信息将k8s集群中注册,否则就会出现这样的报错。这里可以参照我之前的笔记来处理
在这里插入图片描述
第三步,根据Configuration的信息写一个Post请求的接口

    @PostMapping(value = "/mutate")
    public AdmissionReview autoFillAnnotation(@RequestBody AdmissionReview admissionReview) {
        //check istio proxy exist
        final AdmissionRequest request = admissionReview.getRequest();
        log.info("{} has been intercepted, webhook server is invoked", request.getObject().getMetadata().getGenerateName());
        try{
            final String json = MAPPER.writeValueAsString(request.getObject());
            final Pod pod = MAPPER.readValue(json, Pod.class);
            if (!checkIstioProxyExist(pod)) {
                return toAdmissionReview(admissionReview, null);
            }
        }catch(Exception e){
            log.error("Fail to check istio proxy exist because {}", e);
            return toAdmissionReview(admissionReview, null);
        }
        //make response
        JsonPatch[] jsonPatches = new JsonPatch[1];
        JsonPatch update = new JsonPatch();
        update.setOp("replace");
        update.setPath("/spec/containers/1/resources/limits/cpu");
        update.setValue("500m");
        jsonPatches[0] = update;

        String patch = null;

        try{
            String json = MAPPER.writeValueAsString(jsonPatches);
            patch = Base64.getEncoder().encodeToString(json.getBytes());
        } catch(Exception e){
            log.error("{} fail to make patch because {}", request.getObject().getMetadata().getGenerateName(), e);
        }

        return toAdmissionReview(admissionReview, patch);
    }

处理之后,把AdmissionReview 打印出来,再将Springboot打包上线,进行测试。

第一个关键节点,就完成了

第二关键节点需要注意的就是在我所使用的包中,直接获取反序列的信息时,不能直接Get到他的Spec属性,这个Spec属性使用了Interface来实现,所以我将其特化为了对应的Pod类,之后就能获取Spec属性字段了。

第三、四个关键节点中,涉及到JsonPatch的使用,可以看官方文档,Json对象中有数组对象的话,我粗粗翻了一遍没有讲解怎么做,所以在查了一下,需要在数组对应的字段名后使用/num的方式指定对数组成员的第几个做修改,我这边因为Istio-sidercar固定是第二个所以我要修改istio-sidecar的resource.cpu.limit属性的话,就是 /spec/containers/1/resources/limits/cpu

到这里大致的流程已经比较详细的讲完了,因为是公司的项目所以不能吧代码直接放到github上给大家参考。
每个人碰上的问题可能大概不同,问题的思路也可能大不相同,希望大家能通过FailPolicy来查看为什么自己的代码不能成功的原因,从而推导出正确的做法。

2022/01/21 更新 toAdmissionReview方法

    private AdmissionReview toAdmissionReview(final AdmissionReview requestBody, final String patch) {
        final AdmissionReview admissionReview = new AdmissionReview();
        admissionReview.setKind(requestBody.getKind());
        admissionReview.setApiVersion(requestBody.getApiVersion());
        final AdmissionResponse admissionResponse = new AdmissionResponse();
        admissionResponse.setAllowed(true);
        admissionResponse.setUid(requestBody.getRequest().getUid());
        admissionReview.setResponse(admissionResponse);
        if (patch == null) {
            return admissionReview;
        } else {
            admissionResponse.setPatch(patch);
            admissionResponse.setPatchType("JSONPatch");
            return admissionReview;
        }
    }


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值