tomcat systemd service

Einen Java Applikationsserver wie tomcat, so scheint es, ist nicht einfach in eine systemd Service Unit zu packen.
Der Grund dafür ist, dass es in den meisten Tutorials und HowTos falsch gemacht wird.
Hier ein stabil funktionierendes template für einen tomcat systemd Service.

Wie Jonathan de Boyne Pollard in seinem Beitrag: Wrapping Apache Tomcat in many pointless extra layers sehr gut heraushebt ist, das meist mit dem falschen Ansatz daran gegangenen wird ein SysV Init Script in ein systemd Unit File zu portieren. Der Beitrag ist sehr lesenswert und soll an dieser Stelle nicht nochmals wiederholt werden.

Aus den Erkenntnissen dieses Beitrages hier ein Template für einen funktionierenden und stabilen tomcat systemd Service:

[Unit]
Description=Apache Tomcat Web Application Container
 
[Service]
User=tomcat
Group=tomcat
 
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/srv/tomcat
Environment=CATALINA_TMPDIR=/srv/tomcat/temp
Environment=JAVA_HOME=/usr/lib/jvm/adoptopenjdk-11-openj9-amd64
 
EnvironmentFile=-/etc/default/tomcat
 
ExecStart=/usr/bin/env ${JAVA_HOME}/bin/java \
$JAVA_OPTS $CATALINA_OPTS \
-classpath ${CLASSPATH} \
-Dcatalina.base=${CATALINA_BASE} \
-Dcatalina.home=${CATALINA_HOME} \
-Djava.io.tmpdir=${CATALINA_TMPDIR} \
-Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
org.apache.catalina.startup.Bootstrap \
start
ExecStop=/usr/bin/env ${JAVA_HOME}/bin/java \
$JAVA_OPTS \
-classpath ${CLASSPATH} \
-Dcatalina.base=${CATALINA_BASE} \
-Dcatalina.home=${CATALINA_HOME} \
-Djava.io.tmpdir=${CATALINA_TMPDIR} \
-Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
org.apache.catalina.startup.Bootstrap \
stop
 
[Install]
WantedBy=multi-user.target

[stextbox id=“note“ caption=“Hinweis“]
Die CATALINA_* und JAVA_HOME könnte man anstelle der „Environment=“ Anweisungen auch in der Datei /etc/default/tomcat unterbringen (die Ja mit „EnvironmentFile=“ benfalls eingebunden wird), so wie das Jonathan gemacht hat.
Ich bevorzuge aber die Umgebungsvariablen, welche zum starten des Services unerlässlich sind auch in der selben Datei zu haben.
[/stextbox]

Wo ist jetzt mein catalina.out?

Diese Frage mögen sich einige nach der Umstellung stellen, denn es wird dann kein „catalina.out“ Logfile mehr geschrieben.

Das liegt daran, dass früher tomcat mit relativ umständlichen „wrapper-scripts“ gestartet wurde, die im Grunde genommen am Ende bloss java mit einigen Parametern aufrufen und die Ausgabe, die sonst auf die Konsole erfolgen würde in die Datei catalina.out umleiteten.

Das Konzept in systemd ist hier anders. Da wird die Ausgabe des Prozesses (in diesem Fall java/tomcat) in einem journal geloggt.

Dieses kann man ansehen, in dem man das Kommando journalctl benutzt:

$ journalctl -u tomcat

Das zeigt euch den Inhalt, der zuvor in catalina.out stand.

Diese Ausgabe kann man natürlich auch in eine Datei umleiten, etwa um diese lokal herunterzuladen um sie genauer zu untersuchen:

journalctl -u tomcat > /var/log/tomcat/catalina.out

Das speichert den aktuellen Inhalt des tomcat journals in die Datei /var/log/tomcat/catalina.out.

Möchte man jedoch wie bisher noch immer Standardmässig eine physische Datei bekommen anstelle das systemd journal zu benutzten, geht dies seit systemd 236.

Dazu fügt man zuerst ins Unit File (tomcat.service) diese drei Zeilen ein:

[Service]

[...]

SyslogIdentifier=tomcat9

StandardOutput=append:/var/log/tomcat/catalina.out
StandardError=append:/var/log/tomcat/catalina.out

