Cloud Arbeiterbienen für die Build Pipeline - Jenkins mit dynamischen Verarbeitungsknoten über AWS Plugin
This content is more than 4 years old and the cloud moves fast so some information may be slightly out of date.
Jenkins als Build Server erfreut sich einer großen Verbreitung. Die architekturelle Frage, die sich dabei stellt ist: Wie groß lege ich den Server aus, damit er neben des Management der Build Projekte auch die Builds selber verarbeiten kann?
Die einfachste Antwort darauf ist ja: AWS CodeBuild verwenden, aber wenn das nicht geht?
Dann bietet das EC2-Plugin eine dynamische Lösung mit AWS Mitteln: Die Worker Knoten werden vollautomatisch dynamisch als EC2 Instanzen erzeugt und wieder abgebaut. So kann einfach eine kostengünstige Lösung aufgebaut werden.
Die Dokumentation zu diesem Plugin beinhaltet kein durchgängiges Beispiel. Daher will ich hier die komplette Installation eines Jenkins mit dynamischen Arbeitsknoten (worker nodes) auf AWS darstellen. In ca. 1/2 Stunde steht alles! Zur Automatisierung habe ich die automatisierbaren Arbeitsschritte in ein Makefile
eingebaut. Der Code und die Scripte sind auf github verfügbar: TecRacer Github Jenkins demo
Unsere Zutaten
Wir brauchen:
- Eine Rolle für die Jenkins Server Instanz
- Eine Security Group für den Server
- Eine Security Group für die Knoten
- Einen Jenkins Server ( im Beispiel auf einer EC2 Instanz)
- Ein Script, um Java zu installieren
- Das EC2 Plugin
- Die Konfiguration für das Plugin
In der Rolle wird dem Server z.B. erlaubt andere Instanzen zu starten und zu stoppen. Mit den beiden Security Groups wird auf Port Ebene die Verbindung erlaubt. Im Beispiel läuft die Verbindung unsicher über öffentliche IP Adressen.
Installation
Vorbereitung für die Instanz: Rollen und Gruppen
Die notwendigen Resourcen sind in templates/jenkins-security.template
kodiert. Also zuerst diese Datei zu cloudformation hochladen. Entweder manuell über die AWs Konsole oder wenn man clouds aws (siehe Blogpost dazu) installiert hat, einfach mit make security
die Aufgabe aus dem Makefile des github Repositories aufrufen. Dabei eventuell im Makefile die Region anpassen, Standard ist eu-central-1 (Frankfurt).
Ein Jenkins Server auf EC2
Wer schon einen Server hat, kann diesen Schritt natürlich überspringen. Weil ich hier ein AMI aus dem Marketplace verwendet, bauen wir den Server manuell auf. In der Konsole
- Service EC2 aufrufen
- Launch Instance aufrufen
- Links “AWS Marketplace” wählen
bitnami jenkins
suchen- “Jenkins Certified by Bitnami” auswählen
- Auf der Übersichtsseite names “Jenkins Certified by Bitnami” den Punkte “continue” auswählen
- Instanztype wählen, z.B.
t3.small
- Bei “Configure Instance Details”
- Das vpc wählen, das in den cloudformation Parametern eingetragen ist. (Kleine Hilfe:
make show-vpc
) Automatisch ausgewählt ist das Standard VPC - Ein public subnet und eine öffentliche IP wählen, sonst haben wir keinen Zugang zu der jenkins WebGui
- Als IAM Rolle
JenkinsRole
wählen
- Das vpc wählen, das in den cloudformation Parametern eingetragen ist. (Kleine Hilfe:
- Bei “Step 6: Configure Security Group”:
- “Select an existing security group” die Gruppe mit der Beschreibung “Jenkins Server” wählen, die durch das Cloudformation Template eben angelegt wurde
- Key Pair brauchen wir für die Demo selber nicht. Wer sich einloggen will: der ec2-user ist hier “ubuntu”
- Launch Instance
Konfiguration des Jenkins Servers
Der Server Administrator hat den Usernamen user
. Das Kennwort wird dynamisch generiert. So ermittelt man das Kennwort:
Kennwort ermitteln
Wenn der Server hochgefahren ist und die Status Checks durch sind:
-
Wähle Actions -> Instance Settings -> Get System Log
-
Da sucht man jetzt nach:
######################################################################### # # # Setting Bitnami application password to 'Ki2drNVDDT0b' # # (the default application username is 'user') # # # #########################################################################
(Das hier gezeigt password ist kein echtes, nur ein Beispiel!)
Auf der Shell der Instanz wäre das:
sudo grep "application password" /var/log/syslog
Jenkins verwenden und das EC2 Plugin konfigurieren
Jetzt kann man sich unter der öffentlichen IP mit dem user und Kennwort aus dem System log anmelden. Achtung: Die Daten gehen unverschlüsselt über das Internet, das ist nur für eine Demo ok, auf keinen Fall für eine länger laufende Instanz. Jetzt wählt man “install selected plugins” und startet den Server einmal neu.
EC2 Plugin installieren
- Zum Plugin Manager gehen: baseURL/jenkins/pluginManager/
- “Available” anwählen
- Auf
EC2
filtern - “Amazon EC2” anwählen
Install without restart
wählen
Navigation zum Plugin Manager über die GUI:
Und GUI Plugin wählen:
Das Plugin “Amazon EC2 Plugin” wählen.
Plugin Konfigurieren
Unter “Jenkins/Konfiguration” (basURL/jenkins/configure) “Cloud hinzufügen”:
Die Konfiguration
- Name (Beispiel): Arbeitsbienen
- Use EC2 instance profile to obtain credentials: anwählen
- Region: eu-central-1
- EC2 Key Pair’s Private Key: Hier einen private SSH Key einkopieren, der unter AWS verfügbar ist. Kann man unter Services-EC2-Network&Security-Key Pairs generieren
- Test Connection klicken. Wenn “Success” angezeigt wird, ist der erste Schritt ok
Jetzt muss noch der Worker Node eingestellt werden. Das ist der etwas komplexere Teil:
AMI konfigurieren
- Description: LinuxWorkerBee (Beispiel)
- AMI: ami-09def150731bdbcc2
- Check AMI klicken, AMI muss angezeigt werden, das Beispiel AMI ist ein aktuelles Linux2 AMI für eu-central-1
- Instance Type: T3Micro (Beispiel)
- Availability Zone: eu-central-1b
Die AZ hängt natürlich von eurem VPC und Subnets ab, mit dem Standard VPC sollte das aber so funktionieren - Security group names: Security Group ID der SG SecurityGroupJenkinsNode. Wird auch abgefragt im Beispiel mit
make show-sg
. - Remote User: ec2-user
- Subnet ID: die Subnet ID muss muss mit der Availability Zone zusammen passen.
- Eine Liste dazu bekommt man hier mit
make show-subnet
Unter Userdata wird das JDK installiert. Es wird für den Jenkins Agenten auf dem Knoten verwendet:
#!/bin/bash -xe
sudo yum -y install java-1.8.0
sudo yum -y remove java-1.7.0-openjdk
In der Übersicht sollte das in etwa so aussehen:
Dann “Hinzufügen” wählen. Zum Schluss “Speichern” wählen. Wenn alles stimmt, haben wir jetzt Arbeitsknoten. Auf zum Test.
Test des Plugins
Build Projekt im Jenkins Server anlegen
-
Neues Projekt anlegen:
-
Name: Demo (Beispiel)
-
Typ: “FreeStyle” Softwareprojekt bauen
-
Buildverfahren: Shell ausführen
-
Befehl: sleep 300
Hier brauchen wir nur einen Befehl, der die Knoten länger beschäftigt…
So in etwa soll das aussehen:
Und
Build ausführen
Wenn wir jetzt einen Build starten:
Dann wird der auf unserem Jenkins Server ausgeführt, noch nicht auf dem dynamischen Knoten.
Warum? Das EC2 Plugin startet erst Nodes, wenn der Server überlastet ist. Überlastung ist definiert als mehr gleichzeitige Builds, als der Server verarbeiten kann. Daher stellen wir zum Test ein, dass unser Jenkins Server gar keine Builds verarbeiten kann:
Jetzt starten wir den Build erneut. Und es passiert…erstmal nichts. Wir müssen noch etwas in den erweiterten Konfigurationen des EC2 Plugins einstellen: Dazu:
-
Wählen wir “erweitert:
-
Stellen für “Instance Cap” z.B. 10 ein. Das ist die Anzahl der builds, die der Worker node verarbeiten kann
-
No delay provisioning: Enabled
Diese Einstellung steuert, das der Jenkins Server nicht nach eine internen (mir unbekannten) Algorithmus die Worker nodes startet, sondern sofort, wenn mehr Arbeit da ist, also der Server und eventuelle andere Knoten verarbeiten kann -
Speichern!
Das sollte in etwa so aussehen:
Um zu schauen, ob die dynamische Provisionieren jetzt klappt, schauen wir uns nochmal an, wieviele EC2 Instanzen gerade laufen:
Build automatisch
Start Prozess des Knotens
Nach dem Start des Builds steht er erst in der Warteschleife:
Dann wird ein Arbeits-Knoten gestartet:
Nach dem Provisionieren des Knotens steht die eingestellt Anzahl an Buildern bereit und das Demo Projekt wird gestartet:
Und auch auf AWS Seite kann ich die Arbeitsknoten sehen:
Was passiert beim Starten des Knotens im Detail?
-
Instanz starten jund provisionieren:
May 05, 2019 11:10:36 AM hudson.plugins.ec2.EC2Cloud INFO: Launching instance: i-093802e00d601dc28
-
Server verbindet sich per ssh:
INFO: Connecting to ec2-52-29-233-168.eu-central-1.compute.amazonaws.com on port 22, with timeout 10000. INFO: Connected via SSH.
-
Java Packete installieren
-
Ausführungs Jar “slave” installieren:
May 05, 2019 11:11:06 AM hudson.plugins.ec2.EC2Cloud INFO: Copying slave.jar to: /tmp
-
Agenten starten:
INFO: Launching slave agent (via Trilead SSH2 Connection): java -jar /tmp/slave.jar ... Agent successfully connected and online
Dann wird der Knoten nach der eingestellten “Ruhezeit” idle time wieder runtergefahren. Die komplette Konfiguration (siehe Anhang) kann man vom Jenkins Server in der “config.xml” sehen. Bei Bitname ist das im Beispiel in
/opt/bitnami/apps/jenkins/jenkins_home
.
Worauf ist noch zu achten
Bei dynamischen Erzeugen muss man beachten, dass es für das Starten von EC2 Instanzen Soft Limits gibt. Diese sind eventuell über den AWS Support hochzusetzen.
Für bestimmte Builds kann auch der Einsatz von Docker Containern hilfreich sein. Dafür gibt es ein anderes Jenkins AWS Plugin.
Fazit
Ist man auf Jenkins festgelegt, kann man dennoch mit dem EC2 Plugin dynamisch Arbeitsknoten erzeugen. Dabei ist die Konfiguration relativ komplex, man kann viel falsch machen, wenn z.B. Subnet und Region nicht stimmen, oder die Security Groups nicht passen usw.. Unter dem Aspekt der Sicherheit müsste man alles in private Subnetze umbauen und den Jenkins mit https versehen.
Außerdem muss man den Jenkins Server patchen und verwalten. Keine Sorgen um dynamisches Provisionieren von Knoten muss man sich bei der Verwendung des AWS Server CodeBuild machen. Eine komplette Anwendung mit Codepipeline lässt sich mit CodeStar einrichten.
Der Werbeblock: Dabei unterstützt tecRacer natürlich gerne!
Es ist also abzuwägen, welche Lösung für Ihre Architekturanforderunge die passende ist.
Anhang
Links
Jenkins Beispiel Konfiguration
<clouds>
<hudson.plugins.ec2.ec2cloud plugin="ec2@1.42">
<name>ec2-Arbeitsbienen</name>
<useinstanceprofileforcredentials>true</useinstanceprofileforcredentials>
<rolearn />
<rolesessionname />
<credentialsid />
<privatekey>
<privatekey>...</privatekey>
</privatekey>
<instancecap>10</instancecap>
<templates>
<hudson.plugins.ec2.slavetemplate>
<ami>ami-09def150731bdbcc2</ami>
<description>LinuxWorkerBee</description>
<zone>eu-central-1c</zone>
<securitygroups>sg-0f0b7d051f93b8296</securitygroups>
<type>T3Micro</type>
<ebsoptimized>false</ebsoptimized>
<monitoring>false</monitoring>
<t2unlimited>false</t2unlimited>
<labels />
<mode>NORMAL</mode>
<userdata>#!/bin/bash -xe sudo yum -y install java-1.8.0 sudo yum -y remove java-1.7.0-openjdk</userdata>
<numexecutors>10</numexecutors>
<remoteadmin>ec2-user</remoteadmin>
<subnetid>subnet-0a03c11b8ff3725e7</subnetid>
<idleterminationminutes>30</idleterminationminutes>
<iaminstanceprofile />
<deleterootontermination>false</deleterootontermination>
<useephemeraldevices>false</useephemeraldevices>
<customdevicemapping />
<instancecap>2147483647</instancecap>
<stoponterminate>false</stoponterminate>
<useprivatednsname>false</useprivatednsname>
<associatepublicip>false</associatepublicip>
<usededicatedtenancy>false</usededicatedtenancy>
<amitype class="hudson.plugins.ec2.UnixData">
<rootcommandprefix />
<slavecommandprefix />
<slavecommandsuffix />
<sshport>22</sshport>
</amitype>
<launchtimeout>2147483647</launchtimeout>
<connectbysshprocess>false</connectbysshprocess>
<connectusingpublicip>false</connectusingpublicip>
<nextsubnet>0</nextsubnet>
</hudson.plugins.ec2.slavetemplate>
</templates>
<region>eu-central-1</region>
<nodelayprovisioning>true</nodelayprovisioning>
</hudson.plugins.ec2.ec2cloud>
</clouds>
Bild
Photo by Damien TUPINIER on Unsplash