Guía · 20 min

Alojar Archestack en AKS

Esta guía instala una instancia de Archestack en Azure Kubernetes Service (AKS) y después la mantiene actualizada por sí sola. El chart de Helm ejecuta la API como varias réplicas idénticas, el builder y la aplicación publicada, y Caddy como proxy de borde con certificados automáticos. PostgreSQL se ejecuta como Azure Database for PostgreSQL Flexible Server, y las imágenes vienen de un registro de contenedores propio. Un pipeline busca cada noche la release más reciente de Archestack, la lleva a desarrollo y pruebas, y lleva esa misma versión a aceptación y producción en cuanto alguien la aprueba. En un único servidor, Alojamiento con Docker es la vía más sencilla.

¿Ya tienes un clúster y quieres Archestack en él hoy mismo, bajo una ruta de un nombre de host que ya sirves? Archestack en Kubernetes, la vía corta lo hace con cinco archivos y un pipeline, y es el punto de partida.

Tu sistema de CI

Qué se ejecuta dónde

                  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 es el proxy de borde, no un controlador de ingress. Las direcciones de los espacios de trabajo y los dominios propios de las empresas reciben un certificado la primera vez que alguien los visita, y Caddy pregunta a la API si un nombre de host pertenece a un espacio de trabajo antes de solicitar uno. cert-manager no puede emitir certificados bajo demanda, así que un controlador de ingress perdería esas direcciones. Sin ellas, Archestack también puede ir bajo una ruta de un nombre de host que el clúster ya sirve.
  • Se conserva la dirección del visitante (externalTrafficPolicy: Local). El limitador de peticiones cuenta las peticiones anónimas según la dirección de quien llama, y sin esto todos los visitantes compartirían la dirección de un nodo.
  • Los archivos y las copias de seguridad están en un único recurso compartido de Azure Files que montan todas las réplicas de la API, así que un logotipo o una copia de seguridad escritos a través de una réplica están en todas ellas. Los paquetes NuGet que usan los scripts son la excepción: cada pod guarda los suyos en su nodo mientras el pod exista (api.nugetCache).
  • La API crea sus propias bases de datos y las migra al arrancar, así que solo necesita un servidor vacío. No hay ningún job de migración aparte.

Versiones y etiquetas

Cada release de Archestack tiene un número de versión, major.minor.build, como 1.4.23. El número de compilación aumenta con cada release y vuelve a empezar en 1 cuando cambia la versión mayor o menor, y puede faltar un número cuando una compilación de release no terminó. Cada release publica dos imágenes en Docker Hub, nickpeelman/omnicore-api y nickpeelman/omnicore-web, cada una con tres etiquetas: la versión, el commit a partir del que se construyó y latest.

Despliega el número de versión, nunca latest:

  • No se despliega nada. Una actualización de latest a latest deja el Deployment sin cambios, así que Kubernetes mantiene los pods que tiene.
  • Las réplicas se desincronizan. El chart solo descarga una imagen cuando el nodo todavía no la tiene. Un nodo que descargó latest la semana pasada sigue ejecutando la versión de la semana pasada, un nodo nuevo ejecuta la de esta semana, y una misma instancia sirve dos versiones.
  • No hay vuelta atrás. La versión anterior no tiene ningún nombre al que volver.

Por eso, el pipeline de más abajo convierte "la más reciente" en un número una sola vez, al inicio de una ejecución, y todos los entornos reciben ese número. También puede seguir una línea: 1.4 toma la 1.4.x más reciente y nunca pasa a 1.5 por sí solo. Los cambios de mayor calado llegan con una nueva versión mayor o menor, y la extensión de VS Code avisa cuando se conecta a una API de otra versión mayor o menor.

Antes de empezar

  • Una suscripción de Azure y, en tu máquina, la CLI az, kubectl y Helm 3.
  • Un proyecto en Azure DevOps, GitHub o GitLab para el pipeline, y allí un repositorio creado a partir del kit de inicio.
  • El chart de Helm: la carpeta infra/helm/archestack del repositorio de Archestack, copiada en ese repositorio en la misma ruta.
  • Una zona DNS a la que puedas añadir registros, o un nombre de host que un clúster ya sirve a través de su controlador de ingress (vía B).

El kit de inicio

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

Quédate con el pipeline de tu sistema de CI y borra los otros dos. grep -rn CHANGE-ME . enumera todos los valores que faltan por rellenar, y deploy.sh rechaza un entorno que todavía tenga alguno. Después, los tres scripts de configuración crean cada entorno en Azure; los ejecuta una persona, una sola vez:

# 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

Cada script crea solo lo que falta, así que uno que se detuvo a medias puede volver a ejecutarse sin más, y cada uno termina indicando qué viene después.

