2.3. Umsetzung

2.3.1. Gitea on Docker Compose mit PostgreSQL

Zum Aufsetzen der Gitea-Instanz auf einem Docker-Host wird Docker Compose verwendet, um die Konfiguration und Bereitstellung von Gitea und PostgreSQL zu vereinfachen. Der Nginx Webserver wird nicht in Docker Compose konfiguriert, sondern in den nächsten Schritten manuell auf dem Host-System eingerichtet, inklusive Reverse-Proxy-Funktionalität. Folgendes Docker-Compose-Beispiel zeigt die Konfiguration von Gitea und PostgreSQL in getrennten Containern.

networks:
  gitea:
  external: false

services:
  server:
    image: gitea/gitea:1.21.7
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=*#+*4sv*sjmEWfY:U88D$$gRzFvSNE9;H
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"
    depends_on:
      - db

  db:
    image: postgres:14
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=*#+*4sv*sjmEWfY:U88D$$gRzFvSNE9;H
      - POSTGRES_DB=gitea
    networks:
      - gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data

Die finale und reproduzierbare Version ist in Gitea/docker-compose.yml abgelegt.

Erklärung der Dockerfile-Konfiguration

Die Docker-Konfiguration umfasst die Einrichtung einer Gitea-Instanz und einer PostgreSQL-Datenbank in getrennten Containern innerhalb eines Docker-Netzwerks.

Netzwerkkonfiguration

  • Das Netzwerk gitea wird definiert und ist ausschließlich innerhalb der Docker-Umgebung sichtbar.

  • Über das isolierte Netzwerk können die Gitea- und PostgreSQL-Container miteinander kommunizieren.

Servicekonfiguration

Gitea-Service
  • Verwendet das offizielle Gitea-Image gitea/gitea:1.21.7.

  • Setzt den Container-Namen auf gitea.

  • Konfiguriert Gitea mit Umgebungsvariablen für Benutzer-IDs und Datenbankverbindungen.

  • Sorgt für den automatischen Neustart des Containers.

  • Verbindet den Service mit dem gitea Netzwerk.

  • Bindet Host-Verzeichnisse für persistente Daten und Systemzeit-Synchronisation.

  • Leitet Ports für Webinterface und SSH weiter.

  • Stellt die Abhängigkeit zum Datenbank-Service sicher.

Datenbank-Service (PostgreSQL)
  • Nutzt das PostgreSQL-Image postgres:14.

  • Konfiguriert PostgreSQL mit Umgebungsvariablen für Benutzer, Passwort und Datenbank.

  • Bindet den Service an das gitea Netzwerk.

  • Speichert Datenbankdaten persist auf dem Host-System.

Diese Konfiguration ermöglicht eine effiziente Einrichtung von Gitea und PostgreSQL in Docker, wobei die Portabilität und einfache Skalierung der Dienste im Vordergrund stehen.

2.3.2. Let’s Encrypt & CertBot

Let’s Encrypt ist eine Zertifizierungsstelle, die kostenlose SSL/TLS-Zertifikate ausstellt, um die Verschlüsselung von Websites zu fördern.

Die CA stellt eine ACME (Automated Certificate Management Environment) API bereit, die von Tools wie Certbot genutzt wird, um Zertifikate automatisch zu erhalten und zu erneuern.

Certbot ist ein Open-Source-Tool, das entwickelt wurde, um die Einrichtung von SSL-Zertifikaten zu vereinfachen und zu automatisieren.

Installation

snap ist ein Paket-Manager für Ubuntu und wird benötigt zum Installieren von certbot.

Mit dem simplen Befehl wird dieses installiert:

sudo apt install snapd

Anschließend kann dieses getestet werden mittels:

sudo snap install hello-world

Um certbot zu installieren nutzen wir den snap Paket-Manager:

sudo snap install --classic certbot

Anschließend fügen wir die ausführbare Datei unserem Pfad hinzu um certbot von jeden Verzeichnis aus bequem aufrufen zu können:

sudo ln -s /snap/bin/certbot /usr/bin/certbot

Der oben aufgeführte Befehl erstellt eine symbolische Verknüpfung zur certbot Datei. Dies bedeutet dass die Datei in /usr/bin/ lediglich auf die Datei in /snap/bin/ verweist und der eigentlich Aufruf dort erfolgt.

Konfiguration

Nun ist es möglich Zertifikate anzufragen. Dies geschieht über den Befehl:

sudo certbot --nginx

Oder über diesen Befehl, wenn wir keine automatische Installation in Nginx wünschen:

sudo certbot certonly --nginx

certbot legt automatisch einen cron Auftrag an, welche genutzt werden um Befehle zu einem bestimmten Zeitpunkt auszuführen wie „Jeden Tag um 08:00 Uhr“.

Dadurch kommt es zu keinen Fehlschlägen bei der SSL Verschlüsselung durch abgelaufene Zertifikate.

Um diese Funktionalität zu testen ist folgender Befehl vorhanden:

sudo certbot renew --dry-run

2.3.3. NGINX

Installation

Um nginx zu installieren sind ein paar Schritte nötig.

Um die benötigten Abhängigkeiten zu installieren nutzen wir folgenden Befehl:

sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring

Um nginx aus der offiziellen Repository zu beziehen wird ein „Signing Key“ benötigt, welcher die Authentifizierung der Datei bestätigt:

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg

Die Ausgabe sollte nun den folgenden Fingerabdruck beinhalten:

pub   rsa2048 2011-08-19 [SC] [expires: 2024-06-14]
573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62
uid nginx signing key <signing-key@nginx.com>

Nun können wir das Repository hinzufügen:

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list

Der letzte Schritt legt das hinzugefügte Repository als bevorzugtes Repository fest um zu verhindern dass nginx aus den Standard Repositories bezogen wird:

echo -e "Package: \*\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
| sudo tee /etc/apt/preferences.d/99nginx

Nun können wir nginx installieren:

sudo apt install nginx

Konfiguration

Um die Reverse Proxy zu konfigurieren ist es nötig ins nginx Verzeichnis zu wechseln:

cd /etc/nginx/

Aus Organisationszwecken erstellen wir nun zwei Ordner im Verzeichnis: sites-available und sites-enabled

mkdir sites-available sites-enabled

Im ersteren Ordner werden alle Konfigurationen aufbewahrt und sobald diese „aktiviert“ werden sollen, werden diese mittels symbolischer Links verknüpft mit dem letzteren Ordner.

Nun wird die Konfigurationsdatei für unseren Gitea Service erstellt im Ordner sites-enabled:

cd sites-available
sudo nano gitea.conf

Innerhalber dieser Datei schreiben wir folgende Anweisungen an nginx:

server {
        listen 80 http2;
        listen 443 http2 ssl;

        server_name dva.mahart.ma;

        ssl_certificate /etc/letsencrypt/live/dva.mahart.ma/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/dva.mahart.ma/privkey.pem; # managed by Certbot


        location / {
                client_max_body_size 512M;

                proxy_set_header Connection $http_connection;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;

                proxy_pass http://127.0.0.1:3000$uri;
        }
}

Diese Konfigurationdatei drückt aus dass wir auf den Ports 80 und 443 auf Anfragen hören möchten, sowie dass wir HTTP2 und SSL für Port 443 unterstützen.

Darauffolgend geben wir unsere Domain and und die Pfade zu unseren SSL Zertifikaten welche wir generiert haben.

Im location / Tag können wir nun bestimmen was passiert wenn die Ressource unter dem Pfad / angefragt wird.

Um Probleme mit Git Pushes zu vermeiden erlauben wir es dem Client größere Anfragen zu senden (512 MB), zudem setzen wir bestimmte Header innerhalb der Anfrage für die weitere Verarbeitung durch Gitea und leiten anschließend die Anfrage weiter and den internen Port 3000 auf welchem Gitea auf Anfragen wartet und diese weiterverarbeitet.

Anschließend „aktivieren“ wir diese Konfiguration mittels einer Verknüpfung:

sudo ln -s /etc/nginx/sites-available/gitea.conf /etc/nginx/sites-enabled/gitea.conf

Damit diese Konfiguration auch von nginx beachtet wird, fügen wir sie und alle zukünftigen Konfigurationen der Hauptkonfigurationsdatei unter /etc/nginx/nginx.conf hinzu:

cd /etc/nginx/
sudo nano /etc/nginx/nginx.conf

