Elasticsearch - oder Java-Anwendungen in da Cloud

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 AWSAzure 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: