Grampsweb on kubernetes - Part 1: Basic deployment
I am working on documenting my family tree, and as I was searching for a self hosted alternative for the paid options (mainly MyHeritage) I came across Grampsweb. It describes itself as “The free, open-source genealogy system for building your family tree – together.”. This sounded perfect for my needs, and I started looking into how to get it running. The guide on https://www.grampsweb.org/ uses Docker Compose
, but let’s see how we can get it running in Kubernetes.
The docker-compose.yaml:
If we look at the example in the installation docs, we can see that the docker-compose.yaml contains some services and a handful of volumes:
Services:
- grampsweb
- grampsweb-celery
- grampsweb-redis
Volumes:
- gramps_users
- gramps_index
- gramps_thumb_cache
- gramps_cache
- gramps_secret
- gramps_db
- gramps_media
- gramps_tmp
Let’s start with the services. They mostly translate to Kubernetes Deployments
Grampsweb:
grampsweb: &grampsweb
image: ghcr.io/gramps-project/grampsweb:latest
restart: always
ports:
- "80:5000" # host:docker
environment:
GRAMPSWEB_TREE: "Gramps Web" # will create a new tree if not exists
GRAMPSWEB_CELERY_CONFIG__broker_url: "redis://grampsweb_redis:6379/0"
GRAMPSWEB_CELERY_CONFIG__result_backend: "redis://grampsweb_redis:6379/0"
GRAMPSWEB_RATELIMIT_STORAGE_URI: redis://grampsweb_redis:6379/1
depends_on:
- grampsweb_redis
volumes:
- gramps_users:/app/users # persist user database
- gramps_index:/app/indexdir # persist search index
- gramps_thumb_cache:/app/thumbnail_cache # persist thumbnails
- gramps_cache:/app/cache # persist export and report caches
- gramps_secret:/app/secret # persist flask secret
- gramps_db:/root/.gramps/grampsdb # persist Gramps database
- gramps_media:/app/media # persist media files
- gramps_tmp:/tmp
If we break it down it contains:
- An image: ghcr.io/gramps-project/grampsweb:latest
- A port definition: “80:5000”
- Some environment values
- Some volume mounts
- A dependency on the grampsweb_redis service.
We can translate it to a kubernetes Deployment like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: grampsweb
labels:
app: grampsweb
spec:
replicas: 1
selector:
matchLabels:
app: grampsweb
template:
metadata:
labels:
app: grampsweb
spec:
containers:
- name: grampsweb
image: ghcr.io/gramps-project/grampsweb:latest
ports:
- containerPort: 5000
protocol: TCP
env:
- name: GRAMPSWEB_CELERY_CONFIG__broker_url
value: redis://grampsweb-redis:6379/0
- name: GRAMPSWEB_CELERY_CONFIG__result_backend
value: redis://grampsweb-redis:6379/0
- name: GRAMPSWEB_RATELIMIT_STORAGE_URI
value: redis://grampsweb-redis:6379/1
- name: GRAMPSWEB_TREE
value: Gramps Web
volumeMounts:
- mountPath: /app/users
name: gramps-users
- mountPath: /app/indexdir
name: gramps-index
- mountPath: /app/thumbnail_cache
name: gramps-thumb-cache
- mountPath: /app/cache
name: gramps-cache
- mountPath: /app/secret
name: gramps-secret
- mountPath: /root/.gramps/grampsdb
name: gramps-db
- mountPath: /app/media
name: gramps-media
- mountPath: /tmp
name: gramps-tmp
volumes:
- name: gramps-users
persistentVolumeClaim:
claimName: gramps-users
- name: gramps-index
persistentVolumeClaim:
claimName: gramps-index
- name: gramps-thumb-cache
persistentVolumeClaim:
claimName: gramps-thumb-cache
- name: gramps-cache
persistentVolumeClaim:
claimName: gramps-cache
- name: gramps-secret
persistentVolumeClaim:
claimName: gramps-secret
- name: gramps-db
persistentVolumeClaim:
claimName: gramps-db
- name: gramps-media
persistentVolumeClaim:
claimName: gramps-media
- name: gramps-tmp
persistentVolumeClaim:
claimName: gramps-tmp
Grampsweb Celery:
For the second service we can see that it duplicates the grampsweb
service with some changes:
grampsweb_celery:
<<: *grampsweb # YAML merge key copying the entire grampsweb service config
ports: []
container_name: grampsweb_celery
depends_on:
- grampsweb_redis
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=2
ports
is overridden to an empty list, container_name
is set to grampsweb_celery
, the dependency on grampsweb_redis is repeated, and a new startup command is added.
Translated into a kubernetes Deployment it looks like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: grampsweb-celery
labels:
app: grampsweb-celery
spec:
replicas: 1
selector:
matchLabels:
app: grampsweb-celery
template:
metadata:
labels:
app: grampsweb-celery
spec:
containers:
- name: grampsweb-celery
image: ghcr.io/gramps-project/grampsweb:latest
args:
- celery
- -A
- gramps_webapi.celery worker
- --loglevel=INFO
- --concurrency=2
env:
- name: GRAMPSWEB_CELERY_CONFIG__broker_url
value: redis://grampsweb-redis:6379/0
- name: GRAMPSWEB_CELERY_CONFIG__result_backend
value: redis://grampsweb-redis:6379/0
- name: GRAMPSWEB_RATELIMIT_STORAGE_URI
value: redis://grampsweb-redis:6379/1
- name: GRAMPSWEB_TREE
value: Gramps Web
volumeMounts:
- mountPath: /app/users
name: gramps-users
- mountPath: /app/indexdir
name: gramps-index
- mountPath: /app/thumbnail_cache
name: gramps-thumb-cache
- mountPath: /app/cache
name: gramps-cache
- mountPath: /app/secret
name: gramps-secret
- mountPath: /root/.gramps/grampsdb
name: gramps-db
- mountPath: /app/media
name: gramps-media
- mountPath: /tmp
name: gramps-tmp
volumes:
- name: gramps-users
persistentVolumeClaim:
claimName: gramps-users
- name: gramps-index
persistentVolumeClaim:
claimName: gramps-index
- name: gramps-thumb-cache
persistentVolumeClaim:
claimName: gramps-thumb-cache
- name: gramps-cache
persistentVolumeClaim:
claimName: gramps-cache
- name: gramps-secret
persistentVolumeClaim:
claimName: gramps-secret
- name: gramps-db
persistentVolumeClaim:
claimName: gramps-db
- name: gramps-media
persistentVolumeClaim:
claimName: gramps-media
- name: gramps-tmp
persistentVolumeClaim:
claimName: gramps-tmp
grampsweb-celery-deployment.yaml
Grampsweb Redis
The grampsweb_redis
service is just a Deployment of the official redis image:
grampsweb_redis:
image: docker.io/library/redis:7.2.4-alpine
container_name: grampsweb_redis
restart: always
Translated to a Kubernetes Deployment it would look like this: (OBS we change the underscore in the name to a dash to follow kubernetes naming conventions)
apiVersion: apps/v1
kind: Deployment
metadata:
name: grampsweb-redis
labels:
app: grampsweb-redis
spec:
replicas: 1
selector:
matchLabels:
app: grampsweb-redis
template:
metadata:
labels:
app: grampsweb-redis
spec:
containers:
- name: grampsweb-redis
image: docker.io/library/redis:7.2.4-alpine
grampsweb-redis-deployment.yaml
Grampsweb Service
In addition to the Deployment, we need a Kubernetes
Service for Grampsweb to route traffic. (not to be confused with Docker compose
services)
In docker-compose.yaml
we see that the container serves content on port 5000, but port 80 should be used on the host. We set up the same thing in the Service:
apiVersion: v1
kind: Service
metadata:
name: grampsweb
labels:
app: grampsweb
spec:
ports:
- name: "80"
port: 80
targetPort: 5000
selector:
app: grampsweb
This Service will default to the type ClusterIP and will only be accessible inside the cluster.
Grampsweb Redis Service
The Service for Redis
is similar, but we do not translate the port:
apiVersion: v1
kind: Service
metadata:
name: grampsweb-redis
labels:
app: grampsweb-redis
spec:
ports:
- name: "redis"
port: 6379
selector:
app: grampsweb-redis
Volumes
We also need to create the volumes defined in docker-compose.yaml. In Kubernetes persistent volumes have two parts. A PersistentVolumeClaim (PVC) and a PersistentVolume (PV) Depending on what storage provider you are using, you might need to configure both, but if you have a storage-class in your cluster with auto provisioning, you just need the PVCs. I am using the nfs-subdir-external-provisioner in my cluster and it will take care of the PVs for me. The PVCs all look very similar, and will be on the form:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: <volume_name>
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
Repeat for all the volumes:
- gramps-users
- gramps-index
- gramps-thumb-cache
- gramps-cache
- gramps-secret
- gramps-db
- gramps-media
- gramps-tmp
You can add multiple Kubernetes objects to a file, and to keep it practical, you can add all the volumes to one file. See grampsweb-all-pvcs.yaml
Putting it all together
If we store the different Deployment and Service definitions in separate files, and put all the volumes in a one, we will end up with something like this:
ls
grampsweb-celery-deployment.yaml grampsweb-redis-deployment.yaml grampsweb-service.yaml
grampsweb-deployment.yaml grampsweb-redis-service.yaml grampsweb-all-pvcs.yaml
Remember to set the namespace
by adding the -n <namespace>
flag to kubectl
for all the following commands if you want to deploy to a different namespace
than default
Since there is no dependency declaration in native kubernetes objects, it is practical to apply them in separate steps:
- The Volumes
- The Redis Deployment and Service
- The Grampsweb Celery Deployment
- The Grampsweb Deployment and Service
Deploy the Volumes
With all the PVC definitions in one file, you can apply them all at once:
kubectl apply -f grampsweb-all-pvcs.yaml
persistentvolumeclaim/gramps-cache created
persistentvolumeclaim/gramps-db created
persistentvolumeclaim/gramps-index created
persistentvolumeclaim/gramps-media created
persistentvolumeclaim/gramps-secret created
persistentvolumeclaim/gramps-thumb-cache created
persistentvolumeclaim/gramps-tmp created
persistentvolumeclaim/gramps-users created
Deploy Redis
kubectl apply -f grampsweb-redis-deployment.yaml -f grampsweb-redis-service.yaml
deployment.apps/grampsweb-redis created
service/grampsweb-redis created
Tail the logs of the Redis container and wait for it to finish start up
kubectl logs deployment/grampsweb-redis
...
<timestamp> Ready to accept connections tcp
Deploy Gampsweb Celery
kubectl apply -f grampsweb-celery-deployment.yaml
deployment.apps/grampsweb-celery created
Tail the logs of the container and wait for grampsweb-celery to finish start up
kubectl logs deployment/grampsweb-celery
...
<timestamp> celery@grampsweb-celery-fbf679f5c-m96md ready.
Deploy Grampsweb
kubectl apply -f grampsweb-deployment.yaml -f grampsweb-service.yaml
deployment.apps/grampsweb created
service/grampsweb created
Testing
Your Grampsweb instance should now be up and running. To test it at this stage you can forward port 8080 on localhost to the internal kubernetes Service:
kubectl port-forward service/grampsweb 8080:80
Remember to include the namespace in the port-forward command, if you deployed to something else than default
Browsing to http://localhost:8080 should now give you the “Welcome to Gramps Web” page, prompting you to set up an admin user.
Among some warnings I got two errors in the logs: assertion 'GDK_IS_SCREEN (screen)' failed
and Error parsing list of recent DBs from file /root/.gramps/recent-files-gramps.xml
. Both can be safely ignored according to this: https://gramps.discourse.group/t/errors-seen-in-logs/4774
Ingress traffic
This step will be different based on your setup and what you use as an ingress controller
. I include mine just as an example.
In my setup, I use Traefik as Ingress Controller
and Certbot to add TLS, so all I need is to an an ingress and set up DNS to finalize the setup:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grampsweb
annotations:
cert-manager.io/cluster-issuer: <ISSUER>
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
tls:
- hosts:
- <HOSTNAME>
secretName: <SECRET_NAME>
rules:
- host: <HOSTNAME>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: grampsweb
port:
number: 80
Replace ISSUER, HOSTNAME, and SECRET_NAME with correct values for your setup.
Profit!
There are, of course, many things you can do to improve the setup like:
- Securing the redis instance
- Moving common Grampsweb config to a ConfigMap
- Using a secret for passwords and keys
but this covers the basic exercise of getting your instance running.
Leave a comment
Your email address will not be published. Required fields are marked *