Architektur-Entscheidungen in Red Hat OpenShift Data Foundation: Internal vs. External Mode

(OpenShift, Storage, Ceph, Security, Architektur)

Wenn wir über stateful Workloads in Kubernetes sprechen, kommen wir an Red Hat OpenShift Data Foundation (ODF) kaum vorbei. Als Software-Defined-Storage-Lösung (basierend auf Ceph, Rook und NooBaa) bietet ODF die notwendige Persistenz für Container-Anwendungen.

Doch bevor das erste PVC (Persistent Volume Claim) erstellt wird, stehen Architekten vor einer entscheidenden Frage: Wie integrieren wir den Storage in den Cluster? Die Wahl der Deployment-Methode beeinflusst maßgeblich Skalierbarkeit, Kosten und Performance.

In diesem Beitrag werfen wir einen technischen Blick auf die zwei primären Betriebsmodi der Version 4.20 – Internal (Hyperconverged) und External – und bieten Ihnen eine fundierte Entscheidungshilfe.

1. Die ODF-Deployment-Modi im Detail

Red Hat ODF bietet Flexibilität, indem es entweder „im Bauch“ des OpenShift Clusters läuft oder eine externe Storage-Instanz konsumiert.

Internal Mode (Hyperconverged)

Im Hyperconverged-Modus laufen ODF-Storage-Pods direkt auf den OpenShift Worker Nodes. Compute (Anwendungen) und Storage (ODF) teilen sich dieselbe Infrastruktur.

  • Architektur: Storage Devices (NVMe/SSD) werden direkt an die Worker Nodes durchgereicht. Der ODF Operator verwaltet den Ceph-Cluster als Container-Workload.
  • Vorteil: Einfachste Installation über die OpenShift Console oder CLI. Ideal für eine schnelle Inbetriebnahme und Edge-Szenarien.

External Mode

Hier fungiert OpenShift lediglich als Client. Der ODF-Operator verbindet den OpenShift Cluster mit einem separaten Red Hat Ceph Storage (RHCS) Cluster, der auf dedizierter Hardware läuft.

  • Architektur: Strikte Trennung von Compute und Storage. OpenShift konsumiert Speicher über das Netzwerk (RBD/CephFS).
  • Vorteil: Unabhängigkeit und Entkopplung des Storage-Lifecycles vom OpenShift Cluster.

External Mode

Hier fungiert OpenShift lediglich als Client. Der ODF-Operator verbindet den OpenShift Cluster mit einem separaten Red Hat Ceph Storage (RHCS) Cluster, der auf dedizierter Hardware läuft.

  • Architektur: Strikte Trennung von Compute und Storage. OpenShift konsumiert Speicher über das Netzwerk (RBD/CephFS).
  • Vorteil: Unabhängigkeit und Entkopplung des Storage-Lifecycles vom OpenShift Cluster.

2. Entscheidungshilfe: Internal vs. External im direkten Vergleich

Welcher Modus ist der richtige? Diese Entscheidung hängt oft von der Größe der Umgebung, dem Budget und den Performance-Anforderungen ab. Hier ist ein direkter Vergleich, der Architekten bei der Planung unterstützt:

KriteriumInternal Mode (Hyperconverged)External Mode (Standalone Ceph)
ZielgruppeEdge, Dev/Test, kleine bis mittlere Produktions-Cluster.Enterprise-Datacenter, Multi-Cluster Umgebungen und Petabyte-Scale.
RessourcennutzungShared: ODF-Pods konkurrieren mit Applikationen um CPU/RAM.Dedicated: Storage läuft auf eigener Hardware. Keine Ressourcenkonkurrenz.
SkalierbarkeitSkaliert mit den OpenShift Nodes. Hoher Speicherbedarf kann höhere OpenShift-Lizenzkosten (Cores) verursachen.Unabhängig: Speicher kann hinzugefügt werden, ohne die OCP Compute-Nodes erweitern zu müssen.
Wartung & LifecycleSimpel: Ein Upgrade-Zyklus. ODF wird mit OpenShift aktualisiert.Entkoppelt: Ceph und OpenShift können unabhängig voneinander gewartet werden.
Multi-ClusterStorage ist meist an einen Cluster gebunden.Ein großer Ceph-Cluster kann Storage für mehrere OpenShift-Cluster bereitstellen.
Kosten (bei Scale)Geringere Initialkosten, aber potenziell höhere TCO (Total Cost of Ownership) pro TB bei sehr großen Datenmengen.Höhere Initialkosten (eigene Hardware), aber oft günstiger pro TB bei Skalierung.

3. Spezialthema Sicherheit: Data-at-Rest Encryption mit HashiCorp Vault

Für regulierte Umgebungen reicht es oft nicht aus, die Keys für die Data-at-Rest Encryption einfach in Kubernetes Secrets zu speichern. Hier kommt die Integration eines externen Key Management Systems (KMS) ins Spiel.