1. Rellena los ajustes

aks/settings.env contiene lo que comparten todos los entornos:

# 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

Todos los nombres de Azure se derivan de él en aks/environment.sh, que leen tanto los scripts de configuración como deploy.sh, así que siempre coinciden. Los nombres que Azure necesita únicos en todo el mundo (el registro, el Key Vault y el servidor de base de datos) terminan en tu prefijo:

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

El values.yaml de cada entorno contiene sus valores del chart y una de dos vías de acceso: nombres de host propios (A) o una ruta de un nombre de host que el clúster ya sirve (B). Quédate con una y borra la otra.

# 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: cualquier número, sin sesiones persistentes. Infraestructura de alojamiento enumera lo que varias réplicas piden a la red que las rodea.
  • api.tenantHostSuffix: activa una dirección por espacio de trabajo y los dominios propios de las empresas, solo con la vía A.
  • api.persistence: tiene que ser ReadWriteMany en cuanto las réplicas se ejecutan en más de un nodo.
  • observability.enabled: Grafana, Prometheus, Tempo y Loki dentro del clúster. Sus paneles de host y de contenedores se quedan vacíos en AKS; Container Insights cubre los nodos y los contenedores.

Bajo una ruta de un nombre de host que ya tienes

La vía B pone Archestack bajo una ruta de un nombre de host que ya sirve el controlador de ingress del clúster, junto a las aplicaciones que ya están en él:

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)

No necesita ningún registro DNS nuevo, ninguna dirección pública ni un certificado propio, y la gente sigue usando la dirección que ya conoce. El chart deja fuera Caddy y genera un único Ingress para las tres rutas; al builder y a la API se les indica su prefijo, así que cada enlace que escriben lo lleva.

# --- 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 clúster que ya tienes. El nombre de host vive en un clúster, normalmente uno que ya estaba ahí antes que Archestack. Copia names.env.example a aks/environments/<env>/names.env y los scripts de configuración dejan ese clúster como está: permiten que el clúster descargue del registro y añaden a su red una subred para la base de datos. Omite setup/2-address.sh.
  • className es la clase de ingress que sirve el nombre de host. El chart define las anotaciones de ingress-nginx para subidas grandes y conexiones de larga duración; otro controlador necesita sus propios equivalentes.
  • El certificado tiene que ser un Secret en el namespace archestack, porque un Ingress solo usa Secrets de su propio namespace. Copia allí el certificado del nombre de host, o haz que cert-manager lo emita allí.
  • No reescribas la ruta (sin rewrite-target). La API necesita ver su prefijo para escribir sus propias direcciones.
  • Mantén los proxies de inicio de sesión fuera de esta ruta. Uno que sustituya la cabecera Authorization rompe todas las llamadas a la API: la aplicación publicada, las claves de la Data API, los tokens de despliegue, VS Code y MCP llevan todos la suya propia.
  • El nombre de host es compartido. Los tokens de inicio de sesión viven en el almacenamiento del navegador para esa dirección, que puede leer cualquier aplicación que esté en ella, así que un script inyectado en cualquiera de ellas llega a las sesiones de Archestack. Si hay dos instancias de Archestack en un mismo nombre de host, cada una cierra la sesión de los usuarios de la otra.
  • No disponible por esta vía: las direcciones de los espacios de trabajo y los dominios propios de las empresas, que necesitan los certificados bajo demanda de Caddy, y Grafana, que el Ingress no enruta. Accede a 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. Registro, clúster y base de datos

#!/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 solo registro sirve a todos los entornos, así que una versión se importa una sola vez y producción ejecuta exactamente la misma imagen que ejecutó aceptación. El script genera la contraseña de la base de datos y la guarda directamente en Key Vault: nadie necesita conocerla. Capacidad y dimensionamiento recoge las cifras medidas en las que se basan los tamaños, que son un punto de partida para producción.

Cada espacio de trabajo tiene dos bases de datos. Un espacio de trabajo sin uso no mantiene ninguna conexión, pero un servidor con mucha actividad necesita margen: el límite de 50 conexiones del nivel Burstable es demasiado bajo. La API no necesita extensiones ni un superusuario, solo un usuario administrador que pueda crear bases de datos.

3. Dirección pública y 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 para la vía A. Apunta a la dirección editor, api y metrics bajo tu zona, además de un comodín * para las direcciones propias de los espacios de trabajo. Caddy obtiene sus certificados de Let's Encrypt, así que los puertos 80 y 443 tienen que ser accesibles desde internet.

