Guten Tag.
Es gibt bereits mehrere Artikel über Jenkins, Jenkins, CD / CD und Kubernetes, aber ich möchte mich hier nicht auf die Analyse der Fähigkeiten dieser Technologien konzentrieren, sondern auf deren einfachste Konfiguration für den Aufbau der CD / CD-Pipeline.
Ich meine, dass der Leser ein grundlegendes Verständnis von Docker hat, und ich werde mich nicht mit den Themen der Installation und Konfiguration von Kubernetes befassen. Alle Beispiele werden auf minikube gezeigt, können aber auch ohne wesentliche Änderungen auf EKS, GKE oder dergleichen angewendet werden.

Umwelt
Ich schlage vor, die folgenden Umgebungen zu verwenden:
- Test - für manuelle Bereitstellung und Verzweigungstests
- Staging - Eine Umgebung, in der alle Änderungen, die in den Master fallen, automatisch bereitgestellt werden
- Produktion - die von echten Benutzern verwendete Umgebung, in der Änderungen erst vorgenommen werden, nachdem ihre Funktionsfähigkeit beim Staging bestätigt wurde
Die Umgebungen werden mithilfe von Kubernetes-Namespaces in einem einzelnen Cluster organisiert. Dieser Ansatz ist zu Beginn so einfach und schnell wie möglich, hat aber auch Nachteile: Namespaces sind in Kubernetes nicht vollständig voneinander isoliert.
In diesem Beispiel verfügt jeder Namespace über denselben ConfigMaps-Satz mit den Konfigurationen dieser Umgebung:
apiVersion: v1 kind: Namespace metadata: name: production --- apiVersion: v1 kind: ConfigMap metadata: name: environment.properties namespace: production data: environment.properties: | env=production
Helm
Helm ist eine Anwendung, mit der auf kubernetes installierte Ressourcen verwaltet werden können.
Installationsanweisungen finden Sie hier .
Um zu beginnen, müssen Sie den Pinnen-Pod initialisieren, um das Ruder mit dem Cluster zu verwenden:
helm init
Jenkins
Ich werde Jenkins verwenden, da es eine ziemlich einfache, flexible und beliebte Plattform für Bauprojekte ist. Es wird in einem separaten Namespace installiert, um sich von anderen Umgebungen zu isolieren. Da ich beabsichtige, in Zukunft das Ruder zu verwenden, kann ich die Installation von Jenkins mithilfe vorhandener Open Source-Diagramme vereinfachen:
helm install --name jenkins --namespace jenkins -f jenkins/demo-values.yaml stable/jenkins
demo-values.yaml enthält die Jenkins-Version, eine Reihe vorinstallierter Plugins, einen Domainnamen und andere Konfigurationen
demo-values.yaml Master: Name: jenkins-master Image: "jenkins/jenkins" ImageTag: "2.163-slim" OverwriteConfig: true AdminUser: admin AdminPassword: admin InstallPlugins: - kubernetes:1.14.3 - workflow-aggregator:2.6 - workflow-job:2.31 - credentials-binding:1.17 - git:3.9.3 - greenballs:1.15 - google-login:1.4 - role-strategy:2.9.0 - locale:1.4 ServicePort: 8080 ServiceType: NodePort HostName: jenkins.192.168.99.100.nip.io Ingress: Path: / Agent: Enabled: true Image: "jenkins/jnlp-slave" ImageTag: "3.27-1" #autoadjust agent resources limits resources: requests: cpu: null memory: null limits: cpu: null memory: null #to allow jenkins create slave pods rbac: install: true
Diese Konfiguration verwendet admin / admin als Benutzernamen und Passwort für die Anmeldung und kann später neu konfiguriert werden. Eine der möglichen Optionen ist Google SSO (hierfür ist das Google-Login-Plugin erforderlich. Die Einstellungen finden Sie unter Jenkins> Jenkins verwalten> Globale Sicherheit konfigurieren> Zugriffssteuerung> Sicherheitsbereich> Mit Google anmelden).
Jenkins wird sofort so konfiguriert, dass für jeden Build automatisch ein einmaliger Slave erstellt wird. Dank dessen erwartet das Team keinen freien Agenten mehr für die Montage, und das Unternehmen kann die Anzahl der erforderlichen Server einsparen.

