Kolumne: Docker rockt Java

Docker rockt Java: EC2 Container Service mit AWS

Sascha Möllering, Peter Roßbach
docker-fuer-java

© Software & Support Media

Bis vor Kurzem mussten Entwickler noch selbst Docker auf ihren EC2-Instanzen in Amazon Web Services installieren und das Management der Container vollständig selbst übernehmen. Mit der Einführung der Unterstützung von Docker in Elastic Beanstalk und OpsWorks wurde der Aufwand für die Nutzung von Containern bereits reduziert. Auf der Konferenz „AWS re:Invent 2014“ hat Amazon den neuen EC2 Container Service vorgestellt, der die Hürde für das Betreiben von Software in Docker-Containern noch einmal signifikant senkt.

Mit dem EC2 Container Service (ECS) von Amazon können verteilte Anwendungen in Containern auf einem Cluster von EC2-Instanzen betrieben werden. Der Cluster wird dabei vollständig von Amazon verwaltet. Durch die Konfiguration von Speicher- und CPU-Verbrauch werden die Container innerhalb des Clusters automatisch verteilt und platziert, je nachdem wo die entsprechenden Ressourcen gerade zur Verfügung stehen. Die Sicherheit innerhalb des eigenen Clusters ist gewährleistet, da eigene EC2-Instanzen gestartet werden. Kunden teilen sich also keine EC2-Instanzen. Darüber hinaus wird der Cluster innerhalb eines eigenen privaten Netzwerks (VPCs) gestartet und bietet somit einen hohen Isolierungsgrad, der über EC2 Security Groups und Network ACLs selbst verwaltet wird. Im Kontext von ECS ist ein Cluster eine logische Gruppierung von Containerinstanzen, auf denen Tasks ausgeführt werden können. Eine Containerinstanz ist eine vollständige EC2-Instanz, auf der Docker (in Version 1.3.3, Update auf 1.5 ist im Package Repo vorhanden) und der ECS-Agent, natürlich in einem eigenen Docker-Container, laufen. Der ECS-Agent ist in Go implementiert und liegt als Open-Source-Software unter der Apache-2-Lizenz vor.

Jede EC2-Instanz wird in einem Cluster registriert. Um eine Anwendung auf dem Cluster laufen zu lassen, ist eine Taskdefinition notwendig, die eine Anwendung beschreibt, welche eine oder mehrere Containerdefinitionen enthält. Diese Definition wird mithilfe einer JSON-Datei spezifiziert. Die Instanziierung einer Taskdefinition wird Task genannt. Als Container wird ein Docker-Container bezeichnet, der als Teil einer Taskdefinition erzeugt wurde. Der ECS-Scheduler plant, welche Instanz die Task ausführt. Der ECS-Agent liefert den eigenen Status und setzt den Taskplan dann lokal über den Docker-Daemon um (Abb. 1).

Abb. 1: Schematische Systemarchitektur einer ECS-Instanz

Abb. 1: Schematische Systemarchitektur einer ECS-Instanz

Erzeugung eines ECS-Clusters

Im folgenden Abschnitt wird beispielhaft mithilfe der AWS-CLI ein ECS-Cluster erzeugt, auf dem eine simple Task läuft. Seit Januar 2015 wird ECS auch im AWS Java SDK unterstützt. Da sich dieser Service zur Zeit des Drucks dieses Artikels noch im Previewstatus befindet und nur in der Region „us-east-1“ erhältlich ist, muss sich der interessierte Leser für diesen Service registrieren, damit das folgende Beispiel nachvollzogen werden kann. Als Vorbereitung zur Nutzung des ECS-Service müssen einige manuelle Konfigurationen in der AWS-Konsole durchgeführt werden (Kasten: ECS-Setup).

ECS-Setup

Für das initiale Setup für die Nutzung von ECS müssen einige Einstellungen vorgenommen werden. Besonders wichtig ist die Einrichtung einer IAM Policy, damit der Nutzer die Rechte hat, das ECS-Cluster zu verwalten. In der Konfiguration der AWS-CLI sollte „us-east-1“ als Standardregion definiert werden, damit diese nicht bei jedem Aufruf mitgegeben werden muss. Das ECS-Setup wird hier genauer beschrieben.

 

Ein ECS-Cluster wird mit aws ecs create-cluster –cluster-name <clustername> erzeugt, in unserem Fall wäre das:

aws ecs create-cluster --cluster-name DockerCluster

Rückgabewerte sind der Name des Clusters, der Status und der ARN. Wenn ein Cluster erfolgreich erzeugt wurde, müssen natürlich entsprechende EC2-Instanzen generiert und im Cluster registriert werden. Basis dafür ist ein spezielles ECS-AMI (amzn-ami-2014.09.1-amazon-ecs-optimized-preview3). Darüber hinaus muss die Instanz beim Erstellen noch mit speziellen EC2 User Data parametrisiert werden:

#!/bin/bash
echo ECS_CLUSTER=DockerCluster >> /etc/ecs/ecs.config