Anstelle von file:<pfad> können auch noch andere direktiven verwendet werden:

  • file:path
    Die Option file:path kann verwendet werden, um ein bestimmtes Dateisystemobjekt mit der Standardausgabe zu verbinden. Die Semantik ähnelt der gleichen Option von StandardInput=, siehe oben. Wenn sich path auf eine reguläre Datei im Dateisystem bezieht, wird sie geöffnet (falls sie noch nicht existiert) zum Schreiben am Anfang der Datei, jedoch ohne sie abzuschneiden. Wenn Standardeingabe und -ausgabe auf denselben Dateipfad gerichtet sind, wird dieser nur einmal geöffnet, sowohl zum Lesen als auch zum Schreiben und dupliziert. Dies ist besonders nützlich, wenn der angegebene Pfad auf einen AF_UNIX-Socket im Dateisystem verweist, da in diesem Fall nur eine einzige Stream-Verbindung sowohl für die Eingabe als auch für die Ausgabe erstellt wird.
  • append:path
    ähnelt file:path oben, öffnet die Datei jedoch im Append-Modus.
  • truncate:path
    ähnelt file:path oben, aber es leert die Datei beim Öffnen (truncate). Bei Einheiten mit mehreren Befehlszeilen, z.B. Type=Oneshot-Dienste mit mehreren ExecStart=, oder Dienste mit ExecCondition=, ExecStartPre= oder ExecStartPost=, die Ausgabedatei wird erneut geöffnet und daher für jede Befehlszeile erneut geleert. Wenn die Ausgabedatei geleert wird, während ein anderer Prozess die Datei noch geöffnet hat, z. durch ein ExecReload=, das gleichzeitig mit einem ExecStart= ausgeführt wird, und der andere Prozess schreibt weiter in die Datei, ohne seinen Offset anzupassen, dann kann der Raum zwischen den Dateizeigern der beiden Prozesse mit NUL-Bytes gefüllt werden, wodurch eine Sparse-Datei erzeugt wird. Daher ist truncate:path normalerweise nur für Einheiten nützlich, in denen nur ein Prozess gleichzeitig ausgeführt wird, wie z. B. Dienste mit einem einzigen ExecStart= und keinem ExecStartPost=, ExecReload=, ExecStop= oder ähnlichem.
  • fd:name
    Die Option fd:name verbindet die Standardausgabe mit einem bestimmten benannten Dateideskriptor, der von einer Socket-Unit bereitgestellt wird. Als Teil dieser Option kann ein Name nach einem „:“-Zeichen angegeben werden (z. B. „fd:foobar“). Wenn kein Name angegeben wird, wird der Name „stdout“ impliziert (d. h. „fd“ entspricht „fd:stdout“). Mindestens eine Socket-Unit, die den angegebenen Namen definiert, muss über die Option Sockets= bereitgestellt werden, und der Dateideskriptorname kann sich vom Namen der sie enthaltenden Socket-Unit unterscheiden. Wenn mehrere Übereinstimmungen gefunden werden, wird die erste verwendet. Siehe FileDescriptorName= in systemd.socket(5) für weitere Details zu benannten Deskriptoren und ihrer Reihenfolge.

Published by

Steven Varco

Steven ist ein Redhat RHCE- und Kubernetes CKA Zertifizierter Linux-Crack und ist seit über 20 Jahren sowohl beruflich wie auch privat auf Linux spezialisiert. In seinem Keller steht ein Server Rack mit diversen ESX und Linux Servern.

6 thoughts on “tomcat systemd service”

      1. Danke für die Antwort, Steven. Bisher habe ich auf dem CLASSPATH jar-Files angegeben, oder Directories, die Bäume mit Klassen enthalten. Das man auch Directories angeben kann, die eine Menge von jar-Files enthalten, ist mir neu. Das kenne ich nur als Angabe nach –module-path.
        Zwischenzeitlich – nach vielen erfolglosen Versuchen nach Beispielen aus dem Netz – funktioniert mein Service unter Debian11 mit der /etc/systemd/system/tomcat.service-Datei:

        # Systemd unit file for tomcat
        [Unit]
        Description=Apache Tomcat Web Application Container
        After=syslog.target network.target

        [Service]
        Type=forking
        User=tomcat
        Group=tomcat

        Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
        Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
        Environment=CATALINA_HOME=/opt/tomcat
        Environment=CATALINA_BASE=/opt/tomcat
        Environment=CATALINA_OPTS=
        Environment=“JAVA_OPTS=-Dfile.encoding=UTF-8 -Dnet.sf.ehcache.skipUpdateCheck=true -Xms2g -Xmx4g“

        ExecStart=/opt/tomcat/bin/startup.sh
        ExecStop=/bin/kill -15 $MAINPID

        [Install]
        WantedBy=multi-user.target

        1. Super, dass du eine Lösung gefunden hast und Danke fürs teilen! 👍

          Ich muss zugeben, dass ich kein Tomcat oder JAVA Profi bin und es mir mehr darum ging aufzuzeigen, dass man mit JAVA Applikationen und systemd einen anderen Ansatz verfolgenb sollte, als leider auch viele Applikations Maintainer noch von den SysV Scripts versuchen zu „portieren“… 🙄

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert