admission controller是一段代码,它会在请求通过认证和授权之后、对象被持久化之前拦截到达 API 服务器的请求。控制器编译进 kube-apiserver 可执行文件,并且只能由集群管理员配置。
这有点类似于插件的概念,官方提供了一系列的插件来帮助我们实现一些api层面的简单处理:
使用准入控制器 | Kubernetes
我们也可以通过自己编写一段代码二次开发来实现更为高级更为复杂的需求,
官方有具体的实例,该实例的用途是
only allow pods to pull images from specific registry.
即仅允许pod从指定的镜像仓库拉取image,否则apiserver将会拒绝本次请求。
从运维角度,我们也可以借此约束非集群管理员的某些行为来实现安全运维的目的,或者通过admission controller的另外一个用途:修改请求,通过自动为k8s声明式的api自动注入配置规则,为非集群管理员的一些非约性的apply行为解绑。
举几个简单场景:
1.开发人员可能只有对应namespace下deployment的create,upduate,delete权限,,我们通过自动注入私仓的dockerconfig.json的secret对象作为imagePullSecret,来让开发人员尽可能少关注与应用本身描述无关的,诸如此类的配置选项。
2.传统微服务架构依赖注册中心,因此在pod终止时如何从注册中心下线来实现优雅停机成为了问题,为了实现低代码侵入,将这部分问题的解决下沉到运维层面,我们可以使用k8s提供prestop机制来实现pod在收到sigterm信号之前就处理掉这个问题。
接下来我们来看具体实现
├── go.mod
├── go.sum
├── lib
│ ├── config.go //用于配置tls证书和密钥
│ ├── convert.go //将具体的error转换成webhook回调返回的对象
│ ├── pods.go //输入请求对象,return一个返回对象,需要我们在其中实现判断逻辑
│ └── schema.go //存放了用于将byte转换成请求对象的反序列化器
├── main.go //http server并配置tls,反序列化后调用pods.go获得返回对象
其中的大部分代码都可以在kubernetes/main.go at release-1.21 · kubernetes/kubernetes · GitHub
中获取到。
main.go中httpserver的handler:
http.HandleFunc("/pods", func(writer http.ResponseWriter, request *http.Request) {
var body []byte
if request.Body != nil {
if data, err := ioutil.ReadAll(request.Body); err == nil {
body = data
}
}
//第二步
reqAdmissionReview := v1.AdmissionReview{} //请求
resAdmissionReview := v1.AdmissionReview{ //响应 ---完整的对象在 https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#response
TypeMeta: metav1.TypeMeta{
Kind: "AdmissionReview",
APIVersion: "admission.k8s.io/v1",
},
}
//第三步,把body decode成对象
deserializer := lib.Codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(body, nil, &reqAdmissionReview); err != nil {
resAdmissionReview.Response = lib.ToV1AdmissionResponse(err)
} else {
resAdmissionReview.Response = lib.AdmitPods(reqAdmissionReview)
}
resAdmissionReview.Response.UID = reqAdmissionReview.Request.UID
marshal, _ := json.Marshal(resAdmissionReview)
writer.Write(marshal)
})
完成编码后,我们需要生成假证书,可以通过cfssl工具生成,参考下面这篇文章:
手把手-安装-cfssl - 光速狼 - 博客园
这里说下大概的步骤——
1.创建ca配置文件 (ca-config.json)
"ca-config.json":可以定义多个 profiles,分别指定不同的过期时间、使用场景等参数;后续在签名证书时使用某个 profile;
"signing":表示该证书可用于签名其它证书;生成的 ca.pem 证书中 CA=TRUE
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"server": {
"usages": ["signing"],
"expiry": "87600h"
}
}
}
}
2.创建ca证书签名(ca-csr.json)
{
"CN": "Kubernetes", //SelfSignedCA
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "zh",
"L": "bj",
"O": "bj",
"OU": "CA"
}
]
}
3.生成ca证书和私钥
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
——生成ca私钥 ca-key.pem 和 ca公钥ca.pem
4.创建服务端证书签名(server-csr.json)
——这个服务端可以是etcd,docker,apiserver等
{
"CN": "admission", //etcd
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "zh",
"L": "bj",
"O": "bj",
"OU": "bj"
}
]
}
5.生成服务端证书server.pem和私钥server-key.pem,也就是签发证书
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=myhook.ops.svc \
-profile=server \
server-csr.json | cfssljson -bare server
注意hostname与service的fqdn域名相对应。
1.cat ca.pem | base64
获取ca公钥填入admission webhook资源yaml的cabundle
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: myhook
webhooks:
- clientConfig:
caBundle: |
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURoakNDQW02Z0F3SUJBZ0lVZU41ZU5td29t
RXZMREFvSUpnYjhTVUZUandFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1NURUxNQWtHQTFVRUJoTUNl
bWd4Q3pBSkJnTlZCQWNUQW1KcU1Rc3dDUVlEVlFRS0V3SmlhakVMTUFrRwpBMVVFQ3hNQ1EwRXhF
ekFSQmdOVkJBTVRDa3QxWW1WeWJtVjBaWE13SGhjTk1qSXdPREF5TURJeU56QXdXaGNOCk1qY3dP
REF4TURJeU56QXdXakJKTVFzd0NRWURWUVFHRXdKNmFERUxNQWtHQTFVRUJ4TUNZbW94Q3pBSkJn
TlYKQkFvVEFtSnFNUXN3Q1FZRFZRUUxFd0pEUVRFVE1CRUdBMVVFQXhNS1MzVmlaWEp1WlhSbGN6
Q0NBU0l3RFFZSgpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMVk5rY1pUcWpkdHJD
Yzh3YkhqMXlVejhucHl2QkVECkJxNmFyZElOU2NBRldyT0wzNVRJVmNOZnFQRWlhbkxhbjJsbkFG
dWp1UE9WaFhYOS9CMzhoSkU1QjJBeXhTYXgKYzdxM1lUWnpYS0xsQ2c1UVc0Wlk2SVFVdTdGbHZ2
T0RIRjEyNTl6OEd4dGFsdjQ1Z0pYSDV5Nnp4WlNBMEJxZgpURnR3bHFRb1krMVN3QkhtZ2lBRWpX
ekxZV0cydHVRRndkZW9YR2tWd0Y2dkwzM1NoM09yb1ZHQTQ2aVRiMUdKCkgxenBKNWpVYlpZbFAx
SFVta0R4dnF1NDJJaGJnN1lPNWIvUktMaWpvVVJza0p2d1dzb3dvVU03dU1GSllrdUsKdFc3MlJ3
S1UwNnoxN20xUmxZZlpzWkt2NU9ONjd3RlR4M0plQUlTcno1OHBKN2cwaUwySTh0MENBd0VBQWFO
bQpNR1F3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUl3SFFZ
RFZSME9CQllFCkZPWWRwMzJkL09VanM0VkpMMDZyRTJtZlAwZkZNQjhHQTFVZEl3UVlNQmFBRk9Z
ZHAzMmQvT1VqczRWSkwwNnIKRTJtZlAwZkZNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUFrYXVN
T01OQU9DTUN2QmFqR0JhYUhXRFpBekdvTAo5cHJhWWRDWEwvQkw4Z25qczJsZmRjcWZZclpUM3pY
Q3IzNXlmUEJSZitNUFRtKzdlQkF5bHlCeWlNK0xPcDRMCkM5MEVWT2NUM0hxK2EvUlBURjJEbmxB
emwva1JkMGN3RFM2WTdLUGovQWxlc3FzVUNQVXVLbVlnb3hadmNqa04KU1NYVEs4VWk2Vncyekd2
MzU5bFR0QjA3Y3paZjhYR09xeEZpQi9tUUVERldOOGxxYkF3b2k1NHVZbHlsaXowcApyWFp0cHhy
N0tza0dHb08rcTVEdTRwVnZUNlFUakd6NzNlYktacnRieURsbzBnbDdCZmxPTHBEM1d0WWw0b3N4
CmlYZ3NYaVNxdmNUcEkrbGJLNUQ3dlMrS1pybXpmaFozWWRWdW9mR083UCtsMFUwKzZaUytjMi9v
Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
service:
name: myhook
namespace: ops
path: /pods
failurePolicy: Fail
sideEffects: NoneOnDryRun
name: myhook.xxx.com
admissionReviewVersions: ["v1", "v1beta1"]
namespaceSelector:
matchExpressions:
- key: env
operator: In
values: ["prod", "pre", "dev", "test"]
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
——service对应webhook的service,namespaceSelector通过label限定作用域命名空间,rules匹配GVK。
2.把上一节生成服务端私钥和证书创建成tls类型的secret
kubectl create secret tls myhook --cert=server.pem --key=server-key.pem -n ops
3.在我们的webhook代码里添加tls认证
tlsConfig := lib.Config{
CertFile: "/etc/webhook/certs/tls.crt",
KeyFile: "/etc/webhook/certs/tls.key",
}
server := http.Server{
Addr: ":443",
TLSConfig: lib.ConfigTLS(tlsConfig),
}
err := server.ListenAndServeTLS("", "")
if err != nil {
panic(err)
}
通过交叉编译我们得到myhook可执行文件。
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
4. 创建webhook deployment(仅测试用,需要指定节点)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myhook
namespace: ops
spec:
replicas: 1
selector:
matchLabels:
app: myhook
template:
metadata:
labels:
app: myhook
spec:
nodeName: k8s1
containers:
- name: myhook
image: alpine:3.12
imagePullPolicy: IfNotPresent
command: ["/app/myhook"]
volumeMounts:
- name: hooktls
mountPath: /etc/webhook/certs
readOnly: true
- name: app
mountPath: /app
ports:
- containerPort: 443
volumes:
- name: app
hostPath:
path: /root/app
- name: hooktls
secret:
secretName: myhook
---
apiVersion: v1
kind: Service
metadata:
name: myhook
namespace: ops
labels:
app: myhook
spec:
type: ClusterIP
ports:
- port: 443
targetPort: 443
selector:
app: myhook
——把服务端证书和私钥通过secret的方式挂在到容器里,并通过443端口对外提供服务。
测试api
最后可以通过apply一个新的pod来测试是否通过webhook的验证规则,如果未通过,则不会继续持久化对象。如果通过,则会触发对apiserver的回调。
% kubectl apply -f newpod.yaml
Error from server: error when creating "newpod.yaml": admission webhook "myhook.xxx.com" denied the request: container's image must be from private hub.
阅读量:2047
点赞量:0
收藏量:0