Damit wird beim Bootstrapping die Maschine über die Umgebungsvariable ECS_CLUSTER im entsprechenden Cluster registriert. Die Docker-Version des AMIs ist im Auslieferungszustand nur 1.3.3 auf der Basis von RHEL 6. Deswegen sollte unbedingt entweder manuell oder im User-Data-Skript ein sudo yum update -y durchgeführt werden, um die aktuelle Docker-Version 1.5 nutzen zu können (Abb. 2).

Abb. 2: AWS Console: Anlegen einer EC2-Instanz für ECS

Abb. 2: AWS Console: Anlegen einer EC2-Instanz für ECS

Natürlich kann eine EC2-Instanz auch für ECS mithilfe der CLI erzeugt werden:

aws ec2 run-instances --image-id ami-801544e8 --count 2 --instance-type t2.small --key-name <ssh-key> --security-group-ids <security-groups> --subnet-id <subnet> --user-data file:///path/to/user-data.sh --associate-public-ip-address

Mit aws ecs list-container-instances –cluster DockerCluster kann nachvollzogen werden, ob die Erzeugung und die Registrierung der Instanz im Cluster erfolgreich war:

aws ecs list-container-instances --cluster DockerCluster

Rückgabewerte sind die Amazon Resource Names (ARNs) der im Cluster registrierten Instanzen. Mithilfe dieser ARNs bzw. dem UUID-Teil der ARN kann über aws ecs describe-container-instances der Zustand der Instanzen näher betrachtet werden:

aws ecs describe-container-instances --cluster DockerCluster --container-instances container_instance_UUID

Hierbei fallen u. a. zwei „registered resources“ mit den Namen CPU und Memory auf: Diese beschreiben die freien Ressourcen, also wieviel freie CPU und wieviel freien Speicher die Instanz zur Verfügung hat. Bei der Definition einer Task kann angegeben werden, wie viele Ressourcen diese benötigt. ECS verteilt die Task entsprechend der freien Kapazitäten innerhalb des Clusters. Leider sind das die einzigen Restriktionen, die zurzeit für den Scheduler definiert werden können. Aktuell ist auch nicht bekannt, was passiert, wenn neben den durch ECS verwalteten Containern noch andere Docker-Container im Cluster laufen, bzw. wie das die Verteilung der Tasks beeinflusst.

Eine simple Taskdefinition mit dem Namen sleep360.json kann dabei wie in Listing 1 aussehen.

Listing 1

{
  "containerDefinitions": [
    {
      "name": "sleep",
      "image": "busybox",
      "cpu": 10,
      "command": [
        "sleep",
        "360"
      ],
      "memory": 10,
      "essential": true
    }
  ],
  "family": "sleep360"
}

Diese Taskdefinition wird über aws ecs register-task-definition –cli-input-json file://path/to/file/sleep360.json in ECS registriert. Mithilfe des Befehls aws ecs list-task-definitions können die Definitionen der Tasks aufgelistet werden. ECS speichert zusätzlich eine Historie dieser Definitionen. In diesem Beispiel wird das Docker-Image busybox aus dem Docker-Hub verwendet. Es ist aber durchaus möglich, Images aus einer privaten oder anderen öffentlichen Docker Registry zu nutzen. Das Image muss dann an dieser Stelle mit repository-url/image:tag referenziert werden. Leider bietet der EC2 Container Service keine „Managed Docker Registry“ als Service an. Doch ist die Implementierung eines solchen Service mithilfe der von Amazon bereitgestellten Bausteine nicht sonderlich schwierig. Anwendungen mit einem Service und einer Datenquelle wie z. B. eine Java-Anwendung und einer Postgres-Datenbank können – wie beim normalen Einsatz von Docker ohne ECS – mit Links zwischen den Containern verbunden werden. Inwiefern das bedeutet, dass Container, zwischen denen Links definiert worden sind, auf einer Instanz laufen, ist an dieser Stelle nicht klar. Denn beispielsweise mit Ambassador-Containern ist es möglich, Links über mehrere Instanzen zu verteilen. Ein Beispiel für einen Service mit einem Link zu einer Postgres-Datenbank könnte wie in Listing 2 gezeigt definiert werden.

Listing 2

[
  {
    "image": "my-registry:5000/java-service:latest",
    "name": "java-service",
    "cpu": 100,
    "memory": 500,
    "essential": true,
    "links": [
      "db"
    ]
    ...
  },
  {
    "image": "postgres",
    "name": "db",
    "cpu": 100,
    "memory": 500,
    "essential": true,
    "environment": [
      {
        "name": "POSTGRES_PASSWORD",
        "value": "<mypwd>"
      }
    ],
    "portMappings": []
  }
]