PersistenceVolume ist ebenfalls standardmäßig so konfiguriert, dass Pipelines beim Neustart oder Aktualisieren gespeichert werden.
Damit die automatischen Bereitstellungsskripts ordnungsgemäß funktionieren, müssen Sie Jenkins die Berechtigung zum Cluster-Administrator erteilen, um eine Liste der Ressourcen in Kubernetes abzurufen und zu bearbeiten.
kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default
In Zukunft können Sie Jenkins bei neuen Versionen von Plugins oder Konfigurationsänderungen mithilfe von helm aktualisieren.
helm upgrade jenkins stable/jenkins -f jenkins/demo-values.yaml
Dies kann über die Benutzeroberfläche von Jenkins selbst erfolgen. Mit helm haben Sie jedoch die Möglichkeit, mit folgenden Funktionen auf frühere Revisionen zurückzugreifen:
helm history jenkins helm rollback jenkins ${revision}
Anwendungserstellung
Als Beispiel werde ich die einfachste Spring-Boot-Anwendung erstellen und bereitstellen. Ähnlich werde ich bei Jenkins das Ruder benutzen.
Die Montage erfolgt in der folgenden Reihenfolge:
- Kasse
- Zusammenstellung
- Unit Test
- Integrationstest
- Artefakt-Baugruppe
- Artefaktbereitstellung in der Docker-Registrierung
- Bereitstellen von Artefakten für das Staging (nur für den Hauptzweig)
Dafür benutze ich die Jenkins-Datei . Meiner Meinung nach ist dies eine sehr flexible (aber leider nicht einfachste) Möglichkeit, die Projektassembly zu konfigurieren. Einer seiner Vorteile ist die Möglichkeit, die Konfiguration der Projektassembly im Repository mit dem Projekt selbst zu behalten.
Kasse

