Una de las ventajas de Kubernetes es su alto grado de “customizacion”. Prácticamente como administradores podemos programar cualquier aspecto del cluster. La potencia de los “Admission controllers” reside en el hecho de que podemos interceptar las peticiones a la API de Kubernetes antes de que el objeto se cree.
Existen dos tipos de Admission Controllers. (Mutating Admission Controllers) que pueden mutar o cambiar un objeto antes de que se cree y (Validating Admission Controllers) que permiten o deniegan un objeto.
En estas primeras entradas vamos a crear un Mutating Admission de una manera sencilla para que se entienda el concepto.
Como se puede observar en la imagen los Mutating Webhook interceptan el Request de la Api, la modifican y envian la respuesta(Response).
Los pasos a seguir serian estos:
- Crear un servidor Https
- Programar el Webhook
En esta primera entrada nos vamos a dedicar solo al punto primero. Crear el servidor Https que escuche las peticiones de la API en GO.
Para empezar nos creamos un proyecto go y ejecutamos el «go mod init».
El código del servidor web es sencillo y no es necesaria ningún tipo de explicación. Utilizaremos esto codigo para simplemente ver si la llamada al webHook es correcta.
package main
import (
"log"
"net/http"
)
func webHook(w http.ResponseWriter, r *http.Request) {
log.Printf(“Called Webhook”)
}
func main() {
http.HandleFunc("/mutate", webHook)
log.Fatal(http.ListenAndServeTLS(":443", "/certs/webhook.crt", "/certs/webhook-key.pem", nil))
}
Creamos un Dockerfile para crear la Imagen.
FROM golang:alpine
WORKDIR /code
COPY . /code
RUN go build -o kcontroller
ENTRYPOINT ["./kcontroller"]
Construimos la imagen y la subimos al repositorio
docker image build -t anmartsan/webhook:latest .
docker push anmartsan/webhook:latest
Quiza la parte mas complicada es la creacion de los certificados que utiliza el web server. Debemos añadir un CSR a kubernetes y validarlo y utilizar ese certificado ya validado en un Secret al que accedera el deployment.
Utilizaremos la herramienta «cfssl». Primero tenemos que editar el JSON del csr. Teniendo en cuenta la documentacion de Kubernetes .
“This admission controller limits the Node
and Pod
objects a kubelet can modify. In order to be limited by this admission controller, kubelets must use credentials in the system:nodes
group, with a username in the form system:node:<nodeName>”
.
El csr.json quedaria de este modo:
{
"CN": "system:node:kcontroller.default.svc.cluster.local",
"hosts": [
"kcontroller.default.svc",
"kcontroller.default.svc.cluster.local"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [{
"O": "system:nodes"
}]
}
cat ./csr/csr.json |cfssl genkey - | cfssljson -bare webhook
2021/03/31 10:31:48 [INFO] generate received request
2021/03/31 10:31:48 [INFO] received CSR
2021/03/31 10:31:48 [INFO] generating key: rsa-2048
2021/03/31 10:31:49 [INFO] encoded CSR
Una vez que tenemos el Certifcated Signing Request y la clave primaria para el servidor web tenemos que aprobarlos contra el CA del cluster kubernetes.
cat webhook.csr |base64 -w 0
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQzlEQ0NBZHdDQVFBd1V6RVZNQk1HQTFVRUNoTU1jM2x6ZEdWdE9tNXZaR1Z6TVRvd09BWURWUVFERXpGegplWE4w………………………………
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: kcontroller.default
spec:
groups:
- system:authenticated
request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQzlEQ0NBZHdDQVFBd1V6RVZNQk1HQTFVRUNoTU1jM2x6ZEdWdE9tNXZaR1Z6TVRvd09BWURWUVFERXpGegplWE4w………………………………
signerName: kubernetes.io/kubelet-serving
usages:
- server auth
- digital signature
- key encipherment
kubectl create -f kubernetes/cert.yaml
certificatesigningrequest.certificates.k8s.io/kcontroller.default created
kubectl certificate approve kcontroller.default
certificatesigningrequest.certificates.k8s.io/kcontroller.default approved
kubectl get csr kcontroller.default
NAME AGE SIGNERNAME REQUESTOR CONDITION
kcontroller.default 87s kubernetes.io/kubelet-serving kubernetes-admin Approved,Issued
Con esto ya tendríamos el certificado y la clave privada para el servidor web.
Creamos ahora el “MutatingWebhookConfiguration”. Para ello podemos los ejemplos de la documentacion de kubernetes “https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/”
El caBundle lo tenemos que sacar de nuestro cluster con por ejemplo:
‘kubectl config view –minify –raw –output ‘jsonpath={..cluster.certificate-authority-data}’
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: kcontroller
webhooks:
- name: kcontroller.default.svc.cluster.local
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
failurePolicy: Ignore
clientConfig:
service:
path: "/mutate"
port: 443
name: kcontroller
namespace: default
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRF……….
sideEffects: None
admissionReviewVersions:
- "v1"
Como se puede ver la operación es «CREATE» de los resources «pods». Es decir el clientConfig se ejecutara en el caso de que la API reciba una operación de creación de Pods.
kubcetl create -f admission.yaml
mutatingwebhookconfiguration.admissionregistration.k8s.io/kcontroller created
Generamos el deployment que se va ha encargar de interceptar las peticiones del admission controller y el secret que llevara la autorizacion TLS (llave privada y certificado que hemos creado antes):
apiVersion: apps/v1
kind: Deployment
metadata:
name: kcontroller
spec:
selector:
matchLabels:
name: kcontroller
replicas: 1
template:
metadata:
labels:
name: kcontroller
spec:
volumes:
- name: certs
secret:
secretName: kcontroller
containers:
- image: anmartsan/webhook
name: kcontroller
resources:
requests:
cpu: "20m"
memory: "55M"
ports:
- containerPort: 443
name: kcontroller
volumeMounts:
- name: certs
mountPath: /certs
imagePullPolicy: Always
restartPolicy: Always
---
apiVersion: v1
kind: Secret
metadata:
name: kcontroller
data:
webhook-key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBd3M0bW91cVFPbUtVN1QzNnYzbmxBWkVwQ0poY2oyN1Rxa3BzRStNaWZZNjRDcDZ4CmlHdWtjQ1FUWHF3MWp5bm9LdW5JNXpXbFBHeH.........................
webhook.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURsakNDQW42Z0F3SUJBZ0lSQUthaVpzNGRiL2wrMXFoYXdIQXByeUl3RFFZSktvWklodmNOQVFFTEJRQXcKRlRFVE1CRUdBMVVFQXhNS2EzVmlaWEp1WlhSbGN6QW.........................
kubectl create -f ./kubernetes/webhook.yaml
Podemos hacer la comprobación para ver si intercepta las llamadas a la creación de un Pod. Simplemente ejecutamos en un terminal
k logs -f kcontroller-f58fb84dc-vpts7
Y en otro terminal diferente creamos un Pod
kubernetes run nginx --image=nginx –restart=Never
Hasta ahora el webHook no hace nada, en el siguiente Post le añadiremos la funcionalidad al webHook.