Installing Gitea Version Control Service

Install a self-hosted Git service. Git with a cup of tea

Gitea is an excellent and easy to use version control system. Today we are going to install it in our cluster as the next step in creating a build pipeline for our container images.

Prerequisite - Installing Postgres

The bitnami Gitea images do not support the arm64 architecture, so we have to install Postgres on our own. To create the Kubernetes objects we require the manifest files to feed to kubectl. The simplest way to obtain them is to use helm and let it do a dry run installation :

  • download the helm chart and unpack the tar ball into a local directory
  • do a dry-run installation to collect the manifest files
helm fetch gitea-charts/gitea --untar=true --untardir=.
helm install --dry-run gitea -f ./values.yaml  . > ./gitea-dry-run-log.txt

Now we can lift the manifest files from ./gitea-dry-run-log.txt.

We need a password for Postgres. Security is not really a concern for this installation, so let’s just make it a little harder for my kids.

#File: 'mkpgpass.sh'
#!/bin/bash
pwd=$(echo "my postgres password" | sha256sum | sed -ne 's/^\(.\{16\}\).*$/\1/ p')
echo "Hashed Password : $pwd"
echo "Password base64 :" $(echo -n $pwd | base64 -w0)
exit 0
Hashed Password : c866b34bd2d7cabd
Password base64 : Yzg2NmIzNGJkMmQ3Y2FiZA==

The base64-encoded password goes into the postgres secret.

#File: 'postgres-secret.yaml'
apiVersion: v1
kind: Secret
metadata:
  namespace: gitea
  name: gitea-postgres-secret
type: Opaque
data:
  # postgrespass
  password: Yzg2NmIzNGJkMmQ3Y2FiZA==

The database create statements are straight from the Gitea documentation. We collect them in a config map and inject that as the file init-gitea.sh in the volume /docker-entrypoint-initdb.d/ of the stateful set.

#File: 'postgres-configmap.yaml'
apiVersion: v1
kind: ConfigMap
metadata:
  name: gitea-initdb
  namespace: gitea
  labels:
    app: gitea-postgres
data:
  init-gitea.sh: |
    echo "Creating 'gitea' database..."
    psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOF
      CREATE ROLE gitea WITH LOGIN PASSWORD 'gitea';
      CREATE DATABASE gitea WITH OWNER gitea TEMPLATE template0 ENCODING UTF8 LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF8';
      \connect gitea;
      CREATE SCHEMA gitea;
      GRANT ALL ON SCHEMA gitea TO gitea;
      ALTER USER gitea SET search_path=gitea;
    EOF
    echo "Created database."

At the bottom of the stateful set definition we have to decide how much memory we want to set aside for the persisten volume claim I decided that 5 GB was enough for me.

#File: 'postgres-statefulset.yaml'
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: gitea-postgres
  namespace: gitea
spec:
  selector:
    matchLabels:
      app: gitea-postgres
  serviceName: gitea-postgres
  template:
    metadata:
      labels:
        app: gitea-postgres
    spec:
      containers:
      - name: postgres
        image: postgres:14
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: gitea-postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: pgdata
          mountPath: /var/lib/postgresql/data
        - name: gitea-initdb
          mountPath: /docker-entrypoint-initdb.d/
      volumes:
        - name: gitea-initdb
          configMap:
            name: gitea-initdb
  volumeClaimTemplates:
  - metadata:
      name: pgdata
    spec:
      storageClassName: longhorn
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi

The Postgres instance and Gitea will be living in the same namespace so a service with a cluster ip-address is sufficient to establish connectivity.

#File: 'postgres-service.yaml'
apiVersion: v1
kind: Service
metadata:
  name: gitea-postgres
  namespace: gitea
  labels:
    app: gitea-postgres
spec:
  type: ClusterIP
  ports:
  - port: 5432
  selector:
    app: gitea-postgres

Now that we have the .yaml files complete, we can kick off the creation of the Postgres service.

#File: 'install-postgres.sh'
#!/bin/bash

kubectl create namespace gitea

dir=$(dirname ${0})
kubectl -n gitea apply -f $dir/postgres-secret.yaml
kubectl -n gitea apply -f $dir/postgres-configmap.yaml
kubectl -n gitea apply -f $dir/postgres-statefulset.yaml
kubectl -n gitea apply -f $dir/postgres-service.yaml

exit 0

If everything goes well, we should see 4 pods coming up :

$ kubectl -n gitea get pods
NAME                                              READY   STATUS
pod/gitea-postgres-0                              1/1     Running
pod/gitea-postgresql-ha-pgpool-554fb9b4cc-dl4lv   1/1     Running
pod/gitea-postgresql-ha-postgresql-0              1/1     Running
pod/gitea-postgresql-ha-postgresql-2              1/1     Running
pod/gitea-postgresql-ha-postgresql-1              1/1     Running

Having a quick look at the logs should return output similar to this :

#kubectl -n gitea logs gitea-postgresql-ha-pgpool-554fb9b4cc-dl4lv
pgpool 19:21:37.75 
pgpool 19:21:37.76 Welcome to the Bitnami pgpool container
pgpool 19:21:37.76 Subscribe to project updates by watching https://github.com/bitnami/containers
pgpool 19:21:37.77 Submit issues and feature requests at https://github.com/bitnami/containers/issues
pgpool 19:21:37.78 
pgpool 19:21:37.78 INFO  ==> ** Starting Pgpool-II setup **
... redacted ...

