Guide · 20 min

Héberger Archestack sur AKS

Ce guide installe une instance Archestack sur Azure Kubernetes Service (AKS), puis la tient à jour d'elle-même. Le chart Helm fait tourner l'API sous forme de plusieurs réplicas identiques, le builder et l'application publiée, ainsi que Caddy en frontal avec des certificats automatiques. PostgreSQL tourne en tant qu'Azure Database for PostgreSQL Flexible Server, et les images proviennent d'un registre de conteneurs qui vous est propre. Chaque nuit, un pipeline recherche la release d'Archestack la plus récente, l'amène en développement et en test, puis mène cette même version jusqu'en recette et en production une fois que quelqu'un l'a approuvée. Sur un seul serveur, Héberger avec Docker est la manière la plus simple.

Vous avez déjà un cluster et voulez y mettre Archestack dès aujourd'hui, sous un chemin d'un nom d'hôte que vous servez déjà ? Archestack sur Kubernetes, la manière courte le fait avec cinq fichiers et un pipeline, et c'est par là qu'il faut commencer.

Votre système de CI

Ce qui tourne où

                  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 est le frontal, pas un ingress controller. Les adresses des espaces de travail et les domaines propres des entreprises reçoivent un certificat la première fois que quelqu'un les visite, et Caddy demande à l'API si un nom d'hôte appartient à un espace de travail avant d'en demander un. cert-manager ne sait pas émettre de certificats à la demande, si bien qu'un ingress controller perdrait ces adresses. Sans ces adresses, Archestack peut aussi se placer sous un chemin d'un nom d'hôte que le cluster sert déjà.
  • L'adresse du visiteur est conservée (externalTrafficPolicy: Local). Le limiteur de débit compte les appelants anonymes par adresse, et sans cela tous les visiteurs partageraient l'adresse d'un nœud.
  • Les fichiers et les sauvegardes forment un seul partage sur Azure Files, que chaque réplica de l'API monte, si bien qu'un logo ou une sauvegarde écrits via un réplica sont présents sur tous. Les paquets NuGet qu'utilisent les scripts font exception : chaque pod garde les siens sur son nœud, aussi longtemps que le pod existe (api.nugetCache).
  • L'API crée ses propres bases de données et les migre à son démarrage, si bien qu'un serveur vide est tout ce dont elle a besoin. Il n'existe pas de tâche de migration distincte.

Versions et tags

Chaque release d'Archestack porte un numéro de version, major.minor.build, comme 1.4.23. Le numéro de build augmente à chaque release et repart à 1 quand la version majeure ou mineure change, et un numéro peut manquer lorsque le build d'une release ne s'est pas terminé. Chaque release publie deux images sur Docker Hub, nickpeelman/omnicore-api et nickpeelman/omnicore-web, chacune avec trois tags : la version, le commit à partir duquel elle a été construite, et latest.

Déployez le numéro de version, jamais latest :

  • Rien ne se déploie. Une mise à niveau de latest vers latest laisse le Deployment inchangé, si bien que Kubernetes garde les pods qu'il a.
  • Les réplicas divergent. Le chart ne récupère une image que lorsque le nœud ne l'a pas encore. Un nœud qui a récupéré latest la semaine dernière continue de faire tourner la version de la semaine dernière, un nouveau nœud fait tourner celle de cette semaine, et une même instance sert deux versions.
  • Pas de retour possible. La version précédente n'a pas de nom auquel revenir.

Le pipeline ci-dessous transforme donc "la plus récente" en un numéro une seule fois, au début d'une exécution, et chaque environnement reçoit ce numéro. Il peut aussi suivre une seule ligne de versions : 1.4 prend la 1.4.x la plus récente et ne passe jamais à 1.5 de lui-même. C'est dans une nouvelle version majeure ou mineure qu'arrivent les changements les plus importants, et l'extension VS Code avertit quand elle se connecte à une API d'une autre version majeure ou mineure.

