Skip to content

Commit b18e034

Browse files
author
shenchangqing
committed
v0.1.0
1 parent 2e8b4cf commit b18e034

12 files changed

+266
-35
lines changed

Dockerfile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM ubuntu:24.04
2+
3+
COPY k8s-webhook /usr/local/bin/
4+
5+
RUN chmod +x /usr/local/bin/k8s-webhook
6+
7+
CMD ["k8s-webhook"]

Makefile

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
VERSION ?= valvalidating-v0.0.1
1+
VERSION_TAG ?= validating-v0.1.0
2+
REGISTRY_HOST ?= ccr.ccs.tencentyun.com/public-proxy/k8s-webhook
23

34
build:
4-
go build -o k8s-webhook main.go
5-
build-image:
6-
docker build -t shenchangqing/k8s-webhook:$(VERSION)
7-
docker push shenchangqing/k8s-webhook:$(VERSION)
8-
deploy-k8s:
9-
sed -i "s/#{VERSION}/${VERSION}/g" kustomize/overlays/dev/kustomization.yaml
10-
kustomize build kustomize/overlays/dev
5+
go build -o k8s-webhook main.go
6+
build-image: build
7+
docker build -t $(REGISTRY_HOST):$(VERSION_TAG) .
8+
docker push $(REGISTRY_HOST):$(VERSION_TAG)
9+
deploy-k8s: build-image
10+
kustomize build kustomize/overlays/dev/ | sed "s/VERSION_TAG/${VERSION_TAG}/g" | kubectl apply -f -
11+

README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1-
## use controller-manager webhook
1+
## k8s webhook template, you can use it by simple wirte logical code
22

33
- need cert-manager generate tls cert/key
4+
5+
### validationg webhook
6+
- 实现逻辑在`k8s/validation.go`
7+
- pod 打了标签`allow-delete=false`, cannot be delete
8+
### mutating webhook
9+
- 实现逻辑在`k8s/mutating.go`
10+
- 创建 pod 时,自动添加标签`k8s-webhook=test`
11+
### usage
12+
- 快速实现webhook功能
13+
- copy this repo, and write your own logic
14+
- Makefile部署,change VERSION_TAG|REGISTRY_HOST,`make deploy-k8s`
15+
16+
17+
## License
18+
19+
Copyright 2023 changqings.
20+
21+
Licensed under the Apache License, Version 2.0 (the "License");
22+
you may not use this file except in compliance with the License.
23+
You may obtain a copy of the License at
24+
25+
http://www.apache.org/licenses/LICENSE-2.0
26+
27+
Unless required by applicable law or agreed to in writing, software
28+
distributed under the License is distributed on an "AS IS" BASIS,
29+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30+
See the License for the specific language governing permissions and
31+
limitations under the License.

k8s/mutation.go

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package k8s
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"net/http"
7+
"strings"
8+
9+
"gomodules.xyz/jsonpatch/v2"
10+
admission_v1 "k8s.io/api/admission/v1"
11+
v1 "k8s.io/api/admissionregistration/v1"
12+
k8s_error "k8s.io/apimachinery/pkg/api/errors"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/client-go/kubernetes"
15+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
16+
)
17+
18+
func MutatingPod() http.Handler {
19+
return &admission.Webhook{
20+
Handler: admission.HandlerFunc(
21+
func(ctx context.Context, req admission.Request) admission.Response {
22+
if req.AdmissionRequest.Operation == admission_v1.Create {
23+
slog.Info("create pod patch labels")
24+
return admission.Patched(
25+
"add label",
26+
jsonpatch.JsonPatchOperation{
27+
Operation: "add",
28+
Path: "/metadata/labels/k8s-webhook",
29+
Value: "test",
30+
},
31+
)
32+
} else {
33+
return admission.Allowed("ok")
34+
}
35+
},
36+
),
37+
}
38+
}
39+
40+
func CreateMutatingWebhook(k8sClient *kubernetes.Clientset) error {
41+
42+
mutateServiceName := strings.Split(webhookServiceName, ".")[0]
43+
caCrt, err := getCaBundle(k8sClient, webhookSecretName, webhookNamespace)
44+
if err != nil {
45+
return err
46+
}
47+
48+
mutate_webhook := v1.MutatingWebhookConfiguration{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Namespace: webhookNamespace,
51+
Name: mutateServiceName,
52+
},
53+
Webhooks: []v1.MutatingWebhook{
54+
{
55+
Name: "pod-webhook.some.cn",
56+
NamespaceSelector: &metav1.LabelSelector{
57+
MatchLabels: map[string]string{
58+
"kubernetes.io/metadata.name": "default",
59+
},
60+
},
61+
Rules: []v1.RuleWithOperations{
62+
{
63+
Operations: []v1.OperationType{
64+
v1.Create,
65+
},
66+
Rule: v1.Rule{
67+
APIGroups: []string{""},
68+
APIVersions: []string{"v1"},
69+
Resources: []string{"pods"},
70+
},
71+
},
72+
},
73+
ClientConfig: v1.WebhookClientConfig{
74+
Service: &v1.ServiceReference{
75+
Namespace: webhookNamespace,
76+
Name: mutateServiceName,
77+
Path: &WebhookMutatePath,
78+
Port: &TLSPort,
79+
},
80+
CABundle: caCrt,
81+
},
82+
AdmissionReviewVersions: []string{"v1"},
83+
SideEffects: func() *v1.SideEffectClass {
84+
var none v1.SideEffectClass = "None"
85+
return &none
86+
}(),
87+
},
88+
},
89+
}
90+
91+
_, err = k8sClient.AdmissionregistrationV1().MutatingWebhookConfigurations().
92+
Create(context.Background(), &mutate_webhook, metav1.CreateOptions{})
93+
if err != nil {
94+
if k8s_error.IsAlreadyExists(err) {
95+
slog.Info("mutatingWebhookConfiguration already exists", "name", mutate_webhook.Name, "namespace", mutate_webhook.Namespace)
96+
return nil
97+
} else {
98+
return err
99+
}
100+
}
101+
slog.Info("mutatingWebhookConfiguration create success", "name", mutate_webhook.Name, "namespace", mutate_webhook.Namespace)
102+
103+
return nil
104+
}

