Guida - 20 min

Ospitare Archestack su AKS

Questa guida installa un'istanza Archestack su Azure Kubernetes Service (AKS) e poi la mantiene aggiornata in automatico. Il chart Helm esegue l'API come più repliche identiche, il builder e l'app pubblicata, e Caddy come edge con certificati automatici. PostgreSQL gira come Azure Database for PostgreSQL Flexible Server, e le immagini vengono da un container registry tutto tuo. Ogni notte una pipeline cerca la release più recente di Archestack, la porta in sviluppo e test, e porta poi quella stessa versione in collaudo e produzione dopo che qualcuno l'ha approvata. Su un unico server, Hosting con Docker è la via più semplice.

Hai già un cluster e vuoi Archestack lì oggi stesso, sotto un percorso di un hostname che servi già? Archestack su Kubernetes, la via breve lo fa con cinque file e una pipeline, ed è il punto da cui partire.

Il tuo sistema CI

Cosa gira dove

                  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)
  • L'edge è Caddy, non un ingress controller. Gli indirizzi dei workspace e i domini propri delle aziende ricevono un certificato la prima volta che qualcuno li visita, e Caddy chiede all'API se un hostname appartiene a un workspace prima di richiederne uno. cert-manager non può emettere certificati su richiesta, quindi con un ingress controller quegli indirizzi andrebbero persi. Senza quegli indirizzi, Archestack può anche stare sotto un percorso di un hostname che il cluster serve già.
  • L'indirizzo del visitatore viene conservato (externalTrafficPolicy: Local). Il rate limiter conta i chiamanti anonimi in base all'indirizzo, e se l'indirizzo non venisse conservato tutti i visitatori condividerebbero quello di un nodo.
  • File e backup stanno in un'unica condivisione di Azure Files montata da ogni replica dell'API, così un logo o un backup scritto tramite una replica si trova su tutte. I pacchetti NuGet che usano gli script sono l'eccezione: ogni pod tiene i propri sul suo nodo, finché il pod esiste (api.nugetCache).
  • L'API crea da sola i propri database e li migra all'avvio, quindi le basta un server vuoto. Non c'è un job di migrazione separato.

Versioni e tag

Ogni release di Archestack ha un numero di versione, major.minor.build, come 1.4.23. Il numero di build aumenta a ogni release e riparte da 1 quando cambia la versione major o minor, e un numero può mancare quando una build di release non è arrivata in fondo. Ogni release pubblica su Docker Hub due immagini, nickpeelman/omnicore-api e nickpeelman/omnicore-web, ciascuna con tre tag: la versione, il commit da cui è stata costruita e latest.

Fai il deploy del numero di versione, mai di latest:

  • Non parte nessun rollout. Un aggiornamento da latest a latest lascia invariato il Deployment, quindi Kubernetes tiene i pod che ha.
  • Le repliche divergono. Il chart scarica un'immagine solo quando il nodo non ce l'ha ancora. Un nodo che ha scaricato latest la settimana scorsa continua a eseguire la versione della settimana scorsa, un nodo nuovo esegue quella di questa settimana, e un'unica istanza serve due versioni.
  • Non si torna indietro. La versione precedente non ha un nome a cui tornare.

Per questo la pipeline qui sotto trasforma "più recente" in un numero una sola volta, all'inizio di un'esecuzione, e ogni ambiente riceve quel numero. Può anche seguire una sola linea: 1.4 prende la 1.4.x più recente e non passa mai da sola a 1.5. È in una nuova versione major o minor che arrivano le modifiche più grandi, e l'estensione VS Code avvisa quando si connette a un'API di un'altra versione major o minor.

Prima di iniziare

  • Una sottoscrizione Azure, e la CLI az, kubectl e Helm 3 sulla tua macchina.
  • Un progetto su Azure DevOps, GitHub o GitLab per la pipeline, e lì un repository creato a partire dallo starter kit.
  • Il chart Helm: la cartella infra/helm/archestack del repository Archestack, copiata in quel repository allo stesso percorso.
  • Una zona DNS a cui puoi aggiungere record, oppure un hostname che un cluster serve già tramite il suo ingress controller (via B).

Lo 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

Tieni la pipeline del tuo sistema CI ed elimina le altre due. grep -rn CHANGE-ME . elenca ogni valore ancora da compilare, e deploy.sh rifiuta un ambiente che ne ha ancora uno. I tre script di setup creano poi ogni ambiente in Azure, eseguiti una sola volta da una persona:

# 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

Ogni script crea solo ciò che manca, quindi uno che si è fermato a metà si può semplicemente eseguire di nuovo, e ognuno termina dicendo qual è il passo successivo.

1. Compila le impostazioni

aks/settings.env contiene ciò che tutti gli ambienti condividono:

# 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

Ogni nome Azure ne deriva in aks/environment.sh, che leggono sia il setup sia deploy.sh, così sono sempre d'accordo. I nomi che Azure richiede univoci in tutto il mondo (il registry, il vault e il server di database) finiscono con il tuo prefisso:

# 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
}

Il values.yaml di ogni ambiente contiene i suoi valori del chart e una delle due vie d'accesso: hostname tutti suoi (A), oppure un percorso di un hostname che il cluster serve già (B). Tienine una, elimina l'altra.

# 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: un numero qualsiasi, senza sessioni sticky. Infrastruttura di hosting elenca cosa chiedono più repliche alla rete che le circonda.
  • api.tenantHostSuffix: attiva un indirizzo per ogni workspace e i domini propri delle aziende, solo con la via A.
  • api.persistence: deve essere ReadWriteMany non appena le repliche girano su più di un nodo.
  • observability.enabled: Grafana, Prometheus, Tempo e Loki dentro il cluster. Su AKS i loro pannelli per host e container restano vuoti; nodi e container sono coperti da Container Insights.

Sotto un percorso di un hostname che hai già

La via B mette Archestack sotto un percorso di un hostname che l'ingress controller del cluster serve già, accanto alle applicazioni che ci sono già:

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)

Non servono un nuovo record DNS, un indirizzo pubblico né un certificato proprio, e le persone continuano a usare l'indirizzo che già conoscono. Il chart lascia fuori Caddy e genera un unico Ingress per i tre percorsi; al builder e all'API viene comunicato il loro prefisso, così ogni link che scrivono lo contiene.

# --- 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
  • Un cluster che hai già. L'hostname vive su un cluster, di solito uno che esisteva già prima di Archestack. Copia names.env.example in aks/environments/<env>/names.env e il setup lascia quel cluster com'è: permette al cluster di scaricare dal registry e aggiunge alla sua rete una subnet per il database. Salta setup/2-address.sh.
  • className è l'ingress class che serve l'hostname. Il chart imposta le annotazioni di ingress-nginx per gli upload di grandi dimensioni e le connessioni di lunga durata; un altro controller ha bisogno dei propri equivalenti.
  • Il certificato deve essere un Secret nel namespace archestack, perché un Ingress usa solo i Secret del proprio namespace. Copia lì il certificato dell'hostname, oppure fallo emettere lì da cert-manager.
  • Non riscrivere il percorso (niente rewrite-target). L'API deve vedere il suo prefisso per scrivere i propri indirizzi.
  • Tieni i proxy di accesso fuori dal percorso. Uno che sostituisce l'header Authorization rompe ogni chiamata all'API: l'app pubblicata, le chiavi della Data API, i token di deploy, VS Code e MCP portano tutti il proprio.
  • L'hostname è condiviso. I token di accesso stanno nello storage del browser per quell'indirizzo, che ogni applicazione su di esso può leggere, quindi uno script iniettato in una qualsiasi di esse raggiunge le sessioni di Archestack. Due istanze Archestack sullo stesso hostname disconnettono gli utenti l'una dell'altra.
  • Non disponibili in questo modo: gli indirizzi dei workspace e i domini propri delle aziende, che hanno bisogno dei certificati su richiesta di Caddy, e Grafana, che l'Ingress non instrada. Raggiungi Grafana con 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 e database

#!/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"

Un unico registry serve tutti gli ambienti, così una versione viene importata una sola volta e la produzione esegue esattamente l'immagine che ha eseguito il collaudo. La password del database la genera lo script e va direttamente in Key Vault: nessuno ha bisogno di conoscerla. Capacità e dimensionamento riporta le cifre misurate dietro le dimensioni, che sono un punto di partenza per la produzione.

Ogni workspace ha due database. Un workspace inattivo non tiene aperta nessuna connessione, ma un server con molto carico ha bisogno di margine: il limite di 50 connessioni del tier Burstable è troppo basso. L'API non ha bisogno di estensioni né di un superuser, solo di un utente admin che possa creare database.

3. Indirizzo pubblico e 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"

Solo per la via A. Fai puntare all'indirizzo editor, api e metrics nella tua zona, più un wildcard * per gli indirizzi propri dei workspace. Caddy ottiene i suoi certificati da Let's Encrypt, quindi le porte 80 e 443 devono essere raggiungibili da internet.

