Today we will learn to deploy Gitea and Postgres inside a Kapsule managed Kubernetes cluster at Scaleway.
The story
A long time ago (in a galaxy far away …) when I started to learn new technology, I decided to rent a dedicated physical server at Soyoustart. I deployed a VMware ESXi hypervisor on it and configured it to run a small ecosystem composed of pfsense (firewall / proxy / VPN), Traefik as common reverse proxy and some tools like Gitea, Graylog, InfluxDB / Grafana, Jenkins, etc …
Today, as I wanted to learn Kubernetes and not focus on the management of a server anymore, I switched off my good old dedicated server and decided to migrate some of the older stuff to Scaleway’s Kapsule service.
Note: I used this server during 5 years and I never once got an issue with it so kudos to Soyoustart for the nice job done there.
The application that I migrate today is Gitea. And as Gitea does need a database to store some of its configuration, I will also deploy a Postgres database container that shall contain all databases for Gitea and also for other future applications.
Deploy database to Kubernetes
I hesitated a long time before deciding to deploy the database to Kubernetes. In my humble opinion, I do not feel that Kubernetes is the best choice to deploy and run stateful workloads. I know it can handle it but it does not feel right to me. Personal opinion.
I was first inclined to use a managed PostgreSQL database from Scaleway. Then I checked my budget and while it is not very expensive, I decided to not engage new expenses and have a look at the option to deploy it and run it inside my small Kapsule cluster. To practice and see if my concerns about running a database inside a Kubernetes cluster were founded.
Step 1 - Check current usage of the cluster
This step will be really rough because I’ve not yet deployed a monitoring stack inside my cluster … yeah, it’s next on my to do list.
I fired K9S (super helpful tool) and took note of cluster usage.
- CPU usage: around 5%
- Memory usage: around 40%
Ok we have some space available, so let’s see which version is eligible.
Step 2 - Choose the version of Postgres
At the time of writing this article, the current version of Postgres available on Docker Hub is 13.2
.
Gitea has no specific requirements regarding the Postgres version so I choosed the version 13.2-alpine
to be up to date.
Step 3 - deploy the database inside Kapsule
Deploying a stateful workload is done by using StatefulSets. As we will only deploy one replica and not configure database replication, we could also use a deployment in that case but why derogate to good practices?
The following components are needed:
- a Secret for the Postgres password
- a headless Service
- a StatefulSet with a Persistent Volume Claim (to store the data) using the default StorageClass configured by Scaleway in Kapsule.
Create the Kubernetes Secret
kubectl create secret generic postgres-secrets --from-literal=POSTGRES_PASSWORD=<your password>
Create the Headless Service
A headless service has no IP address and thus does not act as layer 4 load-balancer / reverse-proxy. It does register a DNS name for each pod being part of the StatefulSet.
apiVersion: v1
kind: Service
metadata:
name: postgres-svc
labels:
app: postgres
spec:
ports:
- port: 5432
name: postgres-port
clusterIP: None
selector:
app: postgres
Create the StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-sfs
spec:
selector:
matchLabels:
app: postgres
serviceName: "postgres-svc"
replicas: 1
template:
metadata:
labels:
app: postgres
spec:
terminationGracePeriodSeconds: 10
containers:
- name: postgres-db
image: postgres:13.2-alpine
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: POSTGRES_PASSWORD
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_HOST_AUTH_METHOD
value: scram-sha-256
- name: POSTGRES_INITDB_ARGS
value: --auth-host=scram-sha-256
ports:
- containerPort: 5432
name: postgres-port
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
There are some things to note here.
PGDATA: should point to a subdirectory of the file system mounted with the volume. It is needed to avoid a CrashLoopBackoff error at the start of the pod because Postgres does not support to create its database files at the root of a mount point.
POSTGRES_HOST_AUTH_METHOD and POSTGRES_INITDB_ARGS: these 2 environment variables configure the Postgres instance to use scram-sha-256
for password encryption instead of md5
. It is a much more secure algorithm whose only caveat is that it is not supported on all version of the Postgres client. It is supported by Gitea though.
volumeClaimTemplates: the persistent volume configuration happens through volumeClaimTemplate
. We cannot use PersistentVolumeClaim
in a StatefulSet because it would try to mount the same volume for each pod being part of the StatefulSet. Using volumeClaimTemplates
enables the creation of one Persistent Volume for each pod of the StatefulSet.
Apply the StatefulSet YAML deployment file.
After a few seconds, the StatefulSet is created and ready.
Prepare the database for Gitea
Start a bash interactive session in the pod postgres-sfs-0
.
kubectl exec -it postgres-sfs-0 -- bash
# Connect to Postgres instance as user postgres
psql -U postgres
Run the preparations for Gitea.
Create gitea
database user
CREATE ROLE gitea WITH LOGIN PASSWORD 'SuperSecretPassword';
Create giteadb
database
CREATE DATABASE giteadb WITH OWNER gitea TEMPLATE template0 ENCODING UTF8 LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8';
Allow gitea
user to access giteadb
database -> Not needed as the standard configuration in file /var/lib/postgresql/data/pgdata/pg_hba.conf
allows to connect to all database from all hosts using scram-sha-256
.
# Last line of file pg_hba.conf
host all all all scram-sha-256
Deploy Gitea
Create gitea
namespace.
kubectl create namespace gitea
It is possible to deploy Gitea on Kubernetes using a Helm chart available here.
This Helm chart can deploy multiple components like a Postgres database or a Memcached cache … we won’t need any of these and we will thus deploy the chart with some specific values.
Create a file gitea-values.yaml
with the following content.
image:
tag: 1.13.6
gitea:
admin:
username: <Gitea admin username - cannot be admin>
password: <Gitea admin password>
email: <Gitea admin email address>
metrics:
enabled: true
database:
builtIn:
postgresql:
enabled: false
config:
database:
DB_TYPE: postgres
HOST: postgres-svc.postgres.svc.cluster.local:5432
NAME: giteadb
USER: gitea
PASSWD: <password of gitea Postgres user>
service:
DISABLE_REGISTRATION: true
SHOW_REGISTRATION_BUTTON: false
cache:
builtIn:
enabled: false
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- git.example.com
tls:
- secretName: gitea-example-tls
hosts:
- git.example.com
Important things to note are:
- specify the Gitea image tag because the default value is not up to date.
- define Gitea admin username, password and email.
- enable Prometheus /metrics endpoint for later integration in Prometheus monitoring.
- disable the deployment of the built-in Postgres database because we already deployed ours.
- configure the database connection parameters.
- disable user registration and the register button. As this instance is exposed publicly, I do not want people to register themselves in my Gitea.
- disable installation of built-in Memcached cache.
- enable Ingress with cert-manager annotations to generate the TLS certificate automatically. The correct DNS name must of course point to the Kapsule cluster for this to work correctly.
Install the Gitea Helm chart with the customized values.
`
# Add the chart repository to Helm
helm repo add gitea-charts https://dl.gitea.io/charts/
# Install the chart - this command can also be used to upgrade the configuration
helm upgrade --install -f gitea-values.yaml gitea gitea-charts/gitea -n gitea
After a minute (some database migrations are running the first time the application is started), the pod is running and the application is accessible through your URL (https://git.example.com).
Note: during my first deployment, I had an issue where the init container could not finish successfully. It always ended in error with a Backoff event. Unfortunately, I could not find any logs at all and the event was not enough to debug the issue. After some investigations, I noticed a typo in my
gitea-values.yaml
file where I forgot a d inenabled
ofmetrics
section … So if you encounter a crash without logs in the init container of your Gitea pod, check the content of yourgitea-values.yaml
file!
Backups !
Well it’s cool, now we have our Gitea instance using our Postgres database ready to host our repositories … but what about backups ?
Gitea offers a complete backup command with gitea dump
. This command should normally be run from inside the Gitea’s container because it needs access to the app.ini
file and the files of the git repositories. It generates a zip files that contains a dump of the database and all files of the git repositories. The generated zip file is stored inside the container … which is not of any use …
I could have played with a Kubernetes CronJob running kubectl exec
command to run gitea dump from inside the container but then I would generate a zip file in my Gitea container. To be completely safe, we would then need to send that file to an external storage like a BLOB storage and this will need to run another cronjob to mount the volume where the file resides and send it to a BLOB storage … lots of stuff that can potentially go wrong.
I thus wanted to use a Kubernetes native backup solution that also integrates with Postgres database to ensure a consistent backup of the database(s). I decided to give a shot at Kasten.
Update: as Kasten requires you to register for a license and all that stuff, I eventually chose to implement another solution described in this post.
And this is a really good content for another post ! Stay tuned for the next episode.