Hosting Archestack na AKS
Ten przewodnik instaluje instancję Archestack w Azure Kubernetes Service (AKS), a potem sam dba o to, żeby była aktualna. Chart Helm uruchamia API jako kilka identycznych replik, builder i opublikowaną aplikację, a także Caddy jako proxy brzegowe z automatycznymi certyfikatami. PostgreSQL działa jako Azure Database for PostgreSQL Flexible Server, a obrazy pochodzą z Twojego własnego rejestru kontenerów. Co noc pipeline wyszukuje najnowsze wydanie Archestack i wdraża je na środowisko deweloperskie i testowe, a gdy ktoś to zatwierdzi, przenosi tę samą wersję dalej, na środowisko akceptacyjne i produkcyjne. Na pojedynczym serwerze prostszym sposobem jest Hosting z Dockerem.
Masz już klaster i chcesz mieć na nim Archestack jeszcze dziś, pod ścieżką nazwy hosta, którą już obsługujesz? Archestack na Kubernetesie, najkrótszą drogą robi to za pomocą pięciu plików i jednego pipeline'u. Od tego warto zacząć.
Co gdzie działa
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 jest proxy brzegowym, a nie kontrolerem ingress. Adresy przestrzeni roboczych i własne domeny firm dostają certyfikat, gdy ktoś odwiedzi je po raz pierwszy, a Caddy, zanim o niego wystąpi, pyta API, czy dana nazwa hosta należy do przestrzeni roboczej. cert-manager nie potrafi wystawiać certyfikatów na żądanie, więc kontroler ingress straciłby te adresy. Bez nich Archestack może też działać pod ścieżką nazwy hosta, którą klaster już obsługuje.
- Adres odwiedzającego zostaje zachowany (
externalTrafficPolicy: Local). Mechanizm limitów żądań liczy anonimowych wywołujących według adresu, a bez tego wszyscy odwiedzający dzieliliby adres jednego węzła. - Pliki i kopie zapasowe to jeden udział w Azure Files, który montuje każda replika
API, więc logo albo kopia zapasowa zapisane przez jedną replikę są dostępne na wszystkich.
Wyjątkiem są pakiety NuGet używane przez skrypty: każdy pod trzyma własne na swoim węźle, dopóki
istnieje (
api.nugetCache). - API samo tworzy swoje bazy danych i migruje je przy starcie, więc wystarczy mu pusty serwer. Nie ma osobnego zadania migracji.
Wersje i tagi
Każde wydanie Archestack ma numer wersji, major.minor.build, na przykład
1.4.23. Numer builda rośnie z każdym wydaniem i zaczyna się znowu od 1, gdy zmienia się
wersja główna lub pomocnicza, a niektórych numerów może brakować, jeśli build wydania się nie
zakończył. Każde wydanie publikuje na Docker Hub dwa obrazy, nickpeelman/omnicore-api i
nickpeelman/omnicore-web, każdy z trzema tagami: wersją, commitem, z którego go
zbudowano, i latest.
Wdrażaj numer wersji, nigdy latest:
- Nic nie zostaje wdrożone. Aktualizacja z
latestdolatestpozostawia Deployment bez zmian, więc Kubernetes zachowuje dotychczasowe pody. - Repliki się rozjeżdżają. Chart pobiera obraz tylko wtedy, gdy węzeł jeszcze go nie
ma. Węzeł, który pobrał
latestw zeszłym tygodniu, nadal uruchamia wersję z zeszłego tygodnia, nowy węzeł uruchamia wersję z tego tygodnia i jedna instancja obsługuje dwie wersje. - Nie ma drogi powrotu. Poprzednia wersja nie ma nazwy, do której można by wrócić.
Dlatego opisany niżej pipeline zamienia "najnowszą" na numer raz, na początku uruchomienia, i każde
środowisko dostaje ten numer. Może też trzymać się jednej linii: 1.4 bierze najnowszą
1.4.x i nigdy sam nie przechodzi na 1.5. Większe zmiany trafiają do nowej
wersji głównej lub pomocniczej, a rozszerzenie VS Code ostrzega, gdy łączy
się z API o innej wersji głównej lub pomocniczej.
Zanim zaczniesz
- Subskrypcja Azure, a na Twojej maszynie CLI
az,kubectli Helm 3. - Projekt w Azure DevOps, GitHub lub GitLab dla pipeline'u, a w nim repozytorium utworzone z zestawu startowego.
-
Chart Helm: folder
infra/helm/archestackz repozytorium Archestack, skopiowany do tego repozytorium pod tę samą ścieżkę. - Strefa DNS, do której możesz dodawać rekordy, albo nazwa hosta, którą klaster już obsługuje przez swój kontroler ingress (wariant B).
Zestaw startowy
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
Zostaw pipeline swojego systemu CI, a dwa pozostałe usuń. grep -rn CHANGE-ME . wypisuje
każdą wartość, którą trzeba jeszcze uzupełnić, a deploy.sh odrzuca środowisko, które
wciąż ma taką wartość. Następnie trzy skrypty konfiguracyjne, uruchamiane jednorazowo przez
człowieka, tworzą każde środowisko w 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 Każdy skrypt tworzy tylko to, czego brakuje, więc taki, który zatrzymał się w połowie, można po prostu uruchomić ponownie, a każdy na koniec mówi, co dalej.
1. Uzupełnij ustawienia
aks/settings.env zawiera to, co wspólne dla wszystkich środowisk:
# 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
Plik aks/environment.sh wyprowadza z niego wszystkie nazwy w Azure; czytają go zarówno
skrypty konfiguracyjne, jak i deploy.sh, więc zawsze są zgodne. Nazwy, które w Azure
muszą być unikalne na całym świecie (rejestr, Key Vault i serwer bazy danych), kończą się Twoim
prefiksem:
# 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
}
Plik values.yaml każdego środowiska zawiera jego wartości chartu i jeden z dwóch
wariantów dostępu: własne nazwy hostów (A) albo ścieżkę nazwy hosta, którą klaster już obsługuje
(B). Zostaw jeden, drugi usuń.
# 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: dowolna liczba, bez sticky sessions. Sekcja Infrastruktura hostingowa wymienia wymagania, jakie kilka replik stawia otaczającej je sieci. -
api.tenantHostSuffix: włącza osobny adres dla każdej przestrzeni roboczej i własne domeny firm, tylko w wariancie A. -
api.persistence: musi mieć trybReadWriteMany, gdy tylko repliki działają na więcej niż jednym węźle. -
observability.enabled: Grafana, Prometheus, Tempo i Loki wewnątrz klastra. Ich panele hostów i kontenerów pozostają na AKS puste; węzły i kontenery obejmuje Container Insights.
Pod ścieżką nazwy hosta, którą już masz
Wariant B umieszcza Archestack pod ścieżką nazwy hosta, którą kontroler ingress klastra już obsługuje, obok aplikacji, które już pod nią działają:
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) Nie potrzebuje nowego rekordu DNS, publicznego adresu ani własnego certyfikatu, a ludzie dalej korzystają z adresu, który już znają. Chart pomija Caddy i generuje jeden Ingress dla trzech ścieżek; builder i API dostają swój prefiks, więc zawiera go każdy link, który tworzą.
# --- 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 - Klaster, który już masz. Nazwa hosta działa na klastrze, zwykle takim, który
istniał przed Archestack. Skopiuj
names.env.exampledoaks/environments/<env>/names.env, a skrypty konfiguracyjne zostawią ten klaster takim, jaki jest: pozwolą mu pobierać obrazy z rejestru i dodadzą do jego sieci podsieć dla bazy danych. Pomińsetup/2-address.sh. -
classNameto klasa ingress, która obsługuje tę nazwę hosta. Chart ustawia adnotacje ingress-nginx dla przesyłania dużych plików i długotrwałych połączeń; inny kontroler potrzebuje własnych odpowiedników. - Certyfikat musi być Secretem w namespace
archestack, bo Ingress używa tylko Secretów z własnego namespace. Skopiuj tam certyfikat tej nazwy hosta albo niech cert-manager go tam wystawi. - Nie przepisuj ścieżki (bez
rewrite-target). API musi widzieć swój prefiks, żeby tworzyć własne adresy. - Nie stawiaj proxy logowania przed Archestack. Proxy, które zastępuje nagłówek
Authorization, psuje każde wywołanie API: opublikowana aplikacja, klucze Data API, tokeny wdrożeniowe, VS Code i MCP wysyłają własny. - Nazwa hosta jest współdzielona. Tokeny logowania są przechowywane w pamięci przeglądarki dla tego adresu, którą może odczytać każda działająca pod nim aplikacja, więc skrypt wstrzyknięty do którejkolwiek z nich dociera do sesji Archestack. Dwie instancje Archestack na jednej nazwie hosta wylogowują nawzajem swoich użytkowników.
- Niedostępne w tym wariancie: adresy przestrzeni roboczych i własne domeny firm,
które wymagają certyfikatów Caddy wystawianych na żądanie, oraz Grafana, do której Ingress nie
kieruje ruchu. Do Grafany dostaniesz się przez
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. Rejestr, klaster i baza danych
#!/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" Jeden rejestr obsługuje wszystkie środowiska, więc każda wersja jest importowana raz, a produkcja uruchamia dokładnie ten obraz, który działał na środowisku akceptacyjnym. Hasło do bazy danych tworzy skrypt i trafia ono prosto do Key Vault: nikt nie musi go znać. Strona Wydajność i dobór serwerów podaje zmierzone liczby, na których opierają się te rozmiary; to punkt wyjścia dla produkcji.
Każda przestrzeń robocza ma dwie bazy danych. Bezczynna przestrzeń robocza nie utrzymuje żadnych połączeń, ale obciążony serwer potrzebuje zapasu: limit 50 połączeń w warstwie Burstable jest za niski. API nie potrzebuje żadnych rozszerzeń ani superużytkownika, tylko użytkownika administracyjnego, który może tworzyć bazy danych.
3. Publiczny adres i 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"
Tylko wariant A. Skieruj editor, api i metrics w swojej
strefie na ten adres, a także rekord wieloznaczny * dla własnych adresów przestrzeni
roboczych. Caddy uzyskuje certyfikaty od Let's Encrypt, więc porty 80 i 443 muszą być osiągalne z
internetu.
4. Hasła i tożsamość pipeline'u
#!/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 - Hasło SMTP podajesz, gdy skrypt o nie zapyta, i trafia ono prosto do Key Vault. Instancja, która nie wysyła poczty, je pomija.
- Pipeline loguje się bez przechowywanego sekretu. Każde środowisko ma własną tożsamość, która ufa tylko tokenowi, jaki jego system CI przekazuje zadaniom tego środowiska. Może ona zalogować się do klastra, odczytać hasła i importować do rejestru, nic więcej.
- Namespace tworzysz Ty, więc pipeline potrzebuje uprawnień tylko wewnątrz niego. Klaster z logowaniem przez Entra ID sam sprawdza te uprawnienia, przez Azure RBAC albo RoleBinding Kubernetes; skrypt nadaje je w tym z nich, którego używa klaster.
- Na koniec skrypt podaje, co ustawić w Twoim systemie CI: zmienne GitHub, zmienne GitLab albo połączenie usługi (service connection) Azure DevOps.
5. Pierwsza instalacja
Pierwsza instalacja to ten sam skrypt, który uruchamia pipeline, tyle że uruchomiony ręcznie. Pierwsze uruchomienie pipeline'u robi to samo.
# 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 Od razu otwórz builder i utwórz pierwszego admina. Dopóki ktoś tego nie zrobi,
adminem zostaje ten, kto pierwszy do niego dotrze. Żeby nie zostawić żadnego okna czasowego, utwórz
pierwszego admina z góry za pomocą api.initialAdmin (zobacz values.yaml
chartu).
Najnowsze wydanie, automatycznie
resolve-version.sh odpytuje rejestr o wszystkie tagi obu obrazów i wybiera wersję, którą
wdroży to uruchomienie:
#!/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 - Wersje są porównywane jako liczby, więc
1.0.10jest nowsza niż1.0.9, a brakujący numer jest pomijany, bez czekania na niego. - Liczy się tylko wersja, którą mają oba obrazy. Build wydania, który zatrzymał się między wypchnięciem obrazu API a wypchnięciem obrazu web, nigdy nie zostanie wdrożony połowicznie.
- Dokładna wersja również jest sprawdzana. Wersja, której obrazy nie mają, zatrzymuje uruchomienie, zanim cokolwiek zostanie zmienione.
- Wersja jest przekazywana dalej tak, jak każdy system CI przekazuje wartości: Azure
DevOps nadaje jej nazwę uruchomieniu, GitHub Actions robi z niej output zadania i pokazuje ją w
podsumowaniu uruchomienia, a GitLab CI przekazuje ją późniejszym zadaniom jako
$VERSION.
Następnie deploy.sh przenosi jedno środowisko na tę wersję:
#!/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 - Obrazy najpierw trafiają do Twojego rejestru. Węzły pobierają je z Azure, w
obrębie regionu, więc dodanie lub wymiana węzła nigdy nie czeka na Docker Hub ani nie podlega jego
limitom anonimowych pobrań. Jeśli import zostanie odrzucony z powodu tych limitów, podaj
az acr importkonto Docker Hub za pomocą--usernamei--password. - Hasła są przekazywane przy każdej aktualizacji. Chart za każdym razem zapisuje hasło SMTP z wartości, więc aktualizacja bez niego zostawia instancję bez możliwości wysyłania poczty. Przekazuje się je w plikach, które może odczytać tylko zadanie, a Azure DevOps i GitHub Actions maskują je w logu.
-
--wait --timeout 30mczeka, aż każdy Deployment będzie gotowy. API migruje każdą przestrzeń roboczą, zanim zacznie odpowiadać, więc na instancji z wieloma przestrzeniami roboczymi wersja z migracjami potrzebuje trochę czasu. - Bez
--atomic. Nieudane wdrożenie zostaje pozostawione do sprawdzenia, a nie cofnięte, bo nowa wersja mogła już zmigrować bazy danych. Stare pody w tym czasie nadal obsługują ruch.
Pipeline
Zestaw zawiera ten sam pipeline dla trzech systemów CI. Każdy uruchamia dwa powyższe skrypty, w tej samej kolejności:
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 - Co noc harmonogram przenosi najnowsze wydanie na środowisko deweloperskie i testowe.
- Uruchomienie z harmonogramu kończy się po środowisku testowym. Środowisko
akceptacyjne i produkcyjne przechodzą na nową wersję tylko w uruchomieniu rozpoczętym ręcznie.
Podaj w nim wersję, na której działa środowisko testowe, na przykład
1.4.23, a środowisko akceptacyjne i produkcyjne dostaną dokładnie to, co zostało przetestowane. Jeśli zostawisznewest, uruchomienie weźmie to, co jest najnowsze w tym momencie. - Jedna wersja na uruchomienie. Wersja jest ustalana raz, przed pierwszym środowiskiem, więc wydanie opublikowane w czasie, gdy uruchomienie czeka na zatwierdzenie, nie wślizgnie się na produkcję.
- Na środowisku, które już działa na tej wersji, nic nie zostaje wdrożone.
Specyfikacja podów się nie zmienia, więc Kubernetes zostawia je uruchomione. Zmiana w jego
values.yamli tak zostaje zastosowana.
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 - Połączenia usług (service connections) mają nazwy
sc-archestack-<env>, po jednym na środowisko, każde z tożsamością tego środowiska.setup/3-pipeline.shwypisuje, co należy uzupełnić, i przyjmuje wartości issuer i subject, które pokazuje połączenie. - Zatwierdzenia. W Pipelines, Environments dodaj sprawdzenie Approvals do
archestack-acciarchestack-prdoraz sprawdzenie Exclusive lock do wszystkich czterech, żeby dwa uruchomienia nigdy nie wdrażały jednocześnie na to samo środowisko. -
always: trueuruchamia harmonogram bez nowego commita, bo wydanie to nowy obraz, a nie zmiana w tym repozytorium.
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 }}" } Każde środowisko jest wdrażane przez ten sam workflow wielokrotnego użytku (reusable workflow):
# .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 }} - Środowiska. Utwórz
archestack-dev,-tst,-acci-prdw Settings, Environments i dodaj wymaganych recenzentów (required reviewers) do środowiska akceptacyjnego i produkcyjnego. W prywatnym repozytorium środowiska wymagają GitHub Pro, Team lub Enterprise. - Logowanie.
azure/loginwymienia token zadania na tożsamość środowiska, której identyfikator klienta (client id) jest zmienną tego środowiska; poleceniagh, które ją ustawiają, wypisujesetup/3-pipeline.sh. Tożsamość ufa tylko zadaniom we własnym środowisku, więc zadanie dla środowiska testowego nigdy nie wdroży produkcji. -
concurrencypilnuje, żeby dwa uruchomienia nie wdrażały jednocześnie na to samo środowisko. - Harmonogram działa tylko z gałęzi domyślnej, a w publicznym repozytorium GitHub wyłącza go po 60 dniach bez aktywności w repozytorium.
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 } - Harmonogram znajduje się w GitLab, a nie w pliku: w Build, Pipeline schedules
dodaj harmonogram dla
mainz0 3 * * *. Push niczego nie uruchamia. - Środowisko akceptacyjne i produkcyjne to ręczne zadania, które blokują pipeline: zadanie produkcyjne można uruchomić dopiero wtedy, gdy wykonało się akceptacyjne. W GitLab Premium dodatkowo oznacz te środowiska jako chronione i wymagaj zatwierdzeń; zatwierdzone zadanie i tak uruchamia się ręcznie.
- Logowanie. Token ID zadania jest wymieniany na tożsamość środowiska, której
identyfikator klienta (client id) jest zmienną o zakresie ograniczonym do tego środowiska. Token
GitLab wskazuje gałąź, a nie środowisko, więc oznacz gałąź
mainjako chronioną. W instancji self-managed ustaw jej adres jakoGITLAB_URLi jakoaudtokenu. -
resource_grouppilnuje, żeby dwa pipeline'y nie wdrażały jednocześnie na to samo środowisko. - Obraz ma przypiętą wersję, a Helm jest pobierany w przypiętej wersji. Zmieniaj je celowo.
Aktualizacje bez przerw w działaniu
- Jeden nowy pod API naraz. Chart dodaje jeden pod, zanim usunie inny
(
maxSurge: 1,maxUnavailable: 0), a stary pod znika dopiero wtedy, gdy nowy jest gotowy. - Starty odbywają się po kolei. Repliki, które startują razem, przygotowują bazy danych jedna po drugiej, z użyciem blokady, więc dwie z nich nigdy nie migrują jednocześnie. Startup probe daje każdemu podowi 15 minut, zanim Kubernetes z niego zrezygnuje.
- Aktualizacje węzłów nie przerywają działania API. PodDisruptionBudget pilnuje, żeby co najmniej jeden pod API działał, gdy AKS opróżnia węzeł.
- Caddy restartuje się tylko wtedy, gdy zmienia się jego własna konfiguracja, na przykład przy nowej nazwie hosta. To jeden pod z certyfikatami na własnym dysku, więc taki restart oznacza krótką przerwę. Aktualizacja Archestack go nie dotyka.
Powrót do poprzedniej wersji
Uruchom pipeline z poprzednią wersją, na przykład 1.4.22, albo wycofaj release za
pomocą Helma:
helm history archestack -n archestack # the revisions, newest last
helm rollback archestack -n archestack --wait --timeout 30m # back to the one before W obu przypadkach bazy danych pozostają w stanie, w jakim zostawiła je nowsza wersja: zmigrowała je przy starcie, a starsza wersja tego nie cofa. Powrót oznacza uruchomienie starszego kodu na nowszym schemacie, więc lepiej postawić na kolejne wydanie, które naprawia problem. Cofnięcie również schematu oznacza odtworzenie serwera za pomocą przywracania do punktu w czasie (point-in-time restore) w Flexible Server.
Pułapki
- Chart pochodzi z Twojego repozytorium, obrazy z rejestru. Pipeline sam pobiera najnowsze obrazy, ale nigdy nowszego chartu. Gdy przechodzisz na nową wersję główną lub pomocniczą, pobierz chart ponownie z repozytorium Archestack.
- Obrazy obserwowalności nie mają numeru wersji. Mają tagi z commitem i
latest, pochodzą z Docker Hub i podążają zaobservability.imageTag. Ustaw tę wartość na jeden z ich tagów commitów, żeby je zamrozić. - Jeden release na namespace. Grafana i Tempo znajdują Prometheus, Tempo i Loki po stałych nazwach usług.
- Za PgBouncerem (port 6432 w Flexible Server) ustaw
postgres.clusterConnectionStringna bezpośrednie połączenie na porcie 5432. Repliki rozmawiają ze sobą przez połączenie PostgreSQL, które musi pozostać otwarte. - Woluminy i własny Secret chartu przetrwają
helm uninstall. Usuń je ręcznie, gdy naprawdę tego chcesz.
Co dalej
Strona Wydajność i dobór serwerów podaje zmierzone liczby, na podstawie których dobierzesz rozmiar klastra i bazy danych. Strona Przestrzenie robocze i hosting wyjaśnia, jak jedna instancja obsługuje wiele przestrzeni roboczych. Gdy instancja już działa, strona Strategia wdrożeń opisuje wdrażanie Twojej aplikacji z repozytorium do jej przestrzeni roboczych.