CONTINUOUS DELIVERY OF CONTINUOUS DELIVERY

Gerd Aschemann TUTORIAL / SCHULUNG

Gerd Aschemann

[email protected]

http://aschemann.net

twitter: @GerdAschemann

Slack: https://jvm­german.slack.com/ (Channel: #build- systems) AGENDA

Vagrant-Setup!

Stufe 0: Motivation/Setup/Architektur… (45 Min)

Stufe 1: Von der eigenen VagrantBox zum Build-Server und zurück (60 + 30 Min)

Stufe 2: Aufbau Jenkins (60 Minuten)

Stufe 3: Automatisierung Jobs (90 Minuten)

Stufe 4: Miniprojekt mit SCM-Manager und Maven Release (60 Minuten)

Abschluß: Diskussion/Kleinere Ergänzungen/Ausblick/Feedback (15 Minuten)

Ein großer Teil der Umsetzung wird im Laufe des Tutorials als Git- Repository bereitgestellt! MOTIVATION

Einführung/Vorstellungsrunde, 10 Min.)

Setup (Vagrant, 10 Min.)

Continuous Delivery (5 Min.)

Architektur (20 Min.) SETUP (VAGRANT + VIRTUALBOX)

Details folgen später!

Vagrant Install

Vagrant Init

Vagrant Start

Vagrant SSH

Vagrant Destroy VAGRANT INSTALL

Download (Vagrant + VirtualBox) + Installation gemäß Anleitung(en)

https://www.vagrantup.com/downloads.html

https://www.virtualbox.org/wiki/Downloads

Alternative: Installation über Package Manager

Debian/Ubuntu: [sudo] apt-get install vagrant virtualbox VAGRANT INIT

Arbeitsverzeichnis anlegen, z.B. demo, und Vagrant initialisieren

$ mkdir demo $ cd demo $ vagrant init -f bento/ubuntu-16.04 lädt Image und erzeugt eine Datei Vagrantfile

# -*- mode: ruby -*- # vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure ... Vagrant.configure(2) do |config| # ... config.vm.box = "bento/ubuntu-16.04" ... VAGRANT START Vagrant wird gestartet mit

$ vagrant up

Erzeugt zahlreiche Ausgaben:

Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'bento/ubuntu-16.04'...... ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM...... ==> default: Mounting shared folders...... VAGRANT SSH Anschließend kann man sich auf der VM einloggen

$ vagrant ssh ... vagrant@vagrant-ubuntu-trusty-64:~$

Hier kann man nun als Benutzer vagrant arbeiten, privilegierte Operationen können mit sudo ausgeführt werden, z.B.

vagrant $ sudo apt-get install screen

Das Arbeitsverzeichnis (demo) wird als /vagrant in die VM eingeblendet (Datei-Austausch, Provisionierung, … ) VAGRANT STOP / DESTROY Nach dem Ausloggen (oder von einer anderen Shell) kann man die VM zerstören:

$ vagrant destroy

Oder auch nur anhalten

$ vagrant suspend

Und weiterlaufen lassen

$ vagrant resume WAS IST CONTINUOUS DELIVERY? Frei nach Martin Fowler

Software ist jederzeit deploybar

Deploybarkeit hat Vorrang vor Erweiterungen

Aussagefähigkeit über Production-readiness nach jedem Change

Push-button Deployment beliebiger Versionen in beliebige Umgebungen

Hohe Automatisierung Continuous Delivery ist NICHT Continuous Deployment! CONTINUOUS DELIVERY CYCLE (?)

Build Deploy

Cycle???

Release? Test CONTINUOUS DELIVERY CASCADE (???)

no no

Develop (OK?) yes (OK?) yes Int. Test UAT Production +Unit Test CONTINUOUS DELIVERY LOOP (!)

Feedback (loop?)

Develop Int. Test UAT Production +Unit Test RÜCKBLICK: DEVELOPMENT

Development

Code Build Test

no (OK?)

yes

Commit/Push DEVELOPMENT NOCH EINFACHER

Development

Code Test

no (OK?)

yes

Commit/Push CONFIGURATION MANAGEMENT

Config Mgmt.

Configure Test

no (OK?)

yes

Use JENKINS SCM­MANAGER SO NICHT: PET BESSER SO: CATTLE CM SYSTEMATISCH: WAS FEHLT?

Configuration Management am "lebenden Herzen"

Feedback-Loop ist sehr kurz!

Feedback-Loop ist sehr lang!?

"Blood, Sweat, and Tears"-Management

Keine systematische Qualitätssicherung! CM EBENEN

Infrastruktur: OS/Linux (Windows, OSX, Android, Embedded, … )

Plattform:

Build Server (Jenkins, Bamboo, Travis, … )

Source Code Repo (Apache/SVN, SCM­Manager, Gitlab, Github, … )

Artifact Repo (Nexus, Artifactory, … )

QS-Management (Findbugs/Checkstyle/PMD, Surefire/Failsafe, … Sonar Qube)

Application: Maven, Gradle, … PLATFORM AS CODE (PAC)

Vagrant

Puppet (+ Shell + )

Jenkins (+ Maven)

Docker/Nexus ZIEL­ARCHITEKTUR (VEREINFACHT)

«Linux/Windows/OSX/...» Developer-PC

develop local deploy PaC Development commit/push PaC DevOps-Engineer

«Linux» Meta-Build-Server (james)

check out PaC check out PaC Source Code Repository Operator deliver Jenkins deliver Test

Prod DETAIL­VIEW: DEVELOPER

DevOps-Engineer

Change

«Linux/Windows/OSX/...» DevOps-PC

LocalDevelopment

«Git» Test Test Local_Repository

vagrant up / vagrant provision

«Vagrant» LocalVM

pull/checkout «Jenkins» upload/download «Nexus» «SCM-Manager» Jenkins Local Nexus Local SCM-Manager Local DETAIL­VIEW: META­BUILD­SERVER

«Linux» Meta-Build-Server

poll «SCM-Manager» Source Code Repository Jenkins

check out

Test

«Git» Test Test Test_Repository

vagrant up / vagrant provision