Die Integration mit HashiCorp Vault

ODF 4.x unterstützt die Nutzung eines externen KMS, um Schlüssel zentral und konform zu verwalten. HashiCorp Vault ist hierbei eine gängige und robuste Lösung.

Die Vorteile der KMS-Anbindung:

  • Trennung der Hoheit: Der Admin des OpenShift Clusters hat keinen direkten Zugriff auf den Master-Key.
  • Compliance & Auditierung: Zentrale Key-Rotation, Audit-Logs und detaillierte Zugriffskontrollen werden im KMS verwaltet.

Die Konfiguration erfolgt durch die Hinterlegung der Vault-Adresse und der notwendigen Authentifizierungsdetails (z.B. über die Kubernetes Authentication Method von Vault) direkt im StorageCluster Custom Resource Definition (CRD). ODF kann dann die Keys für die Geräteverschlüsselung (Device Encryption) oder die PV-Verschlüsselung sicher aus Vault beziehen.

Wichtiger Hinweis für Architekten: Bei der Nutzung von Vault muss dessen Hochverfügbarkeit (HA) gewährleistet sein. Ist das KMS nicht erreichbar, können verschlüsselte Pods nach einem Neustart unter Umständen nicht auf ihre Daten zugreifen.

4. Fazit und Unsere Empfehlung

Die Wahl zwischen Internal und External Mode ist eine strategische Entscheidung, die die zukünftige Entwicklung Ihrer OpenShift-Plattform bestimmt.

  • Wählen Sie den Internal Mode, wenn Agilität und eine einfache, konsolidierte Infrastruktur im Vordergrund stehen – ideal für kleinere bis mittlere Workloads und Edge-Szenarien.
  • Wählen Sie den External Mode, wenn Sie massive Skalierbarkeit, Performance-Garantien und die Notwendigkeit haben, Storage über mehrere OCP-Cluster hinweg zentral zu verwalten.

Die Migration zwischen diesen Modi ist komplex. Investieren Sie daher Zeit in die initiale Planung, idealerweise mit einem erfahrenen Partner, der Sie durch die spezifischen Anforderungen der ODF 4.20 Dokumentation leiten kann.


Benötigen Sie Unterstützung bei der Architekturplanung Ihrer OpenShift Data Foundation?

Als spezialisierter IT-Dienstleister helfen wir Ihnen, die optimale ODF-Architektur für Ihre Enterprise-Workloads zu entwerfen und umzusetzen – von Hyperconverged-Setups bis zur komplexen KMS-Integration mit HashiCorp Vault. Sprechen Sie uns an, um Ihre OpenShift-Strategie zu besprechen.

GitOps/DevOps Build Server – Schritt für Schritt

Wir erstellen einen Buildserver für einen GitOps Mechanismus, der auf einer eigenen virtuellen Maschine gehostet werden kann.

Zielsetzung

Am Ende möchten wir, nachdem ein Git Push in einen bestimmten Branch durchgeführt wurde, eine Automatische Pipeline auf einem Build Server ausgeführt haben, der anschließend die Applikation auf einem Zielsystem deployed.

Plan

Wie wollen wir das nun erreichen? Zunächst beschaffen wir uns einen installierten Ubuntu Server 20.04. In meinem Fall habe ich diesen auf einem lokalen System als virtuelle Maschine installiert. Denkbar sind aber auch sämtliche Hosting- oder Cloudanbieter, bei denen man Ubuntu Server beziehen kann. Beispiele wären Hetzner, JiffyBox, Contabo oder Microsoft Azure.

Auf die Installation oder Beschaffung eines entsprechenden Ubuntu Servers gehe ich jetzt nicht näher ein.

Wenn der Server steht, wird ein K3S installiert. Das ist eine Kubernetes Distribution, die speziell für den Einsatz auf IoT oder Edge Devices konzipiert ist. Da unser Server auch in Zukunft alleine bleiben möchte, eignet sich K3S ideal für den Anwendungsfall.

K3S Installation

Wenn der Ubuntu Server (oder nach belieben auch eine andere Distribution) bereit ist, kann die Installation vom K3S durchgeführt werden. Das ist tatsächlich sehr einfach und beschränkt sich im wesentlichen auf einen Bash Einzeiler:

curl -sfL https://get.k3s.io | sh -

Danach wird ein Bash Script ausgeführt, was uns alle nötigen Binaries herunterlädt und die Services anlegt.

Danach wurde ein SystemD Service „k3s“ erstellt, der alle weiteren Prozesse für uns startet.

Die Dokumentation zum K3S ist hier zu finden: https://rancher.com/docs/k3s/latest/en/installation/install-options/

Nun können wir mit „kubectl“ auf den Cluster zugreifen.

sudo kubectl get node

Die Cluster Config unter „/etc/rancher/k3s/k3s.yaml“ ist nur für root lesbar, deshalb müssen wir den kubectl Befehl mit sudo ausführen.

Tekton Installation