4. Contraseñas y la identidad del 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 contraseña SMTP se pide y se guarda directamente en Key Vault. Una instancia que no envía correo la omite.
  • El pipeline inicia sesión sin un secreto guardado. Cada entorno tiene una identidad propia, que solo confía en el token que su sistema de CI entrega a los jobs de ese entorno. Puede iniciar sesión en el clúster, leer las contraseñas e importar al registro, y nada más.
  • El namespace lo creas tú, así que el pipeline solo necesita permisos dentro de él. Un clúster con inicio de sesión de Entra ID comprueba esos permisos por sí mismo, mediante Azure RBAC o un RoleBinding de Kubernetes; el script concede uno u otro, según lo que use el clúster.
  • El script termina indicando qué configurar en tu sistema de CI: variables de GitHub, variables de GitLab o la conexión de servicio de Azure DevOps.

5. La primera instalación

La primera instalación es el script que ejecuta el pipeline, lanzado a mano. Una primera ejecución del pipeline hace lo mismo.

# 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

Abre el builder de inmediato y crea el primer administrador. Hasta que alguien lo haga, quien llegue primero a él se convierte en el administrador. Para no dejar ninguna ventana de tiempo, define de antemano el primer administrador con api.initialAdmin (consulta el values.yaml del chart).

La release más reciente, de forma automática

resolve-version.sh pide al registro todas las etiquetas de ambas imágenes y elige la versión que despliega esta ejecución:

#!/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
  • Las versiones se comparan como números, así que 1.0.10 es más reciente que 1.0.9, y un número que falta se salta en lugar de esperarlo.
  • Solo cuenta una versión que lleven ambas imágenes. Una compilación de release que se detuvo entre subir la API y subir la imagen web nunca se despliega a medias.
  • También se comprueba una versión exacta. Una versión que las imágenes no tienen detiene la ejecución antes de tocar nada.
  • La versión viaja como cada sistema de CI transmite los valores: Azure DevOps pone a la ejecución el nombre de la versión, GitHub Actions la convierte en una salida del job y la muestra en el resumen de la ejecución, y GitLab CI la pasa a los jobs posteriores como $VERSION.

Después, deploy.sh lleva un entorno a esa versión:

#!/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
  • Las imágenes entran primero en tu registro. Los nodos descargan desde Azure, dentro de la región, así que añadir o sustituir un nodo nunca tiene que esperar a Docker Hub ni a sus límites de descargas anónimas. Si se rechaza una importación por esos límites, pasa a az acr import una cuenta de Docker Hub con --username y --password.
  • Las contraseñas se pasan en cada actualización. El chart escribe la contraseña SMTP a partir de los valores cada vez, así que una actualización sin ella deja la instancia sin poder enviar correo. Viajan en archivos que solo puede leer el job, y Azure DevOps y GitHub Actions las enmascaran en el log.
  • --wait --timeout 30m espera hasta que cada Deployment esté listo. La API migra todos los espacios de trabajo antes de responder, así que una versión con migraciones tarda un rato en una instancia con muchos espacios de trabajo.
  • Sin --atomic. Un despliegue fallido se deja para revisarlo en lugar de deshacerlo, porque puede que la nueva versión ya haya migrado las bases de datos. Mientras tanto, los pods antiguos siguen atendiendo.

El pipeline

El kit tiene el mismo pipeline para tres sistemas de CI. Cada uno ejecuta los dos scripts de arriba, en el mismo orden:

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
  • Cada noche, la programación lleva la release más reciente a desarrollo y pruebas.
  • Una ejecución programada se detiene después de pruebas. Aceptación y producción solo avanzan con una ejecución lanzada a mano. Pásale la versión que se ejecuta en pruebas, como 1.4.23, y aceptación y producción reciben exactamente lo que se probó. Si se deja en newest, toma lo que sea más reciente en ese momento.
  • Una versión por ejecución. La versión se resuelve una sola vez, antes del primer entorno, así que una release publicada mientras una ejecución espera una aprobación no se cuela en producción.
  • Un entorno que ya está en esa versión no despliega nada. La especificación de los pods no cambia, así que Kubernetes los deja en marcha. Un cambio en su values.yaml se aplica de todos modos.
Tu sistema 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
  • Las conexiones de servicio se llaman sc-archestack-<env>, una por entorno, cada una con la identidad de ese entorno. setup/3-pipeline.sh imprime qué rellenar y toma el emisor y el sujeto que muestra la conexión.
  • Aprobaciones. En Pipelines, Environments, añade una comprobación Approvals a archestack-acc y archestack-prd, y una comprobación Exclusive lock a los cuatro, para que dos ejecuciones nunca desplieguen en un mismo entorno a la vez.
  • always: true hace que la programación se ejecute sin un commit nuevo, porque una release es una imagen nueva, no un cambio en este repositorio.

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

