//

Pull-Requests mit Docker deployen

13.1.2017 | 4 Minuten Lesezeit

Die Git-Repositorys in meinem aktuellen Projekt sind auf Bitbucket Cloud gehostet. Änderungen am Code werden nur über Pull-Requests in die Code-Basis eingepflegt. Jenkins baut die Pull-Requests und vergibt entsprechend sein Approval, wenn der Build grün ist. Außerdem wird noch von mindestens einem Teammitglied ein Code-Review durchgeführt. Ein Code-Review erfordert zwar grundsätzlich nicht, dass der Reviewer den Code baut und die Anwendung lokal deployt und testet, aber manchmal möchte man sich das Ergebnis der Änderungen doch anschauen, gerade wenn z. B. CSS/HTML angepasst wurde. Ein visueller Test kann hier helfen, die Änderungen nachzuvollziehen und zu verifizieren.

Um es dem Reviewer möglichst einfach zu machen, sollte ein Weg gefunden werden, einen Pull-Request per Knopfdruck testbar zu machen. Die Anwendung wurde erst kürzlich auf Spring Boot migriert. Momentan bauen wir noch eine War-Datei, die auf einem Tomcat deployt wird. Die War-Datei lässt sich aber auch direkt mit dem embeddeten Tomcat ausführen und damit sehr einfach in ein Docker-Image verpacken.

Als Datenbank kommt MongoDB zum Einsatz. Für die lokale Entwicklung bauen wir dafür ebenfalls ein Docker-Image. Das Besondere daran ist, dass dieses Image bereits Daten enthält. Die Natur unserer Anwendung macht es nötig, dass immer aktuelle Daten in der Datenbank sind. Deshalb gibt es einen Job, der nächtlich Testdaten für die Integrationsinstanz der MongoDB generiert. Jenkins baut nun ebenfalls nächtlich ein Docker-Image für die MongoDB. Dabei wird ein Dump von der Integrationsinstanz gezogen, der dann ins Docker-Image importiert wird. Das Image wird in die Docker-Registry gepusht. Morgens kann man sich dann als Entwickler ein frisches Image pullen.

Unser Jenkins läuft ebenfalls unter Docker, ein Container für den Master und einer für den Slave, der alles baut. Der Docker-Socket des Hosts ist dabei in den Slave gemountet. Wenn nun Jenkins einen Pull-Request baut, erstellt er gleich auch ein Docker-Image davon, das mit der Nummer des Pull-Requests getaggt wird. Über das Build-Pipeline-Plugin wird es nun ermöglicht, einen Pull-Request und dazu eine frische MongoDB-Instanz auf dem Jenkins-Host zu deployen.

Der Job zum Bauen des Pull-Requests hat einen manuellen Downstream-Job (Teil des Build-Pipeline-Plugins ), der die Anwendung samt Datenbank deployt.

Ein Pythonskript erzeugt dazu per Docker ein Netzwerk und startet die Container.

1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3 
4import socket
5from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
6from subprocess import Popen, STDOUT, PIPE
7 
8 
9def docker(*arguments):
10    cmdline = ['docker']
11    cmdline.extend(arguments)
12 
13    print()
14    print(' '.join(arg for arg in cmdline))
15 
16    proc = Popen(cmdline, stdout=PIPE, stderr=STDOUT)
17    output = []
18    while True:
19        line = proc.stdout.readline()
20        if not line:
21            break
22 
23        line = line.rstrip()
24        print('docker> %s' % line)
25        output.append(line)
26 
27    proc.wait()
28    return output
29 
30 
31def start_myproject():
32    print('Starting MyProject: %s' % version)
33 
34    docker('network', 'create', '-d', 'bridge', network_name)
35 
36    docker('run', '-d', '--name', mongo, '--network', network_name, '--network-alias', mongo, '-P', 'myrepo/mongo')
37 
38    docker('run', '-d', '--name', myproject, '--network', network_name, '-P', 'myrepo/myproject:%s' % version,
39           '--spring.profiles.active=dev', '--database.hosts=%s' % mongo)
40 
41    docker('port', mongo)
42    docker('port', myproject)
43 
44 
45def destroy_myproject():
46    print('Destroying MyProject: %s' % version)
47 
48    docker('stop', myproject)
49    docker('rm', myproject)
50 
51    docker('stop', mongo)
52    docker('rm', mongo)
53 
54    docker('network', 'rm', network_name)
55 
56 
57args_parser = ArgumentParser(description='Manages MyProject/Docker dev deployments')
58sub_parsers = args_parser.add_subparsers()
59 
60start_parser = sub_parsers.add_parser('start', help='Start MyProject and Mongo')
61start_parser.add_argument('--version', '-v', required=True, help='The MyProject version to start')
62start_parser.set_defaults(func=start_myproject)
63 
64destroy_parser = sub_parsers.add_parser('destroy', help='Destroy MyProject and Mongo')
65destroy_parser.add_argument('--version', '-v', required=True, help='The MyProject version to destroy')
66destroy_parser.set_defaults(func=destroy_myproject)
67 
68args = args_parser.parse_args()
69 
70version = args.version
71network_name = version
72mongo = 'mongo_%s' % version
73myproject = 'myproject_%s' % version
74 
75args.func()
76

Der Port für die Anwendung wird von Docker vergeben. Die URL findet man dann im Log des Jobs, an das man über den Build-Pipeline-View schnell rankommt. Der Job erhält als Parameter die Nummer des Pull-Requests und weiß dadurch, welches Image zu starten ist.

Der Job zum Deployen hat wiederum einen manuellen Downstream-Job, mit dem sich das Deployment wieder zerstören lässt.

Damit man sich die Logs der Anwendung anschauen oder auch mal eine Shell auf dem Container öffnen kann, ohne sich per SSH auf den Jenkins-Host zu verbinden, haben wir noch Shipyard installiert. Damit bekommt man einen Überblick über alle laufenden Docker-Container und kann ggf. aufräumen, falls mal ein Deployment vergessen wurde.

Wieso kein Pipeline-Job?

Leider ist das Bitbucket Pull Request Builder Plugin nicht mit den neuen Pipeline-Jobs des Jenkins kompatibel. Es gibt dazu ein Ticket , in dem auf das Bitbucket Branch Source Plugin verwiesen wird, das in unserem Fall aber nicht in Betracht kommt, da es mit Webhooks arbeitet. Für den master-Build verwenden wir allerdings bereits einen Pipeline-Job.

Beitrag teilen

Gefällt mir

0

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen

Wir helfen Deinem Unternehmen

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.