Für die Ausführung von Pipelines fällt meine Wahl auf Tekton. Das ist schön leichtgewichtig und bringt alles wichtige mit. Damit wir das auf den K3s Cluster installieren können, ist auch nur die Anwendung eines Kubernetes YAML nötig. Das sieht folgendermaßen aus.

kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

Danach werden alle Kubernetes Ressourcen für Tekton im Cluster angelegt. Weiterhin wird ein neuer Namespace „tekton-pipelines“ erstellt, in dem die Ressourcen abgelegt werden.

Die Dokumentation dazu ist hier zu finden: https://tekton.dev/docs/pipelines/install/

Wer Lust hat und einen besseren Überblick über die Tekton Pipelines haben möchte, kann anschließend noch das Tekton Dashboard installieren. Das funktioniert ähnlich wie die Installation der Tekton Pipelines.

kubectl apply --filename \
https://storage.googleapis.com/tekton-releases/dashboard/latest/tekton-dashboard-release.yaml

Danach werden die entsprechenden Ressourcen im Cluster erstellt.

Danach wurde eine Service erstellt, der das Tekton Dashboard bereitstellt.

Da dieser allerdings nur als ClusterIP erstellt wurde, erstellen wir jetzt noch einen NodePort Service, der den gleichen Selector wie der ClusterIP Service bereitstellt. Dazu erstellen wir folgendes YAML File.

apiVersion: v1
kind: Service
metadata:
  labels:
    app: tkn-dashboard
  name: tkn-dashboard
  namespace: tekton-pipelines
spec:
  ports:
  - name: "9097"
    port: 9097
    protocol: TCP
    targetPort: 9097
  selector:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/name: dashboard
    app.kubernetes.io/part-of: tekton-dashboard
  type: NodePort

Wir speichern die Datei unter dem Namen „tkn-dashboard.yaml“ und wenden es mit diesem Befehl an:

kubectl -n tekton-pipelines create -f tkn-dashboard.yaml

Danach ist unser Service im Namespace „tekton-pipelines“ zu sehen:

So können wir nun auf das Dashboard per Browser zugreifen, indem wir unseren Server auf dem Port „30057“ aufrufen. Der Port kann variieren, da wir diesen in der Erstellung nicht festgelegt haben.

ArgoCD Installation

Nachdem nun unser CI Tool installiert ist, wäre es sinnvoll noch ein CD Tool zu installieren. Meine Wahl fällt auf ArgoCD, das sich dies gut mit Tekton kombinieren lässt und ich einfach schon die meiste Erfahrung damit habe.

Die offizielle Installationsanleitung könnt ihr hier finden: https://argo-cd.readthedocs.io/en/stable/getting_started/

Die Installation ist denkbar einfach, ähnlich wie bei Tekton muss ein YAML File auf dem Kubernetes Cluster angewendet werden:

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml

Danach werden alle nötigen Ressourcen im Cluster installiert.

Nun ist ArgoCD ebenfalls vorerst nur als ClusterIP zu erreichen. Also müssen wir zum erreichen des Dashboards auch hier einen NodePort Service erstellen. Die Definition sieht so aus:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: argocd-dashboard
  name: argocd-dashboard
  namespace: argocd
spec:
  ports:
  - name: "8080"
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: argocd-server
  type: NodePort

Die Datei nennen wir „argocd-dashboard.yaml“. Danach wird der NodePort Service mit folgenden Befehl erstellt:

kubectl -n argocd create -f argocd-dashboard.yaml

Wenn dieser erstellt wurde, können wir mit „kubectl -n argocd get svc“ den Status abfragen.

Nun können wir, in unserem Fall, über http://buildserver:32380/ auf das Dashboard zugreifen.

Um an die Zugangsdaten für den „admin“ Zugang im ArgoCD heranzukommen, geben wir folgenden Befehl ein:

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo

Danach können wir uns im ArgoCD anmelden.

Nun wird es Zeit für ein praktisches Beispiel.

Die Applikation

Um eine Pipeline zu testen, habe ich eine Blazor Server Applikation, basierend auf dotnet 6 erstellt. Diese beinhaltet ein Dockerfile, was für die Erstellung eines Dockercontainers benutzt werden soll. Dieser soll dann anschließend auf unserem Kuberenetes Cluster deployed werden. ArgoCD könnte dabei auch einen anderen Kubernetes Cluster als Ziel für das Applikationsdeployment ansteuern. Der Einfachheit halber wird das aber auf dem vorhandenen K3S Cluster durchgeführt.

Um über ArgoCD eine Pipeline erstellen zu können, müssen wir als erstes die Pipeline für den Docker Build entwerfen. Dafür werde ich in dem Repository meiner Applikation einen Unterordner „tekton“ erstellen, in dem alle nötigen Pipeline Files liegen werden.

Eine gute Anleitung für den Start ist hier zu finden: https://tekton.dev/docs/how-to-guides/kaniko-build-push/