include /etc/nginx/sites-enabled/*.conf

Dadurch sollte die Konfigurationdatei ungefähr so aussehen:

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
        worker_connections  1024;
}


http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                                          '$status $body_bytes_sent "$http_referer" '
                                          '"$http_user_agent" "$http_x_forwarded_for"';

        access_log  /var/log/nginx/access.log  main;

        sendfile        on;
        #tcp_nopush     on;

        keepalive_timeout  65;

        #gzip  on;

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*.conf;
}

Nun ist erfolgreich eine Reverse Proxy Konfiguration mit SSL Verschlüsselung erstellt.

Die NGINX Konfigurationsdatei ist zusätzlich unter Gitea/nginx.conf abgelegt.

2.4. Uncomplicated Firewall (UFW)

UFW ist nützlich für die Verwaltung von Firewall-Regeln auf Linux-Systemen. Es handlet sich um eine einfache Schnittstelle, um Netzwerkverbindungen auf unserem System zu steuern und Zugriffe auf Dienste entweder zu ermöglichen oder zu blockieren.

2.4.1. Verwaltung von IPTables:

UFW verwaltet IPTables im Hintergrund, indem es eine vereinfachte Benutzeroberfläche bereitstellt, um Regeln zu erstellen, zu ändern und zu löschen. IPTables ist ein leistungsstarkes Framework für das Filtern und Weiterleiten von Netzwerkdaten in Linux. UFW ermöglicht es Benutzern, IPTables-Regeln zu erstellen, ohne sich mit der komplexen Syntax von IPTables befassen zu müssen.

2.4.2. Default-Optionen:

Default Deny Incoming (Standardmäßig eingehenden Datenverkehr ablehnen): Diese Option blockiert jeglichen eingehenden Datenverkehr, der nicht explizit erlaubt ist. Dadurch wird das System vor unautorisiertem Zugriff von außen geschützt.

Default Allow Outgoing (Standardmäßig ausgehenden Datenverkehr zulassen): Diese Option erlaubt standardmäßig allen ausgehenden Datenverkehr, was sicherstellt, dass Anwendungen auf dem System problemlos auf externe Ressourcen zugreifen können.

2.4.3. Bedienung von UFW:

Hier sind einige grundlegende Befehle, um UFW zu verwenden:

  • UFW aktivieren: sudo ufw enable

  • UFW deaktivieren: sudo ufw disable

  • Regel hinzufügen: sudo ufw allow [Port/Protokoll]

  • Regel entfernen: sudo ufw delete [Nummer der Regel]

  • Status anzeigen: sudo ufw status verbose

  • sudo ufw allow 80/tcp - erlaubt eingehende TCP-Verbindungen auf Port 80, dem Standardport für HTTP-Verbindungen

  • sudo ufw allow 443/tcp - gestatten wir eingehenden TCP-Verkehr auf Port 443, dem Standardport für HTTPS-Verbindungen

  • sudo ufw allow ssh bzw. sudo ufw allow 22/tcp - erlauben eingehende SSH-Verbindungen auf Port 22, dem Standard-Port für SSH (Secure Shell)

  • sudo ufw allow 22/udp - erlaubt eingehenden UDP-Verkehr auf Port 22

Das vollständige reproduzierbare Script für die Umsetzung von UFW findet sich in Gitea/ufw.sh.

2.4.4. Traefik

Zur Demonstration der Einfachheit von Traefik wurde eine alternative Konfiguration für die Gitea-Instanz erstellt. Traefik ist ein moderner Reverse-Proxy und Load-Balancer, der speziell für Container-Umgebungen entwickelt wurde.

Die folgende YAML zeigt die Konfiguration von Traefik in Docker Compose:

traefik:
  image: "traefik:v2"
  container_name: "traefik"
  command:
    #- "--log.level=DEBUG"
    #- "--api.insecure=true"
    - "--api.dashboard=true"
    - "--providers.docker=true"
    - "--providers.docker.exposedbydefault=false"
    - "--providers.docker.network=web"
    - "--entrypoints.web.address=:80"
    - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
    - "--entrypoints.websecure.address=:443"
    - "--entrypoints.websecure.http.middlewares=https_config@docker,www-redirect@docker"
    - "--entrypoints.websecure.http.tls.options=default"
    - "--entrypoints.websecure.http.tls.certresolver=myresolver"
    - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
    #- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
    - "--certificatesresolvers.myresolver.acme.email=mahartma@mahartma.com"
    - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"

  labels:
    traefik.enable: true

    # https redirect
    traefik.http.middlewares.https_config.redirectscheme.scheme: "https"
    traefik.http.middlewares.https_config.redirectscheme.permanent: true

    # www -> non-www
    traefik.http.middlewares.www-redirect.redirectregex.regex: "^https://www.(.*)"
    traefik.http.middlewares.www-redirect.redirectregex.replacement: "https://$${1}"
    traefik.http.middlewares.www-redirect.redirectregex.permanent: true

    # basic-auth
    # Note: when used in docker-compose.yml all dollar signs in the hash need to be doubled for escaping.
    # echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g
    traefik.http.middlewares.dev-auth.basicauth.users: "username:TODO"

    # dashboard
    traefik.http.routers.api.rule: Host(`traefik.dva.mahart.ma`)
    traefik.http.routers.api.service: api@internal
    traefik.http.routers.api.middlewares: dev-auth
    traefik.http.routers.api.entrypoints: websecure

    com.centurylinklabs.watchtower.enable: true

  ports:
    - "443:443"
    - "80:80"

  volumes:
    - "/var/lib/traefik/letsencrypt:/letsencrypt"
    - "/var/run/docker.sock:/var/run/docker.sock:ro"

  networks:
    - gitea
    - default

  restart: unless-stopped

Beachtlich ist, dass diese Docker-Compose Definition alle Features abdeckt, die vorher über NGINX konfiguriert wurden. Traefik bietet also eine einfache und effiziente Möglichkeit, Reverse-Proxy- und Load-Balancer-Funktionalitäten in Container-Umgebungen bereitzustellen.

Die Konfiguration bietet zusätzlich die Möglichkeit für basic auth und ein Dashboard für das Monitoring von Traefik.

Die Docker-Compose Definition ist unter Gitea/docker-compose-traefik.yml abgelegt.

2.4.5. Watchtower

Was ist Watchtower?

Watchtower ist ein Open-Source-Projekt, das entwickelt wurde, um Docker-Container automatisch zu überwachen und zu aktualisieren. Es überwacht laufende Container auf dem System und überprüft regelmäßig, ob neue Versionen der Images verfügbar sind. Wenn eine neue Version gefunden wird, aktualisiert Watchtower automatisch den entsprechenden Container, ohne dass manuelle Eingriffe erforderlich sind.

Installation und Verwendung

Die installation von Watchtower kann einfach als Docker realisierbar. Ein schneller Start ist mit folgenden Befehl möglich:

docker run -d \
    --name watchtower \
    -v /var/run/docker.sock:/var/run/docker.sock \
    containrrr/watchtower

Dieser Befehl startet den Watchtower-Container im Hintergrund und bindet an den Docker-Socket an das entsprechende Verzeichnis im Container, um auf die Docker-API Zugriff zu erhalten.

In unserem Fall verwenden wir eine docker-compose.yml, um die Anwendung mit Parametern zu erweitern:

version: "3"

services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --label-enable --interval=43200 --cleanup
    environment:
      WATCHTOWER_NOTIFICATIONS: email
      WATCHTOWER_NOTIFICATION_EMAIL_FROM: notifications@mail.net
      WATCHTOWER_NOTIFICATION_EMAIL_TO: ops@mail.com
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER: mail.server.biz
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER: notifications@mail.net
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT: 587
      WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG: web1
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD: TODO

dieser kann mit folgendem command gestartet werden:

docker-compose up -d

Zur Konfiguration von Watchtower können Labels verwendet werden, um das Verhalten des Containers zu steuern.

Folgendes Label aktiviert Auto-Updates für einen Container und muss lediglich an den Container angehängt werden:

labels:
  - "com.centurylinklabs.watchtower.enable=true"

Major und Minor Updates werden abhängig von der Konfiguration automatisch installiert. Wenn der Container auf Version 2 läuft, werden die Minor-Versionen (2.1, 2.2, 2.3 usw.) automatisch installiert. Wenn der Container auf Version 2.1 läuft, werden die Patch-Versionen (2.1.1, 2.1.2, 2.1.3 usw.) automatisch installiert.

Folgerung

Watchtower ist ein äußerst nützliches Werkzeug zur Automatisierung von Docker-Container-Aktualisierungen. Die nahtlose Integration in bestehende Infrastruktur macht es zu einer attraktiven option, Automatisierung für Docker umzusetzen. Durch die Nutzung von Watchtower können Administratoren sicherstellen, dass ihre Docker-Container immer auf dem neuestem Stand sind, ohne dass ein manuelles Eingreifen erforderlich ist.

Insgesamt ist Watchtower eine wertvolle Ergänzung für jede Docker-Umgebung, um die wiederkehrende Aufgabe von Container-Updates erledigen zu lassen.