Konfiguration von Kubernetes Admission Controller
Nachdem das Konzept von Kubernetes Admission Controllern in dem Beitrag Kubernetes Admission Controller beschrieben wurde und in Beispiel für Admission Controller gezeigt wurde, wie ein Admission Controller Webhook implementiert werden kann, geht es nun darum, diesen in Betrieb zu nehmen.
Der Webhook an sich kann sowohl in dem selben Kubernetes-Cluster betrieben werden, in dem er auch verwendet wird, als auch völlig unabhängig von Kubernetes.
Damit Kubernetes bei einem so mächtigen Element sicherstellen kann, dass mit dem korrekten Webhook kommuniziert wird, muss die Verbindung TLS gesichert sein und Kubernetes dem Zertifikat vertrauen.
Für den Aufbau der Trust-Chain werden entweder die vom zugrundeliegenden Betriebssystem bereitgestellten CA-Zertifikate verwendet, oder es kann in der Webhook Konfiguration explizit ein CA Zertifikat angegeben werden.
Um das Beispiel möglichst vollständig nachvollziehbar zu gestalten, wird mit Hilfe der internen Certificate Authority von Kubernetes ein Zertifikat erstellt, wie in diesem Artikel beschrieben:
Verwendung von Kubernetes Zertifikaten.
Wird der Webhook in Kubernetes betrieben, so muss vorher natürlich ein Container-Image gebaut werden und für das Image ein Deployment samt entsprechendem Service in Kubernetes angelegt werden.
Der Webhook für den Admission Controller wird, wenn er in Kubernetes betrieben wird, typischwerweise lediglich in einem Namespace bereitgestellt. Unabhängig davon kann der Admission Controller in beliebigen Namespaces zur Verwendung konfiguriert werden.
In der folgenden Übersicht werden die relevante Daten und die im Beispiel verwendeten Namen der zugehörigen Objekte in Kubernetes aufgeführt:
-
Namespace: drone
-
TLS Secret: ttlmac-service-tls
-
Image: ttlmac
-
Deployment: ttlmac-deployment
-
Service: ttlmac-service (FQN ttlmac-service.drone.svc.cluster.local)
-
Webhook: ttl-job-mutator-webhook
Ob in einem Kubernetes Cluster die Admission Controller als Feature aktiviert sind, kann durch durch die Kubernetes API abgefragt werden.
Dazu werden die verfügbaren APIs abgefragt und auf den Typ admissionregistration.k8s.io
geprüft.
$ kubectl api-versions | grep admissionregistration.k8s.io
admissionregistration.k8s.io/v1beta1
Als nächstes wird ein TLS Zertifikat für den Webhook erzeugt.
Dabei ist es nicht erforderlich, dass zwingend, wie im Beispiel, die Kubernetes-CA verwendet wird, auch ein self-signed Zertifikat kann verwendet werden.
Wichtig ist dabei, dass ein SAN DNS Eintrag mit dem Wert angelegt wird, über den später der Zugriff auf den zugehörigen Service erfolgt.
Allgemein ist dies dann <service>.<ns>.svc
.
$ mkdir certs
$ SANS="subjectAltName=DNS:ttlmac-service.drone.svc.cluster.local,DNS:ttlmac-service.drone.svc"
$ openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -days 365 \
-subj "/CN=ttlmac-service.drone.svc" -addext $(printf "$SANS")
$ cat <<EOF | kubectl create -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
name: ttlmac-service.drone.svc
spec:
groups:
- system:authenticated
request: $(cat certs/server.csr | base64 | tr -d '\n')
usages:
- digital signature
- key encipherment
- server auth
EOF
$ kubectl certificate approve ttlmac-service.drone.svc
$ sleep 5
$ kubectl get csr ttlmac-service.drone.svc -o jsonpath='{.status.certificate}' | base64 --decode > certs/server.crt
$ kubectl delete certificatesigningrequest.certificates.k8s.io/ttlmac-service.drone.svc
Nach diesen Befehlen sollten die Dateien server.crt
und server.key
erstellt worden sein, die das Zertifikat und den zugehörigen privaten Schlüssel beinhalten.
Damit diese für den Webhook zur Verfügung stehen, wird daraus ein Kubernetes Secret vom Typ tls
erzeugt.
$ kubectl -n drone create secret tls --cert=certs/server.crt --key=certs/server.key ttlmac-service-tls
Nun kann der Webhook selbst als Deployment angelegt werden. Die Schritte zur Erzeugung eines Container-Images und Bereitstellung in einer geeigneten Registry werden an dieser Stelle als bereits erledigt angesehen.
Im Deployment wird das eben erzeugte Secret referenziert, um damit der Anwendung die für die TLS Absicherung benötigten Dateien bereitzustellen.
apiVersion: apps/v1
kind: Deployment
metadata:
name: ttlmac-deployment
namespace: drone
labels:
app: ttlmac
spec:
selector:
matchLabels:
app: ttlmac
template:
metadata:
labels:
app: ttlmac
name: ttlmac
spec:
containers:
- name: ttlmac-app
image: localhost:5000/ttl-mac:3
env:
- name: PYTHONUNBUFFERED
value: "1"
ports:
- containerPort: 4443
volumeMounts:
- name: tls
mountPath: /app/certs
readOnly: true
volumes:
- name: tls
secret:
secretName: ttlmac-service-tls
---
kind: Service
apiVersion: v1
metadata:
name: ttlmac-service
namespace: drone
spec:
selector:
app: ttlmac
ports:
- protocol: TCP
port: 443
targetPort: 4443
type: ClusterIP
Kubernetes muss nun noch zur Verwendung des Admission Controllers konfiguriert werden.
Dies geschieht durch eine MutatingWebhookConfiguration
oder eine ValidatingWebhookConfiguration
.
Für die Konfiguration wird jedoch noch das CA-Zertifikat benötigt, mit dem die Vertrauensbeziehung sichergestellt wird.
Wird dies ausgelassen, so wird lediglich den System-Zertifikaten vertraut - was aber möglicherweise mehr sind, als erwünscht.
Ist das Zertifikat im Property caBundle
spezifiziert, so muss das TLS Zertifikat genau von dieser CA signiert worden sein.
Da das Zertifikat mit der Kubernetes internen CA erstellt wurde, und diese typischerweise nicht in den Systemzertifikaten aufgeführt ist, muss das entsprechende CA-Zertifikat von Kubernetes in jedem Fall spezifiziert werden.
Die Ermittlung des CA-Zertifikats kann beispielsweise mit kubectl
erfolgen.
$ kubectl get configmap -n kube-system extension-apiserver-authentication \
-o=jsonpath='{.data.client-ca-file}' | base64 | tr -d '\n'
LS0tLS1CRUdJTiBDRVJUSU...
Nun kann der Admission Controller konfiguriert werden.
Neben dem zu verwendenden Dienst und dem anzufragenden HTTP-Path muss spezifiziert werden, bei welchen Kubernetes-API-Operationen der Admission Controller aufzurufen ist.
Da das Ziel ist, bei Jobs das Attribut ttlSecondsAfterFinished
zu setzen, falls dies nicht definiert ist, wird die folgende Regel zum Aufruf verwendet:
-
operations: [ "CREATE" ]
- bei neu erzeugten Objekten -
apiGroups: ["batch"]
- bei Objekten der API-Gruppebatch
-
apiVersions: ["*"]
- egal, welche API Version -
resources: ["jobs"]
- bei Objekten vom Typjobs
Damit der Mutating Admission Controller nur in einem spezifischen Namespace angewendet wird, wird dies noch über den namespaceSelector
auf den Namespace mit Namen drone
eingeschränkt.
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: ttl-job-mutator-webhook
labels:
app: mutating-admission-webhook
webhooks:
- name: ttl-job-mac.drone.svc.cluster.local
clientConfig:
service:
name: ttlmac-service
namespace: drone
path: "/"
caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0.....S0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
rules:
- operations: [ "CREATE" ]
apiGroups: ["batch"]
apiVersions: ["*"]
resources: ["jobs"]
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- {key: name, operator: In, values: [drone]}
matchLabels:
#only-auto-ttl: enabled
Nachdem der Webhook konfiguriert wurde, kann er getestet werden. Am einfachsten geschieht dies, indem ein passender Job erzeugt wird. Im erzeugten Objekt sollte dann die durch den Mutating Admission Controller vorgenommene Veränderung sichtbar sein.
$ $ kubectl run demo -n drone --image=alpine --restart=OnFailure -it -- \
/bin/sh -c "sleep 20; echo 'working'; sleep 90;"
If you don't see a command prompt, try pressing enter.
working
Falls es ein Problem mit dem Webhook gibt, so wird Kubernetes die Erzeugung des Job Objekts zurückweisen.
Dazu wurde in der Konfiguration failurePolicy: Fail
hinterlegt, so kommt es nicht zu unbemerkten Fehlern.
Das erzeugte Objekt wird mit kubectl
inspiziert und sollte dann das ttlSecondsAfterFinished
Property aufweisen.
$ kubectl -n drone get job demo -o yaml
...
ttlSecondsAfterFinished: 3600
...
Falls die Ausgabe nicht das Property enthält, der Job jedoch erfolgreich erzeugt wurde, handelt es sich um eine spezielle Situation:
Es wurde zwar erfolgreich der Admission Controller aufgerufen, der API Server kennt jedoch das Property ttlSecondsAfterFinished
nicht.
Grund dafür ist in der Regel, dass das Feature-Gate nicht aktiviert ist.
In dem Folgeartikel Kubernetes Feature Gates wird erklärt, wie das Feature Gate im Kubernetes Cluster aktiviert werden kann, damit das Attribut auch ausgewertet wird.
Zu den Themen Kubernetes, Docker und Cloud Architektur bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:
Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.