Mein Test Repository ist hier zu finden: https://github.com/saxonydevops/BlazorApp1

Die Build Pipeline

Um eine Tekton Pipeline laufen zu lassen, die einen Docker Container erstellt und diesen in ein Repository pusht, benötigen wir verschiedene Kubernetes Objekte.

  • Pipeline
  • PipelineRun
  • PersistentVolumeClaim
  • Secret

Die Pipeline sieht so aus:

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: clone-build-push
spec:
  description: | 
    This pipeline clones a git repo, builds a Docker image with Kaniko and
    pushes it to a registry
  params:
  - name: repo-url
    type: string
  workspaces:
  - name: shared-data
  tasks:
  - name: fetch-source
    taskRef:
      name: git-clone
    workspaces:
    - name: output
      workspace: shared-data
    params:
    - name: url
      value: $(params.repo-url)

Der PipelineRun so:

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: clone-build-push-run
spec:
  pipelineRef:
    name: clone-build-push
  podTemplate:
    securityContext:
      fsGroup: 65532
  workspaces:
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
  params:
  - name: repo-url
    value: https://github.com/saxonydevops/BlazorApp1.git

Damit der Push in unser Docker Hub Repository gelingt, müssen wir ein Secret mit den Zugangsdaten im Namespace anlegen, in dem unsere Pipeline ausgeführt wird.

Weiteres folgt in Teil 2….

Ansible Runner as Docker Container

After a long time it’s time for an article again 🙂

First of all, we are in a Windows client environment, but we want to manage Linux servers and applications on them with Ansible. We can run Docker Desktop on the Windows client, which runs with the WSL2 backend.

Now you might say „Well, install a Linux distribution via WSL2 and run Ansible through it!“. But I don’t want so much overhead on the Windows client. Besides, I can take my Ansible runner later and run it on any other machine with Docker.

We can do that afterwards:

  • Running Ansible playbooks using a local Docker container
  • Loading Ansible Roles for Execution
  • Testing Ansible Playbooks

Let’s go!

Image preparation

First, we need to build a Docker image that we can use to run Ansible. This can look like this:

FROM ubuntu:22.04

ENV container=docker
ENV pip_packages "ansible"
ENV ANSIBLE_HOST_KEY_CHECKING "False"

# Update System
RUN apt -y update && apt -y upgrade

# Install requirements.
RUN apt -y install python3 python3-pip openssh-client

# Upgrade Pip
RUN pip3 install --upgrade pip

# Install Ansible via Pip.
RUN pip3 install $pip_packages

CMD ["/bin/bash"]

I use the current Ubuntu LTS version as a base image. Then a system update and the installation of the Python packages is carried out via APT. Then pip3 is updated again, if necessary. Then the packages from the environment variable „pip_packages“ are installed. In this case it is only „ansible“. For test purposes, additional packages can be specified here and packed into another image.

Creating the image

Now the Docker image can be created. To do this, we execute the following command in the folder in which the „Dockerfile“ is also located:

docker build -t dkr-ansible-runner:0.1 .

This creates the image.

The image can then be started using the following command:

docker run -it dkr-ansible-runner:0.1 /bin/bash

With this, we go into an interactive session in the container and start a bash. This way we can test commands for the first time. Later we will load a corresponding volume with our playbook and possibly roles, as well as inventory and SSH key files and test the performance on a server in the network.

Playbook and Inventory

To perform a run with „ansible-playbook“, we need a playbook and an inventory definition. Furthermore, we should have distributed the SSH keys for the login.

Under Windows, the tool „puttygen“ can be used to generate an SSH key pair. The file can be obtained here:

https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

In order for Ansible to be usable without further problems, the passphrase should be omitted at this point.

Afterwards, the key, Linux compatible, can be saved via „Conversions“ -> „Export OpenSSH Key“.

We create a folder on our Windows machine where we store the Ansible playbook, the inventory and the SSH key file. In my case, this is „D:\DEV\ansibletest-playbook“.

The inventory file consists of two lines:

[all]
192.168.0.123  ansible_ssh_private_key_file=/opt/test-playbook/id_rsa

In the first line, the host group is specified, in this case „[all]“. In the second line, the IP address of the host is specified and then a host variable is defined. In this variable I define where the SSH private key is located that I need for the SSH login to the server. We have just created the key. Later, when starting the container, this key is mounted on „/opt/test-playbook/“.

The playbook looks like this:

---
- hosts: all
  remote_user: root

  tasks:
    - name: Ansible Example Ping
      ansible.builtin.ping:

Here it is said that we want to execute the task „ping“ on the host group „all“, as user „root“. This is included in Ansible and is intended to test the connection to the target host.

Now we have all the files together to do a test run.

Running the Playbook

To run the playbook now, we need to start the created container with the appropriate parameters and a command line.

In my example, it looks like this:

docker run --rm -v /d/DEV/ansible/test-playbook:/opt/test-playbook dkr-ansible-runner:0.1 /bin/bash -c "ansible-playbook -i /opt/test-playbook/inventory.ini /opt/test-playbook/play.yml"

I define with „–rm“ that I want to delete the container after the run. Then I use „-v“ to say which local folder I want to mount on which folder in the container. Since I am on Windows, I have to specify the local folder in the syntax „/drive/folder/“. Then the image I created is specified. Afterwards, a command can be specified which is to be started with the container. In my case, I want to start an „ansible-playbook“ here. For reasons of better readability and compatibility, I run this in a Bash session.

So now we can run all the playbooks or roles we want, if we just put them in the right folder!

Ansible Runner auf Docker Basis

Nach langem ist es mal wieder Zeit für einen Artikel 🙂

Zunächst einmal zur Ausgangslage: Wir befinden uns in einem Windows Client Umfeld, möchten aber Linux Server und Applikationen darauf mit Ansible verwalten. Wir können Docker Desktop auf dem Windows Client ausführen, was mit dem WSL2 Backend läuft.

Jetzt könnte man sagen „Naja, dann installiere doch eine Linux Distribution über die WSL2 und führe Ansible darüber aus!“. Aber ich möchte nicht so viel Overhead auf dem Windows Client. Außerdem kann ich später meinen Ansible Runner nehmen und auf jeder anderen Maschine mit Docker ausführen.

Das können wir anschließend durchführen:

  • Ausführen von Ansible Playbooks mit Hilfe eines lokalen Docker Containers
  • Laden von Ansible Roles für die Ausführung
  • Testen von Ansible Playbooks

Los geht’s!

Vorbereitung des Images

Zunächst müssen wir uns ein Docker Image bauen, mit dem wir Ansible ausführen können. Das kann folgendermaßen aussehen:

FROM ubuntu:22.04

ENV container=docker
ENV pip_packages "ansible"
ENV ANSIBLE_HOST_KEY_CHECKING "False"

# Update System
RUN apt -y update && apt -y upgrade

# Install requirements.
RUN apt -y install python3 python3-pip openssh-client

# Upgrade Pip
RUN pip3 install --upgrade pip

# Install Ansible via Pip.
RUN pip3 install $pip_packages

CMD ["/bin/bash"]

Ich verwende die aktuelle Ubuntu LTS Version als Basisimage. Danach wird ein Systemupdate und die Installation der Python Pakete über APT durchgeführt. Anschließend wird pip3 noch einmal aktualisiert, falls nötig. Danach werden die Pakete aus der Environment Variable „pip_packages“ installiert. In dem Fall ist es nur „ansible“. Für Testzwecke können hier noch weitere Pakete angegeben werden und in ein weiteres Image gepackt werden.

Erstellen des Images

Jetzt kann das Docker Image erstellt werden. Dazu führen wir im Ordner in dem sich auch das „Dockerfile“ befindet, folgenden Befehl aus:

docker build -t dkr-ansible-runner:0.1 .

Damit wird das Image erstellt.

Anschließend kann das Image über folgenden Befehl gestartet werden:

docker run -it dkr-ansible-runner:0.1 /bin/bash

Damit gehen wir in eine interaktive Sitzung in den Container und starten eine Bash. So können wir Befehle erste einmal testen. Später werden wir ein entsprechendes Volume mit unseren Playbook und ggf. Roles, sowie Inventory und SSH Key Dateien laden und die Auführung auf einem Server im Netzwerk testen.

Playbook und Inventory

Um einen Lauf mit „ansible-playbook“ durchzuführen, benötigen wir ein Playbook und eine Inventory Definition. Weiterhin sollten wir für die Anmeldung die SSH Keys verteilt haben.

Unter Windows kann für die Generierung eines SSH Key Pairs das Tool „puttygen“ verwendet werden. Die Datei kann hier bezogen werden:

https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

Damit Ansible ohne weitere Probleme nutzbar ist, sollte auf die Passphrase an dieser Stelle verzichtet werden.

Danach kann der Key, Linux kompatibel über „Conversions“ -> „Export OpenSSH Key“ gespeichert werden.

Wir legen auf unserer Windows Maschine einen Ordner an, in dem wir das Ansible Playbook, das Inventory und das SSH Key File speichern. In meinem Fall ist das „D:\DEV\ansible\test-playbook“.

Das Inventory File besteht aus zwei Zeilen:

[all]
192.168.0.123  ansible_ssh_private_key_file=/opt/test-playbook/id_rsa

In der ersten Zeile wird die Hostgruppe angegeben, in diesem Fall „[all]“. In der zweiten Zeile wird die IP Adresse des Hosts angegeben und danach wird eine Host Variable definiert. In dieser lege ich fest, wo sich der SSH Private Key befindet, den ich für die SSH Anmeldung am Server benötige. Den Key haben wir gerade erzeugt. Später beim Containerstart wird dieser Key über einen Mount auf „/opt/test-playbook/“ eingehangen.

