【无标题】

原理

准入webhook工作流程图

【无标题】_第1张图片

准入控制器阶段

【无标题】_第2张图片

步骤

需求

​ 给k8s集群增加镜像校验能力,不允许使用带:latest标签的镜像创建资源,允许使用携带具体标签的镜像来创建资源,比如:nginx:1.16

先决条件

  • 1)确保启用ImagePolicyWebhook控制器。 这里 是一组推荐的 admission 控制器,通常可以启用。

检查ImagePolicyWebhook是否启用,若能查询到,则启用了:

kubectl exec kube-apiserver-k8s-master -n kube-system – kube-apiserver -h | grep “enable-admission-plugins”

检查MutatingAdmissionWebhook是否启用:(默认启用)

kubectl exec kube-apiserver-k8s-master -n kube-system – kube-apiserver -h | grep -i “MutatingAdmissionWebhook”

检查ValidatingAdmissionWebhook是否启用:(默认启用)

kubectl exec kube-apiserver-k8s-master -n kube-system – kube-apiserver -h | grep -i “ValidatingAdmissionWebhook”

  • 2)确保启用了admissionregistration.k8s.io/v1版本的API

从k8s v1.16开始,admissionregistration.k8s.io/v1 API就是默认开启状态,若要显示开启,参考文档

如下命令能查到,说明启用

kubectl api-versions | grep admissionregistration.k8s.io/v1

编写admission webhook服务器

自定义开发一个类似http服务器,用于接收apiserver的HTTPS的post请求,并从请求判断是否符合预定义的校验策略,然后响应校验结果。

请求类似:

{
  "apiVersion":"imagepolicy.k8s.io/v1alpha1",
  "kind":"ImageReview",
  "spec":{
    "containers":[
      {
        "image":"myrepo/myimage:latest"
      }
    ],
    "namespace":"mynamespace"
  }
}

响应类似:

{
    "apiVersion": "imagepolicy.k8s.io/v1alpha1",
    "kind": "ImageReview",
    "status": {
        "allowed": false,
        "reason": "检查镜像失败!镜像标签不允许使用latest!"
    }
}

本文档中实际从dockerhub中拉取的现成的镜像lizhenliang/image-policy-webhook,也可以使用如下源码和Dockerfile自行编译镜像。

使用Python源码和Dockerfile可以构建一个类似镜像 lizhenliang/image-policy-webhook,如下

#Dockerfile
FROM python
RUN useradd python
RUN mkdir /data/www -p
COPY . /data/www
RUN chown -R python /data
RUN pip install flask -i https://mirrors.aliyun.com/pypi/simple/
WORKDIR /data/www
USER python
CMD python main.py


#main.py源码
from flask import Flask,request
import json

app = Flask(__name__)

@app.route('/image_policy',methods=["POST"])
def image_policy():
    post_data = request.get_data().decode()
    #print("POST数据: %s" %post_data)
    data = json.loads(post_data)
    for c in data['spec']['containers']:
        if ":" not in c['image'] or ":latest" in c['image']:  # 如果镜像里不带冒号或者带:latest说明是镜像使用latest标签
            allowed, reason = False, "检查镜像失败!镜像标签不允许使用latest!"
            break
        else:
            allowed, reason = True, "检查镜像通过."
    print("检查结果: %s" %reason)
    result = {"apiVersion": "imagepolicy.k8s.io/v1alpha1","kind": "ImageReview",
              "status": {"allowed": allowed,"reason": reason}}

    return json.dumps(result,ensure_ascii=False)

if __name__ == "__main__":
    app.run(host="0.0.0.0",port=8080,ssl_context=('/data/www/webhook.pem','/data/www/webhook-key.pem'))

自签https证书

几个用处:

1)webhook服务器需要提供https访问;

2)apiserver访问webhook需要通过https访问,所以需要提供证书来进行身份认证。

举例,亦可以用其他认证方式,甚至关闭认证(生产禁止)

1)ca.crt & ca.key: 用于签发证书的ca

2)webhook.pem & webhook-key.pem:起webhook https服务时用的证书

