So richten Sie den CI / CD-Workflow für Node.js-Apps mit Jenkins und Kubernetes ein

von Konstantin Feofantov

Einführung

Continuous Integration und Continuous Delivery sind zwei der Methoden, die die DevOps-Philosophie heute prägen. Grundsätzlich umfasst dies die Implementierung von Entwicklungs- und Integrationsworkflows, bei denen Entwickler ihre Änderungen häufig im zentralen Repository festschreiben, um sicherzustellen, dass ihre Festschreibungen funktionsfähig und einsatzbereit für die Produktion sind.

Diese Workflows verwenden Automatisierungs-Engines, die an die Aufgaben angepasst sind. In diesem Tutorial werde ich erklären, wie ein CI / CD-Workflow einer auf Kubernetes gehosteten Node.js-Anwendung eingerichtet wird. Für den Automatisierungsteil werde ich Jenkins verwenden, eines der derzeit wohl beliebtesten CI / CD-Tools.

Ich gehe davon aus, dass Sie bereits einen Kubernetes-Cluster mit einem installierten Ruder- / Pinnen-Server haben. Ich gehe auch davon aus, dass Sie mit Git vertraut sind und ein Repository haben. In diesem Artikel verwenden wir Bitbucket, aber Sie können zum Beispiel jede andere Git-Plattform wie Github oder Gitlab verwenden.

Workflow und Architektur

Wir verwenden den folgenden Workflow:

Beachten Sie, dass wir Kubernetes-Namespaces als Implementierungsumgebungen verwenden. Um dieses Lernprogramm zu vereinfachen, verwende ich eine 2-Tier-Bereitstellungsarchitektur mit Integrations- und Produktionsumgebungen. Sie können das System jedoch problemlos auf eine 3-Tier- oder 4-Tier-Architektur erweitern.

Führen Sie die folgenden Befehle in kubectl aus, um diese Namespaces zu erstellen:

kubectl erstelle den Namespace myapp-integration
Kubectl erstellen Namespace MyApp-Produktion

Überprüfen Sie, ob die Namespaces erstellt wurden:

kubectl bekommt Namespaces

Workflow einrichten

Die Anwendung, die wir starten werden, besteht aus zwei Teilen:

  • Nginx-Reverse-Proxy
  • NodeJS App

Der Anwendungscode wird in einem Git-Repository mit der folgenden Verzeichnisstruktur gehostet:

/
   -src /
     index.js
   -Tests /
     integration-tests.sh
     Produktionstests.sh
   -einsatz /
     nginx-reverseproxy.yaml
     nodejs.yaml
   Jenkinsfile

Schauen wir uns diese Dateien genauer an.

Das src-Verzeichnis enthält eine grundlegende Node.js-Anwendung, die einen http-Server an Port 8080 erstellt und abhängig vom besuchten Pfad eine Nachricht zurückgibt:

  • "Dies ist Homepage" beim Besuch von "/"
  • "Willkommen bei dir1, wie kann ich dir helfen?" Beim Besuch von "/ dir1"
  • "Die Information über die Person mit der ID 1 ist X" beim Besuch von "/ dir2 / person / 1"
// index.js
var http = require ('http');
var url = require ('url');
var server = http.createServer (Funktion (req, res) {
 var page = url.parse (req.url) .pathname;
 console.log (Seite);
 res.writeHead (200, {"Content-Type": "text / plain"});
 
 if (page == '/') {
   res.write ('Dies ist die Homepage');
 }
 sonst wenn (Seite == '/ dir1') {
  res.write ('Willkommen bei dir1, wie kann ich dir helfen?');
 }
 sonst wenn (Seite == '/ dir2 / Person / 1') {
  res.write ('Die Information über Person mit ID 1 ist X');
 }
 erneut senden();
});
server.listen (8080);

Das Testverzeichnis enthält zwei Skripts mit Tests, die ausgeführt werden müssen, um sicherzustellen, dass die Anwendung ausgeführt wird. In diesem Lernprogramm werden der Einfachheit halber dieselben Tests in der Integrations- und Produktionsumgebung durchgeführt, Sie können (und sollten) jedoch je nach Umgebung unterschiedliche Tests durchführen.

In diesem Fall verwenden wir curl, um die drei Pfade in der Node.js-App zu testen. Wenn der Server die richtige Antwort zurückgibt, ist der Test erfolgreich. Andernfalls wird das Skript mit einem Fehler beendet.

Integrationstests.sh:

