Neuigkeiten von trion.
Immer gut informiert.

Kubernetes Deployment mit CI Server

In dem Beitrag zu Kubernetes Continuous Integration wurde am Beispiel von Drone CI gezeigt, wie Builds in einem Kubernetes Cluster umgesetzt werden können. Nun ist es oft der Wunsch von Kunden wie Entwicklern, möglichst schnell auf den aktuellen Stand auch zugreifen zu können. Dies Verfahren wird auch Continuous Deployment genannt, da stets der aktuelle Entwicklungsstand zur Nutzung bereit steht.

Dieser Beitrag zeigt daher auf, wie mit Kubernetes und Drone ein solches Deployment umgesetzt werden könnte. Damit das Setup nicht zu komplex wird, wird lediglich der aktuelle master-Branch in Kubernetes deployt.

Zunächst einmal muss der Ansatz von Kubernetes gelobt werden, eine API bereitzustellen. Damit ist ein Deployment natürlich eine relativ einfach zu gestaltende Aufgabe:

  1. Zugriff auf die API ermöglichen

  2. Bei erfolgreichem Build das Deployment beauftragen

  3. Kubernetes erledigt den Rest

Kubernetes API Zugriff

Eine API ermöglicht allgemein Funktionalität bereitzustellen, so dass diese auch in einem erweiterten oder neuen Kontext verwendet werden kann. Schon früher war unter dem Stichwort Automatisierung eine Schnittstelle bei vielen Produkten vorhanden, die eine Integration in Werkzeuge ermöglicht hat. Oft kam so eine Schnittstelle in Form von Kommandozeilenwerkzeugen (CLI), manchmal aber auch als Library oder Remote-Schnittstelle daher.

Kubernetes bietet ebenfalls eine Remote Schnittstelle als HTTP API und durch das Kommandozeilenwerkzeug kubectl auch eine durch Nutzer und Programme leicht nutzbare Implementierung. Mit der Kubernetes API können alle Mechanismen von Kubernetes angesteuert werden, somit auch Deployments.

Damit der Zugriff abgesichert ist, sieht Kubernetes Tokens vor, mit denen bei einem API Zugriff eine Authentifizierung stattfindet.
Die Verwaltung der Tokens erfolgt durch Secret Objkete, die wiederum einem Service Account zugeordnet sind.

Ein solcher Service Account muss nun also erzeugt werden, um aus dem Build heraus auf die Kubernetes API zuzugreifen. Der Service Account kann entweder durch kubectl oder ein passendes Manifest erzeugt werden.

Anlegen von Kubernetes Service Account mit kubectl für Deployment
$ kubectl create serviceaccount drone-deploy
serviceaccount/drone-deploy created

Anschließend kann man den Service Account und das zugehörige Secret inspizieren.

Kubernetes Service Account und Secret wurden erzeugt
$ kubectl -n default get serviceaccount
NAME                  SECRETS   AGE
dashboard             1         206d
default               1         208d
drone-deploy          1         56s
$ kubectl -n default get secrets
NAME                              TYPE                                  DATA   AGE
default-token-bgbws               kubernetes.io/service-account-token   3      208d
drone-deploy-token-j2pvc          kubernetes.io/service-account-token   3      68s

Der tatsächliche Tokenwert wird durch Kubernetes Controller im Hintergrund erzeugt. Damit das Token verwendet werden kann, muss es anschließend abgerufen werden. Da der Zugriff auf die API durch TLS gesichert erfolgt, sollte auch das verwendete CA Zertifikat abgerufen werden, um so sicherzustellen, dass mit dem legitimen Endpunkt kommuniziert wird.

Abrufen des Token zur Verwendung im Buildserver
$ kubectl -n default get secret drone-deploy-token-j2pvc -o yaml | egrep 'ca.crt:|token:'
  ca.crt: LS0tLS....o=
  token: ZXlKaG3....dw==

Nun müssen noch die erforderlichen Berechtigungen vergeben werden, damit dieser Service Account Änderungen am Kubernetes Zustand durchführen darf.
Konkret sollen lediglich Deployments erzeugt oder geändert werden. Passende Services, LoadBalancer oder Ingress Konfigurationen werden als passend vorhanden vorausgesetzt.

Die Rechte werden einer Rolle gewährt, die Rolle muss dann noch dem Service Account über ein Role-Binding zugeordnet werden.

Kubernetes Cluster-Role und Cluster-Role Binding für Service Account
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: drone-deploy
  namespace: default
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["create","get","list","patch","update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: drone-deploy
  namespace: default
subjects:
  - kind: ServiceAccount
    name: drone-deploy
    namespace: default
roleRef:
  kind: Role
  name: drone-deploy
  apiGroup: rbac.authorization.k8s.io

Als letztes wird jetzt nur noch der API Endpunkt benötigt, über den die Kubernetes API angesprochen werden soll. Der API Endpunkt von Kubernetes ist standardmäßig als Service mit dem Namen kubernetes im Namespace default angelegt. Daher kann aus Kubernetes heraus auf https://kubernetes.default zugegriffen werden.

Mit diesen Daten kann nun der Buildserver konfiguriert werden.

Secrets in Drone

Es gibt unterschiedliche Wege, um einem Dienst die Verwendung eines Service Accounts zu ermöglichen. Oft wird das Token als Volume Mount den jeweiligen Pods bzw. Containern zur Verfügung gestellt. Wird der Build außerhalb des Kubernetes Clusters durchgeführt, müssen die Daten entsprechend extern konfiguriert werden.

Der Drone Buildserver bietet dafür ebenfalls ein Konzept an, dass Secrets genannt wird. Werte aus den Secrets werden im Build als Umgebungsvariablen zur verfügung gestellt.

Je nach verwendetem Plugin muss noch darauf geachtet werden, ob die Werte, wie von Kubernetes herausgegeben, als Base64-encoded String verwendet werden sollen, oder ob diese vorher noch umgewandelt werden müssen. Auch der Name der bereitzustellenden Secrets kann je nach verwendetem Plugin unterschiedlich sein.

Die Build Secrets in Drone können über das Drone CLI oder die Weboberfläche verwaltet werden. Bei Verwendung der Drone Oberfläche erfolgt dies über die Einstellungen des jeweiligen Projekts.

Pipeline mit Deployment

In der Drone Pipeline wird zum Deployment in Kubernetes das Drone Plugin quay.io/hectorqin/drone-kubectl verwendet. Das Plugin ist schlicht gehalten und verwendet ein Shell-Script um kubectl mit den entsprechenden Parametern aufzurufen.

Die folgenden Secrets müssen konfiguriert werden:

KUBERNETES_CERT

CA Zertifikat für TLS Absicherung der Kubernetes API (Base64 encoded)

KUBERNETES_TOKEN

Das Token zur Authentifizierung (Wert ohne Encoding)

Da das Plugin für das Token einen Wert ohne Base64 Encoding erwartet, muss entweder durch ein passendes Programm das Encoding entfernt werden, oder es wird das Token ohne Encoding bei Kubernetes abgerufen.

Abruf des Token Wertes ohne Encoding
$ kubectl describe secret drone-deploy | grep 'token:'
token:      eyJhbG....

Nun kann eine passende Pipeline zur Verwendung in Drone erzeugt werden.

Continous Deployment in Kubernetes mit Drone Pipeline

Zur Demonstration wird die Pipeline aus dem Kubernetes Continuous Integration Artikel erweitert: Es fehlt lediglich der Deployment Schritt, da die Pipeline bereits das passende Container Image baut und es in der Container Registry zur Verfügung stellt.

Drone Pipeline Step zum Deployment in Kubernetes
---
- name: deploy
  image: quay.io/hectorqin/drone-kubectl   # (1)
  environment:
    #PLUGIN_DEBUG: true
  settings:
    kubernetes_template: drone-deploy.yaml         # (2)
    kubernetes_namespace: default                  # (3)
    kubernetes_server: https://kubernetes.default  # (4)
    kubernetes_cert:
      from_secret: kubernetes_cert                 # (5)
    kubernetes_token:
      from_secret: kubernetes_token                # (6)
  1. Zu verwendendes Drone Plugin Image für diesen Schritt

  2. Deployment Template für das durchzuführende Deployment in Kubernetes

  3. Zu verwendender Namespace

  4. Endpunkt des Kubernetes API Servers

  5. CA Zertifikat für API Server

  6. Token, mit dem die Authentifizierung gegenüber Kubernetes erfolgt

Die Pipeline ist nun vollständig und kann ausgeführt werden, sobald ein Deployment Template angelegt wurde. Als Ausgabe liefert der Deployment Schritt Informationen zur API Version des API Servers, des verwendeten kubectl und am Schluss zum Aktualisierten Deployment.

Ausgabe des Deployment Steps der Kubernetes Pipeline
Applying to https://kubernetes.default:
User "default" set.
Cluster "default" set.
Context "default" created.
Switched to context "default".
Client Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.3", GitCommit:"721bfa751924da8d1680787490c54b9179b1fed0", GitTreeState:"clean", BuildDate:"2019-02-01T20:08:12Z", GoVersion:"go1.11.5", Compiler:"gc", Platform:"linux/arm64"}
Server Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.2", GitCommit:"cff46ab41ff0bb44d8584413b598ad8360ec1def", GitTreeState:"clean", BuildDate:"2019-01-10T23:28:14Z", GoVersion:"go1.11.4", Compiler:"gc", Platform:"linux/arm64"}
deployment.apps/k8s-visualizer configured

Deployment Template

Damit ein Deployment angelegt oder aktualisiert werden kann, wird ein Deployment Template benötigt. Es handelt sich deswegen um ein Template, da einige Werte durch Platzhalter variabel gestaltet sind, damit diese durch Werte aus einem konkreten Build gefüllt werden können.

Das Template ist im Grund ein reguläres Kubernetes Deployment Objekt. Kubernetes bietet Deployments mit verschiedenen Rollout-Strategien an, in diesem Fall wird ein RollingUpdate gewählt, wodurch ein für Nutzer unterbrechungsfreies Depoyment realisiert wird.

Deployment Template für Anwendung in Kubernetes
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8s-visualizer
  namespace: default
  labels:
    name: k8s-visualizer
    app: k8s-visualizer
spec:
  replicas: 2
  minReadySeconds: 1
  revisionHistoryLimit: 2
  strategy:
    rollingUpdate:
        maxSurge: 1
        maxUnavailable: 1
    type: RollingUpdate
  selector:
    matchLabels:
      app: k8s-visualizer
      name: k8s-visualizer
  template:
    metadata:
      labels:
        app: k8s-visualizer
        name: k8s-visualizer
        ref: ${DRONE_COMMIT_SHA:0:8}
        build-time: $(date -d @$DRONE_BUILD_STARTED '+%Y-%m-%d--%H.%M.%S')
    spec:
      containers:
      - name: k8s-visualizer
        image: registry.internal/trion/k8s-visualizer:drone
        imagePullPolicy: Always
        ports:
        - containerPort: 80

Da das Template durch ein Shellscript ausgewertet wird, können sogar Shellfunktionen verwendet werden, um Variablenwerte zu bestimmen. Im Beispiel wird ein Label build-time vergeben, in dem das aktuelle Datum passend formatiert eingefügt wird.

Statt eines festen Tags könnte auch hier eine Drone Variable, wie z.B. die Buildnummer, verwendet werden.




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.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Zur Desktop Version des Artikels