Cada entorno lo despliega el mismo flujo de trabajo reutilizable:

# .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 }}
  • Entornos. Crea archestack-dev, -tst, -acc y -prd en Settings, Environments, y añade revisores obligatorios a aceptación y producción. En un repositorio privado, los entornos requieren GitHub Pro, Team o Enterprise.
  • Inicio de sesión. azure/login cambia el token del job por la identidad del entorno, cuyo id de cliente es una variable del entorno para la que setup/3-pipeline.sh imprime los comandos gh. La identidad solo confía en los jobs de su propio entorno, así que un job de pruebas nunca puede desplegar en producción.
  • concurrency impide que dos ejecuciones desplieguen en un mismo entorno a la vez.
  • La programación solo se ejecuta desde la rama predeterminada, y en un repositorio público GitHub la desactiva tras 60 días sin actividad en el repositorio.

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 programación vive en GitLab y no en el archivo: en Build, Pipeline schedules, añade una para main con 0 3 * * *. Un push no inicia nada.
  • Aceptación y producción son jobs manuales que bloquean el pipeline: producción solo puede lanzarse cuando aceptación ya se ha ejecutado. En GitLab Premium, protege además esos entornos y exige aprobaciones; un job aprobado se sigue lanzando a mano.
  • Inicio de sesión. El token de ID del job se cambia por la identidad del entorno, cuyo id de cliente es una variable con ámbito limitado a ese entorno. El token de GitLab nombra la rama, no el entorno, así que protege main. En una instancia autogestionada, pon su dirección como GITLAB_URL y como el aud del token.
  • resource_group impide que dos pipelines desplieguen en un mismo entorno a la vez.
  • La imagen está fijada, y Helm se descarga en una versión fijada. Cambia esas versiones a propósito.

Actualizaciones sin tiempo de inactividad

  • Un pod nuevo de la API cada vez. El chart añade un pod antes de quitar uno (maxSurge: 1, maxUnavailable: 0), y un pod antiguo solo se retira cuando uno nuevo está listo.
  • El arranque es por turnos. Las réplicas que arrancan a la vez preparan las bases de datos una tras otra bajo un bloqueo, así que dos de ellas nunca migran al mismo tiempo. Una startup probe da a cada pod 15 minutos antes de que Kubernetes lo dé por perdido.
  • Las actualizaciones de nodos mantienen la API en marcha. Un PodDisruptionBudget mantiene al menos un pod de la API en ejecución mientras AKS drena un nodo.
  • Caddy solo se reinicia cuando cambia su propia configuración, como un nombre de host nuevo. Es un único pod con sus certificados en un disco propio, así que ese reinicio supone una interrupción breve. Una actualización de Archestack no lo toca.

Volver atrás

Ejecuta el pipeline con la versión anterior, como 1.4.22, o revierte la 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

En ambos casos, las bases de datos se quedan como las dejó la versión más nueva: las migró al arrancar, y una versión anterior no deshace eso. Volver atrás ejecuta código más antiguo sobre un esquema más nuevo, así que es preferible la siguiente release que corrija el problema. Devolver también el esquema a su estado anterior implica restaurar el servidor con la restauración a un momento dado de Flexible Server.

A tener en cuenta

  • El chart sale de tu repositorio; las imágenes, del registro. El pipeline toma por sí solo las imágenes más recientes, nunca un chart más nuevo. Cuando pases a una nueva versión mayor o menor, vuelve a tomar el chart del repositorio de Archestack.
  • Las imágenes de observabilidad no llevan número de versión. Se etiquetan con un commit y latest, vienen de Docker Hub y siguen observability.imageTag. Ponlo en una de sus etiquetas de commit para que no cambien.
  • Una release por namespace. Grafana y Tempo encuentran Prometheus, Tempo y Loki por nombres de servicio fijos.
  • Detrás de PgBouncer (el puerto 6432 de Flexible Server), configura postgres.clusterConnectionString con una conexión directa por el 5432. Las réplicas se comunican entre sí por una conexión de PostgreSQL que tiene que permanecer abierta.
  • Los volúmenes y el Secret propio del chart sobreviven a helm uninstall. Bórralos a mano cuando de verdad quieras hacerlo.

Hacia dónde va esto a continuación

Capacidad y dimensionamiento recoge las cifras medidas para dimensionar el clúster y la base de datos. Espacios de trabajo y alojamiento explica cómo una instancia atiende a muchos espacios de trabajo. Una vez que la instancia esté en marcha, Estrategia de despliegue describe cómo desplegar tu aplicación en sus espacios de trabajo desde un repositorio.