Docker mit Portainer im Überblick behalten

Docker ist für den produktiven Betrieb von Anwendungen in den letzten Jahren ein sehr wichtiges und etabliertes Produkt geworden. Allerdings ist Docker keine vollständige Plattform, sondern muss beispielsweise mit Kubernetes zur Orchestrierung, Administration und Überwachung ergänzt werden.

Aber auch für Anwendungsentwickler und für die Bereitstellung einer Testumgebungen ist Docker äußerst praktisch. Allerdings ist hier das Aufsetzen eines Kubernetes-Clusters häufig dann doch etwas zu kompliziert. Aber wir können auch Docker mit Portainer im Überblick behalten.

Was ist Portainer?

Portainer ist eine schlanke We b-Anwendung, die die Verwaltung einer Docker-Umgebung über ein komfortables Web-Frontend ermöglicht. Ein erster Überblick ist über ein Online-Demo verfügbar.

Das Online Demo einer Portainer-Installation

Portainer ermöglicht eine komplette Verwaltung der Docker-Umgebung:

  • Pull von Images
  • Erzeugen, Starten, Stoppen und Überwachen von Containern
  • Anlegen von Volumes
  • Verwaltung der Docker-Netzwerke

Installation auf eine lokale Docker-Umgebung

 Hierzu wird Portainer sinnvollerweise selbst in einem Docker-Container betrieben. Allerdings muss der lokale Unix-Socket des Hosts gemounted werden. Ebenso ist es sinnvoll, die Portainer-Konfiguration in ein Volume auszulagern:

docker volume create portainer_data
docker run -d -p 9000:9000 -p 8000:8000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

Die Web-Seite ist über das Port-Mapping 9000 unter der Host-Adresse verfügbar:

Ein Portainer mit Zugriff auf die lokale Docker Engine

 


Seminare zum Thema

Weiterlesen

Docker Compose

Übersicht: Was ist Docker Compose?

Die Definition eines Docker-Containers erfolgte bisher durch die Parametrisierung des docker create-Befehls. Dies wurde so auch in früheren Artikeln so beschrieben und ist auch ein durchaus korrekter Ansatz. Allerdings werden diese Befehle spätestens in dem Moment sehr unhandlich und unübersichtlich, wenn mehrere Container miteinander verlinkt werden und damit voneinander abhängig sind.

Mit Docker Compose wird die Definition verlinkter Container besser gelöst: Statt eines Aufrufs von docker createmit vielen Parametern werden diese in einer einfach strukturierten Textdatei abgelegt. Als Dateiformat hat sich die Docker-Community für YAML entschieden, eine sinnvolle und nachvollziehbare Entscheidung.

Damit wird werden Dependencies zwischen Docker-Containern deutlich vereinfacht. Dies kann auch sofort dem WordPress-Beispiel aus der Docker Compose-Dokumentation entnommen werden:

 

version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}

Selbst ohne bisherige Kenntnisse in Docker Compose ist einem Leser mit Docker-Hintergrund sofort klar, was mit dieser Datei bezweckt werden soll:

  1. Es wird ein MySQL-Container aus dem mysql:5.7-Image erzeugt.
  2. Dieser benutzt ein Volume namens db_data
  3. und wird automatisch gestartet, sobald die Docker-Engine aktiv ist.
  4. Benutzername, Passwort und der Name der Datenbank werden über Environment-Parameter gesetzt.
  5. Ein weiterer Container beruht auf dem wordpress:latest-Image und stellt einen WordPress-Server bereit.
  6. Auch hier erfolgt die Konfiguration über Environment-Parameter, zusätzlich erfolgt ein Port-Mapping.
  7. Mit depends-on wird dieser Container aber nun mit dem MySQL-Container verlinkt. Damit steht für beide Container ein gemeinsames Netzwerk bereit, so dass der Web Server von WordPress direkt auf den MySQL-Server zugreifen kann.

Falls nun Docker Compose installiert ist wird diese Information ganz einfach in einer Datei namens docker-compose.yml abgelegt.

