Configuration externalisée

La configuration de Tomcat est centralisée dans le fichier server.xml. Ce fichier est relativement simple à comprendre et à modifier, ce qui le rend adapté à des configurations classiques. En revanche, cette approche devient moins pratique dans des environnements modernes ou dynamiques, en particulier dans le cloud, où la configuration doit souvent être externalisée.

Lecture de la configuration

Au démarrage de Tomcat, le fichier server.xml est lu et interprété par le composant interne appelé digester. Ce dernier est chargé de parser les fichiers XML de configuration et de construire les objets Java correspondants.

Le digester offre une fonctionnalité de substitution de placeholders. Ceux-ci sont remplacés par des valeurs issues de différentes sources de propriétés, ce qui permet de rendre la configuration plus flexible et adaptable à l’environnement d’exécution.

Par défaut, les placeholders sont remplacés par des system properties.

Propriétés système

Un placeholder est défini à l’aide de la syntaxe ${key} ou ${key:-default-value}. Si la propriété n’est pas définie, la valeur par défaut est utilisée.

Dans l’exemple ci-dessous, on déclare une datasource dans server.xml, avec la possibilité de définir ses attributs principaux dans des propriétés système.

<Resource name="jdbc/my-global-ds"
          type="javax.sql.DataSource" auth="Container"
          driverClassName="org.database.Driver"
          url="${datasource.url:-jdbc:derby://localhost:1527/sewadb}"
          username="${datasource.username:-sa}"
          password="${datasource.password:-sapwd}" />

Les propriétés peuvent être définies comme propriétés système au démarrage de Tomcat, par exemple via le script setenv.sh :

# setenv.sh
CATALINA_OPTS="-Ddatasource.url=jdbc:database://server:1234/schema \
               -Ddatasource.username=dbusername \
               -Ddatasource.password=dbpassword"

Il est également possible de les définir dans le fichier catalina.properties :

# catalina.properties
datasource.url=jdbc:database://server:1234/schema
datasource.username=dbusername
datasource.password=dbpassword

La substitution des propriétés ne se limite pas au fichier server.xml. Elle s’applique à la majorité des fichiers XML de configuration de Tomcat, notamment :

  • la configuration globale (conf/server.xml, conf/context.xml, conf/web.xml) ;

  • la gestion des utilisateurs (conf/tomcat-users.xml) ;

  • la configuration des applications (fichiers de contexte, web.xml, fragments web).

Par défaut, Tomcat ne remplace les placeholders qu’à partir des propriétés système. Toutefois, à l’image de frameworks comme Spring Boot ou Quarkus, il est possible d’enrichir ce mécanisme grâce aux property sources.

lorsqu’on configure d’autres property sources, celle par défaut reste active. Il est toutefois possible de la désactiver :

# catalina.properties
org.apache.tomcat.util.digester.REPLACE_SYSTEM_PROPERTIES=false

Variables d’environnement

Tomcat peut être configuré pour lire les propriétés à partir des variables d’environnement. Pour cela, il faut déclarer une source de propriétés spécifique avec org.apache.tomcat.util.digester.PROPERTY_SOURCE en propriété système ou dans catalina.properties :

# catalina.properties
org.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource

Les variables sont ensuite définies dans l’environnement, par exemple via setenv.sh :

# setenv.sh
DB_URL=jdbc:database://server:1234/schema
DB_USERNAME=dbusername
DB_PASSWORD=dbpassword

Elles peuvent alors être utilisées directement dans les fichiers de configuration :

<Resource name="jdbc/my-global-ds"
          type="javax.sql.DataSource" auth="Container"
          driverClassName="org.database.Driver"
          url="${DB_URL:-jdbc:derby://localhost:1527/sewadb}"
          username="${DB_USERNAME}"
          password="${DB_PASSWORD}" />

Avec Kubernetes

Dans un environnement Kubernetes, Tomcat propose une source de propriétés dédiée permettant d’exploiter les mécanismes de service binding :

# catalina.properties
org.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.ServiceBindingPropertySource

Pour le mettre en oeuvre, on peut utiliser de ConfigMap ou des Secret.

apiVersion: v1
kind: Secret
metadata:
  name: tomcat-secret
data:
  db_password: c2Fwd2QK
Le secret db_password est stocké en base64.

Ce secret doit ensuite être monté en volume dans le pod qui doit l’utiliser.

apiVersion: apps/v1
kind: Pod
metadata:
  name: tomcat
spec:
  containers:
  - name: tomcat
    image: jtips/tomcat
    imagePullPolicy: Never
    ports:
    - name: http
      containerPort: 8080
      protocol: TCP
    env:
    - name: SERVICE_BINDING_ROOT
      value: /binding
    volumeMounts:
    - name: secret-binding
      mountPath: "/binding/secrets"
      readOnly: true
  volumes:
  - name: secret-binding
    secret:
      secretName: tomcat-secret

Avec ce volume, il y a un fichier /binding/secrets/db_password dans le pod, qui contient le mot de passe. On peut donc l’utiliser dans la configuration server.xml, avec le préfixe chomp pour enlever d’éventuels caractères indésirables.

<Resource name="jdbc/my-global-ds"
          type="javax.sql.DataSource" auth="Container"
          driverClassName="org.database.Driver"
          url="jdbc:derby://localhost:1527/sewadb"
          username="sa"
          password="${chomp:secrets.db_password}" />

La variable d’environnement SERVICE_BINDING_ROOT sert à spécifier le répertoire racine des bindings. La clé de substitution est forcément en deux parties.

Source personnalisée

Tomcat permet également de définir une source de propriétés personnalisée en implémentant l’interface PropertySource. Cette source personnalisée est ensuite déclarée dans org.apache.tomcat.util.digester.PROPERTY_SOURCE.

Cette personnalisation ouvre de nombreuses possibilités :

  • lecture depuis un autre fichier de propriétés ;

  • utilisation de formats alternatifs (XML, YAML, etc.) ;

  • gestion de valeurs chiffrées ;

  • intégration avec un caveau de secrets.

Certains ont déjà fait ce travail et l’ont partagé. Par exemple, Tomcat External PropertySource permet d’utiliser un fichier properties externe, avec éventuellement du chiffrement. Vault for Apache Tomcat utilise PicketLink, comme les anciennes versions de WildFly, pour extraire des valeurs stockées en caveau.