3)apiserver-client.pem & apiserver-client-key.pem: apiserver访问webhook服务时,携带的的证书

cfssl_gen_certs.sh如下: (ps:需要先安装cfssl,cfssljson

#!/bin/bash

# 保存证书的目录
: ${1?'missing key directory'}
key_dir="$1"
chmod 0700 "$key_dir"
cd "$key_dir"

cat > ca-config.json <<EOF
{
    "signing": {
        "default": {
            "expiry": "87600h"
        },
        "profiles": {
            "kubernetes": {
                "expiry": "87600h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth",
                    "client auth"
                ]
            }
        }
    }
}
EOF

#"CN": "kubernetes",生成自签证书时,需要当做-profile的值传入
cat > ca-csr.json <<EOF
{
    "CN": "kubernetes",   
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF
# 生成ca.pem、ca-key.pem、ca.csr
/usr/local/bin/cfssl gencert -initca ca-csr.json | /usr/local/bin/cfssljson -bare ca -

###########################################################################################################
# 注意“host"为空时,创建的证书不能用于website。
# 10.4.0.16  为容器IP,用webhook服务的实际容器IP替换
cat > webhook-csr.json <<EOF
{
    "CN": "webhook",
    "hosts": [
        "zpsimon.demo.com"
    ],

    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF

# 生成 webhook-csr.pem、webhook-csr-key.pem、webhook-csr.csr
/usr/local/bin/cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes webhook-csr.json | /usr/local/bin/cfssljson -bare webhook

###########################################################################################################

cat > apiserver-client-csr.json <<EOF
{
    "CN": "apiserver",
    "hosts": [],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Beijing",
            "ST": "Beijing"
        }
    ]
}
EOF

# 生成apiserver-client.pem、apiserver-client-key.pem、apiserver-client.csr
/usr/local/bin/cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes apiserver-client-csr.json | /usr/local/bin/cfssljson -bare apiserver-client

将apiserver-client.pem、apiserver-client-key.pem、webhook.pem传输到k8s的master节点

scp apiserver-client.pem     root@k8s-master:/zp/cks/webhook/
scp apiserver-client-key.pem root@k8s-master:/zp/cks/webhook/
scp webhook.pem              root@k8s-master:/zp/cks/webhook/

部署准入webhook服务

直接从现有的dockerhub拉取镜像lizhenliang/image-policy-webhook

运行容器的宿主机:10.90.207.20

cd /zp/webhook/cfssl_cert

nerdctl run -d -u root --name=image-policy-webhook \
-v $PWD/webhook.pem:/data/www/webhook.pem \
-v $PWD/webhook-key.pem:/data/www/webhook-key.pem \
-e PYTHONUNBUFFERED=1 \
-p 8080:8080 \
lizhenliang/image-policy-webhook

看到如下输出,说明webhook服务已经正常启动
【无标题】_第3张图片

先用curl命令验证一下:

echo "" >> /etc/hosts
curl --noproxy "*" --cacert ca.pem --location 'https://zpsimon.demo.com:8080/image_policy' \
--header 'Content-Type: application/json' \
--data '{
  "apiVersion":"imagepolicy.k8s.io/v1alpha1",
  "kind":"ImageReview",
  "spec":{
    "containers":[
      {
        "image":"myrepo/myimage:latest"
      }
    ],
    "namespace":"mynamespace"
  }
}' -vvv
########output#######
{"apiVersion": "imagepolicy.k8s.io/v1alpha1", "kind": "ImageReview", "status": {"allowed": false, "reason": "检查镜像失败!镜像标签不允许使用latest!"}}

注意:
# 1.  10.4.0.4为容器的IP,nerdctl inspect命令可以查看
# 2. --data 格式需要满足一定的格式

如上,结果如预期,将带有":latest"tag的镜像拦截

配置kube-apiserver访问webhook

编写保存访问webhook服务器凭据和地址配置文件(形如kubeconfig文件)

用于apiserver访问webhook时需要一些必要信息(如webhook用到的证书,apiserver提供的证书(用于向webhook展示身份有效性)、webhook服务器的地址等)

connect_webhook.yaml:

cd /zp/cks/webhook
echo "10.90.207.20 zpsimon.demo.com" >> /etc/hosts    # 配置webhook服务地址的解析
cat > connect_webhook.yaml<<-EOF
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority: /zp/cks/webhook/webhook.pem   # webhook服务器启动用到的证书
    server: https://zpsimon.demo.com:8080/image_policy    # 远程webhook服务的接口地址,必须是https(官方要求)
  name: webhook
contexts:
- context:
    cluster: webhook
    user: apiserver
  name: webhook
current-context: webhook
# users 指的是 API 服务器的 Webhook 配置
users:
- name: apiserver
  user:
    client-certificate: /zp/cks/webhook/apiserver-client.pem # Webhook 准入控制器使用的证书
    client-key: /zp/cks/webhook/apiserver-client-key.pem     # 证书匹配的密钥
EOF

配置admissionconfiguration文件

启动 API 服务器时,通过 --admission-control-config-file参数指定刚刚创建的准入控制配置文件(即AdmissionConfiguration的位置)。

cd /zp/cks/webhook
cat > admission_configuration.yaml<<-EOF
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
  configuration:
    imagePolicy:
      kubeConfigFile: /zp/cks/webhook/connect_webhook.yaml   #即上一步骤的 connect_webhook.yaml位置
      allowTTL: 50   #控制批准请求的缓存时间,单位:秒,即同一个请求在该时间内不会再次请求webhook
      denyTTL: 50  #控制拒绝请求的缓存时间,单位:秒,即同一个请求在该时间内不会再次请求webhook
      retryBackoff: 500   #控制重试间隔,单位:毫秒
      defaultAllow: true  #如果后端webhook失效,则默认允许 
EOF

调整kube-apiserver.yaml的配置

在k8s master节点修改/etc/kubernetes/manifests/kube-apiserver.yaml

#修改1,加上ImagePolicyWebhook
    - --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook

#添加1,通过 --admission-control-config-file 参数指定准入控制配置文件的位置
    - --admission-control-config-file=/zp/cks/webhook/admission_configuration.yaml

#添加2,kube-apiserver需要加到宿主机上的/zp/cks/webhook目录下apiserver-client.pem,apiserver-key.pem
    - mountPath: /zp/cks/webhook
      name: image-policy
      readOnly: true

#添加3
  volumes:
  - hostPath:
      path: /zp/cks/webhook
      type: DirectoryOrCreate
    name: image-policy

保存退出,kubelet会自动重启kube-apiserver,若没重启,可systemctl restart kubelet来重启kubelet

特别注意

要重点检查下,一旦出错,kube-apiserver会起不来,集群就无法操作了,可用通过crictl ps -a 查看Exited的容器,并使用crictl logs <容器ID> 来查看报错日志

测试验证

通过样例

[root@k8s-master webhook]# k run pod1 --image=nginx:1.16
pod/pod1 created
[root@k8s-master webhook]# k get po 
NAME        READY   STATUS    RESTARTS         AGE
busybox01   1/1     Running   53 (6h54m ago)   26d
pod1        1/1     Running   0                4s

webhook服务的日志也有显示:

10.90.118.39 - - [13/Nov/2023 04:05:12] "POST /image_policy?timeout=30s HTTP/1.1" 200 -
检查结果: 检查镜像通过.

不通过样例

[root@k8s-master webhook]# k run pod1 --image=nginx:latest
Error from server (Forbidden): pods "pod1" is forbidden: image policy webhook backend denied one or more images: 检查镜像失败!镜像标签不允许使用latest!

webhook服务的日志也有显示:

10.90.118.39 - - [13/Nov/2023 04:05:55] "POST /image_policy?timeout=30s HTTP/1.1" 200 -
检查结果: 检查镜像失败!镜像标签不允许使用latest!

参考文档:

  • https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/#imagepolicywebhook
  • https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/
  • 样例1: https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#experimenting-with-admission-webhooks
  • 样例2:
    博客: https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/
    github代码: https://github.com/stackrox/admission-controller-webhook-demo#deploying-the-webhook-server

你可能感兴趣的:(K8S,kubernetes)