Das Playbook sieht folgendermaßen aus:

---
- hosts: all
  remote_user: root

  tasks:
    - name: Ansible Example Ping
      ansible.builtin.ping:

Hier wird gesagt, dass wir auf die Hostgruppe „all“, als User „root“ die Aufgabe „ping“ ausführen möchten. Diese ist im Ansible enthalten und ist dafür gedacht um die Verbindung zum Zielhost zu testen.

Nun haben wir alle Dateien zusammen um einen Testlauf durchzuführen.

Ausführen des Playbooks

Um das Playbook nun auszuführen, müssen wir den erstellten Container mit den entsprechenden Paramtern und einer Befehlszeile starten.

Das sieht in meinem Beispiel so aus:

docker run --rm -v /d/DEV/ansible/test-playbook:/opt/test-playbook dkr-ansible-runner:0.1 /bin/bash -c "ansible-playbook -i /opt/test-playbook/inventory.ini /opt/test-playbook/play.yml"

Ich definiere mit „–rm“ das ich den Container nach erfolgtem Durchlauf wieder löschen möchte. Danach sage ich mit „-v“, welchen lokalen Ordner ich auf welchen Ordner im Container mounten möchte. Da ich mich auf Windows befinde, muss ich den lokalen Ordner in der Syntax „/laufwerk/ordner/“ angeben. Danach wird das von mir erstellte Image angegeben. Anschließend kann ein Kommando angegeben werden, was mit dem Container gestartet werden soll. In meinem Fall möchte ich hier ein „ansible-playbook“ starten. Ich führe das aus Gründen der besseren Lesbarkeit und Kompatibilität in einer Bash Sitzung aus.

Somit können wir nun alle Playbooks oder Roles ausführen, die wir möchten, wenn wir sie nur in den richtigen Ordner legen!

SQL Server Filegroup Full or not?

Today an article from the everyday life of a SQL Server DBA 😉

It was reported by the customer that the affected application showed an error message that read „File group ‚PRIMARY‘ is full“. Thereupon the first remote diagnosis from the DBA was first simple and I wanted to check the Max Size of the database files.

This was not so easy, because the database was switched offline by the customer. So there was the process of offline switching, which was apparently stopped by further transactions on the database. This made the SQL Server Management Studio impossible to use and it was not possible to perform any further meaningful diagnostics on the database.

Background on the SQL Server environment:

  • 2x Windows Server 2016 virtual machine
  • Windows Server Failover Cluster
  • SQL Server 2016 Failover Cluster environment

To shorten the waiting time a bit, since it was a production system, I decided to failover the SQL Server instance. This causes a restart of the SQL Services and so the hope, a normal interaction with the database service again.

The service restart naturally led to the recovery process of the SQL Server service on all databases that had an unclean termination status. Since the databases were of a certain size, the process initially took about 25 minutes.

During the recovery process, I kept noticing Windows Event Viewer (System) messages:

The device, DeviceHarddisk10DR10, ist not ready for access yet.

This led to the assumption that there might be something wrong with the hard disk subsystem and that the SQL Server itself is not to blame for the problem.

After the instance simply swung back to the first node after 25 minutes, and the database recovery started all over again, this suspicion was confirmed.

The service now started correctly, but a subset of the databases were still in recovery mode. This took another 40 minutes. During this time, the SQL Server Error Log kept displaying the following error messages:

SQL Server has encountered 4 occurence(s) of I/O requests taking longer than 15 seconds to complete on file [...] in database id ...

This suggests that the hard disk subsystem or the network components concerned have a problem, since a shared storage system (SAN) is used here.

Due to the sometimes very slow responding storage layer, some databases were unfortunately in „recovery pending“ status.

To clean this up again, the following T-SQL commands were executed:

USE master
GO

ALTER DATABASE <<dbname>> SET EMERGENCY
GO

ALTER DATABASE <<dbname>> SET SINGLE_USER
GO

DBCC CHECKDB(N'<<dbname>>',REPAIR_ALLOW_DATA_LOSS) WITH NO_INFOMSGS,ALL_ERRORMSGS
GO

If the repair is successful, the database can be switched back to multi-user mode.

ALTER DATABASE <<dbname>> SET MULTI_USER
GO

Unfortunately the error could not be clarified conclusively yet, if I know something new I would update this further.

SQL Server Filegroup Full?

Heute ein Beitrag aus dem Alltag eines SQL Server DBA’s 😉

Gemeldet wurde vom Kunden, dass die betroffene Anwendung eine Fehlermeldung gezeigt hat, die „Dateigruppe ‚PRIMARY‘ ist voll“ lautet. Daraufhin war die erste Ferndiagnose vom DBA erst einmal einfach und ich wollte die Max Size der Datenbankdateien überprüfen.

