Oracle REST data services oder auch kurz ORDS hat sich zu einem schweizer Taschenmesser in Sachen REST Unterstützung für Oracle Datenbanken entwickelt und sollte bei keiner Datenbank Installation fehlen. ORDS hat einiges zu bieten – ursprünglich diente er fast ausschließlich als Listener bzw. Browser Zugang für die Low Code Entwicklungsumgebung “APEX”, Application Express. Doch inzwischen bietet er gleich mehrere Tools und REST APIs zu unterschiedlichen Zwecken, die auf Wunsch auch einzeln aktivierbar sind :
- Browser Zugang für Application Express (APEX) zur Erstellung und zum Betrieb von flüssigen und bunten User Interfaces

- Betrieb des Werkzeugkastens “Database Actions”, im Prinzip einem Browser-basierten Nachfolger des einstmals lokal zu installierenden SQL*Developer. Mit SQL Kommandozeile, Datenmodellierung, Definition von REST Services aus Datenbank-Strukturen, Laden und Entladen von Daten, Performance Monitoring für die Datenbank
- Eine REST API nach GraphQL Standard und einen GraphQL Server, um generierte REST Services per Datenbank-Metadaten zu verknüpfen
- REST APIs zur Verwaltung, Ablage, Suche von JSON Dokumenten nach SODA und MongoDB Manier
- Eine REST API für Transactional Event Queues zur Verwaltung und dem Umgang mit Events und Nachrichten
- REST APIs für das Lifecycle Management von Datenbanken, Pluggable Databases und Schemas, einmal nach Open Service Broker Standard und einmal vereinfacht mit Oracle-eigener Implementation (PDB Lifecycle Management)
- REST APIs für das Auslesen von Metadaten aus Datenbank-Strukturen (Data Dictionary), Laufzeit-Informationen, Data Guard Konfiguration, Data Pump Operationen ,…
Eine Liste der unterstützten REST APIs und deren Parametern ist Teil der ORDS Dokumentation, z.B. im Kapitel “All REST Endpoints”.
Genau wie andere Datenbank-Satellitentools wie beispielsweise GoldenGate, Connection Manager, Observability Exporter uvm. passt ORDS in einen recht kleinen von Oracle bereitgestellten Container und kann bequem unter Kubernetes betrieben werden. Auch hochverfügbar und abgesichert mit Kubernetes-Bordmitteln.
In unserer Reihe “will it blend ?” oder frei übersetzt “passt das in den Mixer ?” möchte ich Ihnen heute an einem Beispiel erklären, wie der ORDS unter Kubernetes eingebunden werden kann.
Im letzten Beitrag dieser Reihe über GoldenGate als Container hatten wir es nicht ganz so bequem, denn ORDS liegt im Gegensatz dazu bereits als vorgefertigter Container auf container-registry.oracle.com zum Download bereit, sogar in gleich zwei Varianten ords und ords-developer. Ein eigenes Image muß nur in Ausnahmefällen erzeugt werden, z.B. mit GraalVM statt Oracle Java als Basis für den Betrieb des optionalen GraphQL Servers: Dieser wurde in JavaScript geschrieben aber läuft als Teil des ORDS Prozesses. Hinweise zur Einrichtung einer GraalVM gibt es im Blog Beitrag über GraphQL versus SQL/PGQ.
Ein passendes aber sehr optionales Dockerfile, um aus einem Standard ORDS – Container einen mit GraalVM und JavaScript support zu erzeugen, könnte wie folgt aussehen :
ARG BASE_IMAGE=container-registry.oracle.com/database/ords-developer:latest
FROM ${BASE_IMAGE}
USER root
COPY graalvm-jdk-17.0.12+8.1 /usr/lib/jvm/graalvm-jdk-17.0.12+8.1
RUN rm /etc/alternatives/java && \
ln -s /usr/lib/jvm/graalvm-jdk-17.0.12+8.1/bin/java /etc/alternatives/java && \
/usr/lib/jvm/graalvm-jdk-17.0.12+8.1/bin/gu install nodejs
USER oracle
EXPOSE 8080
WORKDIR /opt/oracle/ords
CMD bash /entrypoint.sh
Da es jedoch in den allermeisten Fällen nicht nötig sein wird ein Image selbst zu erzeugen, möchte ich weitere Erklärungen dazu stark straffen und nur der Vollständigkeit halber nennen:
- Das Basis-image ist hier das ords-developer Image auf container-registry.oracle.com.
- Dort hinein wird eine vorher herunterzuladende und entpackte graalvm Version 17 kopiert.
- Dann wird über das graal update Tool der JavaScript Support nachinstalliert.
- Der Start des Containers erfolgt wieder als non-root Benutzer namens “oracle”, der im Basis Image bereits angelegt wurde.
- Ein Kommando wie “podman build -t ords-developer-graal:latest” gefolgt von “podman push ords-developer-graal:latest” erzeugt das Image und lädt es nach docker.io hoch, dem default Host. Es sei denn, Sie nennen einen anderen Host Namen für den Upload, der vorangestellt im Namen des Image steht (“myhost.com/ords-developer-graal:latest“)
Warum gibt es zwei Standard-Images, ords und ords-developer ?
Das ords-developer Image ist leichter einzurichten und zu starten, denn es verwendet einige Default-Werte, auf die man in diesem Image keinen Einfluss hat. Zum Beispiel wird das Kennwort des Proxy-Benutzers ORDS_PROXY_USER beim Start in der verbundenen Datenbank auf “oracle” festgesetzt. Eigentlich kann nur der Connect String und ein Benutzer mit SYSDBA Rechten und seinem Kennwort als Parameter in Form einer Datei “ords_secrets/conn_string.txt” übergeben werden. Die verbundene Datenbank wird sofort beim Start mit APEX eingerichtet bzw. wenn bereits vorhanden aktualisiert. Ich nehme an, dass dieses Vorgehen im Regelfall nicht erwünscht ist und habe einen flexiblen Weg mit beliebigen Konfigurationsmöglichkeiten mit dem Standard ords Image gewählt, den ich nun beschreiben möchte.
Das Deployment über das Standard-Image “ords”
Um kein eigenes, neues Image erzeugen zu müssen sondern möglichst elegant das zu verwenden, was vorhanden ist müssen wir in zwei Schritten vorgehen. Im ersten Schritt wird eine ORDS Konfiguration erzeugt, im zweiten Schritt wird die Konfiguration eingebunden und ORDS gestartet. Dafür nehmen wir einen initContainer zu Hilfe. Diese Container starten vor dem eigentlichen Container um z.B. Dateisysteme vorzubereiten oder Programme zur Konfiguration zu starten, die im eigentlichen Container vielleicht gar nicht installiert sind.
Der initContainer könnte in einem Skript einen oder mehrere “ords config” Aufrufe tätigen, der Haupt Container startet den ords mit “ords serve”. Hier ein Auszug aus einer entsprechenden deployment YAML Datei:
spec:
initContainers:
- name: set-config
#.... Umgebungsvariablen für das Skript...
command:
- /bin/bash
- /scripts/ords_setup.sh
containers:
- name: app
command:
- ords
- serve
Wichtig ist, dass beide Container dasselbe Verzeichnis befüllen bzw. auslesen, damit beim Beenden des initContainers die erzeugte Konfiguration nicht wieder verlorengeht. Um keine persistenten Volumes benutzen zu müssen und Platz zu sparen, gibt es das Konstrukt “emptyDir”, das auf dem Cluster-Knoten temporär ein leeres Verzeichnis anlegt und bindet, solange die Container gestartet sind:
# in beiden Containern, d.h. 2x einzutragen
volumeMounts:
- mountPath: /etc/ords/config
name: config
# Ein Volume für beide Container im selben Pod
volumes:
- name: config
emptyDir: {}
Deswegen gibt es in Kubernetes die sogenannten Pods: das sind einer oder mehrere Container, entweder nacheinander oder gleichzeitig gestartet, die sich Ressourcen teilen oder logisch zusammengehören. Z.B. ein Container mit Metrik-Erfassung mit einem Applikations-Container. Oder ein Konfigurations-Container mit einem Applikations-Container wie in diesem Fall.
Das Shell Skript, das die Konfiguration im ersten Container vornimmt, wird ebenfalls als Volume eingebunden. Das Volume entstammt aus einer Kubernetes “ConfigMap”, deren Inhalte als Dateien eingebunden werden können. Das Volume wird wie im folgenden Beispiel-Auszug eingebunden:
# nur im initContainer einzutragen volumeMounts: - name: scripts mountPath: /scripts volumes: - name: scripts configMap: defaultMode: 420 name: ords-init-config
Der Inhalt der genannten ConfigMap ords-init-config ist im Prinzip eine Datei mit einem Shell Skript. In das Shell Skript können beliebige Kommandos eingefügt werden. Vorausgesetzt, der benutzte Container kennt diese Kommandos. Ein Auszug könnte wie folgt aussehen:
kind: ConfigMap metadata: name: ords-init-config data: ords_setup.sh: |- #!/bin/bash ords install ...... <<EOF $ADMIN_PWD $PROXY_PWD EOF
Das wäre bereits die gesamte Magie hinter der Installation. Ein vollständiges Deployment mit Angabe von Namespaces und welche ORDS Container zu verwenden sind könnte wie folgt aussehen:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ords-nondev
namespace: ords-zoo
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: ords-nondev
app.kubernetes.io/name: ords-nondev
template:
metadata:
labels:
app.kubernetes.io/instance: ords-nondev
app.kubernetes.io/name: ords-nondev
spec:
initContainers:
- name: set-config
env:
- name: ADMIN_USER
valueFrom:
secretKeyRef:
key: admin_user
name: ords-secret
- name: ADMIN_PWD
valueFrom:
secretKeyRef:
key: admin_pwd
name: ords-secret
- name: PROXY_PWD
valueFrom:
secretKeyRef:
key: proxy_pwd
name: ords-secret
- name: DB_HOST
valueFrom:
secretKeyRef:
key: db_host
name: ords-secret
- name: DB_PORT
valueFrom:
secretKeyRef:
key: db_port
name: ords-secret
- name: DB_SERVICENAME
valueFrom:
secretKeyRef:
key: db_servicename
name: ords-secret
command:
- /bin/bash
- /scripts/ords_setup.sh
image: container-registry.oracle.com/database/ords:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /scripts
name: scripts
- mountPath: /etc/ords/config
name: config
containers:
- image: container-registry.oracle.com/database/ords:latest
imagePullPolicy: IfNotPresent
name: app
command:
- ords
- serve
ports:
- containerPort: 8080
name: http-ords
protocol: TCP
volumeMounts:
- mountPath: /etc/ords/config
name: config
volumes:
- name: scripts
configMap:
defaultMode: 420
name: ords-init-config
- name: config
emptyDir: {}
Angenommen, dieses YAML Dokument trüge den Namen “ords-deploy.yaml“, dann ließe es sich mit “kubectl apply -f ords-deploy.yaml” in Ihr Kubernetes Cluster übertragen. Dabei kommt es aber noch zu Fehlermeldungen, daß der Namespace “ords-zoo” nicht vorhanden sei und dass ein Secret Namens ords-secret fehlte genauso wie eine ConfigMap Namens ords-init-config.
Den Namespace ords-zoo erzeugen Sie wie folgt:
$ kubectl create namespace ords-zoo
Die nötigen Kennwörter für die Datenbankverbindung kommen in diesem Fall aus einem Kubernetes Secret Namens ords-secret. Die Container Images stammen direkt von container-registry.oracle.com – natürlich können Sie sie vorher in Ihre persönliche Container Registry übertragen und von dort aus nach Kubernetes laden. Das Secret könnte seine privaten Daten aus einer Vault-Lösung laden oder wie hier in unserem Beispiel in normalerweise unerwünschtem Klartext enthalten. Auch diese Datei, nennen wir sie ords-secret.yaml, kann mit “kubectl apply -f ords-secret.yaml” an Ihr Cluster übertragen werden. Bitte passen Sie vorher Host Namen und Kennwörter an Ihre Gegebenheiten an:
apiVersion: v1 kind: Secret metadata: name: ords-secret namespace: sidb23c type: Opaque stringData: admin_pwd: MyComplexAdminPW123 admin_user: sys db_host: myhost.oraclevcn.com db_port: "1521" db_servicename: FREEPDB1 proxy_pwd: oracle
Die ConfigMap ords-init-config, die sämtliche ORDS Konfigurationen per Skript vornimmt, könnte wie folgt aussehen:
apiVersion: v1
kind: ConfigMap
metadata:
name: ords-init-config
namespace: ords-zoo
data:
ords_setup.sh: |-
#!/bin/bash
ords install --admin-user $ADMIN_USER --proxy-user --db-hostname $DB_HOST --db-port $DB_PORT --db-servicename $DB_SERVICENAME --feature-sdw true --password-stdin <<EOF
$ADMIN_PWD
$PROXY_PWD
EOF
cd /etc/ords/config
curl -o apex.zip https://download.oracle.com/otn_software/apex/apex-latest.zip
jar xvf apex.zip
rm apex.zip
chmod -R 755 apex
ords config set standalone.static.path /etc/ords/config/apex/images
Nehmen wir an, diese Datei heißt ords-init-config.yaml. Dann kann sie per “kubectl apply -f ords-init.yaml” an Ihr Cluster übertragen werden. Das in der ConfigMap enthaltene Skript trägt nicht nur die Connect Strings und Kennwörter für die Datenbank-Verbindung ein, sondern lädt auch gleich diverse Icons und Bilder aus dem Internet herunter und bindet sie in die APEX Verzeichnisse ein. Das Feature “database actions” bzw “feature-sdw” wird ebenfalls aktiviert. Weitere Aufrufe sind denkbar, z.B. Herunterladen oder Kopieren einer bereits bestehenden Konfiguration von einem git-repository oder von einem sonstigen Web Server.
Jetzt müßte auch der erneute Aufruf von “kubectl apply -f ords-deploy.yaml” fehlerfrei funktionieren und der Container heruntergeladen und gestartet werden. Wenn Sie Ihre Datenbank Kennwörter und Hostnamen korrekt angepasst haben. Das lässt sich wie folgt prüfen:
$ kubectl get deployment -n ords-zoo NAME READY UP-TO-DATE AVAILABLE AGE ords-nondev 1/1 1 1 1d $ kubectl get configmap -n ords-zoo NAME DATA AGE ords-init-config 1 1d $ kubectl get secret -n ords-zoo NAME TYPE DATA AGE ords-secret Opaque 6 1d $ kubectl get pod -n ords-zoo NAME READY STATUS RESTARTS AGE ords-nondev-56d97d9594-lck4h 1/1 Running 0 1d
Eine Log-Ausgabe der beiden Container set-config und app im systemgenerierten Pod-Namen ords-nondev-56d97d9594-lck4h sollte vielversprechend aussehen, wenn auch sehr umfangreich.
Falls der Download der APEX Icons in Ihrer Umgebung nicht funktioniert, so lassen Sie den Eintrag vorerst im Skript weg. Wahrscheinlich lässt Ihre Firewall den Download nicht zu. Die Konfigurationsschritte und unzip-Operationen sollten beispielsweise im set-config Container sichtbar sein, ähnlich wie hier:
$ kubectl logs ords-nondev-56d97d9594-lck4h -n ords-zoo set-config
2024-11-12T14:52:15Z INFO ORDS has not detected the option '--config' and this will be set up to the default directory.
ORDS: Release 24.3 Production on Tue Nov 12 14:52:18 2024
Copyright (c) 2010, 2024, Oracle.
Configuration:
/etc/ords/config
Oracle REST Data Services - Non-Interactive Install
Retrieving information.
Connecting to database user: ORDS_PUBLIC_USER url: jdbc:oracle:thin:@//myhost.oraclevcn.com:1521/FREEPDB1
The setting named: db.connectionType was set to: basic in configuration: default
The setting named: db.hostname was set to: myhost.oraclevcn.com in configuration: default
The setting named: db.port was set to: 1521 in configuration: default
The setting named: db.servicename was set to: FREEPDB1 in configuration: default
The setting named: plsql.gateway.mode was set to: proxied in configuration: default
The setting named: db.username was set to: ORDS_PUBLIC_USER in configuration: default
The setting named: db.password was set to: ****** in configuration: default
The setting named: feature.sdw was set to: true in configuration: default
The global setting named: database.api.enabled was set to: true
The setting named: restEnabledSql.active was set to: true in configuration: default
The setting named: security.requestValidationFunction was set to: ords_util.authorize_plsql_gateway in configuration: default
2024-11-12T14:52:23.172Z INFO Oracle REST Data Services schema version 24.3.0.r2620924 is installed.
2024-11-12T14:52:23.174Z INFO To run in standalone mode, use the ords serve command:
2024-11-12T14:52:23.174Z INFO ords --config /etc/ords/config serve
2024-11-12T14:52:23.174Z INFO Visit the ORDS Documentation to access tutorials, developer guides and more to help you get started with the new ORDS Command Line Interface (http://oracle.com/rest).
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 276M 100 276M 0 0 104M 0 0:00:02 0:00:02 --:--:-- 104M
inflated: META-INF/MANIFEST.MF
inflated: META-INF/ORACLE_C.SF
inflated: META-INF/ORACLE_C.RSA
created: apex/
inflated: apex/LICENSE.txt
inflated: apex/apex_rest_config.sql
inflated: apex/apex_rest_config_cdb.sql
inflated: apex/apex_rest_config_core.sql
inflated: apex/apex_rest_config_nocdb.sql
inflated: apex/apexins.sql
.....
Sie können den Pod übrigens ganz einfach skalieren und verfügbarer machen, indem Sie den Parameter “replica = 1” in der Datei ords-deployment.yaml anpassen auf den Wert 2, dann werden zwei Pods gestartet und load balanced. Über Pod Autoscaler ließe sich diskutieren, denn eine automatische Skalierung bei Hochlast würde hier auch funktionieren.
Auf jeden Fall: herzlichen Glückwunsch, wenn Sie an diesem Punkt angelangt sind und der Container bei Ihnen läuft !
Die Netzwerk Anbindung
Nun, da der Container startet kommt sicher die Frage auf, wie man mit dem Browser den Container anspricht und seine vielen REST Services und User Interfaces? Und geht das mit HTTPS oder nur mit HTTP ? Die Antwort kommt wieder in Form von kubectl Kommandos und Netzwerk-Ressourcen in mehreren Varianten.
Eine Quick-and-dirty Anbindung zum Test geht mittels Port-Forwarding zwischen dem Kubernetes Cluster und dem Rechner, mit dem Sie kubectl Kommandos absetzen. Finden Sie heraus, wie der ORDS Container in Ihrem Cluster heißt und leiten Sie den ORDS Port 8080 im Container weiter an Ihren Rechner:
$ kubectl get pod -n ords-zoo NAME READY STATUS RESTARTS AGE ords-nondev-56d97d9594-lck4h 1/1 Running 0 2m9s $ kubectl port-forward ords-nondev-56d97d9594-lck4h 8888:8080 -n ords-zoo & Forwarding from 127.0.0.1:8888 -> 8080 Forwarding from [::1]:8888 -> 8080 $ curl http://127.0.0.1:8888/ords/ -v Handling connection for 8888 * Trying 127.0.0.1... * TCP_NODELAY set * Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0) > GET /ords/ HTTP/1.1 > Host: 127.0.0.1:8888 > User-Agent: curl/7.61.1 > Accept: */* > < HTTP/1.1 302 Found < Location: http://127.0.0.1:8888/ords/_/landing < Transfer-Encoding: chunked < * Connection #0 to host 127.0.0.1 left intact
Kommt auch in Ihrer Umgebung eine Weiterleitung (HTTP 302) an die ORDS landing page? Hervorragend! Dann gehen wir zum nächsten Schritt und erstellen eine permanente Netzwerkanbindung. In vielen Kubernetes Umgebungen existiert ein zentraler Einstiegspunkt, ein Gateway mit externer IP Adresse und Port, das über einen externen Load Balancer angesprochen wird. Solche Gateways können mit istio, nginx oder anderen Tools implementiert sein. Von dort aus lassen sich Ingresses definieren, sprich Weiterleitungen an Container bzw. Pods unter bestimmten Kriterien wie URLs oder verwendeten Hostnamen. Ein Ingress spricht mit einem Kubernetes Service, vorzugsweise vom Typ “ClusterIP”, und der bindet sich über Metadaten wie Name der Applikation usw. an die gestarteten Container und betreibt abermals Load Balancing zu den Containern hin.
Ein passender Kubernetes Service könnte wie folgt aussehen:
apiVersion: v1
kind: Service
metadata:
name: ords-nondev
namespace: ords-zoo
spec:
ports:
- name: http-ords
port: 8888
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/instance: ords-nondev
app.kubernetes.io/name: ords-nondev
sessionAffinity: None
type: ClusterIP
Nennen Sie die Datei “ords-service.yaml” und senden Sie sie mittels “kubectl apply -f ords-service.yaml” an Ihr Cluster.
Möchten Sie auf einen Ingress vorerst verzichten, so können Sie aus dem Kubernetes Service ords-nondev vom Typ ClusterIP einen Service vom Typ LoadBalancer machen. Dann steht Ihr ORDS Service an vorderster Front im Kubernetes Cluster, wird direkt in Ihren externen LoadBalancer eingetragen und erhält dessen externe IP Adresse. Aber auch diese Konfiguration ist normalerweise eher zu Testzwecken gedacht.
Ein passender Ingress in Ihrer Umgebung ähnelt, aber gleicht nicht dem nachfolgenden. Denn Sie sollten auf jeden Fall einen Hostnamen verwenden, der in Ihrem Netzwerk auflösbar ist und auf die IP Adresse Ihres Gateways verweist (oft ein CNAME Eintrag, also ein Alias). Eventuell verwenden Sie auch nicht istio als ingressClassName, sondern nginx oder ganz andere. Ich nutze zu Testzwecken gerne Hostnamen des nip.io Dienstes – sie sind immer auflösbar, weil die zugehörigen IP Adressen in ihrem Namen enthalten sind.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ords-ingress
namespace: ords-zoo
spec:
ingressClassName: istio
rules:
- host: ords.130-61-137-234.nip.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ords-nondev
port:
number: 8888
Eine weitere Besonderheit in meiner Umgebung: Das Gateway ist per HTTPS ansprechbar und leitet alle Anfragen an den unverschlüsselten ORDS weiter. Das Gateway terminiert den SSL Verkehr. Im Browser muss ich also https anstelle von http als Andresse angeben. Hier im Beispiel: https://ords.130-61-137-234.nip.io/ords/_/landing. Auch das dürfte in Ihrer Umgebung anders funktionieren:

Erscheint die Landing Page von ORDS, so funktioniert auch dieser Netzwerkzugang. Sind die Bereiche für SQL Developer Web und APEX nicht ausgegraut, so funktioniert übrigens auch die Verbindung vom ORDS Container zur Datenbank.
Sollten auch Sie einen SSL Zugang kurz vor dem ORDS terminiert haben und der ORDS selbst unverschlüsselt laufen so kann es sein, daß die Weiterleitung nach einem erfolgten Login nicht richtig funktioniert. Das Tool “Database Actions” kann dann auch keine SQL Kommandos ausführen mit einem in JavaScript versteckten Fehler “not authorized”. Wenn dem so ist so können Sie den ORDS zwingen, in SSL zu “denken” obwohl er selbst keine SSL Verschlüsselung durchführt. Durch einen kleinen zusätzlichen Parameter security.forceHTTPS auf true gesetzt (in der vorhin eingeführten ConfigMap ords-init-config.yaml) werden von ORDS intern erzeugte URLs und Cookies auf HTTPS umgeändert. Wahlweise könnten Sie auch einen reverse Proxy wie nginx vorschalten, der HTTP-Zugriffe auf HTTPS umleitet. Oder Sie konfigurieren ORDS einfach komplett mit Zertifikaten auf SSL Zugang um, und genau das tun wir im nächsten Kapitel.
Gratulation, denn Sie sind eigentlich bereits fertig !
Netzwerkzugang Teil 2: Die SSL Verschlüsselung
Möchten Sie auch den internen Zugang zwischen Gateway und ORDS verschlüsseln, soll ORDS ebenfalls per SSL bzw. HTTPS ansprechbar sein ? Dann ist nicht viel zu tun, Sie müssen dem ORDS die nötigen Zertifikate auf den Weg geben und ihm mitteilen, wo sie liegen. Und den zu verwendenden SSL Port nicht vergessen, zum Beispiel 8443 im Container. Ports kleiner 1024 sind als non-root Benutzer nicht gestattet, und ORDS läuft als oracle-Benutzer.
Die bisherigen YAML Dateien sind zu diesem Zweck ein wenig anzupassen und mit “kubectl apply -f ....” wieder ans Cluster zu senden.
Das Konfigurations-Skript in der ConfigMap ords-init-config.yaml erhält weitere Aufrufe für die Einträge “standalone.https.cert“, “standalone.https.cert.key“, “standalone.https.host” und “standalone.https.port“. Bitte achten Sie darauf, dass der genannte Hostname zum verwendeten Zertifikat passt und dass ORDS nur über diesen Hostnamen angesprochen wird. Anderslautende HTTPS requests werden dann nicht mehr akzeptiert.
Das vollständige YAML sähe dann wie folgt aus:
apiVersion: v1
kind: ConfigMap
metadata:
name: ords-init-config
namespace: ords-zoo
data:
ords_setup.sh: |-
#!/bin/bash
ords install --admin-user $ADMIN_USER --proxy-user --db-hostname $DB_HOST --db-port $DB_PORT --db-servicename $DB_SERVICENAME --feature-sdw true --password-stdin <<EOF
$ADMIN_PWD
$PROXY_PWD
EOF
cd /etc/ords/config
curl -o apex.zip https://download.oracle.com/otn_software/apex/apex-latest.zip
jar xvf apex.zip
rm apex.zip
chmod -R 755 apex
ords config set standalone.static.path /etc/ords/config/apex/images
ords config set standalone.https.host ords.130-61-137-234.nip.io
ords config set standalone.https.port 8443
ords config set standalone.https.cert /ssl/tls.crt
ords config set standalone.https.cert.key /ssl/tls.key
Das Deployment in ords-deploy.yaml erhält ein weiteres Volume im gerade eben gewählten Verzeichnis “/ssl“, in dem die Zertifikate von außen eingeschossen werden. Das Volume verweist auf ein noch anzulegendes Kubernetes Secret ords-ssl-secret, das die Zertifikate enthalten wird. Weiterhin wird in dem Deployment der neue Port 8443 bekanntgemacht. Sie können gerne den alten Port Eintrag 8080 entfernen, wenn nur noch SSL gesprochen werden soll. Das vollständige Deployment sieht dann wie folgt aus:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ords-nondev
namespace: ords-zoo
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: ords-nondev
app.kubernetes.io/name: ords-nondev
template:
metadata:
labels:
app.kubernetes.io/instance: ords-nondev
app.kubernetes.io/name: ords-nondev
spec:
initContainers:
- name: set-config
env:
- name: ADMIN_USER
valueFrom:
secretKeyRef:
key: admin_user
name: ords-secret
- name: ADMIN_PWD
valueFrom:
secretKeyRef:
key: admin_pwd
name: ords-secret
- name: PROXY_PWD
valueFrom:
secretKeyRef:
key: proxy_pwd
name: ords-secret
- name: DB_HOST
valueFrom:
secretKeyRef:
key: db_host
name: ords-secret
- name: DB_PORT
valueFrom:
secretKeyRef:
key: db_port
name: ords-secret
- name: DB_SERVICENAME
valueFrom:
secretKeyRef:
key: db_servicename
name: ords-secret
command:
- /bin/bash
- /scripts/ords_setup.sh
image: container-registry.oracle.com/database/ords:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /scripts
name: scripts
- mountPath: /ssl
name: ssl
- mountPath: /etc/ords/config
name: config
containers:
- image: container-registry.oracle.com/database/ords:latest
imagePullPolicy: IfNotPresent
name: app
command:
- ords
- serve
ports:
- containerPort: 8080
name: http-ords
protocol: TCP
- containerPort: 8443
name: https-ords
protocol: TCP
volumeMounts:
- mountPath: /etc/ords/config
name: config
- mountPath: /ssl
name: ssl
volumes:
- name: scripts
configMap:
defaultMode: 420
name: ords-init-config
- name: config
emptyDir: {}
- name: ssl
secret:
defaultMode: 420
secretName: ords-ssl
Es wird zu einem Problem mit dem Deployment kommen, denn die Basis für das ssl Volume, das Kubernetes Secret ords-ssl-secret, fehlt noch. Haben Sie Private Key und Zertifikat als Dateien zur Hand? Dann legen Sie mittels “kubectl create secret ...” ein entsprechend benanntes Secret an und übergeben Ihre Dateien (tls.key und tls.crt in diesem Beispiel) dorthin. Bitte beachten Sie auch, dass der Schluessel im PKCS8 Format und ohne Kennwort erwartet wird! Eventuell ist Ihr bereitgestellter Schlüssel erst noch zu konvertieren, openssl bietet da viele Möglichkeiten. Mein klassischer RSA PEM Schlüssel musste zuerst mit “openssl pkcs8 -in tls.key -topk8 -out tls.key.pk8 -nocrypt” konvertiert werden, dann liess sich ORDS auch mit einem Secret starten , das wie folgt angelegt wurde:
$ kubectl create secret generic ords-ssl -n ords-zoo --from-file=tls.key="tls.key.pk8" --from-file=tls.crt="tls.crt"
Nun müßte auch das Deployment funktionieren (bitte abermals versuchen mit “kubectl apply -f ords-deploy.yaml") . Ich möchte an dieser Stelle etwas Werbung machen für das Kubernetes-Tool cert-manager. Es kann für Sie die Erzeugung von Zertifikaten abnehmen, das ords-ssl-secret selbsttätig anlegen und sogar rechtzeitig vor Ablauf der Zertifikate diese automatisch erneuern. Das funktioniert mit self-signed Zertifikaten, mit externen CAs wie letsEncrypt oder aber cert-manager wird zu einer eigenen CA, wenn Sie ihm “ordentliche” root-Zertifikate geben, keine self-signed.
Damit der neue Port 8443 auch verwendet wird, ist der Service ords-nondev anzupassen. Entweder Sie fügen den Port 8443 hinzu oder Sie tauschen am besten den bestehenden Container-Port 8080 und den Namen http-ords aus. Die Namensgebung hat bei manchen Netzwerkdiensten wie istio eine besondere Bewandnis, d.h. istio reagiert auch auf den Namen der Ports um zu entscheiden, mit welchem Protokoll zwischen Gateway und Dienst gesprochen werden soll. Ändern Sie den Port 8888 bitte nicht, sonst müssten Sie noch den Ingress anpassen. Der Service mit ausgetauschtem Port könnte wie folgt aussehen:
apiVersion: v1
kind: Service
metadata:
name: ords-nondev
namespace: ords-zoo
spec:
ports:
- name: https-ords
port: 8888
protocol: TCP
targetPort: 8443
selector:
app.kubernetes.io/instance: ords-nondev
app.kubernetes.io/name: ords-nondev
sessionAffinity: None
type: ClusterIP
Falls Sie istio als IngressClass verwenden kann es nötig sein mitzuteilen, daß der interne Netzverkehr zum ORDS Container hin verschlüsselt erfolgen muss. Eine sogenannte DestinationRule kann hierbei helfen, sonst kommt es im Problemfall zu HTTP Fehlermeldungen dass der Dienst nicht gefunden wurde oder die SSL Connection abrupt beendet wurde.
apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: ords-https-destrule namespace: ords-zoo spec: host: ords-nondev trafficPolicy: tls: mode: SIMPLE
Das sollte es gewesen sein! Ob der nun voll verschlüsselte Zugang funktioniert können Sie den Container-Logs entnehmen: sind die Zertifikate im Container vorhanden, haben sie den richtigen Namen, ist der Schluessel im PKCS8 Format, funktionierte die ORDS Konfiguration? Neben den Container Logs können Sie sich per Shell mit dem Container verbinden und sich selbst einmal umschauen:
NAME READY STATUS RESTARTS AGE
ords-nondev-sidb23c-754b9cd9f-tpdhj 1/1 Running 0 10m
$ kubectl exec --stdin --tty -n ords-zoo ords-nondev-sidb23c-754b9cd9f-tpdhj -- /bin/bash
Defaulted container "app" out of: app, set-config (init)
[oracle@ords-nondev-sidb23c-754b9cd9f-tpdhj ords]$ ls /ssl
tls.crt tls.key
Die Container Logs sollten keine Java Fehlermeldungen der Art “oracle.dbtools.standalone.StandaloneException: The provided key is not RSA or PKCS8 encoded” enthalten. Bei solchen Fehlern terminiert sich auch der Container, er erscheint dann schnell als “CrashLoopBackOff” in der Liste der pods mit “kubectl get pods -n ords-zoo“.
Damit sollte auch dieser Teil funktionieren. Herzlichen Dank dass Sie so lange mitgelesen haben und -wie immer- viel Spaß beim Testen!
Fazit und Ausblick:
ORDS und andere Satelliten-Container rund um die Oracle Datenbank unter Kubernetes zu betreiben macht viel Sinn. Die Vorteile von Kubernetes wie Skalierung, Verfügbarkeit, leichte Versionierung und Patching durch einfachen Austausch der Container können leicht genutzt werden. Oftmals bietet Oracle die Container zum Download und zum produktiven Einsatz mit docker und podman. Automatismen für Kubernetes-Deployments wie Helm Charts, ein zentrales Helm Chart Repository sind in Arbeit und für manche moderne Komponente bereits verfügbar, beispielsweise dem microTX Transaktionsmanager und dem Oracle Backend for Microservices. Andere Komponenten wie Oracle Datenbanken im Container, Metrik Exporter für Grafana und übrigens auch ORDS (wenn auch etwas eingeschränkt, was dynamische Konfiguration und Netzkonfiguration betrifft ) können über den Oracle Operator for Kubernetes (OraOperator) automatisiert installiert werden. Für den OraOperator ist ein Helm Chart derzeit ebenfalls in Planung, genauso wie dessen Registrierung auf operatorhub.io .
Einige Links:
Alle YAML Dateien zu diesem Blog auf github
ORDS Container zum Download und Beschreibung auf container-registry.oracle.com
Hinweise zur SSL Konfiguration des ORDS auf oracle-base
ORDS Konfiguration mittels OraOperator for Kubernetes