«Vagrant» TestVM

pull/checkout «Jenkins» upload/download «Nexus» «SCM-Manager» Jenkins_Test Nexus Test SCM-Manager_Test DETAIL­VIEW: PRODUKTION

Operator

pull/checkout

«Linux» Production Server

«Git» Production_Repository

deploy deploy deploy

pull/checkout «Jenkins» upload/download «Nexus» «SCM-Manager» Jenkins Production Nexus Production SCM-Manager Production VAGRANT

Setup virtueller Maschinen

Provider

Lokal/On-Premise: VirtualBox, libvirt (Qemu/KVM), Parallels, VMware, …

Cloud: AWS

Provisioner

Default: Shell

Infrastruktur CM-Tools: , Chef, Ansible, SaltStack?, … VAGRANTFILE

Ruby-Syntax

Provider + Box

Konfiguration

Name

Speicher

Netzwerk (Port forwards)

Dateisystem(e): /vagrant

Provisioner VAGRANTFILE (SAMPLE)

Vagrant.configure(2) do |config| # ... config.vm.box = "ubuntu/trusty64" config.vm.provider "virtualbox" do |vbox, override| vbox.name = "demo-jugf" vbox.memory = 2048 override.vm.network "private_network", ip: "192.168.50.17", virtualbox__intnet: true # Jenkins override.vm.network "forwarded_port", guest: 8080, host: "17080" # Nexus override.vm.network "forwarded_port", guest: 8081, host: "17081" ... STUFE 1: VAGRANT→GIT→SCM­ MANAGER→JENKINS

Vagrant ins Git

Git in den SCM-Manager

Job in Jenkins anlegen

Job ausführen VAGRANT→GIT

Git Repository anlegen

git init

Vagrantfile einchecken

git add Vagrantfile git commit -m'Vagrant started'

Optional: .gitignore anlegen und einchecken (s.u.) GIT→SCM (ANLEGEN)

Repository im SCM-Manager anlegen GIT→SCM (ZUGRIFFSRECHTE)

Zugriffsrechte im SCM-Manager vergeben:

Reiter: Permissions

Account hinzufügen (+)

Keine Gruppe (?)

Name: demo

Permissions: WRITE GIT→SCM (SCREENSHOT) PUSH LOCAL REPOSITORY

Remote-Repository URL hinzufügen

Jeder Nutzer/Gruppe bekommt ein eigenes Repository

git remote add origin http://demo@james:8082/scm/git/

User/Host/Port/Pfad beachten!

Push des aktuellen Standes

git push --set-upstream origin master JENKINS: JOB ANLEGEN

Neuen Job tutorial- als Freestyle project anlegen JENKINS: CHECKOUT AUS GIT

Source Code Management: Git mit

URL: http://localhost:8082/scm/git/;

Achtung:

Ohne User!

Ggf. anderer Host/Port?

Credentials: jenkins/* (ggf. mit Add hinzufügen) JENKINS: CHECKOUT AUS GIT (SC) JENKINS: BUILD TRIGGER + ANSI COLOR

Build-Trigger (ähnlich crontab-Eintrag)

Poll-SCM: Schedule H/3 * * * *

H/3 verteilt Minute gleichmäßig auf Stunde

* verteilen Stunde/Tag/Monat/Wochentag

Ansi-Color (Plugin) wg. Vagrant JENKINS: BUILD TRIGGER + ANSI COLOR (SC) JENKINS: BUILD EXECUTE SHELL

Build

Execute shell

Aktuell nur vagrant up

Save! JENKINS: BUILD EXECUTE SHELL (SC) JENKINS: FERTIGER JOB

Let’s Go: Build Now JENKINS: CONSOLE OUTPUT DISKUSSION 1A

Wie unterscheiden wir verschiedene Instanzen in der VBox? DISKUSSION 1B

Was passiert bei erneutem Start? DISKUSSION 1C

Wie kommt man von außen auf die VM?

Wie stoppt man die VM? STUFE 1A: VAGRANT­BOXEN MIT NAMEN Vagrantfile

Vagrant.configure(2) do |config| config.vm.box = "bento/ubuntu-16.04" config.vm.hostname = 'tutorial-' // (1) config.vm.provider "virtualbox" do |config| config.name = "tutorial-" // (2) end end

1 Der Hostname ist nicht der Name der VirtualBox 2 Hier bekommt die Box einen Namen, das Setzen ist abhängig vom Provider AUSFLUG: IGNORIEREN VON DATEIEN FÜR GIT

Kleiner Tipp für git: Datei .gitignore mit z.B. .vagrant/ anlegen und einchecken: .gitignore

.vagrant/

$ git add .gitignore $ git commit -m'Ignore local vagrant files' STUFE 1B: RE­BUILD IM JENKINS?

Problem: Box läuft schon im Jenkins

Symptom: vagrant up macht nichts

Lösung: Vor dem vagrant up noch ein vagrant destroy -f ausführen STUFE 1C: WIE KOMMT MAN VON AUSSEN AUF DIE BOX? Per Port-Mapping kann man die Box von außen erreichen (neben dem dynamischen Mapping des ssh-Ports)?

Vagrantfile

Vagrant.configure(2) do |config| config.vm.box = "bento/ubuntu-16.04" config.vm.hostname = 'demo' config.vm.provider "virtualbox" do |config, override| // (1) config.name = "demo" # SSH access override.vm.network "forwarded_port", guest: 22, host: "10022" // (1) end end

1 override beachten! Wenn alle den gleichen Port nehmen, haben wir im Jenkins ein Problem! PROVISIONIERUNG MIT VAGRANT PER SHELL

Vagrant kann nach Start der VM eine weitere Provisionierung durchführen. Vagrantfile

Vagrant.configure(2) do |config| config.vm.box = "bento/ubuntu-16.04" config.vm.hostname = 'demo' config.vm.provider "virtualbox" do |config, override| config.name = "demo" # SSH access override.vm.network "forwarded_port", guest: 22, host: "10022" end

config.vm.provision "shell", path: "apt-update.sh" // (1) end

1 Beispielsweise kann ein Shell-Skript ausgeführt werden: PROVISION: APT-GET UPDATE … apt-update.sh

#!/bin/bash

set -eu

sudo apt-get update sudo apt-get -qq dist-upgrade PROVISIONIERUNG: ALTERNATIVEN Alternativen sind

Puppet

Chef

Ansible

Saltstack

… OPTIMIERUNG: APT­CACHE IN VAGRANT NOTICE: Installation in Vagrant zieht mittlerweile einige Debian/Ubuntu Packages an (Netzlast/Zeit)!

Lösung: Vagrant mountet Dateisysteme/Verzeichnisse in VM!

Vagrantfile (Ausschnitt)

Vagrant.configure(2) do |config| config.vm.hostname = "demo" config.vm.box = "bento/ubuntu-16.04" config.vm.synced_folder "cache/apt-archives", "/var/cache/apt/archives" # (1)

config.vm.provider "virtualbox" do |config, override| ...

1 Lokales Verzeichnis cache/apt-archives wird als /var/cache/apt/archives in VM montiert MOUNT VON VERZEICHNISSEN

Das Verzeichnis muss vorher existieren! In Git leere Datei einchecken!

$ mkdir -p cache/apt-archives $ echo "This dir must not be empty" > cache/apt-archives/.readme $ git add cache Vagrantfile $ git commit -m'Added apt caching!' OPTIMIERUNG PER HTTP CACHE (SQUID)

Setup des Cache (hier Squid) ist nicht Gegenstand des Tutorials

Environment-Variablen zur Cache-Nutzung

$HOME/.vagrantenv

export http_proxy=http://192.168.2.17:3128/ # (1) export HTTP_PROXY=$http_proxy export no_proxy=localhost,127.0.0.1 export NO_PROXY=$no_proxy

export VAGRANT_HTTP_PROXY=$http_proxy export VAGRANT_APT_HTTP_PROXY=$http_proxy export VAGRANT_NO_PROXY=$no_proxy

1 IP-Adresse statt Hostnamen! HTTP CACHE IN JENKINS (JAMES)

Im Jenkins-Home eine Datei .vagrantenv mit Environment-Variablen anlegen

In Jenkins Shell-Execution die Environment-Variablen einlesen:

test -r $HOME/.vagrantenv && . $HOME/.vagrantenv

vagrant destroy -f vagrant up

# vagrant resume # (1)

1 Später: Box nach erfolgreichem Setup wieder abschalten STUFE 2A: JENKINS AUFBAUEN!

Jenkins direkt aufbauen?!

Jenkins per Puppet aufbauen! JENKINS DIREKT INSTALLIEREN?

Bloß wie?

sudo apt-get install jenkins

Funktioniert? … Nicht!

Jenkins ist kein Teil von Debian/Ubuntu?

Außerdem: Jenkins braucht Java … und Git … und … ALTERNATIVE: INSTALLATION DURCH PUPPET

Puppet hat ein Jenkins-Modul

Das Modul kann nicht nur Jenkins installieren

Sondern auch

Plugins installieren

(Build-) Jobs anlegen VORARBEIT: PUPPET INSTALLIEREN

Install-Skript für Puppet vorbereiten install-puppet.sh

#!/bin/bash

set -eu

sudo apt-get install -qq puppet PROVISIONIERUNG VON VAGRANT ERWEITERN Vagrantfile

Vagrant.configure(2) do |config| config.vm.box = "bento/ubuntu-16.04" config.vm.hostname = 'demo' config.vm.synced_folder "cache/apt-archives", "/var/cache/apt/archives"

config.vm.provider "virtualbox" do |config, override| config.name = "demo" # SSH access override.vm.network "forwarded_port", guest: 22, host: "10022" # Jenkins override.vm.network "forwarded_port", guest: 8080, host: "10080" // (1) end

config.vm.provision "shell", path: "apt-update.sh" config.vm.provision "shell", path: "install-puppet.sh" // (2) end

1 Port Forwarding für Jenkins eintragen 2 Weiteren Provisioner hinzufügen (es kann viele geben)! JENKINS PER PUPPET

Es gibt ein Puppet-Modul für Jenkins: (rtyler/jenkins) https://forge.puppetlabs.com/rtyler/jenkins

Puppet kann (per Default) keine lokalen Puppet-Module installieren!

Skript, das das Puppet-Module installiert install-jenkins.sh

#!/bin/bash

set -eu

test -d /etc/puppet/modules/jenkins \ || sudo puppet module install rtyler/jenkins # (1)

sudo puppet apply /vagrant/install-jenkins.pp # (2)

1 Installation des Puppet-Moduls, falls es nicht schon existiert 2 Installation von Jenkins per Puppet Manifest (Voller Pfad!)

PROVISIONIERUNG VON JENKINS (CONT.) install-jenkins.pp

package { 'git' : ensure => installed, } # (1) include jenkins # (2) $plugins = [ # (3) 'git', 'git-client', 'jobConfigHistory', 'scm-api', ] jenkins::plugin { $plugins : } # (4)

1 Sicherstellen, dass Git auch installiert ist 2 Die eigentlich Jenkins-Installation 3 Liste (Variable/Array) von Plugins 4 Installation der Plugins Man beachte die Erweiterungen in /etc/puppet/modules und /etc/apt/sources.list.d EXKURS: PUPPET

Puppet-Architektur

Puppet-Klassen und Manifeste

Puppet Module PUPPET ARCHITEKTUR(EN)

Default: Puppet Master + Puppet Agents

Zentraler Master enthält Module und Konfigurationen

Verteilte Agenten (pro Rechner einer) fragen Master regelmäßig ab

Lokale Konfigurationen gemäß Klassifikationen und Fakten

Alternative: Lokale/Explizite Verwendung von Puppet PUPPET KLASSEN UND MANIFESTE

Syntax zunächst ähnlich wie bei (objektorientierten) Skriptsprachen

class …

include …

Deklarative Semantik (nicht imperativ!):

Was sollte getan werden?

Genauer: Was soll wie aussehen/installiert werden?

Keine Abarbeitungsreihenfolge! PUPPET MODULE

Puppet Module enthalten weitere Komponenten, z.B.,

Templates

Tests

Bibliotheken/Referenzen auf andere Module

Implementierung in Ruby STUFE 2B: JENKINS­INSTALLATION ABRUNDEN

Tests

Test durch Perl-Module

Weitere Plugins TESTS! TESTS!! TESTS!!!

Continuous Integration braucht automatisierte Tests

Continuous Delivery erst recht

Wie kann man prüfen, ob Jenkins läuft?

Beispiel: http://localhost:8080/ enthält Jenkins Footer TEST JENKINS In Perl: test-jenkins.pl

#!/usr/bin/env perl

use strict; use warnings;

use WWW::Mechanize; use Test::HTML::Content (tests => 1);

# Create a new mechanize object my $mech = WWW::Mechanize->new(); my $url = 'http://localhost:8080/'; # Associate the mechanize object with a URL $mech->get($url); # Test for the logo in the content of the URL xpath_ok($mech->content, '/html/body/footer', 'Jenkins HTML contains footer'); JENKINS­INSTALLATION UM TEST ERGÄNZEN Einbinden in Installation: install-jenkins.sh (erweitert)

#!/bin/bash

set -eu

test -d /etc/puppet/modules/jenkins || sudo puppet module install rtyler/jenkins

# TODO: Make this relocatible! sudo puppet apply /vagrant/install-jenkins.pp

/vagrant/test-jenkins.pl # (1)

1 Test hinzugefügt! TEST THE TEST Einfach mal provisionieren:

$ vagrant provision

Warum schlägt der Test fehl?

Die Perl-Module fehlen noch! VIRTUALBOX FÜR TESTS PRÄPARIEREN

Nach der Puppet-Installation gleich ein Manifest ausführen install-puppet.sh

#!/bin/bash

set -eu

sudo apt-get install -qq puppet

sudo puppet apply /vagrant/install-puppet.pp # (1)

1 Aufruf eines Puppet-Manifests zur Installation weiterer Pakete PERL­MODULE INSTALLIEREN install-puppet.pp

# The minimal set of packages we would like to see! package { "git": } # (1)

# Some Packages required for testing # (2) package { "libwww-mechanize-perl": } package { "libtest-html-content-perl": }

# And other nice packages # (3) package { "screen": }

1 Redundanz zur Jenkins-Installation? 2 Perl + Module für den Test 3 Hier kann man gerne noch weitere kleine Helfer installieren! TESTS BRAUCHEN ZEIT!

Im Jenkins vermutlich ein weiterer Fehler? Dieses Problem kann sicher jeder selbst lösen? install-jenkins.sh (mit Test)

... sudo puppet apply /vagrant/install-jenkins.pp

echo "Wait a minute and allow Jenkins to start and initialize!" sleep 60 # (1)

/vagrant/test-jenkins.pl

1 Jenkins ist ein Java-Programm mit eingebettetem Applikations-Server (Servlet-Engine Jetty) → der Start braucht Zeit! JENKINS ADMIN USER

Auch Authentisierungsprobleme lassen sich lösen :-)

Jenkins braucht einen Admin-User bzw. sein Passwort setup-jenkins-admin-pw.pp

$jenkins_users_admin_config = "/var/lib/jenkins/users/admin/config.xml" augeas {"Set admin password": lens => 'Xml.lns', incl => $jenkins_users_admin_config, context => "/files$jenkins_users_admin_config", changes => [ 'set user/properties/hudson.security.HudsonPrivateSecurityRealm_-Details/passwordHash/#text "#jbcrypt:$2a$10$SAi.psdqzo1zKBmZ8rLH6.RcBRlcWI4QqtaLWnDYe.ORXU7d4e.DW"', ] } TEST DER INSTALLATION

Fehler bei der Installation: /var/log/jenkins/jenkins.log

Wie lassen sich diese einfangen?

Logrotate verwenden und nach Jenkins-Installation das Logfile prüfen: install-jenkins.sh mit logrotate

# Ensure that there is a fresh Jenkins log file - later we will check for SEVERE messages test -r /etc/logrotate.d/jenkins && sudo logrotate -f /etc/logrotate.d/jenkins

sudo puppet apply /vagrant/install-jenkins.pp

echo 'Wait a minute and allow Jenkins to start and initialize!' sleep 60 #

# First check whether there are SEVERE errors during Jenkins restart if egrep '^SEVERE: ' /var/log/jenkins/jenkins.log; then echo Stopping Jenkins execution due to installation errors >&2 exit 1 fi

/vagrant/test-jenkins.pl

JENKINS PLUGINS VERVOLLSTÄNDIGEN Weitere Jenkins Plugins

$plugins = [ 'display-url-api', 'git', 'git-client', 'javadoc', 'jobConfigHistory', 'junit', 'mailer', 'matrix-project', 'maven-plugin', 'scm-api', 'script-security', 'ssh-credentials', 'structs', 'workflow-scm-step', 'workflow-step-api', ] STUFE 2C: KLEINERE OPTIMIERUNGEN

Hiera

Etckeeper DAS HIERA­PROBLEM

Beim Start der VM bzw. bei Beginn der Puppet-Installation gibt es immer eine Reihe von Warnungen bzgl. hiera.

Ursache (technisch): Puppet sucht eine Datei /etc/puppet/hiera.yaml

Die Warnungen sollen verschwinden!

Wie bekommt man eine bestimmte Reihenfolge von Puppet- Ausführungen hin?

Wie kann man Dateien anlegen oder anpassen? HIERA VORBEREITEN (1)

Source aus Vorlage! setup-hiera.pp (1)

# Install hiera (at least to make warnings disappear :-) file { '/etc/puppet': # (1) ensure => 'directory', # (2) owner => 'root', # (3) group => 'root', mode => 0755, }

1 Deklaration einer Ressource, hier: Anlegen von Dateien (viele andere Resourcen möglich: file_line, exec, … ) 2 Typen für `ensure` (present ⇒ Datei, directory, absent ⇒ Löschen, … ) 3 Besitzer/Gruppe/Zugriffsrechte etc. HIERA VORBEREITEN (2) setup-hiera.pp (1)

file { '/etc/puppet/hiera.yaml': # (1) owner => 'root', group => 'root', mode => 0444, # (2) content => "--- :backends: - yaml

:logger: console

:hierarchy: # We will extend that later ... - common

:yaml: :datadir: /etc/puppet/hieradata ", require => File['/etc/puppet'], # (3) }

1 Inhalte als here document (Shell/Perl-Namensgebung!) 2 Jede Resource hat einen eindeutigen Namen innerhalb ihres Typs (oft z.B. Name einer Datei) 3 Reihenfolge: Das Anlegen einer Ressource kann von einer anderen Ressource abhängen (Typ/Subtyp mit beginnendem Großbuchstaben + Name der Ressource) TRACKEN VON ÄNDERUNGEN: ETCKEEPER

Das hat nichts mit Vagrant, Puppet oder Jenkins zu tun

Typisches Problem: Was hat sich in /etc geändert?

etckeeper (Debian/Ubuntu) erlaubt die Verwaltung von /etc per SCM (git, hg, bzr, darcs?)

Manuelles commit von Änderungen in /etc ETCKEEPER (CONT.)

Optional: Keine Installation von Paketen ohne commit anderer Änderungen

Alternative: Nächtliches Auto-Commit

Natürlich gibt es auch ein Puppet-Modul!

Hier: tracken von Änderungen von Puppet durch etckeeper ETCKEEPER (SHELL) install-puppet.sh (erweitert)

#!/bin/bash

set -eu

sudo apt-get install -qq puppet

# Avoid initial hiera error warnings! sudo puppet apply /vagrant/setup-hiera.pp

# Some standard resources like file_line etc. test -r /etc/puppet/modules/stdlib || sudo puppet module install puppetlabs-stdlib # Then add etckeeper module test -r /etc/puppet/modules/etckeeper || sudo puppet module install thomasvandoren-etckeeper sudo puppet apply /vagrant/setup-etckeeper.pp

# Now install the rest! sudo puppet apply /vagrant/install-puppet.pp ETCKEEPER (PUPPET) setup-etckeeper.pp (erweitert)

package { 'git': }

class {'etckeeper': require => Package['git'], # (1) }

# Tweak etckeeper # Order does not matter! file_line { 'etckeeper:no-nightly-commit': # (2) path => '/etc/etckeeper/etckeeper.conf', line => 'AVOID_DAILY_AUTOCOMMITS=1', match => '#AVOID_DAILY_AUTOCOMMITS=1', }

1 Etckeeper nach git installieren! 2 Nächtliche Auto-Commits ausschalten! STUFE 3: JENKINS­JOB(S) ANLEGEN

Jenkins soll Java-Projekte mit Maven in Docker bauen

Beispiel: Hello World (https://github.com/devopssquare/helloworld)

Verwendung von Jenkins Job-DSL (Groovy)

Erst mal manuell, dann automatisiert! KLEINE AUFGABE(N):

Installation von Docker

Manuell (apt-get install docker)

via Puppet https://forge.puppet.com/garethr/docker

Installation des Jenkins Job-DSL Plugin

https://wiki.jenkins­ ci.org/display/JENKINS/Job+DSL+Plugin

Manuell in Jenkins Console

via Puppet (Transitive Dependencies!) HERAUSFORDERUNGEN

Mögliche Probleme:

Jenkins darf Docker nicht aufrufen?

Docker braucht lange (Basis-Images sind doch recht groß) STUFE 3A: INSTALLATION VON DOCKER

Die gute Nachricht: Auch für Docker gibt es ein Puppet-Modul

Installiert Docker als System-Service

Kann auch Docker-Container als Services installieren DOCKER­IMAGES

Zahlreiche Images bei Docker-Hub (https://hub.docker.com) verfügbar, z.B.,

Datenbanken: PostgreSQL, MySQL, Neo4j, MongoDB, …

Web-Server (Apache, NGinx, … jeweils mit vielen Modulen)

Application-Server (Java: Wildfly, Tomcat, … Aber auch andere)

Build-Tools (Maven, Gradle, Android SDK, GULP für JavaScript, … )

Aufgabe: Installation von Docker per Puppet

Copy/Paste von anderem Modul (z.B. Puppet) Es wäre schön, wenn der User vagrant später in der Guppe docker wäre, damit er direkt mit Docker arbeiten kann. DOCKER PER PUPPET install-docker.pp

class { 'docker': manage_kernel => false, # tcp_bind => 'tcp://0.0.0.0:2375', socket_bind => 'unix:///var/run/docker.sock', }

# TODO make this dependant on the "real" virtualization (e.g., Vagrant w/ AWS has user "ubuntu" instead) user { 'vagrant': ensure => present, groups => ['docker'], require => Class['docker'], } DOCKER PUPPET­MODUL install-docker.sh

#!/bin/bash

set -eu

test -r /etc/puppet/modules/docker || sudo puppet module install garethr/docker

sudo puppet apply /vagrant/install-docker.pp

Provisionierung in Vagrant nicht vergessen! DOCKER: START EINES CONTAINERS

Auf der Vagrant-VM kann man nun einen Docker-Container starten

docker run -ti java:8 /bin/bash

Was passiert?

Dauert es lange?

Das java:8-Image ist ca. 650 MB groß. Auch hier haben wir wieder ein typisches Problem mit der Netzbelastung. DOCKER REGISTRY/­CACHE Auch für Docker läuft in unserem lokalen Netz schon ein Cache, ein sogenannter Registry Mirror. Durch folgende Option kann dieser genutzt werden.

diff --git a/install-docker.pp b/install-docker.pp index 0424671..b584422 100644 --- a/install-docker.pp +++ b/install-docker.pp @@ -1,7 +1,10 @@ +$docker_registry_mirror = 'http://192.168.2.17:5000' + class { 'docker': manage_kernel => false, # tcp_bind => 'tcp://0.0.0.0:2375', socket_bind => 'unix:///var/run/docker.sock', + extra_parameters => "--registry-mirror=$docker_registry_mirror --insecure-registry $docker_registry_mirror" }

# TODO make this dependant on the "real" virtualization (e.g., Vagrant w/ AWS has user "ubuntu" instead) EXKURS: DOCKER­HINTERGRÜNDE

Docker Idee

Docker-Historie (LXC)

Images und Container

Docker Build

Docker-Komposition DOCKER: IDEE(N)

Liefere komplette Anwendungen ab Oberkante Kernel

vom Entwickler gebaut, bis in Produktion geliefert (über alle Stages gleich)

Anwendung ist ein Prozess (z.B. Apache, Datenbank, … )

Threads vs. Forks (1 Prozessgruppe)

Ermöglicht transparente Verteilung mit loser Kopplung

Komposition von Anwendungen

Verteilung der Anwendungen über zentralen Hub/Registry (lokale/private Instanzen) DOCKER IST LXC

Stammt vom LinuX Container-Projekt ab (seit 2008)

Ermöglicht leichtgewichtige Container in einem Linux-Kernel

AKA leichtgewichtige Virtualisierung

Außensicht: Gesamte Anwendung wie ein Prozess DOCKER/LXC INNENSICHT

Innensicht (Realisierung/Technik)

Nutzt wenige Ressourcen/Capabilities (cgroups, namespaces, netlink/netfilter, … )

Container (Laufzeit) sind geladene Images

Images sind in Schichten aufgebaut (AUFS: advanced multi layered unification filesystem)

Dat(ei)en, die zur Laufzeit im Container entstehen, sind nicht persistent! DOCKER: VOM IMAGE ZUM CONTAINER

Docker-Daemon lädt Images vom Docker-Hub

Images gehören Nutzer + haben Namen (und Version/Tag)

Namensschema: /[:], z.B.

ascheman/dukecon-server:latest

ascheman/dukecon-server:1.0.1 DOCKER­CONTAINER IMPORT

Container importieren

andere Container (Volumes/interne IP)

Dateisysteme (Volumes)

Container exportieren

Ports

Volumes DOCKER: BUILD

Images werden (in der Regel) durch ein Buildfile gebaut, das Dockerfile

Images bauen aufeinander auf (FROM-Anweisung)

Jedes Image bzw. jede Anweisung bildet eine (Sub-) Schicht (Commit)

Jede Schicht speichert nur die Deltas zur vorhergehenden Schicht DOCKER: BUILD OPTIONEN

Es können u.a.,

Befehle ausgeführt werden (RUN)

Dateien hinzugefügt (ADD/COPY)

Environment-Variablen definiert werden (ENV)

Volumes exportiert werden (VOLUME)

Ports freigegeben werden (PORT)

Benutzer festgelegt werden (USER)

Prozesse (die eigentliche Anwendung) gestartet werden (CMD/ENTRYPOINT) DOCKER­NUTZUNG/KOMPOSITION

Das Dockerfile beschreibt die statische Sicht auf ein Image (Interface!)

Zur Laufzeit können dynamische Parameter definiert werden, z.B.,

Bindung von exportierten Ports an reale Ports des Gastgebers

Import von Volumes anderer Container/vom Host

Ressourcen-Nutzung (CPU, Speicher, … )

Setzen von Environment-Variablen

Setzen von Parametern für die Anwendungen (soweit von diesen vorgesehen) STUFE 3B: MAVEN/DOCKER­BUILD

Maven/Docker manuell aufrufen

Maven/Docker in Jenkins (Job manuell anlegen)

Maven/Docker in Jenkins per Seed-Job MAVEN/DOCKER MANUELL Mit Docker können wir Maven ausführen

docker run -i --rm --name hello-world-install \ -v ~/.m2/repository:/root/.m2/repository \ -v "$PWD":/usr/src -w /usr/src \ maven:alpine mvn clean install MAVEN/DOCKER IN JENKINS

Seed-Job manuell

Seed-Job per Puppet

Jobs starten SEED­JOB ANLEGEN

Anlegen eines Seed­Jobs mit dem Job DSLs-Plugins

Der Seed-Job dient dazu, andere Jobs anzulegen

Direkt (s.u.)

Ausführen eines Jobs, der https://github.com/devopssquare/helloworld­seed.git liest und die Groovy-Skripte ausführt.

Final: Anlegen des Seed-Jobs per Puppet ANLEGEN DES SEED­JOBS MANUELL

Neues Freestyle project in Jenkins anlegen

Neuer Build-Step Process Job DSLs

job ("helloworld-install") { scm { github ("devopssquare/helloworld", "master") } triggers { scm("H/10 * * * *") } steps { shell ('docker run -i --rm --name hello-world-install -v ~/.m2/repository:/root/.m2/repository -v "$PWD":/usr/src -w /usr/src maven:alpine mvn clean install } } SEED­JOB PER PUPPET ANLEGEN Anlegen eines Seed-Jobs in Jenkins

include jenkins

jenkins::job {"helloworld-seed": config => ' ' }

Quotes beachten! JENKINS: JOBS STARTEN

Jenkins hat ein Command Line Interface, über das per Remote-Http- Zugriff, z.B. Konfigurationen angepasst und Jobs gestartet werden können

java -jar /usr/share/jenkins/jenkins-cli.jar --help

Starten eines Jobs

java -jar /usr/share/jenkins/jenkins-cli.jar \ -s http://localhost:8080 build helloworld-seed

===

Voraussetzung für die Nutzung des Jenkins CLI ist ein Zugang per SSH Public-Keys setup-jenkins-cli.pp (Vollständige Inhalte per Download/Git)! include jenkins

# Enable CLI access via SSH credentials $jenkins_users_admin_config = "/var/lib/jenkins/users/admin/config.xml" augeas {"Set admin ssh key": lens => 'Xml.lns', incl => $jenkins_users_admin_config, context => "/files$jenkins_users_admin_config", changes => [ 'set user/properties/org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl/authorizedKeys/#text "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCodR+WmWcNvw+LIPoNrJ8xT2+4gJOvOh4Zj/ExGdHAG+eZAEMAmZ/z3x1c+I33w6TlxaSE09AqkjatKnae5yev3qSK4fsgEUp1n36WWPrc5V5i1yXuT6Eex+w5SaHUhD9IhyjySyAfZR4NxryiluWL/w9f3AaBVyPHNyf1C8BI3wVPL4H0ZAeBSxms0PW5YLhvXlRSfZhcSuSEeGM+yZPqr9bS3Ifr3dDXV4MUhP643G/Nu5EMzo6iCNU/oRrA2CNs1UZG4UO/H56sCiTrG3pUzxwesST8COMb+vGdQsrp6JfBkurCASATNQZW2jHGL+sw5M/QoOVKEt0UZ73NZFiB jenkins@develop"', ], }

$jenkins_configfile = "/var/lib/jenkins/config.xml" augeas {"Enable Jenkins CLI": require => Class['jenkins'], lens => 'Xml.lns', incl => $jenkins_configfile, context => "/files$jenkins_configfile", changes => [ 'set hudson/slaveAgentPort/#text "0"', ] } -> file { '/var/lib/jenkins/.ssh': JENKINS JOBS (CONT.)

Um den o.g. Build-Job zu starten, muss zunächst der DSL-Job gestartet werden

Anschließend kann der eigentliche Maven-Build gestartet werden SEED­JOB STARTEN Seed-Job starten

exec {'build helloworld-seed': command => "java -jar /usr/share/jenkins/jenkins-cli.jar -i /var/lib/jenkins/.ssh/id_rsa -s http://localhost:8080/ build -s helloworld-seed", require => Jenkins::Job['helloworld-seed'], path =>"/usr/bin", }

Analog für Build von helloworld SEED­JOB TESTEN Test des Seed-Jobs

# Analog zu anderen Tests ... # First test case my $url1 = 'http://admin:admin@localhost:8080/job/helloworld-seed/'; # Associate the mechanize object with a URL $mech->get($url1); # Test for the footer in the content of the URL xpath_ok($mech->content, '/html/body/footer', 'Jenkins Job "helloworld-seed" HTML contains footer');

my $url2 = 'http://admin:admin@localhost:8080/job/helloworld-install-mavendocker/'; # Associate the mechanize object with a URL $mech->get($url2); # Test for the footer in the content of the URL xpath_ok($mech->content, '/html/body/footer', 'Jenkins Job "helloworld-install-mavendocker" HTML contains footer'); DOWNLOAD MAVEN­ARTEFAKTE

Problem:

Java-Komponenten, die mit Maven gebaut werden, haben typischerweise sehr viele Abhängigkeiten (Dependencies, Artefakte)

Jeder Build lädt diese Artefakte aus dem Internet (Bandbreite) CACHING MAVEN

Idee: Caching

Lokales Caching: Verzeichnis ~/.m2/repository/

Verwendung eines Artefakt-Servers als Cache (Maven Mirror-Option), hier Nexus

Wenn die VM immer wieder neu aufgebaut wird (Jenkins), greift das lokale Caching nicht! EINSCHUB: VAGRANT SPEICHERPROBLEME?

Die VM ist mit 512 MB/0 Swap (Defaults) eigentlich zu klein für größere Java-Prozesse

Abhilfe: Vergrößern des Speichers in der VM, z.B. auf 2GB

diff --git a/Vagrantfile b/Vagrantfile index d177821..fc27cee 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -8,6 +8,7 @@ Vagrant.configure(2) do |config|

config.vm.provider "virtualbox" do |config, override| config.name = "demo" + config.memory = 2048 # SSH access override.vm.network "forwarded_port", guest: 22, host: "10022" # Jenkins ARCHITEKTUR MIT NEXUS

«Vagrant» ExtendedVM

pull/checkout «Jenkins» upload/download «Nexus» «SCM-Manager» Jenkins Nexus Test SCM-Manager NEXUS ALS DOCKER­CONTAINER install-nexus.pp

docker::run { 'nexus': image => 'sonatype/nexus', volumes => ['/data/nexus/sonatype:/sonatype-work'], # (1) ports => ['8081:8081'], }

1 Hier nicht beschrieben: Verzeichnis /data/nexus/sonatype anlegen und als Volume in den Docker-Container montieren (Daten persistent außerhalb des Containers ablegen). REPOSITORY­SERVER (ALS CACHE)

Anlegen einer Datei ~/.m2/settings.xml (für Jenkins)

Inhalte der Datei settings.xml per Download!

diff --git a/install-jenkins.pp b/install-jenkins.pp index a6d03eb..388a871 100644 --- a/install-jenkins.pp +++ b/install-jenkins.pp @@ -72,3 +72,20 @@ jenkins::job { 'test-build-job': ' } + +file { '/var/lib/jenkins/.m2': + ensure => 'directory', + owner => 'jenkins', + group => 'jenkins', + mode => 0755, + require => Class['jenkins'], +} + +file { '/var/lib/jenkins/.m2/settings.xml': + ensure => 'present', + owner => 'jenkins', + group => 'jenkins', + mode => 0644, + source => '/vagrant/settings.xml', + require => File['/var/lib/jenkins/.m2'], +}

MAVEN­BUILD ANPASSEN(?)

Muss der Maven-Build noch angepasst werden? STUFE 4: AUFBAU VON SCM­MANAGER ALS DOCKER­CONTAINER

Miniprojekt: SCM-Manager installieren und in Plattform und Prozesse integrieren!

Der SCM-Manager steht als Image sdorra/scm-manager im Docker- Hub zur Verfügung

Admin-Login mit scmadmin/scmadmin MINIPROJEKT: AUFGABEN

Installiere/Starte den Docker-Container (SCM-Manager) per Puppet

Initiale Replikation des/der produktiven Git-Repositories nach SCM- Manager

Umstellung des Jenkins-Builds auf (eigenen) SCM-Manager

Automatischer Aufruf von Maven-Release … MINIPROJEKT: TIPPS UND TRICKS SCM-Manager hat eine REST-Schnittstelle

Anlegen eines git-Repositories in SCM-Manager (JSON)

{ "name": "helloworld", "type": "git", "public": "true" }

REST-Auruf SCM-Manager per curl

curl -v -H 'Content-Type: application/json' -d @create-scm-repository.json \ http://demo:demo123@localhost:8082/scm/api/rest/repositories OFFENE FRAGEN Beantworte dir folgende Fragen bzw. löse sie, um SCM-Manager nutzen zu können

Was hat sich in /etc/init.d/ geändert?

Welcher Port ist in Nutzung und kann man diesen von Außen ansprechen?

Wenn nein, wie kann man diesen zugreifbar machen (Tip: Puppet-Doku zu docker::run) DOCKER: WEITERE FRAGEN

SCM-Manager legt seine Daten (Configs, Repositories) unter /var/lib/scm ab, wie kann man dieses auf ein Verzeichnis (Volume) des Hosts abbilden?

Wie kann der SCM-Manager von außerhalb der Vagrant-VM angesprochen werden (Port-Freigabe)?

Wie kann das erfolgreiche Setup des SCM-Managers getestet werden? OPTIONEN

Modulares Setup

Vagrant Tipps

Jenkins-Build mit Maven (ohne Docker) MODULARES SETUP

Module

Kompositionen

Stay tuned: https://github.com/dukecon/dukecon_infra wird in Kürze auf das modulare Setup umgestellt (umfangreiches Beispiel)! BASE MODUL

modules/ (base/)

scripts/init.sh

puppet/init.pp BASE/SCRIPTS/INIT.SH

Install puppet

Install puppet modules

stdlib

etckeeper SEQUENCE DIAGRAM FÜR MODULES

scripts/init.sh Puppet puppet/init.pp OS

Install Software Optional

Install Puppet Modules Optional

Apply Manifest

Run

Install Software

new Jenkins

Configure Software

Start Jenkins Default

Run

scripts/init.sh Puppet puppet/init.pp OS Jenkins INIT.SH

... dir=`dirname $0`

basedir="${dir}/.."

$sudo apt-get update $sudo apt-get install -qq puppet

test -r /etc/puppet/modules/etckeeper || $sudo puppet module install thomasvandoren-etckeeper test -r /etc/puppet/modules/stdlib || $sudo puppet module install puppetlabs-stdlib

# Avoid warning from initial puppet run test -r /etc/puppet/hiera.yaml || $sudo touch /etc/puppet/hiera.yaml

$sudo puppet apply "$basedir/puppet/init.pp" BASE/PUPPET/INIT.PP

Purge packages (NFS, … )

Install packages (git, perl-test, … )

Setup etckeeper! INIT.PP

# Delete some default packages ... package { 'nfs-common': ensure => "purged" } package { 'rpcbind': ensure => "purged" }

# The minimal set of packages we would like to see! package { "git": } package { "screen": } package { "apticron": }

# Some Packages required for testing package { "libwww-mechanize-perl": } package { "libtest-html-content-perl": } #... # Tweak etckeeper file_line { 'etckeeper:git': path => '/etc/etckeeper/etckeeper.conf', line => 'VCS="git"', }

file_line { 'etckeeper:no-nightly-commit': path => '/etc/etckeeper/etckeeper.conf', COMPOSITES

composites/

scripts/run.sh

lists/minimal SEQUENCE DIAGRAMM FÜR COMPOSITES

composites/ composites/ modules/ modules/ modules/ scripts/ lists/ jenkins/ docker/ nexus/ run.sh $composite init.sh init.sh init.sh

read modules iterate over module list

run

run

run

composites/ composites/ modules/ modules/ modules/ scripts/ lists/ jenkins/ docker/ nexus/ run.sh $composite init.sh init.sh init.sh RUN.SH

dir=`dirname $0`

modulesdir=/vagrant/modules test -d ${modulesdir} || modulesdir=${dir}/../../modules

compositesdir=/vagrant/composites test -d ${compositesdir} || compositesdir=${dir}/..

for comp in "$*" do echo "Applying '${comp}'" modules=`cat ${compositesdir}/lists/${comp}`

for module in ${modules} do ${modulesdir}/${module}/scripts/init.sh done done VAGRANTFILE: ERWEITERUNGEN/TIPPS

Environment

Boxname

Memory

IP/Port Ranges

Caching + Persistenz EXTENDED VAGRANTFILE

name = ENV['VAGRANT_NAME'] || "demo-jugf" memory = ENV['VAGRANT_MEMORY'] || 2048 ip_unique = ENV['VAGRANT_IP_UNIQUE'] || "17" port_unique = ENV['VAGRANT_PORT_UNIQUE'] || "#{ip_unique}" ... config.vm.hostname = name config.vm.synced_folder "cache/apt-archives", "/var/cache/apt/archives" config.vm.provider "virtualbox" do |vbox, override| vbox.name = name vbox.memory = memory override.vm.network "private_network", ip: "192.168.50.#{ip_unique}", virtualbox__intnet ... override.vm.network "forwarded_port", guest: 8055, host: "#{port_unique}055" ... MAVEN INSTALL PER PUPPET

diff --git a/install-jenkins.pp b/install-jenkins.pp index a95a835..5b6d459 100644 --- a/install-jenkins.pp +++ b/install-jenkins.pp @@ -20,3 +20,6 @@ $absent_plugins = [ jenkins::plugin { $absent_plugins : enabled => false } + +# Install maven +class { 'maven': } diff --git a/install-jenkins.sh b/install-jenkins.sh index 502245d..5cf1ff5 100755 --- a/install-jenkins.sh +++ b/install-jenkins.sh @@ -3,6 +3,7 @@ set -eu

test -d /etc/puppet/modules/jenkins || sudo puppet module install rtyler/jenkins +test -d /etc/puppet/modules/maven || sudo puppet module install maestrodev-maven

# TODO: Make this relocatible! sudo puppet apply /vagrant/install-jenkins.pp SEED­JOB MIT MAVEN (MANUELL)

Neues Freestyle project in Jenkins anlegen

Neuer Build-Step Process Job DSLs

mavenJob ("helloworld-install") { scm { github ("devopssquare/helloworld", "master") } triggers { scm("H/10 * * * *") } goals ('clean install') }