Und dann genügt tatsächlich der Befehl docker-compose up, um die gesamte Anwendung zu starten!

Einige Details

Installation

Die Installation ist detailliert hier beschrieben, wird aber hier exemplarisch für eine Linux-Distribution, z.B. SLES 12, durchgeführt:

Als erstes wird die für die Distribution relevante Compose-Version von GitHub geladen und ausführbar gemacht:

sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

Zusätzlich kann die Bash-Completion installiert werden:

curl -L https://raw.githubusercontent.com/docker/compose/1.21.2/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose

Ab jetzt sollte der Befehl docker-compose verfügbar sein, also beispielsweise

docker-compose -v

Arbeitsweise

Docker Compose ist keine Erweiterung der Docker-Engine sondern “nur” ein Generator für Docker Befehle auf Grundlage der yml-Datei. So erzeugt der up-Befehl von docker compose ganz normale Docker-Container, die beispielsweise über ein docker ps aufgelistet werden können. Entsprechend wird der down-Befehl diese Container wieder löschen.

Kommando-Referenz

Selbstverständlich müssen die über docker compose definierten Container nicht jedesmal erneut mit up erzeugt werden. Zum Starten und Stoppen dienen die Befehle start und stop, die die vorhandenen Container benutzen.

Die vollständige Kommando-Referenz ist Bestandteil der Docker-Compose Dokumentation, ebenso die Command Line-Befehle.

Compose und Build-Prozess

Für eine agile Entwicklung kann die docker-compose.yml auch ein Dockerfile angeben und vor der Erstellung der Container erst einmal das zugehörige Image neu bauen. Damit kann compose auch sinnvoll in den Buildprozess einer Docker-Anwendung integriert werden.

 

Weiterlesen

Nexus als Docker-Registry

Nachdem im vorigen Artikel Nexus für Maven eingerichtet wurde zeige ich nun, wie Nexus als Docker-Registry benutzt werden kann.

Einrichten der Repositories

Docker unterstützt für Images kein Snapshot-Konzept. Folglich sind drei Repositories notwendig:

  • DockerProxy als Proxy für Dockerhub
  • DockerHost für die eigenen Images
  • DockerGroup als Gruppe für Hosted und Proxy

Zugriff über http

Docker verlangt im Standard eine gesicherte Kommunikation über https. Um unsichere Verbindungen zu erlauben muss die Docker-Engine in /etc/docker/daemon.json für “unsichere” Registries konfiguriert werden:

{
 "insecure-registries": [
   "10.72.2.55:8082", 
   "10.72.2.55:8083", 
   "10.72.2.55:8084"], 
 "disable-legacy-registry": true 
}

Nach Neustart der Docker-Engine ist damit ein Zugriff über http erlaubt:

service docker restart

Vorsicht: Alle laufenden Container sind nach dem Neustart gestoppt!

Authentifizierung und Pull

Die Authentifizierung gegen das Repository erfolgt durch

docker login -u admin -p admin123 10.72.2.55:8084

Hierbei wird im User-Home eine Datei mit den angegebenen Credentials angelegt und für jegliche Kommunikation mit dieser Registry benutzt.
Nun können Images vom Nexus geladen werden, der selbst wiederum die Daten von Dockerhub lädt und chached.
docker pull 10.72.2.55:8084/nginx

Push eines eigenen Images

Der Push erfolgt auf das Hosted Repository. Dazu ist ein weiterer Login notwendig:

docker login -u admin -p admin123 10.72.2.55:8083

Nun muss das vorhandene Image neu getagged werden, und zwar mit den Repository-Informationen:

docker tag javacream/javabase:1.0 10.72.2.55:8083/javacream/javabase:1.0

dann kann letztendlich gepushed werden:

docker push 10.72.2.55:8083/javacream/javabase:1.0

Push über Maven

In einem Maven-basierten Build-Prozess, der in diesem Artikel vorgestellt wurde, wird das Bauen der Docker-Images in einem Parent-POM definiert. Soll noch gepushed werden wird einfach der Name des Repositories mit konfiguriert:

<properties>
<repository_base_url>http://10.72.2.55:8081</repository_base_url>
<docker.namespace.prefix>10.72.2.55:8083/javacream</docker.namespace.prefix>
</properties>

Dann erfolgt das Push einfach über docker:push. Die Anmeldeinformationen erfolgen über die durch den docker login gespeicherten Informationen.

Benutzung von Nexus in Docker-Seminaren

Mit den oben genannten Konfigurationen kann in der Umgebung der Integrata ein fertig eingerichteter Nexus-Server benutzt werden. Dieser ist gemäß der obigen Ausführungen konfiguriert.  Allerdings darf nur der Referenten Images pushen und somit den Teilnehmern zur Verfügung stellen.

 

Weiterlesen

DockerDuke

Docker und Java – Teil 4: Ein RESTful Web Service mit Spring Boot

Im letzten Teil dieser Serie programmieren wir einen RESTful Web Service mit Spring Boot und stellen diesen über ein Docker-Image zur Verfügung. Dabei stützen wir uns auf den im dritten Teil beschriebenen Build-Prozess.

Spring

Was ist Spring Boot?

Mit Hilfe von Spring Boot können auch komplexe Server-Anwendung als einfaches Java-Archiv ausgebracht und gestartet werden. Dazu werden durch einen ausgefeilten Build-Prozess die Anwendungsklassen zusammen mit allen notwendigen Server-Bibliotheken in ein einziges ausführbares Java-Archiv gepackt.

Der Build-Prozess selbst wird wie üblich mit Hilfe eines Maven-Parents definiert. Dieser wird von der Spring-Community zur Verfügung gestellt.