Das war nun gar nicht so einfach, da die Datenbank vom Kunden gerade offline geschalten wurde. Also gab es den Prozess der Offline Schaltung, die anscheinen durch weitere Transactions auf die Datenbank aufgehalten wurde. Dadurch war die Bedienung durch das SQL Server Management Studio unmöglich und man konnte auch keine weiteren sinnvollen Diagnosen auf der Datenbank durchführen.

Hintergrund zur SQL Server Umgebung:

  • 2x virtuelle Windows Server 2016 Maschine
  • Windows Server Failover Cluster
  • SQL Server 2016 Failover Cluster Umgebung

Damit die Wartezeit etwas verkürzt wird, da es sich ja um ein produktives System handelte, habe ich mich zum Schwenk der SQL Server Instanz entschieden. Das bewirkt einen Neustart der SQL Services und so die Hoffnung, ein wieder normales Interagieren mit den Datenbankdienst.

Der Dienstneustart führte natürlich zum Recovery Prozess den SQL Server Dienstes auf allen Datenbanken, die einen unsauberen Beendigungsstatus hatten. Da die Datenbanken eine gewisse Größe hatten, dauerte der Vorgang zunächst ca. 25 Minuten.

Im Verlauf des Recovery Prozesses sind mir immer wieder Meldungen Windows Event Viewer (System) aufgefallen:

The device, \Device\Harddisk10\DR10, ist not ready for access yet.

Das führte zur Vermutung, dass eventuell etwas mit dem Festplatten Subsystem nicht in Ordnung ist und der SQL Server selbst keine Schuld an dem Problem trägt.

Nachdem die Instanz nach 25 Minuten einfach wieder auf den ersten Knoten zurück geschwenkt ist, und die Datenbank Recovery wieder von vorn begonnen wurde, konnte dieser Verdacht bestätigt werden.

Der Dienst ist nun korrekt gestartet, eine Teilmenge der Datenbanken war aber weiterhin im Recovery Modus. Das hat weitere 40 Minute in Anspruch genommen. Im SQL Server Error Log waren während dessen immer wieder folgende Fehlermeldungen zu sehen:

SQL Server has encountered 4 occurence(s) of I/O requests taking longer than 15 seconds to complete on file [...] in database id ...

Das legt die Vermutung nahe, dass das Festplatten Subsystem, oder die betreffenden Netzwerkkomponenten ein Problem haben, da hier mit einem geteilten Speichersystem (SAN) gearbeitet wird.

Durch die teilweise sehr langsam reagierende Storage Schicht, waren einige Datenbanken leider im „Recovery pending“ Status.

Um dies wieder zu bereinigen, wurden folgende T-SQL Befehle ausgeführt:

USE master
GO

ALTER DATABASE <<dbname>> SET EMERGENCY
GO

ALTER DATABASE <<dbname>> SET SINGLE_USER
GO

DBCC CHECKDB(N'<<dbname>>',REPAIR_ALLOW_DATA_LOSS) WITH NO_INFOMSGS,ALL_ERRORMSGS
GO

Ist die Reparatur erfolgreich, kann die Datenbank wieder in den Multi User Modus geschalten werden.

ALTER DATABASE <<dbname>> SET MULTI_USER
GO

Leider konnte der Fehler noch nicht abschließend geklärt werden, falls ich etwas neues weiß, würde ich das hier weiter aktualisieren.

Kubernetes / OpenShift Error „layer not known“

Today there was again a not immediately understandable error in the Kubernetes cluster. I have an OKD (OpenShift) cluster 4.5 with 3 masters and 2 workers running here.

After a number of nodes became unavailable, I rebooted the entire cluster once. Unfortunately, the initiated reboot took a long time for some nodes. Therefore, I restarted individual nodes via hard reset without further ado.

After all nodes were restarted and in „Ready“ status, one of the nodes could not start pods :-/

So it was off to troubleshoot on the node and in the cluster. First, I look at which pods cannot be started:

oc get pod --all-namespaces -o wide

And see that all cannot be started on the same host. So look a little closer at the pod:

oc describe pod -n <namespacevompod> <podname>

And see here the following error:

Failed to create pod sandbox: rpc error: code = Unknown desc = error creating pod sandbox with name "XXXX": layer not known

That doesn’t get me much further now. So I go via SSH to the node. Here I check if all services are running

systemctl

And see that the service „nodeip-configuration.service“ is failed. So take a closer look here:

systemctl status nodeip-configuration.service

And here the following error message appears:

podman[11387]: Error: error creating container storage: layer not known

Now it is at least clearer which „layer“ is meant here. So it should be a storage layer. So I asked Google again and found an entry:

https://bugzilla.redhat.com/show_bug.cgi?id=1857224

Ok. Apparently the node took the hard reset badly and everything is no longer clean under „/var/lib/containers“. So I follow the recommendation:

systemctl stop kubelet
systemctl stop crio
rm -rf /var/lib/containers/
systemctl start crio
systemctl start kubelet

And voila, the pods on the node can start again!

Autodiscover and Mailcow

