5.2. Projekt

Automatisierung des Sphinx Bericht Deployment auf dem RZ-Webserver

5.2.1. Ist-Zustand

Bisher lief die Bereitstellung des HTML-Berichts so ab, dass nachdem alle ihre Commits gepusht hatten, ein manuelles Kopieren auf den Webserver notwendig war.

Falls ein Commit vergessen wurde, musste dies erneut vorgenommen werden. Um diesen wiederkehrenden Vorgang zu beschleinigen erstellten wir in der Vergangenheit bereits mit Abgabe 2 ein Skript mit dem Namen deploy-html.bat.

@echo off
set tmpdir="%temp%\dva-deploy"
set "rootDir=%cd%"
set rzuser=julwie21

echo Cleanup temp dir
mkdir %tmpdir%
rmdir /S /Q %tmpdir%
mkdir %tmpdir%

echo make dir browsable
echo Options +Indexes > %tmpdir%\.htaccess

call make.bat html

if exist "%rootDir%\build\html\index.html" (
	xcopy "%rootDir%\build\html\" "%tmpdir%" /E /I /Y
    ) else (
        echo ERROR: index.html not found
		exit /b 1
    )
)


dir "%tmpdir%"

echo RZ-User: %rzuser%
ssh %rzuser%@login.rz.hs-augsburg.de rm -rf /www/%rzuser%/dva/
scp -r %tmpdir%* %rzuser%@login.rz.hs-augsburg.de:/www/%rzuser%/dva/

REM show result
timeout /t 5
start https://tha.de/~julwie21/dva/