Avant de commencer

  • Un abonnement Azure, ainsi que la CLI az, kubectl et Helm 3 sur votre machine.
  • Un projet sur Azure DevOps, GitHub ou GitLab pour le pipeline, et un dépôt sur cette même plateforme, créé à partir du kit de démarrage.
  • Le chart Helm : le dossier infra/helm/archestack du dépôt Archestack, copié dans ce dépôt au même chemin.
  • Une zone DNS dans laquelle vous pouvez ajouter des enregistrements, ou un nom d'hôte qu'un cluster sert déjà via son ingress controller (voie B).

Le kit de démarrage

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

Gardez le pipeline de votre système de CI et supprimez les deux autres. grep -rn CHANGE-ME . liste chaque valeur qui reste à renseigner, et deploy.sh refuse un environnement qui en contient encore une. Les trois scripts de mise en place, qu'une personne exécute une seule fois, créent ensuite chaque environnement dans Azure :

# 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

Chaque script ne crée que ce qui manque, si bien qu'un script arrêté à mi-chemin peut simplement être relancé, et chacun se termine en indiquant la suite.

1. Renseigner les paramètres

aks/settings.env contient ce que tous les environnements partagent :

# 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

Chaque nom Azure en découle dans aks/environment.sh, que la mise en place et deploy.sh lisent tous les deux, si bien qu'ils concordent toujours. Les noms qu'Azure exige uniques au niveau mondial (le registre, le coffre de clés et le serveur de base de données) se terminent par votre préfixe :

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

Le values.yaml de chaque environnement contient ses valeurs du chart et l'une des deux voies d'accès : des noms d'hôte qui lui sont propres (A), ou un chemin d'un nom d'hôte que le cluster sert déjà (B). Gardez-en une, supprimez l'autre.

# 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 : n'importe quel nombre, sans sessions persistantes. Infrastructure d'hébergement liste ce que plusieurs réplicas demandent au réseau qui les entoure.
  • api.tenantHostSuffix : active une adresse par espace de travail et les domaines propres des entreprises, avec la voie A uniquement.
  • api.persistence : doit être ReadWriteMany dès que des réplicas tournent sur plus d'un nœud.
  • observability.enabled : Grafana, Prometheus, Tempo et Loki à l'intérieur du cluster. Leurs panneaux d'hôtes et de conteneurs restent vides sur AKS ; Container Insights couvre les nœuds et les conteneurs.

Sous un chemin d'un nom d'hôte que vous avez déjà

La voie B place Archestack sous un chemin d'un nom d'hôte que l'ingress controller du cluster sert déjà, à côté des applications qui s'y trouvent déjà :

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)

Elle ne demande ni nouvel enregistrement DNS, ni adresse publique, ni certificat propre, et les utilisateurs continuent d'utiliser l'adresse qu'ils connaissent déjà. Le chart laisse Caddy de côté et génère un seul Ingress pour les trois chemins ; le builder et l'API reçoivent leur préfixe, si bien que chaque lien qu'ils écrivent le porte.

# --- 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 que vous avez déjà. Le nom d'hôte se trouve sur un cluster, généralement un cluster qui existait avant Archestack. Copiez names.env.example vers aks/environments/<env>/names.env, et la mise en place laisse ce cluster tel quel : elle autorise le cluster à récupérer les images du registre et ajoute à son réseau un sous-réseau pour la base de données. Ignorez setup/2-address.sh.
  • className est la classe d'ingress qui sert le nom d'hôte. Le chart définit les annotations d'ingress-nginx pour les envois de fichiers volumineux et les connexions de longue durée ; un autre ingress controller a besoin de ses propres équivalents.
  • Le certificat doit être un Secret dans le namespace archestack, car un Ingress n'utilise que les Secrets de son propre namespace. Copiez-y le certificat du nom d'hôte, ou demandez à cert-manager de l'y émettre.
  • Ne réécrivez pas le chemin (pas de rewrite-target). L'API doit voir son préfixe pour écrire ses propres adresses.
  • Tenez les proxys de connexion à l'écart. Un proxy qui remplace l'en-tête Authorization casse chaque appel à l'API : l'application publiée, les clés de la Data API, les jetons de déploiement, VS Code et MCP apportent tous le leur.
  • Le nom d'hôte est partagé. Les jetons de connexion sont conservés dans le stockage du navigateur pour cette adresse, que chaque application qui s'y trouve peut lire, si bien qu'un script injecté dans l'une d'elles atteint les sessions Archestack. Deux instances Archestack sur un même nom d'hôte déconnectent mutuellement leurs utilisateurs.
  • Non disponibles par cette voie : les adresses des espaces de travail et les domaines propres des entreprises, qui ont besoin des certificats à la demande de Caddy, ainsi que Grafana, que l'Ingress ne route pas. Accédez à Grafana avec 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. Registre, cluster et base de données

#!/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 seul registre sert tous les environnements, si bien qu'une version n'est importée qu'une fois et que la production fait tourner exactement l'image que la recette a fait tourner. Le mot de passe de la base de données est généré par le script et va directement dans Key Vault : personne n'a besoin de le connaître. Capacité et dimensionnement donne les chiffres mesurés derrière les tailles, qui sont un point de départ pour la production.

Chaque espace de travail possède deux bases de données. Un espace de travail inactif n'occupe aucune connexion, mais un serveur chargé a besoin de marge : la limite de 50 connexions du niveau Burstable est trop basse. L'API n'a besoin ni d'extensions ni de superutilisateur, seulement d'un utilisateur admin capable de créer des bases de données.

3. Adresse publique et 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"

Voie A uniquement. Faites pointer editor, api et metrics sous votre zone vers l'adresse, ainsi qu'un enregistrement générique * pour les adresses propres des espaces de travail. Caddy obtient ses certificats auprès de Let's Encrypt, donc les ports 80 et 443 doivent être joignables depuis internet.

4. Mots de passe et identité du 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
  • Le mot de passe SMTP est demandé et va directement dans Key Vault. Une instance qui n'envoie pas de mails s'en passe.
  • Le pipeline se connecte sans secret stocké. Chaque environnement a sa propre identité, qui ne fait confiance qu'au jeton que son système de CI remet aux jobs de cet environnement. Elle peut se connecter au cluster, lire les mots de passe et importer dans le registre, rien d'autre.
  • C'est vous qui créez le namespace, si bien que le pipeline n'a besoin de droits qu'à l'intérieur de celui-ci. Un cluster avec la connexion Entra ID vérifie lui-même ces droits, via Azure RBAC ou un RoleBinding Kubernetes ; le script les accorde par celui des deux que le cluster utilise.
  • Le script se termine par ce qu'il faut configurer dans votre système de CI : variables GitHub, variables GitLab ou connexion de service Azure DevOps.

5. La première installation

La première installation, c'est le script que le pipeline exécute, lancé à la main. Une première exécution du pipeline fait la même chose.

# 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

Ouvrez immédiatement le builder et créez le premier admin. Tant que personne ne l'a fait, la première personne qui y accède devient l'admin. Pour ne laisser aucune fenêtre ouverte, définissez d'avance le premier admin avec api.initialAdmin (voir le values.yaml du chart).

La release la plus récente, automatiquement

resolve-version.sh demande au registre tous les tags des deux images et choisit la version que cette exécution déploie :

#!/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
  • Les versions sont comparées comme des nombres, si bien que 1.0.10 est plus récente que 1.0.9, et qu'un numéro manquant est ignoré plutôt qu'attendu.
  • Seule compte une version que portent les deux images. Un build de release qui s'est arrêté entre l'envoi de l'image de l'API et celui de l'image web n'est jamais déployé à moitié.
  • Une version exacte est vérifiée elle aussi. Une version que les images n'ont pas arrête l'exécution avant que quoi que ce soit ne soit modifié.
  • La version circule de la manière dont chaque système de CI transmet les valeurs : Azure DevOps nomme l'exécution d'après elle, GitHub Actions en fait une sortie de job et l'affiche dans le résumé de l'exécution, et GitLab CI la transmet aux jobs suivants sous la forme de $VERSION.

deploy.sh amène ensuite un environnement à cette 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
  • Les images vont d'abord dans votre registre. Les nœuds récupèrent les images depuis Azure, au sein de la région, si bien qu'ajouter ou remplacer un nœud n'attend jamais Docker Hub ni ses limites sur les téléchargements anonymes. Si un import est refusé à cause de ces limites, donnez à az acr import un compte Docker Hub avec --username et --password.
  • Les mots de passe sont transmis à chaque mise à niveau. Le chart écrit à chaque fois le mot de passe SMTP à partir des valeurs, si bien qu'une mise à niveau sans lui laisse l'instance incapable d'envoyer des mails. Ils transitent par des fichiers que seul le job peut lire, et Azure DevOps et GitHub Actions les masquent dans le journal.
  • --wait --timeout 30m attend que chaque Deployment soit prêt. L'API migre chaque espace de travail avant de répondre, si bien qu'une version avec des migrations prend un certain temps sur une instance qui compte de nombreux espaces de travail.
  • Pas de --atomic. Un déploiement en échec est laissé tel quel pour être examiné plutôt qu'annulé, car la nouvelle version a peut-être déjà migré les bases de données. Les anciens pods continuent de servir entre-temps.

Le pipeline

Le kit contient le même pipeline pour trois systèmes de CI. Chacun exécute les deux scripts ci-dessus, dans le même ordre :

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
  • Chaque nuit, la planification amène la release la plus récente en développement et en test.
  • Une exécution planifiée s'arrête après le test. La recette et la production ne changent de version que lors d'une exécution lancée à la main. Donnez-lui la version qui tourne en test, comme 1.4.23, et la recette et la production reçoivent exactement ce qui a été testé. Laissée sur newest, elle prend ce qui est le plus récent à ce moment-là.
  • Une version par exécution. La version est résolue une seule fois, avant le premier environnement, si bien qu'une release publiée pendant qu'une exécution attend une approbation ne se glisse pas en production.
  • Un environnement déjà sur cette version ne redéploie rien. La spécification des pods est inchangée, si bien que Kubernetes les laisse tourner. Une modification de son values.yaml est quand même appliquée.
Votre système de 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
  • Les connexions de service s'appellent sc-archestack-<env>, une par environnement, chacune avec l'identité de cet environnement. setup/3-pipeline.sh affiche ce qu'il faut renseigner et prend en entrée l'émetteur et le sujet qu'affiche la connexion.
  • Approbations. Sous Pipelines, Environments, ajoutez une vérification Approvals à archestack-acc et archestack-prd, et une vérification Exclusive lock aux quatre, pour que deux exécutions ne déploient jamais dans un même environnement au même moment.
  • always: true déclenche l'exécution planifiée sans nouveau commit, car une release est une nouvelle image, pas une modification de ce dépôt.

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

Chaque environnement est déployé par le même workflow réutilisable :

# .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 }}
  • Environnements. Créez archestack-dev, -tst, -acc et -prd sous Settings, Environments, et ajoutez des relecteurs requis à la recette et à la production. Sur un dépôt privé, les environnements nécessitent GitHub Pro, Team ou Enterprise.
  • Connexion. azure/login échange le jeton du job contre l'identité de l'environnement, dont l'identifiant client est une variable de cet environnement pour laquelle setup/3-pipeline.sh affiche les commandes gh. L'identité ne fait confiance qu'aux jobs de son propre environnement, si bien qu'un job destiné au test ne peut jamais déployer en production.
  • concurrency empêche deux exécutions de déployer dans un même environnement au même moment.
  • La planification ne s'exécute qu'à partir de la branche par défaut, et sur un dépôt public, GitHub la désactive après 60 jours sans activité dans le dépôt.

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 planification se trouve dans GitLab plutôt que dans le fichier : sous Build, Pipeline schedules, ajoutez-en une pour main à 0 3 * * *. Un push ne lance rien.
  • La recette et la production sont des jobs manuels qui bloquent le pipeline : la production ne peut être lancée qu'une fois la recette exécutée. Sur GitLab Premium, protégez aussi ces environnements et exigez des approbations ; un job approuvé se lance quand même à la main.
  • Connexion. Le jeton d'ID du job est échangé contre l'identité de l'environnement, dont l'identifiant client est une variable limitée à cet environnement. Le jeton de GitLab nomme la branche, pas l'environnement, donc protégez main. Sur une instance autogérée, définissez son adresse comme GITLAB_URL et comme aud du jeton.
  • resource_group empêche deux pipelines de déployer dans un même environnement au même moment.
  • La version de l'image est fixée, et Helm est téléchargé dans une version fixée. Faites-les évoluer délibérément.

Mises à niveau sans interruption de service

  • Un nouveau pod d'API à la fois. Le chart ajoute un pod avant d'en retirer un (maxSurge: 1, maxUnavailable: 0), et un ancien pod ne part qu'une fois qu'un nouveau est prêt.
  • Le démarrage se fait à tour de rôle. Les réplicas qui démarrent ensemble préparent les bases de données l'un après l'autre sous un verrou, si bien que deux d'entre eux ne migrent jamais en même temps. Une startup probe laisse à chaque pod 15 minutes avant que Kubernetes ne l'abandonne.
  • Les mises à niveau des nœuds laissent l'API disponible. Un PodDisruptionBudget maintient au moins un pod d'API en fonctionnement pendant qu'AKS draine un nœud.
  • Caddy ne redémarre que lorsque sa propre configuration change, par exemple pour un nouveau nom d'hôte. C'est un seul pod, avec ses certificats sur un disque qui lui est propre, donc ce redémarrage n'est qu'une courte interruption. Une mise à niveau d'Archestack n'y touche pas.

Revenir en arrière

Exécutez le pipeline avec la version précédente, comme 1.4.22, ou faites un retour arrière de la release avec Helm :

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

Dans les deux cas, les bases de données restent telles que la version plus récente les a laissées : elle les a migrées à son démarrage, et une version plus ancienne ne défait pas cela. Revenir en arrière fait tourner du code plus ancien sur un schéma plus récent ; préférez donc la prochaine release qui corrige le problème. Ramener aussi le schéma en arrière implique de restaurer le serveur avec la restauration à un point dans le temps de Flexible Server.

Pièges

  • Le chart vient de votre dépôt, les images du registre. Le pipeline prend de lui-même les images les plus récentes, jamais un chart plus récent. Quand vous passez à une nouvelle version majeure ou mineure, reprenez le chart dans le dépôt Archestack.
  • Les images d'observabilité ne portent pas de numéro de version. Elles ont pour tags un commit et latest, viennent de Docker Hub et suivent observability.imageTag. Réglez-le sur l'un de leurs tags de commit pour les figer.
  • Une release par namespace. Grafana et Tempo trouvent Prometheus, Tempo et Loki par des noms de service fixes.
  • Derrière PgBouncer (le port 6432 de Flexible Server), réglez postgres.clusterConnectionString sur une connexion directe sur le port 5432. Les réplicas communiquent entre eux par une connexion PostgreSQL qui doit rester ouverte.
  • Les volumes et le Secret propre au chart survivent à helm uninstall. Supprimez-les à la main si c'est bien ce que vous voulez.

Pour aller plus loin

Capacité et dimensionnement donne les chiffres mesurés pour dimensionner le cluster et la base de données. Espaces de travail et hébergement explique comment une instance sert de nombreux espaces de travail. Une fois l'instance en service, Stratégie de déploiement explique comment déployer votre application dans ses espaces de travail à partir d'un dépôt.