Kubernetes / Helm installation
This page covers deploying Datafeeder on an existing geOrchestra platform (Gateway, Console/LDAP, GeoServer,
GeoNetwork) with the datafeeder-python Helm chart.
Read Configuration first for the meaning of each setting; this page only lists the
Kubernetes-specific steps and gotchas.
1. Databases
Run the initialization scripts before the first install.
The chart generates a <release>-database-backend secret, but none for the data database: if
backend.data_db.existingSecret is unset, the backend pod fails with CreateContainerConfigError. Point it at an
existing secret (e.g. the same as backend.database.existingSecret if both share a database), or create it:
kubectl create secret generic <release>-database-data \
--from-literal=host=<host> --from-literal=port=5432 \
--from-literal=dbname=<data-db-name> \
--from-literal=user=<user> --from-literal=password=<password>
2. Platform accounts
GeoServer / GeoNetwork: a dedicated LDAP account
The backend calls GeoServer and GeoNetwork through the Gateway (GEOSERVER_INTERNAL_URL /
GEONETWORK_INTERNAL_URL must be Gateway URLs), so backend.config.geoserver.username / geonetwork.username must
be a real LDAP user allowed to create layers and publish metadata. The chart does not create it. Prefer a dedicated
account over reusing the platform's shared technical account, so it can be rotated and audited independently.
Airflow REST API: an Airflow-only account
backend.config.airflow.username / password authenticate against Airflow's own user, created by the
sub-chart's createUserJob from airflow.createUserJob.defaultUser (default admin / admin; ignore the
deprecated webserver.defaultUser). The chart's default backend password (change-me) doesn't match, which causes
401 / Login Failed for user: admin on upload. Set both to the same value:
backend:
config:
airflow:
username: admin
password: <same-value>
airflow:
createUserJob:
defaultUser:
password: <same-value>
With ArgoCD
createUserJob is an immutable Helm-hook Job that keeps the Application OutOfSync. Enable it, sync once,
then disable it and sync again. If the user is still missing (airflow users list), create it with
airflow users create ... from an Airflow pod.
3. Airflow Connections
Airflow turns every AIRFLOW_CONN_<CONN_ID> env var (e.g. from the sub-chart's airflow.secret list) into a
Connection (Airflow docs):
| Connection | Required? | Purpose |
|---|---|---|
AIRFLOW_CONN_DATA_PG |
Yes | Data DB |
AIRFLOW_CONN_DATAFEEDER_PG |
Yes | geOrchestra DB (datafeeder schema) |
AIRFLOW_CONN_SOURCE_DB_1 |
Only for the Database source type | See adding a source database |
AIRFLOW_CONN_LOGS_S3 |
No | Platform-specific (e.g. S3 log shipping) |
One secret key with the full URI
Each secretKeyRef must point to a single key holding the complete URI
(postgresql://user:password@host:port/dbname). Secrets exposing only discrete host/user/password/...
keys (like those used by the backend) need an extra key with the pre-built URI.
4. Exposing Airflow
The chart sets Airflow's api.base_url to /airflow but doesn't expose it (airflow.ingress.apiServer.enabled is
false). Either enable that ingress, or add a /airflow route in the geOrchestra Gateway pointing at the
<release>-airflow-api-server service.
5. Airflow worker memory
Keep airflow.workers.resources.limits.memory at its 5Gi default (see prerequisites), 4Gi
at the very least: with e.g. 2Gi, the liveness probe alone can get the worker OOM-killed.
6. Airflow logs persistence
The chart provisions a <release>-airflow-logs-pvc, but nothing uses it: logs go to a RAM-backed emptyDir by
default, count against the worker's memory limit, and are lost when the pod restarts. Since the frontend lets users
download failed-run logs, fetched from Airflow on demand, those downloads then fail. Wire the PVC up:
airflow:
logs:
persistence:
enabled: true
existingClaim: <release>-airflow-logs-pvc
7. Restricting Airflow egress (optional)
The chart ships no NetworkPolicy. Airflow pods (label release: <release>) only need to reach:
- DNS (
kube-dns, port 53); - the other Airflow components (
tier: airflow); - the backend on port
8000(DAG callbacks,BACKEND_INTERNAL_URL); - the PostgreSQL databases (or their pgbouncer);
- the public internet, for URL / WFS sources — excluding private ranges so that user-supplied URLs can't reach internal services.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: datafeeder-airflow-egress
spec:
podSelector:
matchLabels:
release: <release>
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- {protocol: UDP, port: 53}
- {protocol: TCP, port: 53}
- to:
- podSelector:
matchLabels:
tier: airflow
release: <release>
- to:
- podSelector:
matchLabels:
app.kubernetes.io/instance: <release>
app.kubernetes.io/component: <release>-backend
ports:
- {protocol: TCP, port: 8000}
- to:
- ipBlock:
cidr: <db-ip>/32 # or a podSelector if PostgreSQL runs in the cluster
ports:
- {protocol: TCP, port: 5432}
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: [10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16]