Dieses Skript legt den Build-Output in einem temporären Ordner ab. Initial wird das Verzeichnis neu angelegt. Vor dem Building wird, um das Verzeichnis dva (https://tha.de/~julwie21/dva/) später verfügbar zu machen, eine Konfigurationsdatei .htaccess anlegt und die entsprechenden Optionen, wie vom RZ angegeben (https://www.tha.de/Rechenzentrum/Eigene-Homepage.html), hinterlegt.

Nun wird das Build Skript von Sphinx ausgeführt. Um zu überprüfen, ob es funktioniert hat, wird überprüft, ob die Datei index.html existiert. Ist diese Datei vorhanden, wird das Kopieren in das temporäre Verzeichnis vorgenommen bevor der Upload durchgeführt werden kann. Beim Upload ist zunächst nur eine Authentifizierung mit Passwort hinterlegt. Zu guter Letzt wird die Webseite im Browser geöffnet, sodass man nochmals von Hand überprüfen kann, ob alles geklappt hat.

Im weiteren Berichtsverlauf wird noch gezeigt wie die passwortlose Authentifizierung per SSH zum Upload genutzt wird. Auch im Remote-Zielverzeichnis findet vor dem Upload eine Bereinigung des vorhandenen Verzeichnisses statt.

Da immer wieder sinnvolle Ergänzungen oder Korrekturen für den Bericht gefunden wurden, häuften sich allerdings auch die manuellen Ausführungen dieses Skripts.

5.2.2. RZ-Authentifizierung

Bei den ersten Überlegungen, die wir anstellten um das Deployment zu automatisieren, galt es zunächst eine passwortlose Authentifizierung einzurichten. Daher erstellten wir einen SSH-Key für den verwendeten RZ-Benutzer und erlaubten mittels Konfigurationsdatei im Home-Verzeichnis an login.rz.hs-augsburg.de einen Verbindungsaufbau mit diesem.

Wir beobachteten allerdings, dass nach einigen Stunden die Gültigkeit des SSH-Keys erloschen ist und keine weitere Anmeldung mehr möglich war. Nach weiteren Versuchen der Fehlerbehebung stellten wir fest, dass nach einer erneuten Passwort-Authentifierung der SSH-Key wieder für einige Stunden gültig war.

Da wir auch nach ausführlicher Recherche das Problem nicht lösen konnten, wandten wir uns an den RZ-Service. Dieser teilte uns freundlich mit, dass für die Anmeldung mittels SSH-Key ein gültiges Kerberos-Ticket vom LDAP Domain Controller benötigt wird. Dieses wird unglücklicherweise nur nach erfolgreicher Passwort-Authentifierung erstellt. Lösen könne man dies jedoch indem man den Public-Key im LDAP des RZ hinterlegt.

5.2.3. Beseitung Sicherheitsproblem (Dienstkonto)

Eine Authentifizierung mit Passwort kam aus Sicherheitsgründen auf keinen Fall in Frage, da mit dem RZ-Konto sensible Dienste wie z.B. die Prüfungsanmeldung oder Hochschul E-Mail Adresse verknüpft sind und ein Passwort für die Pipeline in Gitlab gespeichert werden müsste. Auch das Hinterlegen des SSH-Keys eines persönlichen RZ-Benutzers in der Pipeline stellt ein Sicherheitsrisiko für das persönliche RZ-Konto dar, da man mit dem Zugriff auf den Private-Key, der für die Verarbeitung genauso erforderlich ist und den man durch einfache Manipulation der Pipeline erlangen könnte, auch weitere Rechte verwundbar macht.

Durch das minimale Ändern des Codes in der Pipeline .gitlab-ci.yml wäre es bereits möglich mithilfe des cat-Befehls diese sensiblen Informationen auszulesen. Daher mussten wir uns für die Pipeline eine andere Lösung einfallen lassen.

Letztendlich beantragten wir einen Benutzer beim RZ, da wir mit dem Support ohnehin schon in Kontakt standen. Über das Portal des Rechenzentrums (https://idm.hs-augsburg.de/gui/request/) war die Erstellung schnell erledigt und die Freischaltung seitens RZ fand innerhalb eines Tages statt.

Zudem ist es Best Practice ein Dienstkonto für Dienste wie diesen und allem möglichen Erdenklichen zu nutzen. Eine persönliche Kennung sollte niemals zur Ausführung eines Dienstes genutzt werden! Eine Sperrung des Kontos aufgrund mehrfacher falscher Passworteingabe, hätte einen Stillstand der Dienstausführung zur Folge.

Für diesen Benutzer konnten wir nun ohne Sicherheitsbedenken ein SSH-Key Paar für den Benutzer dvagrp07 erstellen. Der Public-Key wurde, wie vom RZ vorgeschlagen dem LDAP bekannt gemacht.

5.2.4. Runner Konfiguration

Für die Ausführung der Pipeline wird ein sogenannter Runner benötigt. Hierzu ließen wir uns im Vorfeld vom RZ einen Linux Server mit zwei Cores und vier GB RAM bereitstellen. Für unsere Zwecke ist dies mehr als ausreichend. Als Betriebssystem setzten wir Ubuntu 22.04 ein.

Bgzl. des Runners entschieden wir uns für einen Specific Runner des Executor-Typen Shell. Dieser bietet die maximale Flexibiltät. Die direkte Installation des Runner auf dem Server ohne Docker ist zwar möglich. Allerdings wollten wir auf die Portabilität und Einfachheit eines Docker-Containers sowie auch auf die Möglichkeit einen defekten Runner schnell neu bereitstellen zu können, nicht verzichten. Daher installierten wir zunächst Docker auf unserem Server und den Runner innerhalb eines Containers.

Zwei sehr populäre Distributionen des Runners stammen von Gitlab selbst gitlab/gitlab-runner und von Bitnami bitnami/gitlab-runner. Der Gitlab Runner von Bitnami ist allerdings deutlich schlanker (ca. 300 MB) als der Gitlab Runner direkt von Gitlab (ca. 550 MB) und reicht für unsere Zwecke völlig aus. Daher setzten wir diesen ein.

Zudem mussten wir am Runner selbst einige Anpassungen mittels eines Dockerfile vornehmen:

FROM bitnami/gitlab-runner:latest

USER root

# make the '/gitlab-runner' folder the current working directory
WORKDIR /gitlab-runner

#####################Install Sphinx##################################################
#Documentation: https://docs.docker.com/engine/install/debian/#install-using-the-repository

RUN apt-get update
RUN apt-get install nano -y
RUN apt-get install python3-sphinx -y

#Prevent problems with fingerprint prompt in ssh
RUN  echo "    StrictHostKeyChecking no" >> /etc/ssh/ssh_config

#Docker commands to finish image
COPY "gitlab-runner_entrypoint.bash" "gitlab-runner_entrypoint.bash"
RUN chmod +x /gitlab-runner/gitlab-runner_entrypoint.bash
ENTRYPOINT ["/bin/bash","/gitlab-runner/gitlab-runner_entrypoint.bash"]

Alle Kommandos sollen als root ausgeführt werden. Damit der Runner das Building der HTML Dateien erledigen kann, muss im Dockerfile die Installation von Sphinx durchgeführt werden.

Um zu verhindern, dass eine Abfrage aufkommt, ob wirklich eine Verbindung hergestellt werden soll, die manuell bestätigt werden müsste, haben wir SSH auf dem Runner entsprechend konfiguriert, dass die known_hosts ignoriert wird. Die Abfrage würde folgendermaßen aussehen:

../_images/ssh_fingerprint_prompt.png

Damit sich der Runner nach einmaliger Registrierung bei jedem Start automatisch bei Gitlab meldet, mussten wir den Entrypoint mit dem Skript gitlab-runner_entrypoint.bash anpassen und dieses ausführbar machen.



Die gitlab-runner_entrypoint.bash sieht wie folgt aus:

#!/bin/bash
if ! [[gitlab-runner status | grep "Token=$RUNNER_TOKEN" ]]; then #register runner if not already registered
        gitlab-runner register --non-interactive --url https://gitlab.informatik.hs-augsburg.de --token "$RUNNER_TOKEN" --executor "$RUNNER_EXECUTOR"
fi 
gitlab-runner run #Make gitlab-runner service available to receive jobs

Es muss überprüft werden, ob dem Runner bereits ein Runner-Token eingetragen wurde. Ist dies nicht der Fall, wird versucht ihn zu registrieren. Falls die Registrierung schon durchgeführt wurde, wird lediglich der run-Befehl ausgeführt und der Runner ist bereit die Pipeline zu verarbeiten.



Zur Bereitstellung des Runners nutzten wir eine docker-compose.yml Datei:

version: "3"
services:
  runner01:
    container_name: runner01
    image: prak-dva/gitlab-runner
    restart: ${GLOBAL_RESTART_POLICY}
    stdin_open: true #docker run -i
    tty: true #docker run -t
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      RUNNER_TOKEN: ${RUNNER_TOKEN_1}
      RUNNER_EXECUTOR: ${RUNNER_EXECUTOR_1}
    volumes:
      - type: bind
        source: /DVA
        target: /DVA
        read_only: true
  

Die Variablen werden über eine .env Datei mit der RESTART_POLICY: always und den Runner spezifischen Variablen gesetzt.

Sowohl Public- als auch Private-key wurden in einem vorherigen Abschnitt auf den Ubuntu Host kopiert um eine Authentifizierung an login.rz.hs-augsburg.de zu ermöglichen. Zur Anmeldung am login.rz.hs-augsburg.de ist der SSH-Key allerdings nicht auf dem Host sondern im Runner vonnöten.

Daher bindeten wir das Verzeichnis in dem die SSH-Keys vorliegen mittels binding mount am Runner an. Der Zugriff beschränkt sich allerdings auf readonly, da wir ein versehentliches Löschen verhindern wollen und Schreibrechte ohnehin nicht vonnöten sind.

5.2.5. Runner Registrierung

Vor der endgültigen Bereitstellung des Containers auf dem Ubuntu Host muss man den Runner noch über die Gitlab Weboberfläche registrieren, den Runner-Token in die .env eintragen und dann erst Docker Compose ausführen. Diese Möglichkeit findet man in den CI/CD Settings des Repositories unter Runners:

../_images/new_project_runner.png

Wie man in der Abbildung sehen kann, haben wir, wie bereits festgelegt, den Tag Shell und die Reservierung ausschließlich für unser Projekt eingestellt.

Zur einfachen Wiederverwendbarkeit für weitere Projekte fassten wir die allgemein notwendigen Schritte in einer README.md zusammen.

5.2.6. Pipeline Konfiguration

Unsere produktive .gitlab-ci.yml sieht folgendermaßen aus:

stages:
  - build
  - test
  - deploy
  - cleanup

variables:
  BUILD_DIR: .
  KEY: "/DVA/id_rsa_tha_dvagrp07"

build:
    stage: build
    script:
      - cd ${BUILD_DIR}
      - ls -la ${BUILD_DIR}
      - bash -c "sphinx-build -M html ./source ./build -W --keep-going" > sphinx-build.log
      - ls -la ./build
      - echo Options +Indexes > ./build/html/.htaccess
    artifacts:
      paths:
        - build/
        - sphinx-build.log
    allow_failure: true #Gitlab will return a warning on error
    tags:
      - shell

test:
    stage: test
    script:
      - echo Test fails if a warning occurred in log file
      - cat sphinx-build.log | grep -v " WARNING"
    dependencies:
    - build
    tags:
      - shell

deploy___tha.de/~dvagrp07:
    stage: deploy
    script:
      - ssh dvagrp07@login.rz.hs-augsburg.de -i ${KEY} cd /www/dvagrp07; rm -rf /www/dvagrp07/*
      - cd ${BUILD_DIR}
      - scp -r -i ${KEY} ./build/html/* dvagrp07@login.rz.hs-augsburg.de:/www/dvagrp07/ 
      - echo "Check result on https://tha.de/~dvagrp07"
    dependencies:
    - build
    only:
      - main
    tags:
      - shell

cleanup:
    stage: cleanup
    script:
      - cd ${BUILD_DIR}
      - rm -rf ./build
      - rm -rf sphinx-build.log
    dependencies:
    - build
    rules:
      - when: always
    tags:
      - shell

Wir unterteilen in vier Stages build, test, deploy und cleanup. Alle Stages sind mit dem Tag shell gekennzeichnet. Dies muss vom Runner unterstützt werden. Der Pfad zum SSH-Key sowie das Build Verzeichnis werden in einer global zugänglichen Variable gespeichert.

In Stage build ist neben dem Build Kommando auch ein Abschnitt artifacts untergebracht. Dies benennt alle Dateien und Ordner, die über weitere Stages hinweg beibehalten werden sollen. Der Wert allow_failure ermöglicht die Pipeline (u. a. für das Cleanup) bei Fehlern nicht abzubrechen.

In der test Stage wird geprüft, ob Warnungen enthalten sind. Dies kennzeichnet Probleme beim Building. Werden Warnungen gefunden, wird die Pipeline abgebrochen.

Wird der Test erfolgreich durchlaufen, wird in der deploy Stage deployt. Hierzu werden die alten Dateien auf dem Webserver gelöscht. Anschließend erfolgt die Übertragung der neuen Dateien aus dem Build.

Abschließend wird in der cleanup Stage eine Bereinigung durchgeführt: Das Build Verzeichnis und die Logdatei werden gelöscht.

Die Bereinigung des Verzeichnisses behielten wir wie beim Skript aus Ist-Zustand bei, da bei einem fehlerhaften Build ein manuelles Eingreifen erforderlich wäre und die komplette Neuerstellung des Builds im Rahmen dieses Studienfachs maximal wenige Sekunden dauern wird.



Mit fast allen Funktionen könnte man die Pipeline auch in einer Stage darstellen.

In einer älteren Version sah die Konfigurationsdatei folgendermaßen aus:

stages:
  - deploy_production

variables:
  COMPOSE_DIR: .
  KEY: "/DVA/id_rsa_tha_dvagrp07"


deploy___tha.de/~dvagrp07:
    stage: deploy_production
    script:
      - cd ${COMPOSE_DIR}
      - ls -la ${COMPOSE_DIR}
      - sphinx-build -M html ./source ./build -W --keep-going
      - ls -la ./build
      - echo Options +Indexes > ./build/html/.htaccess
      - ssh dvagrp07@login.rz.hs-augsburg.de -i ${KEY} rm -rf /www/dvagrp07/*
      - scp -r -i ${KEY} ./build/html/* dvagrp07@login.rz.hs-augsburg.de:/www/dvagrp07/ 
      - rm -R ./build
      - echo "Check result on https://tha.de/~dvagrp07"
    only:
      - main
    tags:
      - shell

Zum weiteren Betrieb haben wir uns allerdings für die Implementierung mit mehreren Stages entschieden.

Die Unterteilung in mehrere Stages ist zudem leichter zu verstehen.