4. Le password e l'identità della 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
  • La password SMTP viene richiesta e va direttamente in Key Vault. Un'istanza che non invia posta la salta.
  • La pipeline accede senza un segreto memorizzato. Ogni ambiente ha un'identità tutta sua, che si fida solo del token che il suo sistema CI consegna ai job di quell'ambiente. Può accedere al cluster, leggere le password e importare nel registry, nient'altro.
  • Il namespace lo crei tu, quindi alla pipeline servono permessi solo al suo interno. Un cluster con accesso tramite Entra ID verifica quei permessi da sé, tramite Azure RBAC o un RoleBinding di Kubernetes; lo script assegna l'uno o l'altro, a seconda di quale usa il cluster.
  • Lo script termina indicando cosa impostare nel tuo sistema CI: variabili di GitHub, variabili di GitLab, o la service connection di Azure DevOps.

5. La prima installazione

La prima installazione è lo script che esegue la pipeline, avviato a mano. Una prima esecuzione della pipeline fa lo stesso.

# 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

Apri subito il builder e crea il primo admin. Finché nessuno lo fa, chi raggiunge per primo il builder diventa l'admin. Per chiudere del tutto questa finestra, imposta in anticipo il primo admin con api.initialAdmin (vedi il values.yaml del chart).

La release più recente, in automatico

resolve-version.sh chiede al registry tutti i tag di entrambe le immagini e sceglie la versione di cui questa esecuzione fa il deploy:

#!/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
  • Le versioni vengono confrontate come numeri, quindi 1.0.10 è più recente di 1.0.9, e un numero mancante viene saltato invece che atteso.
  • Conta solo una versione che hanno entrambe le immagini. Una build di release che si è fermata tra il push dell'API e il push dell'immagine web non viene mai distribuita a metà.
  • Anche una versione esatta viene verificata. Una versione che le immagini non hanno ferma l'esecuzione prima che si tocchi qualsiasi cosa.
  • La versione viaggia nel modo in cui ogni sistema CI passa i valori: Azure DevOps dà all'esecuzione il nome della versione, GitHub Actions ne fa un output del job e la mostra nel riepilogo dell'esecuzione, e GitLab CI la passa ai job successivi come $VERSION.

deploy.sh porta poi un ambiente a quella versione:

#!/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
  • Le immagini finiscono prima nel tuo registry. I nodi scaricano da Azure, dentro la regione, quindi aggiungere o sostituire un nodo non aspetta mai Docker Hub né i suoi limiti sui pull anonimi. Se un import venisse rifiutato per quei limiti, dai ad az acr import un account Docker Hub con --username e --password.
  • Le password vengono passate a ogni aggiornamento. Il chart scrive ogni volta la password SMTP a partire dai valori, quindi un aggiornamento senza di essa lascia l'istanza senza la possibilità di inviare posta. Viaggiano in file che solo il job può leggere, e Azure DevOps e GitHub Actions le mascherano nel log.
  • --wait --timeout 30m aspetta che ogni Deployment sia pronto. L'API migra ogni workspace prima di rispondere, quindi una versione con migrazioni richiede un po' di tempo su un'istanza con molti workspace.
  • Niente --atomic. Un rollout fallito viene lasciato com'è per esaminarlo invece di essere annullato, perché la nuova versione potrebbe aver già migrato i database. Nel frattempo i vecchi pod continuano a servire.

La pipeline

Il kit contiene la stessa pipeline per tre sistemi CI. Ognuna esegue i due script qui sopra, nello stesso ordine:

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
  • Ogni notte la pianificazione porta la release più recente in sviluppo e test.
  • Un'esecuzione pianificata si ferma dopo l'ambiente di test. Collaudo e produzione si muovono solo con un'esecuzione avviata a mano. Indicale la versione che gira in test, come 1.4.23, e collaudo e produzione ricevono esattamente ciò che è stato testato. Lasciata su newest, prende qualunque versione sia la più recente in quel momento.
  • Una versione per esecuzione. La versione viene determinata una sola volta, prima del primo ambiente, così una release pubblicata mentre un'esecuzione aspetta un'approvazione non si infila in produzione.
  • Un ambiente che è già a quella versione non fa nessun rollout. La specifica dei pod è invariata, quindi Kubernetes li lascia in esecuzione. Una modifica al suo values.yaml viene comunque applicata.
Il tuo sistema CI

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
  • Le service connection si chiamano sc-archestack-<env>, una per ambiente, ciascuna con l'identità di quell'ambiente. setup/3-pipeline.sh stampa cosa compilare e prende l'issuer e il subject che la connessione mostra.
  • Approvazioni. In Pipelines, Environments, aggiungi un controllo Approvals a archestack-acc e archestack-prd, e un controllo Exclusive lock a tutti e quattro, così due esecuzioni non fanno mai il rollout sullo stesso ambiente nello stesso momento.
  • always: true esegue la pianificazione anche senza un nuovo commit, perché una release è una nuova immagine, non una modifica in questo 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 }}" }

