Archestack auf AKS hosten
Diese Anleitung installiert eine Archestack-Instanz auf Azure Kubernetes Service (AKS) und hält sie danach von selbst auf dem neuesten Stand. Das Helm-Chart betreibt die API als mehrere identische Replikate, dazu den Builder, die veröffentlichte App und Caddy als Edge mit automatischen Zertifikaten. PostgreSQL läuft als Azure Database for PostgreSQL Flexible Server, und die Images kommen aus einer eigenen Container Registry. Eine Pipeline sucht jede Nacht das neueste Archestack-Release heraus, bringt es in Entwicklung und Test und bringt genau diese Version weiter in Abnahme und Produktion, sobald jemand sie genehmigt. Auf einem einzelnen Server ist Hosting mit Docker der einfachere Weg.
Du hast schon einen Cluster und willst Archestack noch heute darauf, unter einem Pfad eines Hostnamens, den du schon bedienst? Archestack auf Kubernetes, der kurze Weg erledigt das mit fünf Dateien und einer Pipeline und ist der richtige Einstieg.
Was wo läuft
LoadBalancer, static public IP (80, 443)
|
Caddy ---- asks the API before every new certificate
+-------------+---------+---------+------------------------------+
editor.<zone> api.<zone> metrics.<zone> <workspace>.<zone>, companies' own domains
| | | |
web-dashboard api (N) grafana web-app
|
Azure Database for PostgreSQL 16, Flexible Server, private to the network
Azure Files: /files and /backups, one share every API replica mounts
omnicore-api and omnicore-web, pulled from your own registry (ACR) - Caddy ist die Edge, kein Ingress Controller. Workspace-Adressen und die eigenen Domains von Unternehmen bekommen ein Zertifikat, wenn jemand sie zum ersten Mal aufruft, und Caddy fragt die API, ob ein Hostname zu einem Workspace gehört, bevor es eines anfordert. cert-manager kann Zertifikate nicht bei Bedarf ausstellen, mit einem Ingress Controller gingen diese Adressen also verloren. Ohne diese Adressen kann Archestack auch unter einem Pfad eines Hostnamens laufen, den der Cluster schon bedient.
- Die Adresse des Besuchers bleibt erhalten
(
externalTrafficPolicy: Local). Der Rate Limiter zählt anonyme Aufrufer nach ihrer Adresse, und ohne diese Einstellung würden sich alle Besucher die Adresse eines Nodes teilen. - Dateien und Backups liegen auf einer einzigen Dateifreigabe in Azure Files, die
jedes API-Replikat einbindet, sodass ein Logo oder ein Backup, das über ein Replikat geschrieben
wurde, auf allen vorhanden ist. Die NuGet-Pakete, die Skripte verwenden, sind die Ausnahme: Jeder
Pod hält seine eigenen auf seinem Node, solange der Pod besteht (
api.nugetCache). - Die API legt ihre Datenbanken selbst an und migriert sie beim Start, ein leerer Server ist also alles, was sie braucht. Einen separaten Migrationsjob gibt es nicht.
Versionen und Tags
Jedes Archestack-Release hat eine Versionsnummer, major.minor.build, etwa
1.4.23. Die Build-Nummer zählt mit jedem Release hoch und beginnt wieder bei 1, wenn
sich die Major- oder Minor-Version ändert, und eine Nummer kann fehlen, wenn ein Release-Build nicht
fertig wurde. Jedes Release veröffentlicht zwei Images auf Docker Hub,
nickpeelman/omnicore-api und nickpeelman/omnicore-web, jedes mit drei
Tags: der Version, dem Commit, aus dem es gebaut wurde, und latest.
Deploye die Versionsnummer, nie latest:
- Nichts wird ausgerollt. Ein Upgrade von
latestauflatestlässt das Deployment unverändert, Kubernetes behält also die Pods, die es hat. - Replikate laufen auseinander. Das Chart lädt ein Image nur herunter, wenn der
Node es noch nicht hat. Ein Node, der
latestletzte Woche heruntergeladen hat, führt weiter die Version der letzten Woche aus, ein neuer Node die dieser Woche, und eine Instanz bedient zwei Versionen. - Es gibt keinen Weg zurück. Die vorherige Version hat keinen Namen, zu dem man zurückkehren könnte.
Die Pipeline unten löst "die neueste Version" deshalb einmal, zu Beginn eines Laufs, in eine Nummer
auf, und jede Umgebung bekommt diese Nummer. Sie kann auch einer Versionslinie folgen:
1.4 nimmt die neueste 1.4.x und wechselt nie von selbst auf
1.5. Mit einer neuen Major- oder Minor-Version kommen die größeren Änderungen, und die
VS Code-Erweiterung warnt, wenn sie sich mit einer API einer anderen
Major- oder Minor-Version verbindet.
Bevor du anfängst
- Ein Azure-Abonnement sowie die
azCLI,kubectlund Helm 3 auf deinem Rechner. - Ein Projekt auf Azure DevOps, GitHub oder GitLab für die Pipeline und dort ein Repository, das du aus dem Starter-Kit angelegt hast.
-
Das Helm-Chart: der Ordner
infra/helm/archestackdes Archestack-Repositorys, unter demselben Pfad in dieses Repository kopiert. - Eine DNS-Zone, in der du Einträge anlegen kannst, oder ein Hostname, den ein Cluster schon über seinen Ingress Controller bedient (Weg B).
Das Starter-Kit
infra/helm/archestack/ the chart, copied from the Archestack repository
setup/ run once per environment, by a person
1-cluster.sh registry, network, Key Vault, database, cluster
2-address.sh way A: the public address for DNS
3-pipeline.sh the SMTP password, the namespace, the pipeline's identity
aks/
settings.env fill in: prefix, region, where the pipeline runs
environment.sh every Azure name, made from settings.env
resolve-version.sh newest, 1.4 or 1.4.23, made one version number
deploy.sh import into your registry, then helm upgrade
azure-pipelines.yml the pipeline, on Azure DevOps
environments/
dev/ tst/ acc/ prd/ fill in: values.yaml, and names.env for a cluster you have
.github/workflows/ the pipeline, on GitHub Actions
.gitlab-ci.yml the pipeline, on GitLab CI
Behalte die Pipeline deines CI-Systems und lösche die anderen beiden. grep -rn CHANGE-ME .
listet jeden Wert auf, der noch auszufüllen ist, und deploy.sh verweigert eine Umgebung,
die noch einen solchen Wert hat. Die drei Setup-Skripte legen dann jede Umgebung in Azure an; eine
Person führt sie einmal aus:
# Once per environment, after az login as someone who may create resources and assign roles
ENVIRONMENT=prd bash setup/1-cluster.sh # registry, network, Key Vault, database, cluster
ENVIRONMENT=prd bash setup/2-address.sh # way A only: the public address, then your DNS
ENVIRONMENT=prd bash setup/3-pipeline.sh # asks the SMTP password; the pipeline's identity Jedes Skript legt nur an, was fehlt, ein Skript, das auf halbem Weg abgebrochen ist, kann also einfach noch einmal ausgeführt werden, und jedes sagt am Ende, was als Nächstes kommt.
1. Die Einstellungen ausfüllen
aks/settings.env enthält, was alle Umgebungen gemeinsam haben:
# aks/settings.env: fill in once. No passwords here: the setup asks for them and keeps them in Key Vault.
PREFIX=CHANGE-ME # 2-10 lowercase letters or digits, unique to you, such as acme
LOCATION=westeurope # the Azure region for every environment
PIPELINE=CHANGE-ME # where the pipeline runs: azure-devops, github or gitlab
REPO= # github: owner/repo; gitlab: group/project; azure-devops: leave empty
GITLAB_URL=https://gitlab.com
Jeder Azure-Name ergibt sich daraus in aks/environment.sh, das sowohl das Setup als
auch deploy.sh lesen, sodass beide immer übereinstimmen. Namen, die in Azure weltweit
eindeutig sein müssen (die Registry, der Key Vault und der Datenbankserver), enden auf dein Präfix:
# aks/environment.sh: one environment's Azure names, made from settings.env, and how to sign in to
# its cluster. The setup scripts and deploy.sh both read it, so they always agree.
# shellcheck source=settings.env
source "$(dirname "${BASH_SOURCE[0]}")/settings.env"
: "${ENVIRONMENT:?set ENVIRONMENT to dev, tst, acc or prd}"
[[ $PREFIX =~ ^[a-z0-9]{2,10}$ ]] || { echo "Fill in PREFIX in aks/settings.env: 2-10 lowercase letters or digits" >&2; exit 1; }
RG=rg-archestack-$ENVIRONMENT
AKS_RG=$RG
AKS=aks-archestack-$ENVIRONMENT
VNET_RG=$RG
VNET=vnet-archestack-$ENVIRONMENT
PG_SUBNET=10.20.16.0/24
PIP=pip-archestack-$ENVIRONMENT
IDENTITY=id-archestack-$ENVIRONMENT
PG=pg-$PREFIX-$ENVIRONMENT # <name>.postgres.database.azure.com, unique across Azure
KV=kv-$PREFIX-$ENVIRONMENT # unique across Azure, at most 24 characters
ACR=acr$PREFIX # one registry for every environment, unique across Azure
ACR_RG=rg-archestack-shared
NAMESPACE=archestack
# A cluster you already have, such as one whose ingress already serves the hostname Archestack
# goes under: aks/environments/<env>/names.env changes the names above (see names.env.example).
OVERRIDES="$(dirname "${BASH_SOURCE[0]}")/environments/$ENVIRONMENT/names.env"
if [ -f "$OVERRIDES" ]; then source "$OVERRIDES"; fi
# Signs kubectl and helm in to the cluster as whoever az is signed in as. A cluster with Entra ID
# sign-in needs kubelogin for that, installed into ~/.local/bin when it is missing.
kube_login() {
az aks get-credentials -g "$AKS_RG" -n "$AKS" --overwrite-existing -o none
if [ "$(az aks show -g "$AKS_RG" -n "$AKS" --query 'aadProfile != null' -o tsv)" = true ]; then
if ! command -v kubelogin > /dev/null; then
mkdir -p "$HOME/.local/bin"
az aks install-cli --install-location "$HOME/.local/bin/kubectl" \
--kubelogin-install-location "$HOME/.local/bin/kubelogin" -o none
export PATH="$HOME/.local/bin:$PATH"
fi
kubelogin convert-kubeconfig -l azurecli
fi
}
Die values.yaml jeder Umgebung enthält ihre Werte für das Chart und einen von zwei
Zugangswegen: eigene Hostnamen (A) oder einen Pfad eines Hostnamens, den der Cluster schon bedient
(B). Behalte einen und lösche den anderen.
# aks/environments/prd/values.yaml: the chart values of this environment. The chart's own values.yaml
# lists every value. The images, the database server and the public address come from deploy.sh,
# the passwords from Key Vault.
#
# Two ways in. Keep one, delete the other, and fill in every CHANGE-ME that is left:
# A. Hostnames of its own (editor., api., metrics., and <workspace>. per workspace) behind Caddy,
# with certificates it gets by itself. Needs DNS records and setup/2-address.sh.
# B. A path of a hostname the cluster's ingress already serves, such as https://apps.acme.com/archestack/.
# No new DNS record, address or certificate. No workspace addresses, own domains or Grafana.
# --- A. Hostnames of its own ---
hosts:
dashboard: editor.CHANGE-ME # editor.<your zone>, such as editor.archestack.acme.com
api: api.CHANGE-ME # api.<your zone>
grafana: metrics.CHANGE-ME # metrics.<your zone>
caddy:
acmeEmail: CHANGE-ME # Let's Encrypt writes here about certificates
persistence:
storageClass: managed-csi
# --- B. A path of a hostname the cluster already serves ---
ingress:
enabled: true
className: nginx # the ingress class that serves the hostname
host: CHANGE-ME # such as apps.acme.com
basePath: /archestack # the builder at https://<host>/archestack/
tls:
- secretName: CHANGE-ME # the hostname's certificate, as a Secret in this namespace
hosts: [CHANGE-ME] # the host again
# --- Both ---
api:
replicas: 3
# A: your zone, for workspaces at <label>.<zone> and companies' own domains, or empty for neither.
# B: empty.
tenantHostSuffix: CHANGE-ME
persistence:
files: { storageClass: azurefile-csi, accessMode: ReadWriteMany, size: 100Gi }
backups: { storageClass: azurefile-csi, accessMode: ReadWriteMany, size: 100Gi }
postgres:
username: archestack
sslMode: Require
# The mail server; its password is in Key Vault. Leave all three empty when the instance sends no mail.
smtp:
host: CHANGE-ME
username: CHANGE-ME
from: CHANGE-ME
observability:
enabled: true # B: Grafana is not routed; reach it with kubectl port-forward
instance: prd
storageClass: managed-csi -
api.replicas: eine beliebige Anzahl, ohne Sticky Sessions. Hosting-Infrastruktur führt auf, was mehrere Replikate vom Netzwerk um sie herum verlangen. -
api.tenantHostSuffix: schaltet eine Adresse pro Workspace und die eigenen Domains von Unternehmen ein, nur mit Weg A. -
api.persistence: mussReadWriteManysein, sobald Replikate auf mehr als einem Node laufen. -
observability.enabled: Grafana, Prometheus, Tempo und Loki im Cluster. Ihre Host- und Container-Panels bleiben auf AKS leer; die Nodes und Container deckt Container Insights ab.
Unter einem Pfad eines Hostnamens, den du schon hast
Weg B legt Archestack unter einen Pfad eines Hostnamens, den der Ingress Controller des Clusters schon bedient, neben die Anwendungen, die bereits dort laufen:
https://apps.acme.com/ what is already there
https://apps.acme.com/archestack/ the builder
https://apps.acme.com/archestack/app/<page> the published app
https://apps.acme.com/archestack/api the API (and /hubs, live updates) Dafür braucht es keinen neuen DNS-Eintrag, keine öffentliche Adresse und kein eigenes Zertifikat, und die Leute nutzen weiter die Adresse, die sie schon kennen. Das Chart lässt Caddy weg und erzeugt einen einzigen Ingress für die drei Pfade; der Builder und die API bekommen ihr Präfix mitgeteilt, sodass jeder Link, den sie schreiben, es enthält.
# --- B. A path of a hostname the cluster already serves ---
ingress:
enabled: true
className: nginx # the ingress class that serves the hostname
host: CHANGE-ME # such as apps.acme.com
basePath: /archestack # the builder at https://<host>/archestack/
tls:
- secretName: CHANGE-ME # the hostname's certificate, as a Secret in this namespace
hosts: [CHANGE-ME] # the host again - Ein Cluster, den du schon hast. Der Hostname liegt auf einem Cluster, meist einem,
der schon vor Archestack da war. Kopiere
names.env.examplenachaks/environments/<env>/names.env, und das Setup lässt diesen Cluster, wie er ist: Es erlaubt dem Cluster, Images aus der Registry herunterzuladen, und fügt seinem Netzwerk ein Subnetz für die Datenbank hinzu. Überspringesetup/2-address.sh. -
classNameist die Ingress-Klasse, die den Hostnamen bedient. Das Chart setzt die Annotationen von ingress-nginx für große Uploads und langlebige Verbindungen; ein anderer Controller braucht seine eigenen Entsprechungen. - Das Zertifikat muss ein Secret im Namespace
archestacksein, weil ein Ingress nur Secrets aus seinem eigenen Namespace verwendet. Kopiere das Zertifikat des Hostnamens dorthin oder lass es von cert-manager dort ausstellen. - Schreibe den Pfad nicht um (kein
rewrite-target). Die API muss ihr Präfix sehen, um ihre eigenen Adressen zu schreiben. - Halte Anmelde-Proxys davon fern. Ein Proxy, der den
Authorization-Header ersetzt, lässt jeden API-Aufruf scheitern: Die veröffentlichte App, Data-API-Schlüssel, Deploy-Tokens, VS Code und MCP bringen alle ihren eigenen mit. - Der Hostname wird geteilt. Anmelde-Tokens liegen im Speicher des Browsers für diese Adresse, den jede Anwendung darauf lesen kann, ein Skript, das in eine davon eingeschleust wird, erreicht also Archestack-Sitzungen. Zwei Archestack-Instanzen auf einem Hostnamen melden jeweils die Benutzer der anderen ab.
- Auf diesem Weg nicht verfügbar: Workspace-Adressen und die eigenen Domains von
Unternehmen, die Caddys Zertifikate bei Bedarf brauchen, und Grafana, das der Ingress nicht
routet. Grafana erreichst du mit
kubectl port-forward.
# A cluster you already have, for one environment: copy this file to aks/environments/<env>/names.env
# and fill it in. The setup then leaves the cluster as it is, allows it to pull from the registry, and
# adds a subnet for the database to the cluster's network. Everything else is made as usual.
# The cluster, such as the one whose ingress already serves the hostname Archestack goes under.
AKS_RG=CHANGE-ME
AKS=CHANGE-ME
# Its network, which has to be one of your own (a cluster made with --vnet-subnet-id), and a free
# address range in it for the database's subnet.
VNET_RG=CHANGE-ME
VNET=CHANGE-ME
PG_SUBNET=CHANGE-ME # such as 10.30.16.0/24 2. Registry, Cluster und Datenbank
#!/usr/bin/env bash
# setup/1-cluster.sh: one environment's registry, network, Key Vault, database and cluster.
# Each part is made only when it is missing, so a run that stopped halfway can simply be run again,
# and a cluster you already have (aks/environments/<env>/names.env) is left as it is.
# ENVIRONMENT=prd bash setup/1-cluster.sh
# Smaller, for dev and test:
# NODE_COUNT=2 ENVIRONMENT=dev bash setup/1-cluster.sh
source "$(dirname "$0")/common.sh"
NODE_COUNT=${NODE_COUNT:-3}
NODE_SIZE=${NODE_SIZE:-Standard_D4s_v5}
PG_SKU=${PG_SKU:-Standard_D2ds_v5}
exists() { "$@" -o none 2>/dev/null; }
# One registry for every environment: a version is imported once and promoted as it is.
az group create -n "$ACR_RG" -l "$LOCATION" -o none
exists az acr show -n "$ACR" || az acr create -g "$ACR_RG" -n "$ACR" --sku Standard -o none
ACR_ID=$(az acr show -n "$ACR" --query id -o tsv)
# The network: a subnet for the cluster, and one delegated to the database.
az group create -n "$RG" -l "$LOCATION" -o none
exists az network vnet show -g "$VNET_RG" -n "$VNET" || az network vnet create -g "$VNET_RG" -n "$VNET" \
--address-prefixes 10.20.0.0/16 --subnet-name aks --subnet-prefixes 10.20.0.0/20 -o none
exists az network vnet subnet show -g "$VNET_RG" --vnet-name "$VNET" -n archestack-postgres \
|| az network vnet subnet create -g "$VNET_RG" --vnet-name "$VNET" -n archestack-postgres \
--address-prefixes "$PG_SUBNET" --delegations Microsoft.DBforPostgreSQL/flexibleServers -o none
# The vault, and the right to write to it: a vault with RBAC gives its creator no access to secrets.
exists az keyvault show -n "$KV" || az keyvault create -g "$RG" -n "$KV" -l "$LOCATION" --enable-rbac-authorization true -o none
az role assignment create --assignee "$(az ad signed-in-user show --query id -o tsv)" \
--role "Key Vault Secrets Officer" --scope "$(az keyvault show -n "$KV" --query id -o tsv)" -o none
# PostgreSQL 16, reachable from the network only. Its password is made here and goes straight
# into the vault, so nobody has to know it. No ';' in it: it goes into a connection string.
exists az postgres flexible-server show -g "$RG" -n "$PG" || {
PG_PASSWORD=$(openssl rand -base64 36 | tr -d '/+=;')
kv_set archestack-postgres-password "$PG_PASSWORD"
az postgres flexible-server create -g "$RG" -n "$PG" -l "$LOCATION" --version 16 \
--tier GeneralPurpose --sku-name "$PG_SKU" --storage-size 128 \
--admin-user archestack --admin-password "$PG_PASSWORD" \
--subnet "$(az network vnet subnet show -g "$VNET_RG" --vnet-name "$VNET" -n archestack-postgres --query id -o tsv)" \
--private-dns-zone "$PG.private.postgres.database.azure.com" --yes -o none
}
# The cluster, allowed to pull from the registry.
if exists az aks show -g "$AKS_RG" -n "$AKS"; then
az aks update -g "$AKS_RG" -n "$AKS" --attach-acr "$ACR_ID" -o none
else
az aks create -g "$AKS_RG" -n "$AKS" --tier standard \
--node-count "$NODE_COUNT" --node-vm-size "$NODE_SIZE" \
--network-plugin azure --network-plugin-mode overlay --pod-cidr 10.244.0.0/16 \
--vnet-subnet-id "$(az network vnet subnet show -g "$VNET_RG" --vnet-name "$VNET" -n aks --query id -o tsv)" \
--enable-managed-identity --generate-ssh-keys --attach-acr "$ACR_ID" -o none
fi
echo "$ENVIRONMENT: $AKS, $PG and $KV are ready."
echo "Next: ENVIRONMENT=$ENVIRONMENT bash setup/2-address.sh, or with a path of a hostname the"
echo "cluster already serves (way B in values.yaml): ENVIRONMENT=$ENVIRONMENT bash setup/3-pipeline.sh" Eine Registry bedient jede Umgebung, sodass eine Version einmal importiert wird und die Produktion genau das Image ausführt, das in der Abnahme lief. Das Datenbankpasswort erzeugt das Skript, und es geht direkt in Key Vault: Niemand muss es kennen. Kapazität und Dimensionierung enthält die gemessenen Zahlen hinter den Größen, die ein Ausgangspunkt für die Produktion sind.
Jeder Workspace hat zwei eigene Datenbanken. Ein ungenutzter Workspace hält keine Verbindungen, aber ein ausgelasteter Server braucht Spielraum: Das Limit des Burstable-Tarifs von 50 Verbindungen ist zu niedrig. Die API braucht keine Erweiterungen und keinen Superuser, nur einen Admin-Benutzer, der Datenbanken anlegen kann.
3. Öffentliche Adresse und DNS
#!/usr/bin/env bash
# setup/2-address.sh: the environment's static public address, for Caddy and your DNS. Not needed
# with a path of a hostname the cluster already serves (way B in values.yaml).
# ENVIRONMENT=prd bash setup/2-address.sh
source "$(dirname "$0")/common.sh"
# In the cluster's node resource group, because that is where its load balancer looks for it by name.
NODE_RG=$(az aks show -g "$AKS_RG" -n "$AKS" --query nodeResourceGroup -o tsv)
az network public-ip create -g "$NODE_RG" -n "$PIP" --sku Standard --allocation-method Static -o none
IP=$(az network public-ip show -g "$NODE_RG" -n "$PIP" --query ipAddress -o tsv)
echo "Point these DNS records at $IP, under the zone in aks/environments/$ENVIRONMENT/values.yaml:"
echo " editor api metrics *"
echo "Next: ENVIRONMENT=$ENVIRONMENT bash setup/3-pipeline.sh"
Nur für Weg A. Lass editor, api und metrics unter deiner Zone
auf die Adresse zeigen, dazu einen Wildcard-Eintrag * für die eigenen Adressen der
Workspaces. Caddy holt seine Zertifikate von Let's Encrypt, die Ports 80 und 443 müssen also aus dem
Internet erreichbar sein.
4. Passwörter und die Identität der Pipeline
#!/usr/bin/env bash
# setup/3-pipeline.sh: the SMTP password, the namespace, and the identity the pipeline deploys
# this environment as.
# ENVIRONMENT=prd bash setup/3-pipeline.sh
source "$(dirname "$0")/common.sh"
case "$PIPELINE" in
azure-devops) ;;
github | gitlab) [ -n "$REPO" ] || { echo "Fill in REPO in aks/settings.env" >&2; exit 1; } ;;
*) echo "Fill in PIPELINE in aks/settings.env: azure-devops, github or gitlab" >&2; exit 1 ;;
esac
# Asked for here, so it is never written to a file. Enter nothing to keep what the vault has,
# or when the instance sends no mail.
read -rsp "SMTP password for $ENVIRONMENT (Enter to skip): " SMTP_PASSWORD; echo
if [ -n "$SMTP_PASSWORD" ]; then kv_set archestack-smtp-password "$SMTP_PASSWORD"; fi
# The pipeline's identity, one per environment, and what it may do: sign in to the cluster,
# read the passwords, and import into the registry.
az identity create -g "$RG" -n "$IDENTITY" -l "$LOCATION" -o none
CLIENT_ID=$(az identity show -g "$RG" -n "$IDENTITY" --query clientId -o tsv)
PRINCIPAL_ID=$(az identity show -g "$RG" -n "$IDENTITY" --query principalId -o tsv)
AKS_ID=$(az aks show -g "$AKS_RG" -n "$AKS" --query id -o tsv)
grant() {
az role assignment create --assignee-object-id "$PRINCIPAL_ID" --assignee-principal-type ServicePrincipal \
--role "$1" --scope "$2" -o none
}
grant "Azure Kubernetes Service Cluster User Role" "$AKS_ID"
grant "Key Vault Secrets User" "$(az keyvault show -n "$KV" --query id -o tsv)"
grant Contributor "$(az acr show -n "$ACR" --query id -o tsv)"
# The namespace, made here by you, so the pipeline only ever needs rights inside it. A cluster
# with Entra ID sign-in checks those rights itself: Azure RBAC, or a Kubernetes RoleBinding.
kube_login
kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - > /dev/null
ENTRA=$(az aks show -g "$AKS_RG" -n "$AKS" --query 'aadProfile != null' -o tsv)
AZURE_RBAC=$(az aks show -g "$AKS_RG" -n "$AKS" --query 'aadProfile.enableAzureRbac == `true`' -o tsv)
if [ "$AZURE_RBAC" = true ]; then
grant "Azure Kubernetes Service RBAC Writer" "$AKS_ID/namespaces/$NAMESPACE"
elif [ "$ENTRA" = true ]; then
kubectl create rolebinding archestack-pipeline -n "$NAMESPACE" --clusterrole=edit --user="$PRINCIPAL_ID" \
--dry-run=client -o yaml | kubectl apply -f - > /dev/null
fi
# No stored secret: the CI system hands the pipeline a token that says who it is, and the
# identity trusts only tokens with this issuer and subject.
federate() {
az identity federated-credential create -g "$RG" --identity-name "$IDENTITY" -n "$1" \
--issuer "$2" --subject "$3" --audiences "$4" -o none
}
TENANT_ID=$(az account show --query tenantId -o tsv)
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
case "$PIPELINE" in
github)
federate "github-$ENVIRONMENT" https://token.actions.githubusercontent.com \
"repo:$REPO:environment:archestack-$ENVIRONMENT" api://AzureADTokenExchange
echo "Done. In GitHub, create the environment archestack-$ENVIRONMENT, then run:"
echo " gh variable set AZURE_CLIENT_ID --env archestack-$ENVIRONMENT --body $CLIENT_ID --repo $REPO"
echo " gh variable set AZURE_TENANT_ID --body $TENANT_ID --repo $REPO"
echo " gh variable set AZURE_SUBSCRIPTION_ID --body $SUBSCRIPTION_ID --repo $REPO"
;;
gitlab)
federate "gitlab-$ENVIRONMENT" "$GITLAB_URL" \
"project_path:$REPO:ref_type:branch:ref:main" "$GITLAB_URL"
echo "Done. In GitLab, Settings, CI/CD, Variables, add:"
echo " AZURE_CLIENT_ID = $CLIENT_ID (environment scope: archestack-$ENVIRONMENT)"
echo " AZURE_TENANT_ID = $TENANT_ID (all environments)"
echo " AZURE_SUBSCRIPTION_ID = $SUBSCRIPTION_ID (all environments)"
;;
azure-devops)
if [ -n "${AZDO_ISSUER:-}" ]; then
federate "azure-devops-$ENVIRONMENT" "$AZDO_ISSUER" "$AZDO_SUBJECT" api://AzureADTokenExchange
echo "Done. sc-archestack-$ENVIRONMENT can deploy $ENVIRONMENT; verify it in Azure DevOps."
else
echo "In Azure DevOps, add a service connection sc-archestack-$ENVIRONMENT: Azure Resource Manager,"
echo "workload identity federation, set up manually, with this identity:"
echo " tenant $TENANT_ID, subscription $SUBSCRIPTION_ID, client id $CLIENT_ID"
echo "It then shows an issuer and a subject identifier. Run this again with them:"
echo " AZDO_ISSUER='<issuer>' AZDO_SUBJECT='<subject identifier>' ENVIRONMENT=$ENVIRONMENT bash setup/3-pipeline.sh"
fi
;;
esac - Das SMTP-Passwort wird abgefragt und geht direkt in Key Vault. Eine Instanz, die keine Mails verschickt, überspringt es.
- Die Pipeline meldet sich ohne gespeichertes Secret an. Jede Umgebung hat eine eigene Identität, die nur dem Token vertraut, das ihr CI-System den Jobs dieser Umgebung mitgibt. Sie darf sich am Cluster anmelden, die Passwörter lesen und in die Registry importieren, sonst nichts.
- Den Namespace legst du an, die Pipeline braucht also nur Rechte darin. Ein Cluster mit Entra ID-Anmeldung prüft diese Rechte selbst, über Azure RBAC oder ein Kubernetes-RoleBinding; das Skript vergibt sie über das, was der Cluster verwendet.
- Das Skript nennt am Ende, was du in deinem CI-System einstellen musst: GitHub-Variablen, GitLab-Variablen oder die Azure DevOps Service Connection.
5. Die erste Installation
Die erste Installation ist das Skript, das die Pipeline ausführt, von Hand gestartet. Ein erster Lauf der Pipeline tut dasselbe.
# From the repository, after az login as the person who ran the setup.
bash aks/resolve-version.sh # writes the newest version to ./version
VERSION=$(cat version) ENVIRONMENT=prd bash aks/deploy.sh
kubectl -n archestack get deploy -o wide # every Deployment ready, and its image
# Grafana's password, generated on the first install and kept from then on
kubectl -n archestack get secret archestack-secrets -o jsonpath='{.data.admin-password}' | base64 -d Öffne sofort den Builder und lege den ersten Admin an. Bis das jemand tut, wird
Admin, wer ihn als Erstes erreicht. Um gar kein Zeitfenster offen zu lassen, lege den ersten Admin
mit api.initialAdmin vorab an (siehe die values.yaml des Charts).
Das neueste Release, automatisch
resolve-version.sh fragt die Registry nach jedem Tag beider Images und wählt die
Version, die dieser Lauf deployt:
#!/usr/bin/env bash
# aks/resolve-version.sh: turn "newest" into the version number this run deploys.
# ARCHESTACK_VERSION=newest the newest release
# ARCHESTACK_VERSION=1.4 the newest 1.4.x
# ARCHESTACK_VERSION=1.4.23 exactly that one
set -euo pipefail
export LC_ALL=C
SOURCE=${SOURCE_NAMESPACE:-nickpeelman}
WANT=${ARCHESTACK_VERSION:-newest}
OUT=${OUT_DIR:-.}
# Every major.minor.build tag of one image, from the registry API. The commit tags and
# "latest" do not match the pattern.
tags() {
local repo="$SOURCE/$1" token
token=$(curl -fsS "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$repo:pull" \
| sed -nE 's/.*"token":"([^"]+)".*/\1/p')
curl -fsS -H "Authorization: Bearer $token" "https://registry-1.docker.io/v2/$repo/tags/list" \
| grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -u
}
# A version counts only when both images carry it: a release build that stopped between
# the two pushes leaves one of them without it. Sorted as versions, so 1.0.10 beats 1.0.9.
versions=$(comm -12 <(tags omnicore-api) <(tags omnicore-web) | sort -V)
if [ "$WANT" = newest ]; then
pick=$(tail -n 1 <<<"$versions")
elif [[ $WANT =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
pick=$(grep -xF "$WANT" <<<"$versions" || true)
elif [[ $WANT =~ ^[0-9]+\.[0-9]+$ ]]; then
pick=$(grep -E "^${WANT//./\\.}\.[0-9]+$" <<<"$versions" | tail -n 1 || true)
else
echo "Version must be newest, major.minor or major.minor.build, not '$WANT'" >&2
exit 1
fi
[ -n "$pick" ] || { echo "No release of $SOURCE/omnicore-api and omnicore-web matches '$WANT'" >&2; exit 1; }
# The version for the next steps: a file, and whatever the CI system passes between jobs.
echo "Deploying Archestack $pick"
mkdir -p "$OUT"
echo "$pick" > "$OUT/version"
if [ -n "${TF_BUILD:-}" ]; then echo "##vso[build.updatebuildnumber]$pick"; fi
if [ -n "${GITHUB_OUTPUT:-}" ]; then
echo "version=$pick" >> "$GITHUB_OUTPUT"
echo "Deploying Archestack **$pick**" >> "$GITHUB_STEP_SUMMARY"
fi
if [ -n "${GITLAB_CI:-}" ]; then echo "VERSION=$pick" > "$OUT/version.env"; fi - Versionen werden als Zahlen verglichen,
1.0.10ist also neuer als1.0.9, und eine fehlende Nummer wird übersprungen, statt auf sie zu warten. - Es zählt nur eine Version, die beide Images tragen. Ein Release-Build, der zwischen dem Push der API und dem Push des Web-Images abgebrochen ist, wird nie zur Hälfte deployt.
- Auch eine genau angegebene Version wird geprüft. Eine Version, die die Images nicht haben, stoppt den Lauf, bevor irgendetwas angefasst wird.
- Die Version wird so weitergereicht, wie jedes CI-System Werte weitergibt: Azure
DevOps benennt den Lauf nach ihr, GitHub Actions macht sie zu einem Job-Output und zeigt sie in der
Zusammenfassung des Laufs, und GitLab CI übergibt sie den späteren Jobs als
$VERSION.
deploy.sh bringt dann eine Umgebung auf diese Version:
#!/usr/bin/env bash
# aks/deploy.sh: copy one Archestack version into your registry and roll it out with Helm.
# Every pipeline runs it once per environment, signed in to Azure. By hand, after az login:
# VERSION=1.4.23 ENVIRONMENT=prd bash aks/deploy.sh
set -euo pipefail
cd "$(dirname "$0")/.."
: "${VERSION:?}" "${ENVIRONMENT:?}"
source aks/environment.sh
SOURCE=${SOURCE_NAMESPACE:-nickpeelman}
VALUES="aks/environments/$ENVIRONMENT/values.yaml"
if grep -v '^[[:space:]]*#' "$VALUES" | grep -q CHANGE-ME; then echo "Fill in every CHANGE-ME in $VALUES first" >&2; exit 1; fi
# Keeps a value out of the pipeline's log. GitLab has no such command; nothing here prints one.
mask() {
if [ -n "${TF_BUILD:-}" ]; then echo "##vso[task.setsecret]$1"; fi
if [ -n "${GITHUB_ACTIONS:-}" ]; then echo "::add-mask::$1"; fi
}
# 1. The two images, imported from Docker Hub into your registry. --force makes a rerun harmless.
for image in omnicore-api omnicore-web; do
az acr import --name "$ACR" --force \
--source "docker.io/$SOURCE/$image:$VERSION" --image "$image:$VERSION"
done
# 2. The passwords, from Key Vault into files only this job can read. They go in on every
# upgrade: the chart writes the SMTP password from the values each time.
secret() { az keyvault secret show --vault-name "$KV" -n "$1" --query value -o tsv 2>/dev/null || true; }
pg=$(secret archestack-postgres-password)
smtp=$(secret archestack-smtp-password)
[ -n "$pg" ] || { echo "Cannot read archestack-postgres-password from $KV (missing, or no Key Vault Secrets User)" >&2; exit 1; }
for value in "$pg" "$smtp"; do
if [ -n "$value" ]; then mask "$value"; fi
done
umask 077
secrets=$(mktemp -d)
trap 'rm -rf "$secrets"' EXIT
printf '%s' "$pg" > "$secrets/postgres"
printf '%s' "$smtp" > "$secrets/smtp"
# 3. The rollout, into the namespace setup/3-pipeline.sh made.
kube_login
helm upgrade --install archestack infra/helm/archestack \
--namespace "$NAMESPACE" \
-f "$VALUES" \
--set-file postgres.password="$secrets/postgres" \
--set-file smtp.password="$secrets/smtp" \
--set-string postgres.host="$PG.postgres.database.azure.com" \
--set-string 'caddy.service.annotations.service\.beta\.kubernetes\.io/azure-pip-name'="$PIP" \
--set-string api.image.repository="$ACR.azurecr.io/omnicore-api" \
--set-string api.image.tag="$VERSION" \
--set-string web.image.repository="$ACR.azurecr.io/omnicore-web" \
--set-string web.image.tag="$VERSION" \
--wait --timeout 30m - Die Images kommen zuerst in deine Registry. Nodes laden die Images aus Azure,
innerhalb der Region, das Hinzufügen oder Ersetzen eines Nodes wartet also nie auf Docker Hub oder
dessen Limits für anonyme Pulls. Wird ein Import wegen dieser Limits abgelehnt, gib
az acr importmit--usernameund--passwordein Docker Hub-Konto mit. - Die Passwörter werden bei jedem Upgrade mitgegeben. Das Chart schreibt das SMTP-Passwort jedes Mal aus den Werten, ein Upgrade ohne dieses Passwort hinterlässt also eine Instanz, die keine Mails verschicken kann. Die Passwörter werden in Dateien übergeben, die nur der Job lesen kann, und Azure DevOps und GitHub Actions maskieren sie im Log.
-
--wait --timeout 30mwartet, bis jedes Deployment bereit ist. Die API migriert jeden Workspace, bevor sie antwortet, eine Version mit Migrationen braucht auf einer Instanz mit vielen Workspaces also eine Weile. - Kein
--atomic. Ein fehlgeschlagener Rollout bleibt zur Untersuchung stehen, statt rückgängig gemacht zu werden, weil die neue Version die Datenbanken womöglich schon migriert hat. Die alten Pods bedienen in der Zwischenzeit weiter.
Die Pipeline
Das Kit enthält dieselbe Pipeline für drei CI-Systeme. Jede führt die beiden Skripte oben in derselben Reihenfolge aus:
Archestack publishes 1.4.23 omnicore-api and omnicore-web, tagged 1.4.23, <commit> and latest
03:00, the nightly run
resolve newest -> 1.4.23 the highest number both images carry
dev import 1.4.23 into your registry, helm upgrade, wait until ready
tst the same 1.4.23
a scheduled run stops here
a run started by hand, version 1.4.23
dev, tst already on 1.4.23, nothing rolls out
acc waits for its approval, then 1.4.23
prd waits for its approval, then 1.4.23 - Jede Nacht bringt der Zeitplan das neueste Release in Entwicklung und Test.
- Ein geplanter Lauf stoppt nach Test. Abnahme und Produktion ziehen nur bei einem
von Hand gestarteten Lauf nach. Gib ihm die Version mit, die in Test läuft, etwa
1.4.23, dann bekommen Abnahme und Produktion genau das, was getestet wurde. Steht die Version aufnewest, nimmt der Lauf, was in diesem Moment am neuesten ist. - Eine Version pro Lauf. Die Version wird einmal ermittelt, vor der ersten Umgebung, sodass ein Release, das veröffentlicht wird, während ein Lauf auf eine Genehmigung wartet, nicht in die Produktion rutscht.
- Eine Umgebung, die schon auf der Version ist, rollt nichts aus. Die Spezifikation
der Pods ist unverändert, Kubernetes lässt sie also weiterlaufen. Eine Änderung an ihrer
values.yamlwird trotzdem angewendet.
Azure DevOps
# aks/azure-pipelines.yml: the newest Archestack release to dev and tst every night,
# to acc and prd on a run started by hand, once each is approved.
trigger: none
schedules:
- cron: "0 3 * * *" # 03:00 UTC
displayName: Nightly, newest release to dev and tst
branches:
include: [main]
always: true # a release is a new image, not a commit in this repository
parameters:
- name: version
displayName: Archestack version (newest, 1.4 for the newest 1.4.x, or 1.4.23)
type: string
default: newest
- name: environments
displayName: Environments, in order
type: object
default: [dev, tst, acc, prd]
pool:
vmImage: ubuntu-latest
stages:
# Once per run, so every environment gets the same number even when a release
# is published while the run waits for an approval.
- stage: resolve
displayName: Resolve the version
jobs:
- job: resolve
steps:
- script: bash aks/resolve-version.sh
displayName: Newest version both images carry
env:
ARCHESTACK_VERSION: ${{ parameters.version }}
OUT_DIR: $(Build.ArtifactStagingDirectory)
- publish: $(Build.ArtifactStagingDirectory)
artifact: version
- ${{ each env in parameters.environments }}:
- stage: deploy_${{ env }}
displayName: Deploy ${{ env }}
${{ if in(env, 'acc', 'prd') }}:
# A scheduled run stops after tst.
condition: and(succeeded(), ne(variables['Build.Reason'], 'Schedule'))
jobs:
- deployment: deploy
environment: archestack-${{ env }}
strategy:
runOnce:
deploy:
steps:
- checkout: self
- download: current
artifact: version
- task: AzureCLI@2
displayName: Import the images, then helm upgrade
inputs:
azureSubscription: sc-archestack-${{ env }}
scriptType: bash
scriptLocation: inlineScript
workingDirectory: $(Build.SourcesDirectory)
inlineScript: |
export VERSION=$(cat "$PIPELINE_WORKSPACE/version/version")
ENVIRONMENT=${{ env }} bash aks/deploy.sh - Service Connections heißen
sc-archestack-<env>, eine pro Umgebung, jede mit der Identität dieser Umgebung.setup/3-pipeline.shgibt aus, was auszufüllen ist, und nimmt den Issuer und das Subject entgegen, die die Verbindung anzeigt. - Genehmigungen. Füge unter Pipelines, Environments
archestack-accundarchestack-prdeinen Approvals-Check hinzu und allen vier einen Exclusive lock-Check, damit nie zwei Läufe gleichzeitig in dieselbe Umgebung ausrollen. -
always: trueführt den Zeitplan auch ohne neuen Commit aus, weil ein Release ein neues Image ist, keine Änderung in diesem Repository.
GitHub Actions
# .github/workflows/archestack.yml: the newest Archestack release to dev and tst every night,
# to acc and prd on a run started by hand, once each is approved.
name: Archestack on AKS
on:
schedule:
- cron: "0 3 * * *" # 03:00 UTC
workflow_dispatch:
inputs:
version:
description: Archestack version (newest, 1.4 for the newest 1.4.x, or 1.4.23)
default: newest
permissions:
id-token: write # sign in to Azure with a token, no stored secret
contents: read
jobs:
# Once per run, so every environment gets the same number even when a release
# is published while the run waits for an approval.
resolve:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.pick.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: pick
run: bash aks/resolve-version.sh
env:
ARCHESTACK_VERSION: ${{ inputs.version || 'newest' }}
OUT_DIR: ${{ runner.temp }}
dev:
needs: resolve
uses: ./.github/workflows/archestack-deploy.yml
with: { environment: dev, version: "${{ needs.resolve.outputs.version }}" }
tst:
needs: [resolve, dev]
uses: ./.github/workflows/archestack-deploy.yml
with: { environment: tst, version: "${{ needs.resolve.outputs.version }}" }
# A scheduled run stops after tst.
acc:
if: github.event_name != 'schedule'
needs: [resolve, tst]
uses: ./.github/workflows/archestack-deploy.yml
with: { environment: acc, version: "${{ needs.resolve.outputs.version }}" }
prd:
if: github.event_name != 'schedule'
needs: [resolve, acc]
uses: ./.github/workflows/archestack-deploy.yml
with: { environment: prd, version: "${{ needs.resolve.outputs.version }}" } Jede Umgebung wird von demselben wiederverwendbaren Workflow deployt:
# .github/workflows/archestack-deploy.yml: one environment to one version, called by archestack.yml.
name: Archestack deploy
on:
workflow_call:
inputs:
environment: { type: string, required: true }
version: { type: string, required: true }
jobs:
deploy:
runs-on: ubuntu-latest
environment: archestack-${{ inputs.environment }}
concurrency: archestack-${{ inputs.environment }} # one rollout per environment at a time
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }} # this environment's identity
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- run: bash aks/deploy.sh
env:
VERSION: ${{ inputs.version }}
ENVIRONMENT: ${{ inputs.environment }} - Umgebungen. Lege unter Settings, Environments
archestack-dev,-tst,-accund-prdan und richte für Abnahme und Produktion Required reviewers ein. In einem privaten Repository brauchen Umgebungen GitHub Pro, Team oder Enterprise. - Anmeldung.
azure/logintauscht das Token des Jobs gegen die Identität der Umgebung ein, deren Client-ID eine Variable der Umgebung ist; diegh-Befehle dafür gibtsetup/3-pipeline.shaus. Die Identität vertraut nur Jobs in ihrer eigenen Umgebung, ein Job für Test kann also nie die Produktion deployen. -
concurrencyverhindert, dass zwei Läufe gleichzeitig in dieselbe Umgebung ausrollen. - Der Zeitplan läuft nur aus dem Standard-Branch, und in einem öffentlichen Repository schaltet GitHub ihn nach 60 Tagen ohne Aktivität im Repository ab.
GitLab CI
# .gitlab-ci.yml: the newest Archestack release to dev and tst on the nightly schedule,
# to acc and prd from a pipeline started by hand, each when a person runs its job.
variables:
ARCHESTACK_VERSION:
value: newest
description: Archestack version (newest, 1.4 for the newest 1.4.x, or 1.4.23)
# Pipelines run on the schedule (Build, Pipeline schedules) and when started by hand, never on a push.
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"
stages: [resolve, dev, tst, acc, prd]
default:
image: mcr.microsoft.com/azure-cli:2.90.0
before_script:
- tdnf install -y curl tar gzip > /dev/null
# Once per pipeline, so every environment gets the same number. Later jobs read it as $VERSION.
resolve:
stage: resolve
script:
- bash aks/resolve-version.sh
artifacts:
reports:
dotenv: version.env
.deploy:
id_tokens:
AZURE_TOKEN:
aud: https://gitlab.com # GITLAB_URL from aks/settings.env, on a self-managed instance
before_script:
- tdnf install -y curl tar gzip > /dev/null
- curl -fsSL https://get.helm.sh/helm-v3.16.2-linux-amd64.tar.gz | tar -xz -C /tmp
- mv /tmp/linux-amd64/helm /usr/local/bin/helm
- az login --service-principal -u "$AZURE_CLIENT_ID" -t "$AZURE_TENANT_ID" --federated-token "$AZURE_TOKEN" -o none
- az account set -s "$AZURE_SUBSCRIPTION_ID"
script:
- bash aks/deploy.sh
dev:
extends: .deploy
stage: dev
environment: archestack-dev
resource_group: archestack-dev # one rollout per environment at a time
variables: { ENVIRONMENT: dev }
tst:
extends: .deploy
stage: tst
environment: archestack-tst
resource_group: archestack-tst
variables: { ENVIRONMENT: tst }
# A scheduled pipeline stops after tst. acc and prd wait until a person runs them, in order.
.by-hand:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- when: manual
allow_failure: false # prd stays blocked until acc has run
acc:
extends: [.deploy, .by-hand]
stage: acc
environment: archestack-acc
resource_group: archestack-acc
variables: { ENVIRONMENT: acc }
prd:
extends: [.deploy, .by-hand]
stage: prd
environment: archestack-prd
resource_group: archestack-prd
variables: { ENVIRONMENT: prd } - Der Zeitplan liegt in GitLab statt in der Datei: Lege unter Build, Pipeline
schedules einen für
mainmit0 3 * * *an. Ein Push startet nichts. - Abnahme und Produktion sind manuelle Jobs, die die Pipeline blockieren: Produktion lässt sich erst starten, wenn Abnahme gelaufen ist. Schütze auf GitLab Premium diese Umgebungen zusätzlich und verlange Genehmigungen; ein genehmigter Job wird trotzdem von Hand gestartet.
- Anmeldung. Das ID-Token des Jobs wird gegen die Identität der Umgebung
eingetauscht, deren Client-ID eine Variable ist, die auf diese Umgebung beschränkt ist. Das Token
von GitLab nennt den Branch, nicht die Umgebung, schütze also
main. Setze auf einer selbst verwalteten Instanz deren Adresse alsGITLAB_URLund alsauddes Tokens. -
resource_groupverhindert, dass zwei Pipelines gleichzeitig in dieselbe Umgebung ausrollen. - Das Image ist auf eine feste Version festgelegt, und Helm wird in einer festen Version heruntergeladen. Aktualisiere beide bewusst.
Upgrades ohne Ausfallzeit
- Ein neuer API-Pod nach dem anderen. Das Chart fügt einen Pod hinzu, bevor es
einen entfernt (
maxSurge: 1,maxUnavailable: 0), und ein alter Pod geht erst, wenn ein neuer bereit ist. - Der Start geht reihum. Replikate, die gleichzeitig starten, bereiten die Datenbanken unter einer Sperre nacheinander vor, sodass nie zwei gleichzeitig migrieren. Eine Startup Probe gibt jedem Pod 15 Minuten, bevor Kubernetes ihn aufgibt.
- Node-Upgrades lassen die API laufen. Ein PodDisruptionBudget hält mindestens einen API-Pod am Laufen, während AKS einen Node leert.
- Caddy startet nur neu, wenn sich seine eigene Konfiguration ändert, etwa durch einen neuen Hostnamen. Caddy läuft als ein einzelner Pod mit seinen Zertifikaten auf einem eigenen Datenträger, dieser Neustart ist also eine kurze Unterbrechung. Ein Archestack-Upgrade rührt Caddy nicht an.
Zurück zur vorherigen Version
Führe die Pipeline mit der vorherigen Version aus, etwa 1.4.22, oder rolle das Release
mit Helm zurück:
helm history archestack -n archestack # the revisions, newest last
helm rollback archestack -n archestack --wait --timeout 30m # back to the one before In beiden Fällen bleiben die Datenbanken so, wie die neuere Version sie hinterlassen hat: Sie hat sie beim Start migriert, und eine ältere Version macht das nicht rückgängig. Ein Zurückgehen führt älteren Code auf einem neueren Schema aus, ziehe deshalb das nächste Release vor, das das Problem behebt. Um auch das Schema zurückzuholen, musst du den Server mit der Point-in-Time-Wiederherstellung von Flexible Server wiederherstellen.
Stolperfallen
- Das Chart kommt aus deinem Repository, die Images aus der Registry. Die Pipeline holt sich die neuesten Images von selbst, aber nie ein neueres Chart. Wenn du auf eine neue Major- oder Minor-Version wechselst, übernimm das Chart erneut aus dem Archestack-Repository.
- Die Observability-Images tragen keine Versionsnummer. Sie sind mit einem Commit
und
latestgetaggt, kommen von Docker Hub und folgenobservability.imageTag. Setze den Wert auf einen ihrer Commit-Tags, um sie auf einem Stand festzuhalten. - Ein Release pro Namespace. Grafana und Tempo finden Prometheus, Tempo und Loki über feste Service-Namen.
- Hinter PgBouncer (Port 6432 von Flexible Server) setzt du
postgres.clusterConnectionStringauf eine direkte Verbindung über 5432. Die Replikate sprechen über eine PostgreSQL-Verbindung miteinander, die offen bleiben muss. - Volumes und das eigene Secret des Charts überstehen
helm uninstall. Lösche sie von Hand, wenn du das wirklich willst.
Wie es weitergeht
Kapazität und Dimensionierung enthält die gemessenen Zahlen, um den Cluster und die Datenbank zu dimensionieren. Workspaces und Hosting erklärt, wie eine Instanz viele Workspaces bedient. Sobald die Instanz läuft, beschreibt Deployment-Strategie, wie du deine App aus einem Repository in ihre Workspaces deployst.