# Developer Good Practice

Plattformweite Grundsätze und Konventionen: Stages, Namensschema, Netz/Edge, Security, Arbeitsweise, Glossar.

# Schnelleinstieg

# Schnelleinstieg

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Dieses Buch sammelt die Grundsätze, die für alle Technologien und Teams der DEXPRO-Plattform gelten: Begriffe, Namen, Stages, Deployment, Netz, Secrets und Arbeitsweise. Die einzelnen Technologien haben eigene Bücher, die hierher verlinken.

## Als Anwender möchte ich …

- … einen Begriff oder eine Abkürzung nachschlagen → [Glossar](https://docs.squeeze.one/books/developer-good-practice/page/glossar)
- … wissen, zu welcher Abteilung und welchem Team mein Projekt gehört → [Abteilungen, GitLab-Gruppen und Teams](https://docs.squeeze.one/books/developer-good-practice/page/abteilungen-gitlab-gruppen-und-teams)
- … wissen, wie Gruppen, Rollen und Pfade heißen → [Abgeleitete Namen](https://docs.squeeze.one/books/developer-good-practice/page/abgeleitete-namen)
- … die Stages und ihre Freigaben verstehen → [Stages und Branches](https://docs.squeeze.one/books/developer-good-practice/page/stages-und-branches)
- … wissen, was im Lab, in DEV und in Produktion läuft → [Lab, DEV und Produktion](https://docs.squeeze.one/books/developer-good-practice/page/lab-dev-und-produktion)
- … ein Deployment-Repo aufsetzen → [Deployment-Repo mit der CI-Component](https://docs.squeeze.one/books/developer-good-practice/page/deployment-repo-mit-der-ci-component)
- … wissen, was ein Merge auslöst → [Merge heißt Deploy](https://docs.squeeze.one/books/developer-good-practice/page/merge-heisst-deploy)
- … ein neues Repo anlegen → [Repo-Struktur](https://docs.squeeze.one/books/developer-good-practice/page/repo-struktur)
- … eine Oberfläche erreichbar machen → [Edge-Grundsatz](https://docs.squeeze.one/books/developer-good-practice/page/edge-grundsatz)
- … wissen, ob mein Dienst über den Edge oder den internen Ingress läuft → [Dienst veröffentlichen: Edge oder interner Ingress](https://docs.squeeze.one/books/developer-good-practice/page/dienst-veroffentlichen-edge-oder-interner-ingress)
- … eine Firewall-Freigabe beantragen → [Firewall-Freigaben anfragen](https://docs.squeeze.one/books/developer-good-practice/page/firewall-freigaben-anfragen)
- … mit Secrets richtig umgehen → [Secrets-Grundsätze](https://docs.squeeze.one/books/developer-good-practice/page/secrets-grundsatze)
- … wissen, wo ein Secret hingehört → [Wohin mit Secrets](https://docs.squeeze.one/books/developer-good-practice/page/wohin-mit-secrets)

## Als Betreuer möchte ich …

- … Rollen und Module versionieren → [Rollen und Module versionieren](https://docs.squeeze.one/books/developer-good-practice/page/rollen-und-module-versionieren)
- … sicher testen → [Testen ohne Risiko](https://docs.squeeze.one/books/developer-good-practice/page/testen-ohne-risiko)
- … wissen, was eine Freigabe braucht → [Freigaben](https://docs.squeeze.one/books/developer-good-practice/page/freigaben)

## Weitere Bücher

- [OpenBao](https://docs.squeeze.one/books/openbao/page/schnelleinstieg): Secret-Store und Zertifizierungsstelle
- [Consul](https://docs.squeeze.one/books/consul/page/schnelleinstieg): Service-Katalog, KV und Service Mesh
- [Traefik](https://docs.squeeze.one/books/traefik/page/schnelleinstieg): Edge-Router und interner Ingress

# Grundbegriffe

# Glossar

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Begriffe und Abkürzungen, die in der Plattform-Dokumentation immer wieder vorkommen. Andere Seiten verlinken hierher, statt sie jedes Mal zu erklären.

## Abteilungen und Produkte

| Begriff | Bedeutung |
|---|---|
| CCI | Cloud Core Infrastructure: Basis-Infrastruktur (VMs, Netz, Consul, OpenBao, Datenbanken) |
| CPI | Container Platform Infrastructure |
| CPCS | Container Platform Core Services: gemeinsame Dienste auf der Container-Plattform |
| CPDS | Container Platform DEXPRO Services: DEXPRO-Produkte auf der Plattform |
| SBF | Squeeze, Breeze, Freeze: der DEXPRO-Produkt-Stack |
| SQUEEZE | DEXPRO-Produkt SQUEEZE |
| BREEZE | Hosting von Otris DOCUMENTS / documentsOS |
| FREEZE | Hosting von Otris EAS/EDA (Archiv) |

## Umgebungen

| Begriff | Bedeutung |
|---|---|
| Stage | Ausbaustufe eines Deployments: `pre-development`, `development`, `staging`, `production` (siehe [Stages und Branches](https://docs.squeeze.one/books/developer-good-practice/page/stages-und-branches)) |
| Lab | Testumgebung für den Plattformbetrieb am Standort Bremen 1. Nicht für Abteilungen gedacht |
| Lab-PoC | Proof of Concept im Lab: funktioniert dort, ist aber noch nicht für DEV oder Produktion freigegeben |
| DEV | Entwicklungsumgebung, die Abteilungen nutzen |
| Standort | Rechenzentrum, z. B. `bremen1`, `nuernberg1`, `frankfurt1`; in Hostnamen abgekürzt (`bre1`) |

## Bausteine

| Begriff | Bedeutung |
|---|---|
| Stack | Deployment-Repo, das eine konkrete Umgebung baut, z. B. `bremen1/consul-servers` |
| Modul | wiederverwendbares Terraform-Modul, z. B. `terraform-modules/proxmox/consul` |
| Rolle | Ansible-Rolle, die ein Modul auf den Hosts ausführt |
| CI-Component | gemeinsame GitLab-Pipeline für alle Deployment-Repos (`ci-components/deployment`) |
| Promotion | Weitergabe eines Commits von einer Stage zur nächsten |
| Edge | Eingangstor von außen (Load Balancer/Reverse Proxy). Oberflächen für Menschen laufen darüber, interne Dienste nie |

## Werkzeuge

| Begriff | Bedeutung |
|---|---|
| OpenBao | Secret-Store und Zertifizierungsstelle der Plattform (siehe [Buch OpenBao](https://docs.squeeze.one/books/openbao/page/schnelleinstieg)) |
| Bitwarden | Passwort-Tresor. Für Secrets nur noch Übergang bzw. Bootstrap |
| GitLab-JWT | signierter Ausweis einer Pipeline, mit dem sie sich ohne gespeichertes Passwort anmeldet |
| OIDC / Entra ID | Anmeldung von Menschen mit dem DEXPRO-Konto |
| SG | Security Group: Sicherheitsgruppe in Entra ID, über die Rechte vergeben werden. Muster `SG-<ABT>-<Team>` |
| Consul | Service-Discovery und Konfigurationsspeicher der Plattform |
| Nomad | Orchestrierung der Container-Plattform |

# Abteilungen, GitLab-Gruppen und Teams

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Die Plattform ist in vier Abteilungen gegliedert. Jede Abteilung ist eine GitLab-Gruppe, und ihr Kürzel taucht überall wieder auf: in OpenBao, in Entra-Gruppen und in Nomad. Wer weiß, zu welcher Abteilung und welchem Team ein Projekt gehört, kann die meisten Namen ableiten.

## Abteilungen

| Kürzel | GitLab-Gruppe | Teams |
|---|---|---|
| `cci` | `cloud-core-infrastructure` | ein Team (gegliedert nach Standort und Projekt) |
| `cpi` | `container-platform-infrastructure` | laut Teamliste |
| `cpcs` | `container-platform-core-services` | laut Teamliste |
| `cpds` | `container-platform-dexpro-services` | `ai`, `breeze`, `freeze`, `peppol`, `squeeze` |

Das Kürzel ist immer der letzte Teil des GitLab-Gruppenpfads in Kleinbuchstaben.

## Teams

- Ein Team ist eine Untergruppe der Abteilung in GitLab.
- Die verbindliche Liste aller Teams ist eine Teamliste (`teams.yaml`). Aus ihr entstehen Rechte und Namen automatisch.
- `shared` ist als Teamname reserviert. Es steht für „die ganze Abteilung“.
- CCI ist ein einziges Team. Statt nach Teams gliedert CCI nach Standort (z. B. `bremen1`) und Projekt.

<p class="callout warning">Ein Standort wie <code>bremen1</code> ist kein Team. Er erscheint nur bei CCI im Pfad.</p>

## Weiterführend

- [Abgeleitete Namen](https://docs.squeeze.one/books/developer-good-practice/page/abgeleitete-namen)
- [Glossar](https://docs.squeeze.one/books/developer-good-practice/page/glossar)

# Abgeleitete Namen

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Aus Abteilung und Team ergeben sich alle Namen nach festen Mustern. Niemand muss sie sich ausdenken: Die Teamliste ist die einzige Quelle, und Terraform erzeugt daraus Rollen, Policies und Gruppen.

## Muster

| Was | Muster | Beispiel |
|---|---|---|
| Entra-Gruppe (Security Group) | `SG-<ABT>-<Team>` | `SG-CPDS-breeze` |
| OpenBao-Mount | `<abt>/` | `cpds/` |
| OpenBao-Policy | `<abt>-<team>` | `cpds-breeze` |
| JWT-Rolle (Pipelines) | `<abt>-<team>-ci` | `cpds-breeze-ci` |
| Nomad-Namespace | `<abt>-<team>` | `cpds-breeze` |
| Secret-Pfad | `<abt>/<team>/<stage>/…` | `cpds/breeze/development/…` |

Für CCI gelten Standort und Projekt statt Team, z. B. Secret-Pfad `cci/<standort>/<stage>/<projekt>/…` (siehe [Pfadschema und Teamtrennung](https://docs.squeeze.one/books/openbao/page/pfadschema-und-teamtrennung)).

## Hostnamen

Hosts heißen `<kürzel>-<cluster>-<standort>-<nr>`, z. B. `cs-002-bre1-01` (Consul-Server, Cluster 002, Bremen 1, Knoten 1). Häufige Kürzel:

| Kürzel | Dienst |
|---|---|
| `cs` / `cl` | Consul-Server / -Client |
| `ns` / `nc` | Nomad-Server / -Client |
| `ng` | Nomad-Client mit GPU |
| `ob` | OpenBao |
| `edge` | Edge-Router |

Der interne Domain-Suffix ist `.dexpro.parts`. Dienste für Menschen laufen unter `*.<umgebung>.<standort>.dexpro.services`, z. B. `*.lab.bre1.dexpro.services`.

## Stolperfallen

- Bei Gruppen und Pfaden zählt die Schreibweise. Kürzel in OpenBao und Nomad sind klein, in Entra-Gruppen groß.
- Namen nie von Hand anlegen, sondern über die Teamliste. Sonst laufen Rechte und Namen auseinander.

## Weiterführend

- [Abteilungen, GitLab-Gruppen und Teams](https://docs.squeeze.one/books/developer-good-practice/page/abteilungen-gitlab-gruppen-und-teams)
- [Buch OpenBao: Konfiguration als Code](https://docs.squeeze.one/books/openbao/page/konfiguration-als-code)

# Stages und Umgebungen

# Stages und Branches

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Jedes Deployment-Repo kennt vier Stages, und jede Stage ist ein eigener Branch. Ein Commit wandert von Stage zu Stage. Ob er weiterwandert, entscheiden feste Gates: mal manuell, mal automatisch.

## Die vier Stages

| Stage = Branch | Zweck | Weiter zur nächsten Stage |
|---|---|---|
| `pre-development` | Einstieg, Default-Branch; im Lab die einzige Stage | manuell (`promote-next-branch`) |
| `development` | Entwicklungsumgebung | automatisch |
| `staging` | Abnahme vor Produktion | manuell (`approve-and-promote-production`) |
| `production` | Produktion | – |

- Einen `main`-Branch gibt es nicht.
- Jede Stage hat ihren eigenen Terraform-State. Der State-Name ist der Branch-Name.
- Alle vier Stage-Branches sind geschützt. Daran hängen Secrets und Freigaben.

## Stages ein- und ausschalten

- `enabled-stages` legt fest, welche Stages wirklich deployen. Inaktive Stages laufen in der Kaskade mit, ändern aber nichts.
- `promote-from` und `promote-to` begrenzen die Kaskade. `promote-to: pre-development` ist der Lab-Modus ohne Promotion.
- Eine Stage wird erst aktiv, wenn ihre Hosts definiert und ihre Variablen gesetzt sind.

## Wie die Stage an anderen Stellen heißt

| Ort | Schreibweise |
|---|---|
| Branch, OpenBao-Pfad, Terraform (`TF_VAR_deployment_stage`) | `pre-development`, `development`, `staging`, `production` |
| Maschinen-Module (VM-Variable `stage`) | nur `dev`, `mngmt`, `prd`. Lab-VMs laufen als `dev`, `staging` läuft mit `prd` |
| Consul-KV und Namen im Lab | `lab`, z. B. `services/lab/…` |

<p class="callout warning">Das sind drei Vokabulare für ähnliche Dinge. Für Secret-Pfade gilt immer der Branch-Name. <code>lab</code>, <code>dev</code> oder <code>prd</code> im OpenBao-Pfad findet keine Pipeline.</p>

## Weiterführend

- [Lab, DEV und Produktion](https://docs.squeeze.one/books/developer-good-practice/page/lab-dev-und-produktion)
- [README der CI-Component, Abschnitte „Promotion“ und „Stages und Kippschalter“](https://gitlab.dexpro.services/cloud-core-infrastructure/ci-components/deployment)

# Lab, DEV und Produktion

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Die Plattform wird am Standort Bremen 1 neu aufgebaut. Zuerst entsteht alles im **Lab** als Proof of Concept. Erst danach folgen DEV und Produktion, und die alten Umgebungen werden abgelöst.

## Die Umgebungen

| Umgebung | Für wen | Stand |
|---|---|---|
| **Lab** | nur Plattformbetrieb (Betreuer) | Neuaufbau läuft (Consul, OpenBao, Edge, Datenbanken, Nomad) |
| **DEV** (neu) | Abteilungen und Teams | geplant, entsteht nach dem Lab-PoC |
| **Produktion** (neu) | Kunden | geplant |
| **Bestand** (alte DEV/PRD) | laufender Betrieb | wird nach dem Lab-PoC abgelöst |

## Regeln

- **Erst Lab, dann der Rest.** Neues wird im Lab fertig gebaut und nachgewiesen, bevor es nach DEV geht.
- **Fehler global beheben.** Was im Lab auffällt, wird in Rollen und Modulen behoben. Diese Korrekturen dürfen veröffentlicht werden.
- **Bestand nicht umbauen.** In alten DEV-, PRD- und Legacy-Umgebungen gibt es bis zur Ablösung keine Umbauten, Rotationen oder Abrisse. Solche Punkte werden als „nach Lab-PoC“ vorgemerkt.
- **Lab ist kein Angebot für Teams.** Was Abteilungen nutzen sollen, entsteht in DEV.

## Unterschiede Lab und DEV

| Thema | Lab | DEV |
|---|---|---|
| Datenbanken (OpenSearch, MongoDB, Journal/Index) | dürfen im Nomad-Cluster laufen | laufen außerhalb von Nomad |
| Menschen-Login an OpenBao | Admin-Token | OIDC mit Entra ID |
| Stages | nur `pre-development` | alle vier |
| Repo-Namen | enthalten `lab`, z. B. `bremen1/lab-edge-routers` | ohne `lab` |

Dienste im Lab sind unter `*.lab.bre1.dexpro.services` erreichbar.

## Weiterführend

- [Stages und Branches](https://docs.squeeze.one/books/developer-good-practice/page/stages-und-branches)
- [Glossar](https://docs.squeeze.one/books/developer-good-practice/page/glossar)

# Deployment

# Deployment-Repo mit der CI-Component

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Alle Terraform/OpenTofu-Deployments laufen über dieselbe GitLab-CI-Component. Ein Deployment-Repo bindet sie ein und bekommt damit Stages, Promotion, Freigaben, Destroy und die Ansible-Umgebung, ohne eigene Pipeline-Logik.

## Voraussetzungen

- Das Repo liegt in der GitLab-Gruppe deiner Abteilung.
- Du hast mindestens die Rolle Reporter im Projekt `cloud-core-infrastructure/ci-components/deployment`. Die Component ist privat.
- Die vier Stage-Branches existieren und sind geschützt (siehe [Stages und Branches](https://docs.squeeze.one/books/developer-good-practice/page/stages-und-branches)).

## Vorgehen

1. `.gitlab-ci.yml` auf das `include` der Component reduzieren:

   ```yaml
   include:
     - component: $CI_SERVER_FQDN/cloud-core-infrastructure/ci-components/deployment/terraform@<version>
       inputs:
         enabled-stages: "development,staging"
         root-config: proxmox
   ```

   - `<version>`: immer eine feste Version bzw. einen SHA, nie einen Branch

2. Keine eigenen `stages:` und keinen `workflow:` definieren. Eigene Jobs nutzen `extends: .deployment:base`.
3. Projekt einrichten: `scripts/setup-project.sh <projekt>` aus dem Component-Repo legt Trigger-Token, Push-Rechte und den Default-Branch `pre-development` an.
4. Bei `root-config: proxmox` gehört die erzeugte `deployment.generated.tf` in die `.gitignore`. Backend und Standard-Provider bringt die Component dann mit.
5. Braucht das Repo Secrets aus OpenBao: `openbao-jwt-role` und `openbao-addr` setzen (siehe [Pipeline per GitLab-JWT anmelden](https://docs.squeeze.one/books/openbao/page/pipeline-per-gitlab-jwt-anmelden)).

## Prüfen

<p class="callout success">Die Pipeline auf <code>pre-development</code> zeigt die Jobs <code>plan</code>, <code>apply-freigeben</code> und <code>deploy</code>. Der Plan ist im Job-Log lesbar.</p>

## Stolperfallen

- Neue Component-Versionen können Inputs entfernen (z. B. `promote-development`). Beim Anheben des Pins die Inputs gegen die README prüfen.
- Ansible läuft immer aus Terraform heraus (`terraform_data` mit `local-exec`), nie als eigener Job.
- Den SSH-Schlüssel für Ansible liefert eine geschützte Gruppenvariable. Deshalb müssen alle Stage-Branches geschützt sein.

## Weiterführend

- [README der CI-Component](https://gitlab.dexpro.services/cloud-core-infrastructure/ci-components/deployment)
- [Merge heißt Deploy](https://docs.squeeze.one/books/developer-good-practice/page/merge-heisst-deploy)

# Merge heißt Deploy

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Ein Merge auf einen Stage-Branch ist kein Code-Review-Abschluss, sondern der Start eines Deployments. Je nach Pin der CI-Component wird sofort angewendet oder erst nach einem Klick. Und je nach Repo wandert der Commit danach weiter in die nächste Stage.

<p class="callout danger">Behandle jeden Merge auf <code>pre-development</code>, <code>development</code>, <code>staging</code> oder <code>production</code> wie ein Deployment. Sieh dir den Plan an, bevor du mergst.</p>

## Was ein Merge auslöst

**Aktuelle Component** (mit Freigabe-Gate):

1. `plan` plant und legt den Plan geschützt ab.
2. `apply-freigeben` wartet auf einen manuellen Klick.
3. `deploy` wendet genau diesen Plan an.

**Ältere Pins** (ohne Gate): Ein Merge auf `pre-development` plant **und wendet sofort an**, ohne Klick.

**Promotion:** Danach wandert der Commit nach den Regeln der Kaskade weiter. Bei älteren Pins ging das bis `development` und `staging` automatisch, z. B. bei den Breeze- und Freeze-Project-Deployern. Mit der aktuellen Component ist der Schritt nach `development` manuell.

## Vorgehen vor einem Merge

1. Den Pin der Component im Repo prüfen: Gibt es `apply-freigeben`?
2. Den Plan gegen den Ziel-State ansehen (MR-Pipeline oder ein Lauf nur mit Plan).
3. Auf unerwartete Replaces achten. Drift durch neue Modul- oder Rollenversionen läuft im selben Apply mit.
4. Mehrere MRs im selben Repo nacheinander mergen, dazwischen rebasen und neu planen.
5. Bei Repos mit Promotion klären, ob die Änderung weiterwandern soll.

## Stolperfallen

<p class="callout warning">Ein Merge, der „nur etwas hinzufügt“, kann trotzdem Replaces anwenden, wenn sich seit dem letzten Lauf Module oder Rollen geändert haben.</p>

- `-o ci.skip` verhindert nur die Pipeline des Branch-Pushs, nicht die nach dem Merge.
- MR-Pipelines mit OpenBao-Anbindung planen nicht. Den Plan liefert dann erst die Pipeline auf dem Stage-Branch vor dem Gate.

## Weiterführend

- [Deployment-Repo mit der CI-Component](https://docs.squeeze.one/books/developer-good-practice/page/deployment-repo-mit-der-ci-component)
- [Stages und Branches](https://docs.squeeze.one/books/developer-good-practice/page/stages-und-branches)

# Rollen und Module versionieren

<p class="callout info">Zielgruppe: Betreuer · Stand: 06.11.2026</p>

Terraform-Module und Ansible-Rollen werden unterschiedlich versioniert. Module haben Versionslinien, Rollen nur zwei Branches. Kompatibilität entsteht immer im Modul, nie in der Rolle.

## Ansible-Rollen

- Zwei Branches: `latest` (Default, Entwicklung) und `stable` (Konsum). Es gibt keine Tags.
- Module binden Rollen immer auf `stable`.
- Eine Rolle bekommt keine Aliase oder Fallbacks für alte Variablennamen.

## Terraform-Module

- Versionslinien als Branches `<major>.<minor>`, z. B. `0.3`. Jeder Merge in den Linien-Branch veröffentlicht die nächste Patchversion.
- Stacks pinnen eine Linie, z. B. `~> 0.3.1` bzw. `>= 0.3.1, < 0.4.0`.
- Ein inkompatibler Umbau bekommt eine neue Linie (z. B. `0.2` → `0.3`). Die alte Linie bleibt für den Bestand.
- Die gerenderten Ansible-Requirements (`requirements.*.yml`) erzeugt ein CI-Job im Modul. Er pusht selbst einen Commit.

## Kompatibilität bei Umbenennungen

1. Die Rolle benennt Variablen einfach um, ohne Alias.
2. Das Modul übergibt alte **und** neue Namen nebeneinander an das Playbook.
3. Geht das nicht, entsteht eine neue Modul-Linie.
4. Modul-MRs werden gemergt, **bevor** die Rolle auf `stable` kommt.

Globale Kontext-Variablen (`site`, `stage`, `role_slug`, `state`, `checkmk_server`) bleiben ohne Präfix.

## Stolperfallen

<p class="callout warning">Das Anlegen eines neuen Linien-Branches veröffentlicht sofort <code>X.Y.0</code> mit dem Inhalt der Vorgängerlinie. Diese Version nicht verwenden, erst ab <code>X.Y.1</code> pinnen.</p>

- Ein Modul, das eine Rolle auf einen Feature-Branch pinnt, hängt an diesem Branch. Den Branch nicht löschen oder force-pushen, bevor das Modul umgestellt ist.
- Nach einer Änderung an CI-Variablen auf den Folgecommit des Requirements-Jobs achten.

## Weiterführend

- [Merge heißt Deploy](https://docs.squeeze.one/books/developer-good-practice/page/merge-heisst-deploy)

# Repo-Struktur

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Jeder Nomad-Job und jeder Infrastruktur-Stack bekommt ein eigenes Repository in der Gruppe seiner Abteilung. Nichts lebt nur lokal oder als Unterordner in einem fremden Repo.

## Regeln

- **Ein Repo je Nomad-Job**, z. B. `container-platform-core-services/nomad-jobs/pilot-graylog`. Ein Job ist oft ein ganzer Stack aus mehreren Diensten: Er enthält dann mehrere Groups und darin mehrere Tasks. Zusammengehörige Dienste stehen deshalb als Groups und Tasks in einem Job, nicht in getrennten Repos. Diese Regel ist Good Practice und wird nicht technisch erzwungen. Wie ein Dienst auf Jobs und Repos aufgeteilt wird, entscheidet der jeweilige Software-Betreuer.
- **Ein Repo je Infrastruktur-Stack** (Terraform, Umgebung eines Standorts), z. B. `cloud-core-infrastructure/bremen1/consul-servers`.
- **Lab-Repos** tragen `lab` im Namen (z. B. `bremen1/lab-edge-routers`), deployen nur `pre-development` und promoten nicht.
- **Wiederverwendbares** gehört in Module (`terraform-modules/…`) bzw. Rollen (`ansible/roles/…`), nicht in Stacks.
- Default-Branch ist `pre-development` (Rollen: `latest`).

<p class="callout warning">Konfiguration, die nur auf einem Rechner oder Server liegt, geht verloren. Auch Testkonfigurationen gehören in ein Repo.</p>

## Weiterführend

- [Abteilungen, GitLab-Gruppen und Teams](https://docs.squeeze.one/books/developer-good-practice/page/abteilungen-gitlab-gruppen-und-teams)
- [Rollen und Module versionieren](https://docs.squeeze.one/books/developer-good-practice/page/rollen-und-module-versionieren)

# Netz

# Edge-Grundsatz

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Der Edge ist das Eingangstor für Menschen. Oberflächen und Dashboards bekommen dort einen eigenen Hostnamen. Maschinen sprechen intern miteinander und nie über den Edge.

## Regeln

- **Für Menschen über den Edge:** Web-Oberflächen (z. B. Traefik-Dashboard, Nomad-UI, Grafana, Graylog) bekommen einen Hostnamen unter der passenden Wildcard (Lab: `*.lab.bre1.dexpro.services`) und einen Router am Edge-Traefik.
- **Keine Umwege:** keine SSH-Tunnel, keine Port-Forwards, keine ungeschützten Admin-Ports.
- **Maschinen intern:** Dienste untereinander (z. B. Backup-Jobs zur Nomad-API, Consul zu Nomad) nutzen interne Namen wie `*.service.consul` und nie Edge-Hostnamen.
- **Nie über den Edge:** interne Jobs, APIs für Maschinen und OpenBao.

<p class="callout danger">Ein interner Dienst, der über den Edge erreichbar ist, ist von außen angreifbar. Im Zweifel bleibt ein Dienst intern.</p>

## Vorgehen: Oberfläche veröffentlichen

1. Hostnamen unter der Wildcard der Umgebung wählen.
2. Bei Diensten in Nomad oder Consul: Route über Tags im Consul-Katalog definieren. Der Edge übernimmt sie automatisch. Wie das geht, steht in [Router per Consul-Tags definieren](https://docs.squeeze.one/books/traefik/page/router-per-consul-tags-definieren). Ob der Weg direkt oder über den internen Ingress läuft, erklärt [Dienst veröffentlichen: Edge oder interner Ingress](https://docs.squeeze.one/books/developer-good-practice/page/dienst-veroffentlichen-edge-oder-interner-ingress).
3. Prüfen, dass die Oberfläche eine Anmeldung hat. Der Edge ersetzt keine Authentifizierung.

## Stolperfallen

- Admin-APIs von Traefik lauschen nur lokal (`127.0.0.1:8080`). Das Dashboard läuft über einen eigenen Router.
- Datenbank-Verbindungen über Edge-TCP-Entrypoints sind nur intern. Die Datenbank sieht dabei die IP des Edges, nicht die des Clients. Grants mit Host-Beschränkung müssen das berücksichtigen.

## Weiterführend

- [Dienst veröffentlichen: Edge oder interner Ingress](https://docs.squeeze.one/books/developer-good-practice/page/dienst-veroffentlichen-edge-oder-interner-ingress)
- [Buch Traefik: Router per Consul-Tags definieren](https://docs.squeeze.one/books/traefik/page/router-per-consul-tags-definieren)
- [Firewall-Freigaben anfragen](https://docs.squeeze.one/books/developer-good-practice/page/firewall-freigaben-anfragen)
- [Buch OpenBao: Erreichbarkeit](https://docs.squeeze.one/books/openbao/page/erreichbarkeit)

# Firewall-Freigaben anfragen

<p class="callout info">Zielgruppe: Anwender · Stand: 06.11.2026</p>

Zwischen den Netzen (z. B. von den GitLab-Runnern zu Diensten im Lab) filtert eine Firewall. Fehlt eine Freigabe, scheitern Pipelines oder Dienste mit Timeouts. So bekommst du eine Freigabe.

## Vorgehen

1. Verbindung genau beschreiben: **Quelle** (Host bzw. Gruppe, z. B. „GitLab-Runner“), **Ziel** (Hostname), **Port** und **Protokoll** (TCP/UDP), **Zweck**.
2. Nur das Nötigste anfragen: einzelne Ports, keine Bereiche „zur Sicherheit“.
3. Die Freigabe beim Netzteam bzw. den Betreuern beantragen. Firewall-Regeln setzen nur sie.
4. Nach der Freigabe die Verbindung testen.

## Prüfen

```bash
nc -vz <ziel-hostname> <port>
```

<p class="callout success">„succeeded“ bzw. „open“ heißt: Die Freigabe wirkt.</p>

## Stolperfallen

- Interne Cluster-Ports (z. B. Raft-, Gossip- oder RPC-Ports zwischen Knoten) werden nie für andere Netze freigegeben.
- Ein Timeout spricht für eine fehlende Freigabe, ein „connection refused“ für einen Dienst, der nicht lauscht.

## Weiterführend

- [Edge-Grundsatz](https://docs.squeeze.one/books/developer-good-practice/page/edge-grundsatz)

# Dienst veröffentlichen: Edge oder interner Ingress

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Du willst einen Dienst erreichbar machen und fragst Dich, ob das über den Edge oder den internen Ingress läuft. Diese Seite erklärt den Unterschied und zeigt die Wege. Wie Du Tags und Router konkret schreibst, steht in den Büchern Traefik und Consul.

## Erst klären: Wer greift zu?

- **Ein Mensch im Browser** (Oberfläche, Dashboard): Der Dienst bekommt einen Hostnamen am Edge. Siehe [Edge-Grundsatz](https://docs.squeeze.one/books/developer-good-practice/page/edge-grundsatz).
- **Ein anderer Dienst** (API, Job, Datenbank-Client): Der Dienst bleibt intern. Er wird über interne Namen wie `<dienst>.service.consul` oder über Connect-Upstreams im Service Mesh erreicht, nie über einen Edge-Hostnamen.

## Edge und interner Ingress

| | Edge | Interner Ingress |
|---|---|---|
| Was | Traefik-Paar auf eigenen VMs mit VIPs | Traefik als Nomad-Job im Cluster |
| Aufgabe | Eingangstor: Hostnamen, Zertifikate, Allowlists, öffentliche Entrypoints | verteilt Anfragen im Cluster weiter an Dienste im Mesh |
| Erreichbar | über die VIPs der Umgebung (intern bzw. öffentlich) | nur vom Edge, über Consul Connect (mTLS), ohne feste Ports 80/443 |
| Routen kommen aus | Tags im Consul-Katalog und Dateien der Rolle `traefik` | der Konfiguration im Nomad-Job des Ingress |
| Zertifikate | Let's Encrypt am Edge | keine eigenen, die TLS-Verbindung endet am Edge |

Wichtig: Der interne Ingress ist kein zweiter Eingang. Jede Anfrage von Menschen kommt zuerst am Edge an.

## Die Wege für einen Dienst

**A: Dienst auf einer VM.** Der Dienst registriert sich im Consul-Katalog mit Traefik-Tags, der Edge routet direkt dorthin.

```text
Browser → Edge → Dienst
```

**B: Nomad-Dienst direkt vom Edge.** Der Dienst hängt im Mesh und trägt `traefik.consulcatalog.connect=true`. Der Edge spricht ihn per Connect an. Ein Beispiel ist Grafana im Lab.

```text
Browser → Edge → Connect → Dienst
```

**C: Nomad-Dienst über den internen Ingress.** Der Ingress trägt die Tags für den Edge und leitet per Connect-Upstream an den Dienst weiter. Ein Beispiel ist der Demo-Dienst im Namespace `cpi` im Lab.

```text
Browser → Edge → Connect → interner Ingress → Connect → Dienst
```

Welchen Weg Dein Nomad-Dienst nimmt (B oder C), stimmst Du mit den Betreuern ab.

## Vorgehen

1. Klären, ob ein Mensch oder ein Dienst zugreift (siehe oben). Für Dienste ist hier Schluss: Sie bleiben intern.
2. Hostnamen unter der Wildcard der Umgebung wählen. <strong>Lab-PoC:</strong> `*.lab.bre1.dexpro.services`.
3. Dienst im Consul-Katalog registrieren: [Dienst im Consul-Katalog registrieren](https://docs.squeeze.one/books/consul/page/dienst-im-consul-katalog-registrieren).
4. Router-Tags setzen, bei Weg C am Ingress statt am Dienst: [Router per Consul-Tags definieren](https://docs.squeeze.one/books/traefik/page/router-per-consul-tags-definieren).
5. Bei Weg B und C die Intentions bei den Betreuern anfragen: Edge → Dienst bzw. Edge → Ingress und Ingress → Dienst.
6. Prüfen, dass die Oberfläche eine eigene Anmeldung hat.

## Stolperfallen

<p class="callout danger">Öffentlich (Entrypoint <code>public</code>) ist ein Dienst nur nach Absprache mit den Betreuern. Ein interner Dienst, der über den Edge erreichbar ist, ist angreifbar.</p>

- **Edge-Hostname für Maschinen:** Dienste, die einen anderen Dienst über dessen Edge-Hostnamen ansprechen, hängen unnötig am Edge und an dessen Allowlists. Nimm interne Namen.
- **Fehlende Intention:** Im Mesh gilt Default-Deny. Ohne Intention kommt keine Anfrage durch, auch nicht vom Edge.

## Weiterführend

- [Edge-Grundsatz](https://docs.squeeze.one/books/developer-good-practice/page/edge-grundsatz)
- [Router per Consul-Tags definieren](https://docs.squeeze.one/books/traefik/page/router-per-consul-tags-definieren)
- [So liest der Edge den Consul-Katalog](https://docs.squeeze.one/books/traefik/page/so-liest-der-edge-den-consul-katalog)
- Beispiele in GitLab: `container-platform-core-services/nomad-jobs/pilot-grafana` (Weg B), `container-platform-core-services/nomad-jobs/traefik-ingress` (Weg C)

# Secrets und Security

# Secrets-Grundsätze

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Ein Secret, das einmal in einem Log, einem Ticket oder einem Commit stand, gilt als bekannt und muss rotiert werden. Diese Grundsätze verhindern das.

## Regeln

- **Nie im Code:** keine Secrets in Repos, `locals`, Default-Werten oder Beispieldateien.
- **Nie in Ausgaben:** Logs, Pipeline-Ausgaben, Tickets, Chats und Doku nennen höchstens den Namen eines Secrets, nie den Wert, auch nicht teilweise.
- **Terraform:** Secrets nicht als Trigger o. Ä. verwenden, die im Plan erscheinen (höchstens als Hash). Werte im State sind unvermeidlich. Deshalb ist der State geschützt.
- **CI-Variablen:** Secrets als masked, hidden und protected anlegen. Protected heißt: nur auf geschützten Branches verfügbar.
- **Skripte:** Trockenlauf als Default. Secrets nur zur Laufzeit lesen (z. B. per stdin), nie als Argument in der Kommandozeile.
- **Neu ablegen:** in OpenBao (siehe [Wohin mit Secrets](https://docs.squeeze.one/books/developer-good-practice/page/wohin-mit-secrets)).

<p class="callout danger">Taucht ein Secret doch in einem Job-Log auf: Fehler beheben, Secret rotieren und danach das Log löschen. In dieser Reihenfolge.</p>

## Stolperfallen

- Befehle wie `env`, `printenv` oder `set -x` geben Secrets aus der Umgebung aus.
- Masking in GitLab greift nur bei einzeiligen Werten. Mehrzeilige Werte (z. B. Schlüssel) deshalb Base64-kodiert ablegen.

## Weiterführend

- [Wohin mit Secrets](https://docs.squeeze.one/books/developer-good-practice/page/wohin-mit-secrets)

# Wohin mit Secrets

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Neue Secrets gehören nach OpenBao. Bitwarden bleibt nur für den Übergang und für die Zugänge, die OpenBao selbst aufbauen.

## Wohin

| Secret | Ablage |
|---|---|
| Zugänge und Passwörter für Deployments und Dienste | OpenBao, Pfad nach Abteilung, Team und Stage |
| Von Terraform erzeugte Passwörter | OpenBao (Umstellung der Module läuft per Schalter) |
| Zugänge des Stacks, der OpenBao baut | Bitwarden, dauerhaft |
| Bestehende Secrets, die noch nicht umgezogen sind | Bitwarden, bis zum Umzug |

## Wie Secrets entstehen

- Menschen legen Secrets nach dem OIDC-Login in der OpenBao-Oberfläche an.
- Pipelines schreiben Secrets per GitLab-JWT.
- Andere Wege sind nicht vorgesehen.

<p class="callout warning">OpenBao gibt es bisher nur im Lab. Dort entstehen Secrets bis zur DEV-Umgebung nur über Pipelines.</p>

## Weiterführend

- [Buch OpenBao: Schnelleinstieg](https://docs.squeeze.one/books/openbao/page/schnelleinstieg)
- [Buch OpenBao: Pfadschema und Teamtrennung](https://docs.squeeze.one/books/openbao/page/pfadschema-und-teamtrennung)
- [Secrets-Grundsätze](https://docs.squeeze.one/books/developer-good-practice/page/secrets-grundsatze)

# Arbeitsweise

# Testen ohne Risiko

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Tests sollen Fehler finden, nicht verursachen. Deshalb laufen sie dort, wo nichts kaputtgehen kann.

## Regeln

- **Der eigene Rechner ist kein Testobjekt.** Keine Playbooks mit `hosts: localhost` und echten Modulen (z. B. `docker_prune`, `file state=absent`, `systemd`) gegen den eigenen Rechner oder dessen Docker/WSL.
- **Offline testen:** Ansible mit `--check`, Stubs und Mocks oder ein **Wegwerf-Container** ohne Netz.
- **Skripte zuerst in einer Wegwerf-Instanz,** z. B. ein OpenBao-Container für OpenBao-Skripte.
- **Echte Umgebungen** nur über die Pipeline und nur im Lab. Riskante Tests nacheinander, nicht parallel.

<p class="callout danger">Ein Test mit echten Modulen gegen eine falsche Zielgruppe löscht echte Daten. Vor jedem Lauf prüfen, gegen welche Hosts er geht.</p>

## Weiterführend

- [Freigaben](https://docs.squeeze.one/books/developer-good-practice/page/freigaben)
- [Lab, DEV und Produktion](https://docs.squeeze.one/books/developer-good-practice/page/lab-dev-und-produktion)

# Freigaben

<p class="callout info">Zielgruppe: Anwender und Betreuer · Stand: 06.11.2026</p>

Manche Aktionen lassen sich nicht zurücknehmen. Für sie gilt: erst zeigen, was passiert, dann ausdrücklich freigeben lassen, dann ausführen.

## Was eine Freigabe braucht

- Deployments und Merges auf Stage-Branches (siehe [Merge heißt Deploy](https://docs.squeeze.one/books/developer-good-practice/page/merge-heisst-deploy))
- Löschen und Abreißen (Ressourcen, Branches, Secrets, Logs)
- Rotationen von Zugängen
- Force-Push, Rebase und Reset auf geteilten Branches
- alles in Produktion

## Wie eine Freigabe aussieht

1. **Konkret:** was genau, wo (Repo, Branch, Stage, Host) und mit welcher Wirkung (Plan, Liste der betroffenen Objekte).
2. **Ausdrücklich:** Ein „klingt gut“ zu einem Plan ist keine Freigabe zum Mergen. Eine Freigabe für den Merge deckt keine anschließende Promotion.
3. **Einmalig:** Eine Freigabe gilt für die gezeigte Aktion, nicht für ähnliche später.

## Stolperfallen

<p class="callout warning">Löschen nur über eine eindeutige ID, nie über eine Namenssuche. Gleichnamige Objekte in anderen Umgebungen werden sonst mitgelöscht.</p>

- Ein „nur hinzufügender“ Plan kann durch Drift trotzdem Replaces enthalten. Den ganzen Plan ansehen.

## Weiterführend

- [Testen ohne Risiko](https://docs.squeeze.one/books/developer-good-practice/page/testen-ohne-risiko)