If you use Mailcow as an email solution, you may encounter the problem that Outlook clients cannot resolve the autodiscover correctly. This can have several causes, I will describe one of them here.

An important point when using Outlook clients with Mailcow is that unfortunately not all versions and editions are supported. We have tested and confirmed that Outlook 2016 does not currently work with Mailcow.

If Autodiscover is configured correctly from the DNS point of view (can be checked nicely via the „DNS?“ button in the Mailcow Admin Console), it could still be that the Outlook client only wants to configure an IMAP account and not an Active Sync account.

Since only in some Outlook versions the „OLCFG.exe“ described in the documentation is still available, unfortunately Autodiscover must work here.

The trick here is a certain entry in the data/web/inc/vars.local.inc.php in the mailcow-dockerized folder. If you go to the $autodiscover_config section in this file, you will find an entry ‚useEASforOutlook‘.

This should be set to ‚yes‘, then the configuration with Autodiscover as Active Sync mailbox with Outlook works. The entry ‚autodiscoverType‘ should of course also be set to ‚activesync‘. Here the complete section from the documentation:

$autodiscover_config = array(
  // General autodiscover service type: "activesync" or "imap"
  // emClient uses autodiscover, but does not support ActiveSync. mailcow excludes emClient from ActiveSync.
  'autodiscoverType' => 'activesync',
  // If autodiscoverType => activesync, also use ActiveSync (EAS) for Outlook desktop clients (>= Outlook 2013 on Windows)
  // Outlook for Mac does not support ActiveSync
  'useEASforOutlook' => 'yes',
  // Please don't use STARTTLS-enabled service ports in the "port" variable.
  // The autodiscover service will always point to SMTPS and IMAPS (TLS-wrapped services).
  // The autoconfig service will additionally announce the STARTTLS-enabled ports, specified in the "tlsport" variable.
  'imap' => array(
    'server' => $mailcow_hostname,
    'port' => array_pop(explode(':', getenv('IMAPS_PORT'))),
    'tlsport' => array_pop(explode(':', getenv('IMAP_PORT'))),
  ),
  'pop3' => array(
    'server' => $mailcow_hostname,
    'port' => array_pop(explode(':', getenv('POPS_PORT'))),
    'tlsport' => array_pop(explode(':', getenv('POP_PORT'))),
  ),
  'smtp' => array(
    'server' => $mailcow_hostname,
    'port' => array_pop(explode(':', getenv('SMTPS_PORT'))),
    'tlsport' => array_pop(explode(':', getenv('SUBMISSION_PORT'))),
  ),
  'activesync' => array(
    'url' => 'https://'.$mailcow_hostname.($https_port == 443 ? '' : ':'.$https_port).'/Microsoft-Server-ActiveSync',
  ),
  'caldav' => array(
    'server' => $mailcow_hostname,
    'port' => $https_port,
  ),
  'carddav' => array(
    'server' => $mailcow_hostname,
    'port' => $https_port,
  ),
);

Docker Nextcloud reset admin password

The time had come again. A Docker Nextcloud environment was created, but unfortunately the admin password can no longer be found. Fortunately, there is the „occ“ tool for Nextcloud, with which the password can be reset. This can be done on normal Nextcloud installations with the following command:

sudo -u www-data php /var/www/nextcloud/occ user:resetpassword admin

Unfortunately, this command does not work in the official Nextcloud Docker container, because there is no „sudo“ installed. However, this is not a major problem. With an adapted „docker exec“ the user change can also be achieved:

docker exec -it -u www-data <<containername>> php /var/www/html/occ user:resetpassword admin

After that, enter a new password twice and you can access Nextcloud again.

Kubernetes YAML Templates with kubectl

Writing YAML files for kubernetes can be tedious and annoying. However, in many cases „kubectl“ can help to quickly and easily create a template for a specific YAML file.

„kubectl“ is the central management tool for a Kubernetes cluster. Besides querying and creating cluster information via the kubernetes API, kubectl can also generate YAML of the given commands.

Currently the following resources can be created with „kubectl create“:

  • clusterrole
  • clusterrolebinding
  • configmap
  • cronjob
  • deployment
  • job
  • namespace
  • poddisruptionbudget
  • priorityclass
  • quota
  • role
  • rolebinding
  • secret
  • secret docker-registry
  • secret generic
  • secret tls
  • service
  • service clusterip
  • service externalname
  • service loadbalancer
  • service nodeport
  • serviceaccount

A simple example is to create a YAML file to generate a deployment:

kubectl create deployment test-deployment --image=nginx --dry-run=client -o yaml > test-deployment.yaml

The result then looks like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: test-deployment
  name: test-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-deployment
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: test-deployment
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

In the YAML file, further parameters can be added or adjusted accordingly. For example, the number of replicas could be increased to 3. To apply the whole thing to the kubernetes cluster, the file is called with the following command:

kubectl create -f test-deployment.yaml

After that, the pods should generate themselves accordingly in the cluster.