Dieser Beitrag ist im August 2018 erschienen
Da mir heute mehrmals das Thema "Java-Anwendungen" in der Cloud über den Weg gelaufen ist (ein Kunde hat gerade die Herausforderung seine FactFinder-Installation betrieben zu bekommen, der andere startet gerade einen PoC mit Elasticsearch bei AWS, um die Suche für seinen Shop umzubauen), habe ich die Gelegenheit genutzt, um mein Wissen zum Thema Elasticsearch - und insbesondere Elasticsearch in Docker-Containern - zu aktualisieren. Wenn dieses Thema aktuell wird, kommen meist auch folgende Punkte mit:
- Wie gut laufen Java-Anwendungen eigentlich in Linux-Containern?
- Was antworten wir, wenn Kunden diese Anwendungen nicht als "managed application" sondern als Software-as-a-Service von uns beziehen wollen?
Java-Applikationen wie der FactFinder (der mutmasslich in einem Tomcat Applicationserver ausgeführt wird) oder Elasticsearch sind eigentlich für den Betrieb in Containern völlig unproblematisch. Die Container müssen nur passend implementiert sein. Leider sind die aktuell so beliebten Docker-Container und Kubernetes-Pods unter Linux dafür nicht passend implementiert. Die Kernelmechanismen, die zur Erzeugung von Docker-Containern verwendet werden (cgroups, namespaces und chroot), decken nicht alle Aspekte ab und virtualisieren nicht umfassend genug. Deshalb zeigen /proc/meminfo u. a. nicht die Limits des Containers an, sondern die der VM oder der Hardware, auf der der Container gerade läuft. Java-Applikationen, die auf Java8 oder älter basieren, werten die für den Container gesetzten Limits nicht aus. In Java9 gibt es eine experimentelle Funktion, die explizit eingeschaltet werden muß. In Java10 werden die Containerlimits automatisch ausgewertet (obwohl damit offenbar auch noch nicht alle Probleme gelöst sind (s. Links)). Anstatt die Container-Implementierung in Linux zu verbessern, mußten also die Applikationen angepasst werden. Leider ist das nicht bei allen Applikationen möglich. Die Auswirkung ist, dass der Kernel, die Applikation, die die Containerlimits erreicht, beendet - der legendäre OOM-Killer schlägt zu.
Linux Container auf Basis von OpenVZ mounten unter /proc ein pseudo-filesystem, sodaß in jedem Fall für Applikationen innerhalb des Containers auch nur die für den Container konfigurierten Limits sichtbar sind.
Auch die Optionen, die im docker-compose.yml von Elasticsearch verwendet werden, deuten auf Memory-Probleme hin. Swappen soll auf jeden Fall verhindert werden:
version: '2.2'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
container_name: elasticsearch
environment:
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata1:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- esnet
elasticsearch2:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
container_name: elasticsearch2
environment:
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "discovery.zen.ping.unicast.hosts=elasticsearch"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata2:/usr/share/elasticsearch/data
networks:
- esnet
volumes:
esdata1:
driver: local
esdata2:
driver: local
networks:
esnet:
In der Dokumentation stehen dann auch die Sätze "Usually Elasticsearch is the only service running on a box, and its memory usage is controlled by the JVM options. There should be no need to have swap enabled." Für Systeme, die dediziert für den Kunden betrieben werden, mag das vielleicht noch vertretbar sein - soll der Dienst "as-a-Service" angeboten werden, verbietet sich dies aber, da die eingesetzte Hardware natürlich so gut wie möglich ausgenutzt werden sollte.
Mich interessierte unter anderem deswegen, ob ich Elasticsearch oder sogar ein Elasticsearch-Cluster auch in einem Docker-Container (also eine auf Docker angepasste lx-branded zone) auf Triton bereitstellen könnte. Als Single-Node Cluster schien das zunächst kein Problem zu sein:
docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node"
docker.elastic.co/elasticsearch/elasticsearch:6.3.2
Leider stellte sich beim Deployment via docker-compose
heraus, dass dem Container zu wenige Threads zur Verfügung stehen. Dies ließ sich durch die Anpassung des entsprechenden Packages aber leicht beheben. Die nächste Schwierigkeit war dann die Clusterkommunikation der Elasticsearch-Knoten. Leider funktionierte der Aufbau des Clusters mit der obigen docker-compose.yml
nicht, was an der Kommunikationsmethode unicast
zur Masterwahl liegen könnte. Für AWS, Azure und GCP kann Elasticsearch eigene Discovery-Methoden verwenden - für Triton muß man dies also selbst implementieren. Zum Glück hat das schon jemand für uns erledigt. Mit Containerpilot und Consul können neue Elasticsearch-Knoten ihren Master finden - auch in Cloud-Umgebungen, die Elasticsearch derzeit nicht mit eigenen Servicediscovery-Modulen unterstützt.
root@e3b75fc5-3621-cdd8-f9dd-c9acbf4a37e9:~/elasticsearch# triton-compose -p es scale elasticsearch=6
Creating and starting es_elasticsearch_6 ... done
root@e3b75fc5-3621-cdd8-f9dd-c9acbf4a37e9:~/elasticsearch# triton ls -l |grep es_
ce73d046-108f-67e2-8e05-9ac37324451b es_consul_1 66758ffa lx sample-128M running DF 10.65.69.97 2018-08-02T13:05:43.584Z
b3c72f77-75f6-c974-8e69-f6c4f8f88002 es_elasticsearch_data_1 e025e434 lx sample-4G running DF 10.65.69.98 2018-08-02T13:05:47.859Z
4f2a6723-2404-ca5a-cbc2-d9c58a6e136a es_elasticsearch_1 e025e434 lx sample-4G running DF 10.65.69.109 2018-08-02T13:05:51.137Z
9889284d-023d-6b7e-f44e-8664b80ed30f es_elasticsearch_master_1 e025e434 lx sample-4G running DF 10.65.69.115 2018-08-02T13:05:54.390Z
2a5ee4a0-50a4-453f-eb78-8a9f565e36aa es_elasticsearch_3 e025e434 lx sample-4G running DF 10.65.69.127 2018-08-02T13:09:43.200Z
2cdcbb1d-c32b-43ed-ad51-b4df16b01e9c es_elasticsearch_2 e025e434 lx sample-4G running DF 10.65.69.128 2018-08-02T13:09:46.128Z
7127347f-ae1d-6928-a68b-e420e3a0422e es_elasticsearch_data_2 e025e434 lx sample-4G running DF 10.65.69.116 2018-08-02T13:11:42.965Z
e8188263-b9ce-ce0e-b86c-e22cdc0722c3 es_elasticsearch_data_3 e025e434 lx sample-4G running DF 10.65.69.117 2018-08-02T13:12:18.717Z
3ef39a4e-7752-ce06-ec2d-f3bf49331503 es_elasticsearch_5 e025e434 lx sample-4G running DF 10.65.69.114 2018-08-02T13:13:17.870Z
5acc9cd0-fa4e-6b24-80b2-e95bd63e6e0f es_elasticsearch_4 e025e434 lx sample-4G running DF 10.65.69.113 2018-08-02T13:13:19.972Z
57a9be24-dc6d-e884-bd16-df611ec3c47e es_elasticsearch_6 e025e434 lx sample-4G running DF 10.65.69.123 2018-08-02T20:46:53.415Z
Damit sieht der entsprechende Cluster so aus:
root@e3b75fc5-3621-cdd8-f9dd-c9acbf4a37e9:~/elasticsearch# curl "http://10.65.69.115:9200/_cluster/health?pretty=true"
{
"cluster_name" : "elasticsearch",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 10,
"number_of_data_nodes" : 9,
"active_primary_shards" : 0,
"active_shards" : 0,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 100.0
}
In der Adminui von Triton sieht das so aus:

Ein paar Fragen sind natürlich noch offen:
- Wie funktioniert die Skalierung abwärts (ohne Unterbrechungen für Kunden)?
- Wie greifen Kunden auf den Cluster zu?
- Wie funktionioniert eigentlich Multi-Mandantenfähigkeit bei Elasticsearch?
- Welche Möglichkeiten gibt es für Backup/Restore?
- Wie sieht es mit dem Monitoring des Clusters aus?
- Wieviel Aufwand ist es, die Docker-Images mit einer aktuellen Elasticsearch-Version zu erzeugen?
Eine Umgebung, in der man Elasticsearch für Kunden "as-a-Service" anbietet, muß natürlich die Möglichkeit haben, auch "nach Verbrauch" abrechnen zu können. Triton bietet immerhin die Möglichkeit minutengenau abzurechnen.
Links:
- Java inside docker: What you must know to not FAIL
- Memory inside Linux containers
- Java and Memory Limits in Containers: LXC, Docker and OpenVZ
- Docker, Cgroups, Memory Constraints, and Java: A Cautionary Tale, or Here be Reapers (sometimes)
- How to size correctly containers for Java 10 applications
- Java SE support for Docker CPU and memory limits