
Installing dawarich on OpenShift
I was listening to a podcast from C't Raus aus der US-Cloud and there they mentioned the app dawarich which could even be hosted by yourself. So I thought I would give it try to run it on my OpenShift. The installation is documented only to run in a docker-compose environment or on a Synology NAS. So I had to do some tweaking which I wanted to share here (still some things to do but its quite ok for now).
Some preparation work
As it is running on OpenShift (opinionated implementation of K8S) there are some things that need to be done beforehand.
Create a project (aka namespace)
$ oc new-project dawarich
Create service account and set SCC
The containers run as root which per default does not work on OpenShift due to the Security Context Constraints (SCCs) (one of the todos:Running pods in Linux user namespaces ). To change the container to run with a different SCC, you must create a service account that is bound to a pod.
$ oc create sa dawarich-sa
To associate the service account with an SCC, use the oc adm policy command. Identify a service account by using the -z option.
$ oc adm policy add-scc-to-user anyuid -z dawarich-sa
Later on we need to set the service account to the deployments. But we need to create these first.
Persistent Storage
dawarich needs four pvcs:
- dawarich_db_data ( dawarich_db Postgres)
- dawarich_public (App)
- dawarich_watched (App)
- dawarich_storage (App)
- dawarich_shared (redis)
Please create these four pvcs. dawarich_db_data and dawarich_shared need RWX storage.
Deploying the App
Create the Deployments
The dawarich app has 4 deployments:
- dawarich-app (RoR)
- dawarich-db (postgres)
- dawarich-sidekiq sidekiq worker (for background jobs)
- redis (redis database)
This ends up in two deployments:
dawarich database (postgres) deployment (based on image postgis/postgis:17-3.5-alpine)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
alpha.image.policy.openshift.io/resolve-names: '*'
app.openshift.io/route-disabled: "false"
labels:
app: dawarich-db
app.kubernetes.io/component: dawarich-db
app.kubernetes.io/instance: dawarich-db
app.kubernetes.io/name: dawarich-db
app.kubernetes.io/part-of: dawarich_redis
app.openshift.io/runtime-namespace: dawarich
name: dawarich-db
namespace: dawarich
uid: 6ab3d44a-6fce-48c3-b82f-7359b68171c5
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: dawarich-db
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: dawarich-db
deployment: dawarich-db
spec:
containers:
- env:
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_USERNAME
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_PASSWORD
- name: DATABASE_NAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_NAME
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_HOST
image: postgis/postgis:17-3.5-alpine
imagePullPolicy: IfNotPresent
name: dawarich-db
ports:
- containerPort: 5432
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-darwarich-data
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: dawarich-sa
serviceAccountName: dawarich-sa
terminationGracePeriodSeconds: 30
volumes:
- name: postgres-darwarich-data
persistentVolumeClaim:
claimName: postgres-darwarich-data-new
The second deployment for the actual app (RoR):
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
alpha.image.policy.openshift.io/resolve-names: '*'
app.openshift.io/route-disabled: "false"
labels:
app: dawarich-app
app.kubernetes.io/component: dawarich-app
app.kubernetes.io/instance: dawarich-app
app.kubernetes.io/name: dawarich-app
app.openshift.io/runtime-namespace: dawarich
name: dawarich-app
namespace: dawarich
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: dawarich-app
strategy:
type: Recreate
template:
metadata:
labels:
app: dawarich-app
deployment: dawarich-app
spec:
restartPolicy: Always
initContainers:
- name: db-migrate
command: [ "bin/rails", "db:migrate" ]
image: freikin/dawarich:latest
imagePullPolicy: IfNotPresent
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
env:
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_USERNAME
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_PASSWORD
- name: DATABASE_NAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_NAME
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_HOST
- name: data-migrate
command: [ "bin/rails", "data:migrate" ]
image: freikin/dawarich:latest
imagePullPolicy: IfNotPresent
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
env:
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_USERNAME
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_PASSWORD
- name: DATABASE_NAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_NAME
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_HOST
containers:
- name: dawarich-app
args:
- server
- -p
- "3000"
- -b
- '::'
command:
- bin/rails
env:
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_USERNAME
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_PASSWORD
- name: DATABASE_NAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_NAME
- name: MIN_MINUTES_SPENT_IN_CITY
value: "60"
- name: APPLICATION_HOSTS
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: APPLICATION_HOSTS
- name: TIME_ZONE
value: Europe/Zurich
- name: APPLICATION_PROTOCOL
value: http
- name: DISTANCE_UNIT
value: km
- name: ROMETHEUS_EXPORTER_ENABLED
value: "false"
- name: SELF_HOSTED
value: "true"
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_HOST
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: REDIS_URL
- name: RAILS_ENV
value: development
- name: STORE_GEODATA
value: "true"
image: freikin/dawarich:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
protocol: TCP
readinessProbe:
failureThreshold: 30
httpGet:
path: /api/v1/health
port: 3000
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 20
successThreshold: 3
timeoutSeconds: 10
resources:
limits:
cpu: "1"
memory: 3Gi
requests:
cpu: 500m
memory: 2Gi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/app/public
name: dawarich-public
- mountPath: /var/app/tmp/imports/watched
name: dawarich-watched
- mountPath: /var/app/storage
name: dawarich-storage
- mountPath: /dawarich_db_data
name: postgres-darwarich-data-new
dnsPolicy: ClusterFirst
schedulerName: default-scheduler
securityContext: {}
serviceAccount: dawarich-sa <------------------- service account we created earlier
serviceAccountName: dawarich-sa
terminationGracePeriodSeconds: 30
volumes:
- name: dawarich-public
persistentVolumeClaim:
claimName: dawarich-public
- name: dawarich-watched
persistentVolumeClaim:
claimName: dawarich-watched
- name: dawarich-storage
persistentVolumeClaim:
claimName: dawarich-storage
The redis deployment
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
alpha.image.policy.openshift.io/resolve-names: '*'
app.openshift.io/route-disabled: "false"
labels:
app: redis
app.kubernetes.io/component: redis
app.kubernetes.io/instance: redis
app.kubernetes.io/name: redis
app.kubernetes.io/part-of: dawarich_redis
app.openshift.io/runtime-namespace: dawarich
name: redis
namespace: dawarich
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: redis
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
labels:
app: redis
deployment: redis
spec:
containers:
- image: redis:7.4-alpine
imagePullPolicy: IfNotPresent
name: redis
ports:
- containerPort: 6379
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /data
name: dawarich-db
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- persistentVolumeClaim:
claimName: dawarich-pg-shared
name: dawarich-db
Aaaaaand finally the sidekiq deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
alpha.image.policy.openshift.io/resolve-names: '*'
app.openshift.io/route-disabled: "false"
labels:
app: dawarich-sidekiq
app.kubernetes.io/component: dawarich-sidekiq
app.kubernetes.io/instance: dawarich-sidekiq
app.kubernetes.io/name: dawarich-sidekiq
app.kubernetes.io/part-of: dawarich_redis
app.openshift.io/runtime-namespace: dawarich
name: dawarich-sidekiq
namespace: dawarich
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: dawarich-sidekiq
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: dawarich-sidekiq
deployment: dawarich-sidekiq
spec:
containers:
- args:
- exec
- sidekiq
command:
- bundle
env:
- name: RAILS_ENV
value: development
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: REDIS_URL
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_USERNAME
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_HOST
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_PASSWORD
- name: DATABASE_NAME
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: DATABASE_NAME
- name: APPLICATION_HOSTS
valueFrom:
secretKeyRef:
name: dawarich-app-env
key: APPLICATION_HOSTS
- name: BACKGROUND_PROCESSING_CONCURRENCY
value: "10"
- name: APPLICATION_PROTOCOL
value: http
- name: DISTANCE_UNIT
value: km
- name: PROMETHEUS_EXPORTER_ENABLED
value: "false"
- name: PROMETHEUS_EXPORTER_HOST
value: dawarich_app
- name: PROMETHEUS_EXPORTER_PORT
value: "9394"
- name: ENABLE_TELEMETRY
value: "false"
- name: SELF_HOSTED
value: "true"
image: freikin/dawarich:latest
imagePullPolicy: Always
name: dawarich-sidekiq
ports:
- containerPort: 3000
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: dawarich-sa
serviceAccountName: dawarich-sa
terminationGracePeriodSeconds: 30
Considerations:
APPLICATION_HOSTS:
if you are running it behind a reverse proxy you need to adapt the FQDN accordingly. See Setting up reverse proxy
DATABASE_HOST:
You need to create a internal service so that the app is able to connect to it:
apiVersion: v1
kind: Service
metadata:
annotations:
labels:
app: dawarich-db
app.kubernetes.io/component: dawarich-db
app.kubernetes.io/instance: dawarich-db
name: data base service name
namespace: dawarich
spec:
clusterIP: 172.30.219.230
clusterIPs:
- 172.30.219.230
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: 5432-tcp
port: 5432
protocol: TCP
targetPort: 5432
selector:
app: dawarich-db
deployment: dawarich-db
sessionAffinity: None
type: ClusterIP
REDIS_URL
You need to create a internal service to be able to connect to the redis service:
kind: Service
apiVersion: v1
metadata:
name: redis service name
namespace: dawarich
spec:
clusterIP: 172.30.141.216
ipFamilies:
- IPv4
ports:
- protocol: TCP
port: 6379
targetPort: 6379
internalTrafficPolicy: Cluster
clusterIPs:
- 172.30.141.216
type: ClusterIP
ipFamilyPolicy: SingleStack
sessionAffinity: None
selector:
app: redis
Also you would need a secret with all the env values you do not want to expose:
kind: Secret
apiVersion: v1
metadata:
name: dawarich-app-env
namespace: dawarich
data:
APPLICATION_HOSTS: your.fqdn.xyz
DATABASE_HOST: service to the db
DATABASE_NAME: database name
DATABASE_PASSWORD: database pw
DATABASE_USERNAME: username for db
REDIS_URL: redis service
As the original setup from dawarich relies on docker compose and the app is meant for development purposes there are some things need to be done manually.
spec:
containers:
- args:
- server
- -p
- "3000"
- -b
- '::'
command:
- bin/rails
We need to add the above arguments (see deployment dawarich-app). With this we override the original startup commands and arguments from the docker compose file. There you see that the entrypoint and the command. The entrypoint is always executed before the actual startup. This is not really a problem until you look at the part of the script:
# Run primary database migrations first (needed before other migrations)
echo "Running primary database migrations..."
bundle exec rails db:migrate
# Run data migrations
echo "Running DATA migrations..."
bundle exec rake data:migrate
#Demo user and password
echo "Running seeds..."
bundle exec rails db:seed
This always creates the demo user and you do not want that. For the initial startup you would need that in order to log in. But later on you want to get rid of it.
So at the initial startup you would need to login to the container and run the script:
# oc rsh dawarich-app-your-pod-id
# cd /usr/local/bin
# ls
bundle bundler erb gem irb racc rake rbs rdbg rdoc ri ruby syntax_suggest typeprof web-entrypoint.sh
# ./web-entrypoint.sh
# exit
Now you should restart the app pod and then everything should be ready and working. As we have the initContainers in the dawarich-app deployments the db and data migrations being executed at every start. So we should be fine that.