k8s/tls.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ import (
1919
)
2020

2121
var (
22-
TLSCertDir = "k8s-tls"
23-
CertName = "cert.crt"
24-
KeyName = "key.crt"
25-
WebhookValidPath = "/webhook/validate"
26-
webhookNamespace = "default"
22+
TLSCertDir = "k8s-tls"
23+
CertName = "cert.crt"
24+
KeyName = "key.crt"
25+
WebhookValidPath = "/webhook/validate"
26+
WebhookMutatePath = "/webhook/mutate"
27+
webhookNamespace = "default"
2728

2829
TLSPort int32 = 9443
2930
//ca

k8s/validation.go

+24-7
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,37 @@ import (
77
"net/http"
88
"strings"
99

10+
admission_v1 "k8s.io/api/admission/v1"
1011
v1 "k8s.io/api/admissionregistration/v1"
1112
k8s_error "k8s.io/apimachinery/pkg/api/errors"
1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
"k8s.io/client-go/kubernetes"
1415
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
1516
)
1617

17-
func ValidatingPod() http.Handler {
18+
func ValidatingPod(k8sClient *kubernetes.Clientset) http.Handler {
1819
return &admission.Webhook{
1920
Handler: admission.HandlerFunc(
2021
func(ctx context.Context, req admission.Request) admission.Response {
21-
if req.Namespace == "default" && req.Operation == "delete" {
22+
podCanNotBeDeleted := false
23+
24+
podName := req.Name
25+
podNamespace := req.Namespace
26+
27+
pod, err := k8sClient.CoreV1().Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{})
28+
if err != nil {
29+
return admission.ValidationResponse(false, "get pod error")
30+
}
31+
32+
if v, ok := pod.Labels["allow-delete"]; ok && v == "false" {
33+
podCanNotBeDeleted = true
34+
}
35+
36+
if req.Operation == admission_v1.Delete && podCanNotBeDeleted {
37+
slog.Info("pod can not be deleted labels allow-delete=false", "name", req.Name, "namespace", req.Namespace)
2238
return admission.ValidationResponse(false, "not allow by webhook")
2339
}
24-
return admission.ValidationResponse(true, "ok, you can do it")
40+
return admission.ValidationResponse(true, "ok")
2541
},
2642
),
2743
}
@@ -42,10 +58,10 @@ func CreateValidatingWebhook(k8sClient *kubernetes.Clientset) error {
4258
},
4359
Webhooks: []v1.ValidatingWebhook{
4460
{
45-
Name: webhookServiceName,
61+
Name: "pod-webhook.some.cn",
4662
NamespaceSelector: &metav1.LabelSelector{
4763
MatchLabels: map[string]string{
48-
"kubernetes.io/metadata": "default",
64+
"kubernetes.io/metadata.name": "default",
4965
},
5066
},
5167
Rules: []v1.RuleWithOperations{
@@ -54,7 +70,7 @@ func CreateValidatingWebhook(k8sClient *kubernetes.Clientset) error {
5470
v1.Delete,
5571
},
5672
Rule: v1.Rule{
57-
APIGroups: []string{"core"},
73+
APIGroups: []string{""},
5874
APIVersions: []string{"v1"},
5975
Resources: []string{"pods"},
6076
},
@@ -82,12 +98,13 @@ func CreateValidatingWebhook(k8sClient *kubernetes.Clientset) error {
8298
Create(context.Background(), &valid_webhook, metav1.CreateOptions{})
8399
if err != nil {
84100
if k8s_error.IsAlreadyExists(err) {
85-
slog.Info("ValidatingWebhookConfiguration already exists, %s.%s", valid_webhook.Name, valid_webhook.Namespace)
101+
slog.Info("validatingWebhookConfiguration already exists", "name", valid_webhook.Name, "namespace", valid_webhook.Namespace)
86102
return nil
87103
} else {
88104
return err
89105
}
90106
}
107+
slog.Info("validatingWebhookConfiguration create success", "name", valid_webhook.Name, "namespace", valid_webhook.Namespace)
91108

92109
return nil
93110
}
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
apiVersion: v1
3+
kind: ServiceAccount
4+
metadata:
5+
name: k8s-webhook
6+
---
7+
apiVersion: rbac.authorization.k8s.io/v1
8+
kind: ClusterRole
9+
metadata:
10+
name: k8s-webhook
11+
rules:
12+
- apiGroups:
13+
- ''
14+
resources:
15+
- pods
16+
verbs:
17+
- get
18+
- apiGroups:
19+
- ''
20+
resources:
21+
- secrets
22+
verbs:
23+
- get
24+
- apiGroups:
25+
- admissionregistration.k8s.io
26+
resources:
27+
- validatingwebhookconfigurations
28+
- mutatingwebhookconfigurations
29+
verbs:
30+
- create
31+
- apiGroups:
32+
- cert-manager.io
33+
resources:
34+
- clusterissuers
35+
- certificates
36+
verbs:
37+
- create
38+
---
39+
apiVersion: rbac.authorization.k8s.io/v1
40+
kind: ClusterRoleBinding
41+
metadata:
42+
name: k8s-webhook
43+
roleRef:
44+
apiGroup: rbac.authorization.k8s.io
45+
kind: ClusterRole
46+
name: k8s-webhook
47+
subjects:
48+
- kind: ServiceAccount
49+
name: k8s-webhook

kustomize/base/deployment.yaml

+5-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ spec:
2626
failureThreshold: 3
2727
httpGet:
2828
path: /health_check
29-
port: http-check
30-
scheme: HTTPS
29+
port: 8080
30+
scheme: HTTP
3131
initialDelaySeconds: 10
3232
periodSeconds: 15
3333
successThreshold: 1
@@ -36,8 +36,8 @@ spec:
3636
failureThreshold: 3
3737
httpGet:
3838
path: /health_check
39-
port: http-check
40-
scheme: HTTPS
39+
port: 8080
40+
scheme: HTTP
4141
initialDelaySeconds: 5
4242
periodSeconds: 15
4343
successThreshold: 1
@@ -54,4 +54,5 @@ spec:
5454
dnsPolicy: ClusterFirst
5555
restartPolicy: Always
5656
schedulerName: default-scheduler
57+
serviceAccountName: k8s-webhook
5758
terminationGracePeriodSeconds: 30

kustomize/base/kustomization.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
apiVersion: kustomize.config.k8s.io/v1beta1
22
resources:
33
- deployment.yaml
4-
- service.yaml
4+
- service.yaml
5+
- clusterrolebinding.yaml

kustomize/base/service.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
apiVersion: v1
33
kind: Service
44
metadata:
5-
name: k8s-webhook
5+
name: pod-webhook
66
spec:
77
ipFamilies:
88
- IPv4
99
ports:
1010
- name: https-443
11-
port: 443
11+
port: 9443
1212
protocol: TCP
1313
targetPort: https
1414
sessionAffinity: None

kustomize/overlay/dev/kustomization.yaml kustomize/overlays/dev/kustomization.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
apiVersion: kustomize.config.k8s.io/v1beta1
22
images:
3-
- newName: shenchangqing/k8s-webhook
3+
- newName: ccr.ccs.tencentyun.com/public-proxy/k8s-webhook
44
name: image_name
5-
newTag: #{VERSION}
5+
newTag: VERSION_TAG
66
commonLabels:
77
app: k8s-webhook
88
kind: Kustomization

0 commit comments

Comments
 (0)