#! / bin / bash
# integration-tests.sh
Echo "Integrationstests werden gestartet ..."
Echo "Root-Pfad wird getestet ..."
res1 = $ (locken -s http: // $ 1 /)
if ["$ res1"! = "Dies ist die Homepage"]; dann
 echo "Pfad / Test fehlgeschlagen. Abbruch ..."
 Ausfahrt 1
fi
echo "Testpfad / Verzeichnis1 ..."
res2 = $ (locken -s http: // $ 1 / dir1)
if ["$ res2"! = "Willkommen bei dir1, wie kann ich dir helfen?" ]; dann
 echo "Pfad / Verzeichnis1-Test fehlgeschlagen. Abbruch ..."
 Ausfahrt 1
fi
echo "Root-Pfad / dir2 / person / 1 ... testen"
res3 = $ (locken -s http: // $ 1 / dir2 / person / 1)
if ["$ res3"! = "Die Information über Person mit der ID 1 ist X"]; dann
 echo "Pfad / Verzeichnis1-Test fehlgeschlagen. Abbruch ..."
 Ausfahrt 1
fi
echo "Integrationstests erfolgreich."

Produktionstests.sh:

#! / bin / bash
# production-tests.sh
echo "Produktionstests starten ..."
Echo "Root-Pfad wird getestet ..."
res1 = $ (locken -s http: // $ 1 /)
if ["$ res1"! = "Dies ist die Homepage"]; dann
 echo "Pfad / Test fehlgeschlagen. Abbruch ..."
 Ausfahrt 1
fi
echo "Testpfad / Verzeichnis1 ..."
res2 = $ (locken -s http: // $ 1 / dir1)
if ["$ res2"! = "Willkommen bei dir1, wie kann ich dir helfen?" ]; dann
 echo "Pfad / Verzeichnis1-Test fehlgeschlagen. Abbruch ..."
 Ausfahrt 1
fi
echo "Root-Pfad / dir2 / person / 1 ... testen"
res3 = $ (locken -s http: // $ 1 / dir2 / person / 1)
if ["$ res3"! = "Die Information über Person mit der ID 1 ist X"]; dann
 echo "Pfad / Verzeichnis1-Test fehlgeschlagen. Abbruch ..."
 Ausfahrt 1
fi
echo "Produktionstests erfolgreich."

Das Bereitstellungsverzeichnis enthält alle Yaml-Dateien, die zum Bereitstellen der App auf Kubernetes erforderlich sind. Das unten stehende Yaml enthält Kubernetes-Ressourcen zum Bereitstellen des Nginx-Reverse-Proxys.

# nginx-reverseproxy.yaml
apiVersion: v1
Art: Service
Metadaten:
  name: nginx-reverseproxy-service
Spezifikation:
 Wähler:
    App: Nginx-Reverseproxy
 Geben Sie Folgendes ein: LoadBalancer #LB, um den Dienst bereitzustellen und eine externe IP-Adresse abzurufen
 Häfen:
  - Name: http
    Hafen: 80
    Protokoll: TCP
---
apiVersion: extensions / v1beta1
Art: Bereitstellung
Metadaten:
  Etiketten:
    App: Nginx-Reverseproxy
  Name: Nginx-Reverseproxy-Bereitstellung
Spezifikation:
  Repliken: 1
  Vorlage:
    Metadaten:
      Etiketten:
        App: Nginx-Reverseproxy
    Spezifikation:
      Behälter:
      - Bild: Nginx: 1,13
        Name: Kubecont-Nginx
        Häfen:
        - containerPort: 80
        volumeMounts:
        - name: config-volume
          mountPath: /etc/nginx/conf.d
      Bände:
        - name: config-volume
          configMap:
            name: nginx-reverseproxy-config
---
apiVersion: v1
Art: ConfigMap
Metadaten:
  name: nginx-reverseproxy-config
Daten:
  default.conf: | -
    Server {
     Servername IhrHostname.com;
     höre 80;
     #deny Zugriff auf .htaccess-Dateien, wenn Apache's Document Root
     #concurs with nginx's one
     #
     location ~ /\.ht {
         leugne alles;
     }
Standort / {
         proxy_pass http: // nodejs-service: 8080; #dies ist der in nodejs.yaml beschriebene Dienst
     }
    }

Und zum Schluss hier der Yaml zum Starten von Node.js auf Kubernetes. Beachten Sie, dass die CongigMap, auf die von nodejs-deployment verwiesen wird, während der Pipeline-Ausführung dynamisch erstellt wird, wie im Folgenden erläutert wird.

# nodejs.yaml
apiVersion: v1
Art: Service
Metadaten:
  name: nodejs-service
Spezifikation:
 Wähler:
    app: nodejs
 Häfen:
  - Name: http
    Port: 8080
    Protokoll: TCP
---
apiVersion: extensions / v1beta1
Art: Bereitstellung
Metadaten:
  Etiketten:
    app: nodejs
  name: nodejs-deployment
Spezifikation:
  Repliken: 1
  Vorlage:
    Metadaten:
      Etiketten:
        app: nodejs
    Spezifikation:
      Behälter:
      - Bild: Knoten: 9.11
        name: kubecont-nodejs
        Befehl: ["node", "/usr/src/app/index.js"]
        Häfen:
        - containerPort: 8080
        volumeMounts:
        - Name: App-Volume
          mountPath: / usr / src / app
      Bände:
        - Name: App-Volume
          configMap:
            name: nodejs-app

Und schließlich haben wir eine Jenkins-Datei, die den CI / CD-Workflow in Jenkins beschreibt. Der Workflow besteht aus drei Schritten:

  • Vorbereitungsphase: kubectl wird installiert und das App-Repository wird geklont
  • Integrationsphase: Aus der Node.js-App wird eine ConfigMap erstellt, und die Kubernetes-Ressourcen werden erstellt. Anschließend wird die Anwendung getestet und schließlich die Umgebung gereinigt
  • Produktionsphase: Die gleichen Schritte wie in der Integrationsphase werden ausgeführt, mit Ausnahme der Reinigung, da die Kubernetes-Ressourcen in der Produktion verbleiben sollen.

Das ist also das Jenkinsfile:

// Jenkinsfile
Knoten {
Bühne ('Vorbereitung') {
      // Kubectl in Jenkins Agent installieren
      sh 'curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux / amd64 / kubectl '
  sh 'chmod + x ./kubectl && mv kubectl / usr / local / sbin'
// Git-Repository klonen
  git url: 'https://bitbucket.org/advatys/jenkins-pipeline.git'
   }
Bühne ('Integration') {
 
      withKubeConfig ([credentialsId: 'jenkins-deployer-credentials', serverUrl: 'https://104.155.31.202']) {
      
         sh 'kubectl create cm nodejs-app --from-file = src / --namespace = myapp-integration -o = yaml - dry-run> deploy / cm.yaml'
         sh 'kubectl apply -f deploy / --namespace = myapp-integration'
         Versuchen{
          // Erfassen der externen IP-Adresse der Node.js-App
          def ip = ''
          def count = 0
          def countLimit = 10
       
          // Warteschleife für die IP-Adressbereitstellung
          println ("Warten auf IP-Adresse")
          while (ip == '' && count 
}
   }
 Bühne ('Produktion') {
      withKubeConfig ([credentialsId: 'jenkins-deployer-credentials', serverUrl: 'https://104.155.31.202']) {
      
       sh 'kubectl create cm nodejs-app --from-file = src / --namespace = myapp-production -o = yaml --trockenlauf> deploy / cm.yaml'
sh 'kubectl apply -f deploy / --namespace = myapp-production'
      
  
      // Erfassen der externen IP-Adresse der Node.js-App
         def ip = ''
         def count = 0
         def countLimit = 10
        
         // Warteschleife für die IP-Adressbereitstellung
         println ("Warten auf IP-Adresse")
         while (ip == '' && count 

Installieren Sie Jenkins

Um Jenkins zu installieren, verwenden wir die Helmkarte, die im offiziellen Stall-Repository verfügbar ist. Verwenden Sie den folgenden Befehl, um Jenkins in Kubernetes mit den erforderlichen Plugins bereitzustellen:

helm install --name my-jenkins-deployment stable / jenkins --version 0.16.1 --values ​​jenkins-params.yaml

Wobei jenkins-params.yaml wie folgt lautet:

# jenkins-params.yaml
Meister:
  Bild: Jenkins / Jenkins
  ImageTag: 2.121
  ServiceType: LoadBalancer
  ServicePort: 80
  AdminPassword: admin_313
  InstallPlugins:
    - Kubernet: 1.5.2
    - Workflow-Aggregator: 2.5
    - Workflow-Job: 2.21
    - Verbindliche Anmeldeinformationen: 1.16
    - Git: 3.9.0
    - kubernetes-cli: 1.0.0
    - custom-tools-plugin: 0.5
    - Bitbucket: 1.1.7
rbac:
  installieren: wahr
  apiVersion: v1
Agent:
  Bild: jenkins / jnlp-slave
  ImageTag: 3.19-1
  Bände:
  - Typ: EmptyDir
    mountPath: / usr / local / sbin

Sobald Sie den Befehl ausgeführt haben, erhalten Sie die Anweisungen zum Abrufen des Kennworts. In unserem Fall sollte der folgende Befehl ausgeführt werden:

printf $ (kubectl get secret --namespace default my-jenkins-deployment -o jsonpath = "{. data.jenkins-admin-password}" | base64 --decode); echo

Wenn alles in Ordnung ist, sollte die Helmfreigabe bereitgestellt werden:

Helm ls

Führen Sie nach einer Weile (ca. 30 Sekunden bis 1 Minute) den folgenden Befehl aus, um eine externe IP-Adresse abzurufen, über die Sie auf Ihre Jenkins-Instanz zugreifen können.

Kubectl erhalten Dienstleistungen

In diesem Fall ist es 35.189.215.166. Navigieren Sie zu dieser IP-Adresse in Ihrem Browser und melden Sie sich als Administrator mit dem zuvor erhaltenen Passwort an (vergessen Sie nicht, diese Anmeldeinformationen zu ändern :)).

Konfigurieren von kubectl in Jenkins für die kontinuierliche Bereitstellung

Jetzt konfigurieren wir die Kubernetes-Anmeldeinformationen, damit Jenkins in unserem Kubernetes-Cluster bereitgestellt werden kann.

Wir müssen einen ServiceAccount in Kubernetes erstellen, der von Jenkins für die Bereitstellung verwendet wird.

kubectl erstellt einen Jenkins-Deployer
kubectl create clusterrolebinding jenkins-deployer-rolle - clusterrolle = cluster-admin - serviceaccount = jenkins-deployer

Führen Sie dann den Befehl aus

Kubectl bekommen Geheimnisse

Sie müssen das Geheimnis auswählen, das mit "jenkins-deployer" beginnt, und die dazugehörigen Anmeldeinformationen abrufen:

kubectl beschreiben geheime jenkins-deployer-token-jvdmf

Gehen Sie im linken Menü der Hauptseite zu Anmeldeinformationen und wählen Sie dann System und Domäne hinzufügen. Sie können beispielsweise den Namen Ihres Unternehmens hinzufügen. Klicken Sie dann im linken Menü auf Anmeldeinformationen hinzufügen.

Füllen Sie das Formular wie folgt aus:

  • Art: Geheimtext
  • Geltungsbereich: Global
  • Geheimnis: das von jenkins-deployer-token-jvdmf (langer String) kopierte Token
  • ID: jenkins-deployer-credentials (wie in der Funktion withKubeConfig in der Jenkins-Datei angegeben)

Jenkins Job erstellen

Gehen Sie zur Hauptseite von Jenkins und klicken Sie im linken Menü auf New Item. Geben Sie dann einen Jobnamen an und wählen Sie Pipeline als Jobtyp.

Aktivieren Sie im nächsten Bildschirm die Option "Erstellen, wenn eine Änderung in Bitbucket übernommen wird". Dies wird verwendet, um das Triggern von Pipelines wie später erläutert zu automatisieren (obwohl diese Funktion optional ist).

Wechseln Sie schließlich zum Abschnitt Pipeline und konfigurieren Sie ihn wie folgt:

  • Definition: Pipeline-Skript von SCM
  • SCM: Git
  • Repositorys: Repository-URL: Ihre Repository-URL

Speichern Sie einfach Ihre Einstellungen.

Workflow starten

Um den Workflow zu starten, wählen Sie die zuletzt erstellte Pipeline aus und klicken Sie im linken Menü auf "Jetzt erstellen". Die Pipeline startet in wenigen Sekunden.

Es ist auch möglich, den Workflow automatisch auszulösen, wenn ein Benutzer eine Änderung am Git-Repository festlegt. Dies ist eine empfohlene Vorgehensweise gemäß den CI / CD-Grundsätzen.

Dazu müssen Sie einen Webhook in Ihrem Git-Repository einrichten. Für Bitbucket müssen Sie die hier erläuterten Anweisungen befolgen. Beachten Sie, dass die URL, die Sie in Bitbucket angeben müssen, JENKINS_URL / bitbucket-hook / ist.

In unserem Fall ist es:

http://35.189.215.166/bitbucket-hook/

Fazit

Ich habe einen einfachen CI / CD-Workflow mit Jenkins und Kubernetes demonstriert. Der Hauptvorteil dieses Stacks liegt in der Flexibilität, da Sie praktisch jeden Workflow implementieren können. Dieser Workflow kann je nach Ihren Entwicklungsanforderungen erweitert oder komplexisiert werden. In jedem Fall wird der Prozess weitaus effizienter sein als auf herkömmliche Weise.

Ich hoffe, Sie finden diesen Beitrag hilfreich. Bitte kommentieren Sie und stellen Sie Fragen - ich freue mich über Ihr Feedback. Vergesst nicht, uns auf Twitter zu folgen und an unserem Telegramm-Chat teilzunehmen, um auf dem Laufenden zu bleiben!

Vielleicht möchten Sie auch unser Containerum-Projekt auf GitHub überprüfen. Wir brauchen Ihr Feedback, um es zu stärken - Sie können ein Problem einreichen oder das Projekt nur mit einem unterstützen. Ihre Unterstützung ist uns sehr wichtig!

Containerum ist Ihre Wissensquelle zu Kubernetes und Docker.