给k8s集群增加镜像校验能力,不允许使用带:latest
标签的镜像创建资源,允许使用携带具体标签的镜像来创建资源,比如:nginx:1.16
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”
admissionregistration.k8s.io/v1
版本的API从k8s v1.16开始,admissionregistration.k8s.io/v1 API就是默认开启状态,若要显示开启,参考文档
如下命令能查到,说明启用
kubectl api-versions | grep admissionregistration.k8s.io/v1
自定义开发一个类似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'))
几个用处:
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/
直接从现有的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
先用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的镜像拦截
用于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
启动 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
在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!