#kubectl -n gitea logs gitea-postgres-0
PostgreSQL Database directory appears to contain a database; Skipping initialization
2023-12-21 19:22:05.399 UTC [1] LOG:  starting PostgreSQL 14.10 (Debian 14.10-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
2023-12-21 19:22:05.401 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
... redacted ...

# kubectl -n gitea logs gitea-postgresql-ha-postgresql-0
postgresql-repmgr 19:21:46.91 
postgresql-repmgr 19:21:46.98 Welcome to the Bitnami postgresql-repmgr container
postgresql-repmgr 19:21:47.02 Subscribe to project updates by watching https://github.com/bitnami/containers
postgresql-repmgr 19:21:47.04 Submit issues and feature requests at https://github.com/bitnami/containers/issues
postgresql-repmgr 19:21:47.06 
postgresql-repmgr 19:21:47.24 INFO  ==> ** Starting PostgreSQL with Replication Manager setup **
... redacted ...

Installing Gitea

We let helm do the actual installation. The values.yaml file is below. The only item to watch out for is postgresql.enabled. Since we provisioned Postgres on our own, this has to be set to false.

#File: 'gitea-values.yaml'
# The Gitea docs recommend this.
memcached:
  enabled: false

# Disable postgresql since it is already installed.
postgresql:
  enabled: false

# The Gitea docs recommend this: Share the IP for HTTP and SSH.
service:
  ssh:
    annotations:
      metallb.universe.tf/allow-shared-ip: gitea

# be careful to pick the right hostname.
gitea:
  config:
    server:
      DOMAIN: git.k3s.kippel.de
    database:
      DB_TYPE: postgres
      HOST: gitea-postgres.gitea.svc.cluster.local:5432
      USER: gitea
      PASSWD: gitea
      NAME: gitea
      SCHEMA: gitea
#File: 'install-gitea.sh'
#!/bin/bash

dir=$(dirname ${0})

helm repo add gitea-charts https://dl.gitea.io/charts/
helm repo update
helm install gitea gitea-charts/gitea --namespace gitea --create-namespace --values $dir/gitea-values.yaml

kubectl -n gitea apply -f $dir/gitea-loadbalancer.yaml

exit 0

Upgrading Gitea

Upgrading Gitea when a new release is published is a straightforward exercise. Just leave it to Helm.

# helm repo update

# helm upgrade gitea gitea-charts/gitea --namespace gitea --values gitea-values.yaml

Release "gitea" has been upgraded. Happy Helming!
NAME: gitea
LAST DEPLOYED: Sat Nov  4 10:28:58 2023
NAMESPACE: gitea
STATUS: deployed
REVISION: 2
NOTES:
1. Get the application URL by running these commands:
  echo "Visit http://127.0.0.1:3000 to use your application"
  kubectl --namespace gitea port-forward svc/gitea-http 3000:3000

Exposing Gitea with a MetalLB Load Balancer Service

I want to use http for the Web-UI and ssh for the git client, so i am going to expose ports 80 and 22 in the load balancer service.

#File: 'gitea-loadbalancer.yaml'
apiVersion: v1
kind: Service
metadata:
  labels:
    app: gitea
  name: gitea
  namespace: gitea
spec:
  type: LoadBalancer
  ports:
  - name: gitea-http
    port: 80
    protocol: TCP
    targetPort: 3000
  - name: gitea-ssh
    port: 22
    protocol: TCP
    targetPort: 2222
  selector:
    app: gitea

After applying the .yaml file with kubectl i took the external ip address and added it to /etc/dnsmasq.hosts on my dnsdhcp server.

#kubectl -n gitea get svc/gitea
NAME   TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                     AGE
gitea  LoadBalancer   10.43.231.147   192.168.100.155   80:32343/TCP,22:30524/TCP   0d

Opening the URL gives me the Gitea logon screen.

Gitea Splashscreen

Gitea Splashscreen

Creating a user account and configuring it for ssh access is similar to Github, no surprises there. After creating a new repository, pushing an existing directory was successful.

git init
git add -A .
git commit -m "first commit"
git remote add origin git@git.k3s.kippel.de:axel/swagger-editor.git
git push -u origin master

git push -u origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 203 bytes | 203.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To git.k3s.kippel.de:axel/swagger-editor.git
 * [new branch]      master -> master
branch 'master' set up to track 'origin/master'.

What’s left ?

We can configure some nice-to-have features in Gitea to integrate it with our CI/CD pipeline :

  • register and activate an ssh key so that Intellij can commit updates to a repo
  • create Jenkins webhooks for repos to automatically trigger image rebuilds upon commit
  • create access token(s) to facilitate repository access for remote applications (Jenkins, PyStatikMan)

Conclusion

The pace is picking up. With a professional version management service like Gitea we can do all kind of things :

  • docker builds from the repo
  • use webhooks on commit for triggering follow-up activities

The Interesting Times Gang would be happy. I hope so are you.

Stay tuned !