Im Fall einer Bitbucket- oder Github-Organisation können Sie Jenkins so konfigurieren, dass das gesamte Konto regelmäßig auf das Vorhandensein von Repositorys mit Jenkinsfile überprüft und automatisch Assemblys für diese erstellt werden. Jenkins wird sowohl Meister als auch Zweige sammeln. Pull-Anfragen werden auf einer separaten Registerkarte angezeigt. Es gibt eine einfachere Option: Fügen Sie ein separates Git-Repository hinzu, unabhängig davon, wo es gehostet wird. In diesem Beispiel werde ich genau das tun. Alles, was benötigt wird, ist im Menü Jenkins> Neues Element> Multibranch Pipeline, wählen Sie den Assemblynamen und binden Sie das Git-Repository.
Zusammenstellung
Da Jenkins für jede Assembly einen neuen Pod erstellt, werden bei Verwendung von Maven oder ähnlichen Kollektoren die Abhängigkeiten jedes Mal erneut heruntergeladen. Um dies zu vermeiden, können Sie PersistenceVolume für .m2- oder ähnliche Caches zuweisen und in den Pod einhängen, der das Projekt erstellt.
apiVersion: "v1" kind: "PersistentVolumeClaim" metadata: name: "repository" namespace: "jenkins" spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi
In meinem Fall konnte dadurch die Pipeline von ca. 4 auf die 1. Minute beschleunigt werden.
Versionierung
Damit CI / CD ordnungsgemäß funktioniert, benötigt jeder Build eine eindeutige Version.
Eine sehr gute Option wäre die Verwendung der semantischen Versionierung . Auf diese Weise können Sie abwärtskompatible und inkompatible Änderungen verfolgen. Eine solche Versionierung ist jedoch schwieriger zu automatisieren.
In diesem Beispiel werde ich eine Version aus der ID und dem Datum des Commits sowie dem Namen des Zweigs generieren, wenn dieser kein Master ist. Zum Beispiel 56e0fbdc-201802231623 oder b3d3c143-201802231548-PR-18 .
Vorteile dieses Ansatzes:
- einfache Automatisierung
- Es ist einfach, den Quellcode und seine Erstellungszeit aus der Version abzurufen
- visuell können Sie die Release-Version des Kandidaten (vom Assistenten) oder experimentell (vom Zweig) unterscheiden.
aber: - Diese Version ist in der mündlichen Kommunikation schwieriger zu verwenden
- Es ist nicht klar, ob es inkompatible Änderungen gab.
Da Docker-Images mehrere Tags gleichzeitig haben können, können Ansätze kombiniert werden: Alle Releases verwenden die generierten Versionen, und diejenigen, die in die Produktion fallen, werden zusätzlich (manuell) mit semantischer Versionierung versehen. Dies ist wiederum mit einer noch größeren Komplexität der Implementierung und der Mehrdeutigkeit verbunden, welche Version die Anwendung anzeigen sollte.
Artefakte
Das Ergebnis der Montage ist:
- Docker-Image mit einer Anwendung, die gespeichert und aus der Docker-Registrierung geladen wird. In diesem Beispiel wird die integrierte Registrierung von minikube verwendet, die durch einen Docker-Hub oder eine private Registrierung von amazon (ecr) oder google ersetzt werden kann (vergessen Sie nicht, ihnen mithilfe des withCredentials-Konstrukts Anmeldeinformationen bereitzustellen).
- Helmdiagramme mit einer Beschreibung der Bereitstellung der Anwendung (Bereitstellung, Dienst usw.) im Helmverzeichnis. Idealerweise sollten sie in einem separaten Repository für Artefakte gespeichert werden. Zur Vereinfachung können sie jedoch verwendet werden, indem das erforderliche Commit von git überprüft wird.
Jenkinsfile
Infolgedessen wird die Anwendung mit der folgenden Jenkins-Datei erstellt:
Jenkinsfile def branch def revision def registryIp pipeline { agent { kubernetes { label 'build-service-pod' defaultContainer 'jnlp' yaml """ apiVersion: v1 kind: Pod metadata: labels: job: build-service spec: containers: - name: maven image: maven:3.6.0-jdk-11-slim command: ["cat"] tty: true volumeMounts: - name: repository mountPath: /root/.m2/repository - name: docker image: docker:18.09.2 command: ["cat"] tty: true volumeMounts: - name: docker-sock mountPath: /var/run/docker.sock volumes: - name: repository persistentVolumeClaim: claimName: repository - name: docker-sock hostPath: path: /var/run/docker.sock """ } } options { skipDefaultCheckout true } stages { stage ('checkout') { steps { script { def repo = checkout scm revision = sh(script: 'git log -1 --format=\'%h.%ad\' --date=format:%Y%m%d-%H%M | cat', returnStdout: true).trim() branch = repo.GIT_BRANCH.take(20).replaceAll('/', '_') if (branch != 'master') { revision += "-${branch}" } sh "echo 'Building revision: ${revision}'" } } } stage ('compile') { steps { container('maven') { sh 'mvn clean compile test-compile' } } } stage ('unit test') { steps { container('maven') { sh 'mvn test' } } } stage ('integration test') { steps { container ('maven') { sh 'mvn verify' } } } stage ('build artifact') { steps { container('maven') { sh "mvn package -Dmaven.test.skip -Drevision=${revision}" } container('docker') { script { registryIp = sh(script: 'getent hosts registry.kube-system | awk \'{ print $1 ; exit }\'', returnStdout: true).trim() sh "docker build . -t ${registryIp}/demo/app:${revision} --build-arg REVISION=${revision}" } } } } stage ('publish artifact') { when { expression { branch == 'master' } } steps { container('docker') { sh "docker push ${registryIp}/demo/app:${revision}" } } } } }
Zusätzliche Jenkins-Pipelines für das Application Lifecycle Management
Angenommen, Repositorys sind so organisiert, dass:
- eine separate Anwendung in Form eines Docker-Images enthalten
- kann mithilfe von Helmdateien bereitgestellt werden, die sich im Helmverzeichnis befinden
- werden mit demselben Ansatz versioniert und verfügen über eine helm / setVersion.sh-Datei zum Festlegen von Revisionen in Helmdiagrammen
Dann können wir mehrere Jenkinsfile-Pipelines erstellen, um den Anwendungslebenszyklus zu verwalten, nämlich:
In der Jenkins-Datei jedes Projekts können Sie einen Bereitstellungspipeline-Aufruf hinzufügen, der jedes Mal ausgeführt wird, wenn der Hauptzweig erfolgreich kompiliert wurde oder wenn der Bereitstellungszweig die Testumgebung explizit anfordert.
Jenkins-Datei zum Bereitstellen der Pipeline ... stage ('deploy to env') { when { expression { branch == 'master' || params.DEPLOY_BRANCH_TO_TST } } steps { build job: './../Deploy', parameters: [ [$class: 'StringParameterValue', name: 'GIT_REPO', value: 'habr-demo-app'], [$class: 'StringParameterValue', name: 'VERSION', value: revision], [$class: 'StringParameterValue', name: 'ENV', value: branch == 'master' ? 'staging' : 'test'] ], wait: false } } ...
Hier finden Sie Jenkinsfile mit allen Schritten.
Auf diese Weise ist es möglich, eine kontinuierliche Bereitstellung in der ausgewählten Test- oder Kampfumgebung zu erstellen, wobei auch Jenkins oder seine E-Mail- / Slack- / usw.-Benachrichtigungen verwendet werden. Dabei wird geprüft, welche Anwendung, welche Version, von wem, wann und wo sie installiert wurde.
Fazit
Mit Jenkinsfile und helm können Sie einfach ci / cd für Ihre Anwendung erstellen. Diese Methode ist möglicherweise am relevantesten für kleine Teams, die kürzlich mit der Verwendung von Kubernetes begonnen haben und (unabhängig vom Grund) keine Dienste verwenden können, die solche Funktionen sofort bereitstellen können.
Konfigurationsbeispiele für Umgebungen, Jenkins und Pipeline zur Verwaltung des Anwendungslebenszyklus finden Sie hier und eine Beispielanwendung mit Jenkinsfile hier .