An dieser Stelle stellt sich natürlich die berechtigte Frage, ob die Nutzung einer Managed-Postgres-Instanz (Amazon Remote Data Service (RDS)) anstatt des eigenen Docker-Containers nicht die bessere Wahl wäre. RDS bietet bereits eine Backup-Strategie in S3-Buckets, eine HA-Implementierung durch die Nutzung von zusätzlichen Instanzen in unterschiedlichen Availability-Zonen (AZs) usw. Darüber hinaus kann mithilfe der AWS-SDKs aus dem Docker-Container auch auf die entsprechenden APIs zugegriffen werden, um die notwendige Infrastruktur programmatisch erstellen zu können. Erfahrungsgemäß ist es sinnvoll, die Managed Services der Cloud-Anbieter zu nutzen und sich auf die Geschäftslogik zu fokussieren, um wirkliche Kundenprobleme zu lösen und damit den eigenen Innovationszyklus zu verkürzen. Die vorhandene Taskdefinition kann folgendermaßen gestartet werden:

aws ecs run-task --cluster DockerCluster --task-definition sleep360:3 --count 1

Damit wird aus der Taskdefinition sleep360 in der ersten Revision eine Task erzeugt. Rückgabewert ist die vollständige Beschreibung aus Container, ARN, Status der Task etc. Um sich einen Überblick über alle im Cluster laufenden Tasks zu verschaffen, muss das list-Kommando ausgeführt werden:

aws ecs list-tasks --cluster DockerCluster

Dieser Befehl gibt den ARN der laufenden Tasks zurück. Die UUID der ARN ist wiederum notwendig, um eine genaue Beschreibung der Task und ihres Zustands zu erhalten:

aws ecs describe-tasks --cluster DockerCluster --task task_UUID

Da ECS auf Docker aufsetzt, ist es zusätzlich möglich, mit den üblichen Docker-Werkzeugen auf die Container lokal zuzugreifen. Mit dem Befehl docker ps -a kann beispielsweise ermittelt werden, welche Container laufen. Mit dem Befehl docker inspect <containerid> können genaue Informationen über den Container abgerufen werden. Leider werden diese und weitere wichtige Informationen, die z. B. das in Docker 1.5 neu eingeführte Statistik-API bereitstellt, nicht automatisiert in CloudWatch angezeigt. Darüber hinaus fehlt die Einbindung in den Elastic Load Balancer (ELB) und Auto Scaling jenseits der bekannten EC2-Mechanismen völlig. Die Kombination aus Metriken des Docker-Daemons, CloudWatch, ELB und Auto Scaling könnten schon in naher Zukunft interessante Skalierungsoptionen bieten.

Wunschzettel

Für die finale Version von ECS sollten noch folgende Dinge für die Produktionsreife des Service implementiert werden:

  • Eine private Docker Registry als Managed Service
  • Bessere Einbindung von CloudWatch (Metriken, Healthcheck und Logs)
  • AWS Console mit UI für ECS
  • Integration mit ELB und Auto Scaling von einzelnen Containern
  • Service Discovery für Container
  • Flexiblere Möglichkeiten des Schedulers

Fazit

ECS ist ein sehr neuer, aber dennoch vielversprechender Service, der die Hürde der Nutzung von Docker-Containern in AWS deutlich senkt. Für die finale Version des Service bleibt zu hoffen, dass noch einige Erweiterungen und Vereinfachungen umgesetzt werden.

Bei den Docker-Implementierungen in OpsWorks und Elastic Beanstalk steht eher der „klassische“ Ansatz im Fokus, bei dem eine Anwendung in einem Container verpackt dauerhaft läuft und der Container mit den AWS-Mitteln verwaltet und skaliert werden kann. Seit Kurzem unterstützt Elastic Beanstalk auch das Deployment von mehreren Docker-Containern gleichzeitig. Ein besonders interessanter Aspekt dieser Implementierung ist, dass die Container direkt mit dem ELB kommunizieren, die Indirektion über die EC2-Instanz somit entfällt.

Der Use Case von ECS hingegen ist eher auf die Verarbeitung einzelner kleiner Arbeitspakete und die Optimierung vorhandener EC2-Ressourcen ausgerichtet. Es ist nicht mehr relevant, wo genau der Container läuft; ECS findet im Cluster die notwendigen Ressourcen für die Task.

DevOps Docker Camp 2017

Das neue DevOps Docker Camp – mit Erkan Yanar

Lernen Sie die Konzepte von Docker und die darauf aufbauende Infrastrukturen umfassend kennen. Bauen Sie Schritt für Schritt eine eigene Infrastruktur für und mit Docker auf!

Geschrieben von
Sascha Möllering
Sascha Möllering
Sascha Möllering arbeitet als Solutions Architect bei der Amazon Web Services Germany GmbH. Seine Interessen liegen in den Bereichen Automation, Infrastructure as Code, Distributed Computing, Container und JVM.
Peter Roßbach
Peter Roßbach
Peter Roßbach ist ein Infracoder, Systemarchitekt und Berater vieler Websysteme. Sein besonderes Interesse gilt dem Design und der Entwicklung von komplexen Infrastrukturen. Er ist Apache Tomcat Committer und Apache Member. Mit der bee42 solutions gmbh realisiert er Infrastrukturprodukte und bietet Schulungen auf der Grundlage des Docker-Ökosystems, aktuellen Webtechnologien, NoSQL-Datenbanken und Cloud-Plattformen an.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.