<project
<groupId>org.javacream</groupId>
<artifactId>org.javacream.training.spring-boot-docker</artifactId>
<version>0.1.0</version>
<packaging>pom</packaging>
<name>Spring Boot Docker</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Benutzen wie dieses POM als Parent für das im 3. Teil der Artikelreihe benutzten Parents, so haben wir den Docker- und den Spring-Boot-Build vereint. Mehr ist tatsächlich nicht zu tun! Im  folgenden ist die vollständige Parent-POM dieser Anwendung gegeben, ergänzt um die (hier noch nicht benutzten) Abhängigkeiten zu Spring Data JPA und einer MySQL-Datenbank.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.javacream.training.docker</groupId>
	<artifactId>org.javacream.training.docker.parent</artifactId>
	<version>1.0</version>
	<packaging>pom</packaging>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.1.RELEASE</version>
		<relativePath />
	</parent>
	<properties>
		<docker.namespace.prefix>javacream</docker.namespace.prefix>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<!-- tag::plugin[] -->
			<plugin>
				<groupId>com.spotify</groupId>
				<artifactId>docker-maven-plugin</artifactId>
				<version>0.4.11</version>
				<configuration>
					<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
					<dockerDirectory>src/main/docker</dockerDirectory>
					<resources>
						<resource>
							<targetPath>/</targetPath>
							<directory>${project.build.directory}</directory>
							<include>${project.build.finalName}.jar</include>
						</resource>
					</resources>
				</configuration>
			</plugin>
			<!-- end::plugin[] -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<executions>
					<execution>
						<id>copy-dependencies</id>
						<phase>package</phase>
						<goals>
							<goal>copy-dependencies</goal>
						</goals>
						<configuration>
							<outputDirectory>${project.build.directory}/libs</outputDirectory>
							<overWriteReleases>false</overWriteReleases>
							<overWriteSnapshots>false</overWriteSnapshots>
							<overWriteIfNewer>true</overWriteIfNewer>
						</configuration>
					</execution>
				</executions>
			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>

			<plugin>
				<groupId>com.spotify</groupId>
				<artifactId>docker-maven-plugin</artifactId>
				<version>0.4.11</version>
				<configuration>
					<imageName>${docker.namespace.prefix}/${project.artifactId}</imageName>
					<dockerDirectory>src/main/docker</dockerDirectory>
					<resources>
						<resource>
							<targetPath>/</targetPath>
							<directory>${project.build.directory}</directory>
							<include>${project.build.finalName}.jar</include>
						</resource>
						<resource>
							<targetPath>/</targetPath>
							<directory>${project.build.directory}</directory>
							<include>libs/*.jar</include>
						</resource>
					</resources>
					<imageTags>
						<imageTag>${project.version}</imageTag>
						<imageTag>latest</imageTag>
					</imageTags>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<distributionManagement>
		<repository>
			<uniqueVersion>false</uniqueVersion>
			<id>nexus</id>
			<name>Corporate Repository</name>
			<url>http://localhost:8081/repository/maven-releases/</url>
			<layout>default</layout>
		</repository>
		<snapshotRepository>
			<uniqueVersion>true</uniqueVersion>
			<id>nexus</id>
			<name>Corporate Snapshots</name>
			<url>http://localhost:8081/repository/maven-snapshots/</url>
			<layout>legacy</layout>
		</snapshotRepository>
	</distributionManagement>
</project>

 

Ein RESTful Web Service

RESTful Web Services werden in Java meistens mit Annotationen realisiert. Dabei wird eine URL auf eine Java-Methodensignatur abgebildet. Dies erfolgt meistens durch Annotationen, entweder mit JAX-RS oder mit den RequestMappings aus Spring-MVC. Im folgenden Beispiel benutzen wir den zweiten Ansatz:

package org.javacream.training.rest.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Application {

	
	@RequestMapping(path = "/echo/{message}", method = { RequestMethod.GET })
	public String echo(@PathVariable("message") String message) {
		return "Echoing from Server: " + message;
	}
}

 

Diese Java-Klasse kann aus Eclipse heraus sofort gestartet werden und anschließend beispielsweise mit Hilfe des curl-Kommandos getestet werden:

curl -X GET http://localhost:8080/echo/Hello

Ein Maven-Build des Projekts erzeugt das Java-Archiv bzw. lädt es in das Artefakt-Repository

mvn package
mvn deploy

Ein Aufruf ist dann unter der Angabe des Java-Archivs möglich:
java -jar org.javacream.training.rest.spring.basic-0.1.0.jar

Natürlich kann das Archiv auch unbenannt werden, dann ist der Aufruf noch einfacher:
java -jar app.jar

Wichtig ist hier, dass das gebildete Artefakt alle notwendigen Bibliotheken mitbringt, um den Web Server für die http-Requests zu starten. Das Archiv ist vollständig.

Der Docker-Build

Zum Erstellen des Docker-Images brauchen wir nun nur noch ein Dockerfile! Dessen Inhalt ist aus den vorherigen Ausführungen heraus allerdings schon fast trivial:

  1. Als Basis nehmen wir eine Java-Grundinstallation.
  2. Dann kopieren wir noch das generierte Artefakt und
  3. definieren als EntryPoint den Java-Aufruf.
  4. Eine Port-Mapping oder ein Mounten des Log-Directories des Containers ist selbstverständlich noch möglich.
FROM openjdk:latest
ADD org.javacream.training.rest.spring.basic-0.0.1.jar app.jar
ENTRYPOINT java -jar app.jar

Der RESTful Web Service wird nun ganz normal gestartet:

docker run --rm javacream:org.javacream.training.rest.spring.basic:0.0.1

und könnte wieder über curl getestet werden.

Soll das Image noch auf das Artefakt-Repository geschoben werden genügt ein

mvn docker:push

Das funktioniert aber natürlich nur, wenn ein eigenes Repository betrieben wird. Aber auch dieses Problem ist bereits gelöst: Nexus und Artefactory unterstützen Docker ganz analog zu Java-Artefakten.

Damit steht das Image anderen Entwicklern, der Test&QS-Abteilung oder den System-Administratoren der Produktionsumgebung zur Verfügung.


Dieser Artikel ist der Abschluss meiner Reihe über “Docker und Java”. Wer mehr über den Einsatz von Java in einer Microservice-Systemarchitektur lesen möchte: Im Frühjahr 2017 erscheint eine Reihe von Artikeln zum Thema “Microservice-Architekturen mit Docker”.


Seminare zum Thema

 

Weiterlesen