Il deploy di ogni ambiente lo fa lo stesso workflow riutilizzabile:

# .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 }}
  • Ambienti. Crea archestack-dev, -tst, -acc e -prd in Settings, Environments, e aggiungi revisori obbligatori a collaudo e produzione. Su un repository privato, gli ambienti richiedono GitHub Pro, Team o Enterprise.
  • Accesso. azure/login scambia il token del job con l'identità dell'ambiente, il cui client id è una variabile dell'ambiente, e setup/3-pipeline.sh stampa i comandi gh per impostarla. L'identità si fida solo dei job del proprio ambiente, quindi un job per il test non può mai fare il deploy in produzione.
  • concurrency impedisce che due esecuzioni facciano il rollout sullo stesso ambiente nello stesso momento.
  • La pianificazione gira solo dal branch predefinito, e su un repository pubblico GitHub la disattiva dopo 60 giorni senza attività nel repository.

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 }
  • La pianificazione sta in GitLab invece che nel file: in Build, Pipeline schedules, aggiungine una per main a 0 3 * * *. Un push non avvia niente.
  • Collaudo e produzione sono job manuali che bloccano la pipeline: la produzione si può avviare solo dopo che il collaudo è stato eseguito. Su GitLab Premium, proteggi anche quegli ambienti e richiedi le approvazioni; un job approvato va comunque avviato a mano.
  • Accesso. L'ID token del job viene scambiato con l'identità dell'ambiente, il cui client id è una variabile limitata a quell'ambiente. Il token di GitLab indica il branch, non l'ambiente, quindi proteggi main. Su un'istanza self-managed, imposta il suo indirizzo come GITLAB_URL e come aud del token.
  • resource_group impedisce che due pipeline facciano il rollout sullo stesso ambiente nello stesso momento.
  • L'immagine è fissata, e Helm viene scaricato a una versione fissata. Aggiornali di proposito.

Aggiornamenti senza downtime

  • Un nuovo pod dell'API alla volta. Il chart aggiunge un pod prima di rimuoverne uno (maxSurge: 1, maxUnavailable: 0), e un vecchio pod se ne va solo quando uno nuovo è pronto.
  • L'avvio avviene a turno. Le repliche che partono insieme preparano i database una dopo l'altra sotto un lock, quindi due di loro non migrano mai nello stesso momento. Una startup probe dà a ogni pod 15 minuti prima che Kubernetes ci rinunci.
  • Gli aggiornamenti dei nodi tengono l'API attiva. Un PodDisruptionBudget mantiene in esecuzione almeno un pod dell'API mentre AKS svuota un nodo.
  • Caddy si riavvia solo quando cambia la sua configurazione, per esempio per un nuovo hostname. È un unico pod con i suoi certificati su un disco tutto suo, quindi quel riavvio è una breve interruzione. Un aggiornamento di Archestack non lo tocca.

Tornare indietro

Esegui la pipeline con la versione precedente, come 1.4.22, oppure fai il rollback della release con Helm:

helm history archestack -n archestack                          # the revisions, newest last
helm rollback archestack -n archestack --wait --timeout 30m     # back to the one before

In entrambi i casi i database restano come li ha lasciati la versione più nuova: li ha migrati all'avvio, e una versione più vecchia non lo annulla. Tornare indietro fa girare codice più vecchio su uno schema più nuovo, quindi preferisci la release successiva che risolve il problema. Riportare indietro anche lo schema significa ripristinare il server con il ripristino point-in-time di Flexible Server.

Trappole

  • Il chart viene dal tuo repository, le immagini dal registry. La pipeline prende da sola le immagini più recenti, mai un chart più recente. Quando passi a una nuova versione major o minor, riprendi il chart dal repository Archestack.
  • Le immagini di osservabilità non hanno un numero di versione. Hanno come tag un commit e latest, vengono da Docker Hub e seguono observability.imageTag. Impostalo su uno dei loro tag di commit per tenerle ferme.
  • Una release per namespace. Grafana e Tempo trovano Prometheus, Tempo e Loki tramite nomi di servizio fissi.
  • Dietro PgBouncer (la porta 6432 di Flexible Server), imposta postgres.clusterConnectionString su una connessione diretta sulla 5432. Le repliche si parlano tramite una connessione PostgreSQL che deve restare aperta.
  • I volumi e il Secret del chart sopravvivono a helm uninstall. Eliminali a mano quando è davvero quello che vuoi.

Dove andare dopo

Capacità e dimensionamento riporta le cifre misurate per dimensionare il cluster e il database. Workspace e hosting spiega come un'istanza serve molti workspace. Una volta che l'istanza gira, Strategia di deployment descrive come fare il deploy della tua app nei suoi workspace da un repository.