k8s multiple regions config

This commit is contained in:
2026-08-21 14:24:31 -03:00
parent c0a657b7f7
commit b1fce379cd
8 changed files with 213 additions and 68 deletions

View File

@@ -48,10 +48,10 @@ A solução tem os seguintes objetivos:
+--------------------+--------------------+ +--------------------+--------------------+
| | | |
v v v v
Deployment ORD Deployment IAD Deployment Target A Deployment Target B / ... / N
| | | |
+--------+--------+ +--------+--------+ +--------+--------+ +--------+--------+
| Pod TIA ORD | | Pod TIA IAD | | Pod TIA A | | Pod TIA B/N |
| | | | | | | |
| bridge | | bridge | | bridge | | bridge |
| agent/livekit | | agent/livekit | | agent/livekit | | agent/livekit |
@@ -62,10 +62,20 @@ A solução tem os seguintes objetivos:
+-------+---------+ +-------+---------+ +-------+---------+ +-------+---------+
| | | |
v v v v
OCI xAI ORD OCI xAI IAD OCI xAI endpoint A OCI xAI endpoint B/N
``` ```
O mesmo `Service` Kubernetes seleciona Pods ORD e IAD. Cada deployment acrescenta o label `tia-region`, útil para métricas e operação, mas ambos compartilham `app=${APP_NAME}-regional`. O mesmo `Service` Kubernetes seleciona Pods de todos os targets configurados em `XAI_TARGETS`. Cada deployment acrescenta o label `tia-region`, útil para métricas e operação, e todos compartilham `app=${APP_NAME}-regional`. A quantidade de targets é arbitrária e dois targets podem inclusive apontar para endpoints diferentes na mesma região OCI.
## 4.1 Configuração dinâmica de targets
A topologia não possui lista fixa de regiões. `XAI_TARGETS` declara os pools/endpoints lógicos, por exemplo:
```bash
XAI_TARGETS="ORD IAD GRU FRA"
```
Cada target define `REGION_ID`, réplicas, endpoint upstream e Secret por prefixo. Um target é uma unidade de pool/deployment, portanto vários targets podem apontar para endpoints distintos na mesma região OCI. Adicionar ou remover target não exige alteração dos scripts nem dos templates Kubernetes.
## 5. Componentes ## 5. Componentes
@@ -126,8 +136,9 @@ Cada deployment recebe seu endpoint próprio por `XAI_POOL_UPSTREAM_URL`.
Exemplo: Exemplo:
```text ```text
ORD -> wss://...us-chicago-1.../xai/v1/tts TARGET_A -> wss://<endpoint-a>/xai/v1/tts
IAD -> wss://...us-ashburn-1.../xai/v1/tts TARGET_B -> wss://<endpoint-b>/xai/v1/tts
TARGET_N -> wss://<endpoint-n>/xai/v1/tts
``` ```
As credenciais reais ficam somente no sidecar de pool. O container `agent` usa uma credencial local dummy porque se conecta apenas a `localhost`. As credenciais reais ficam somente no sidecar de pool. O container `agent` usa uma credencial local dummy porque se conecta apenas a `localhost`.
@@ -303,13 +314,13 @@ Operação normal:
```text ```text
LB LB
|-- ORD Pod 1 -> ready |-- Target A Pod 1 -> ready
|-- ORD Pod 2 -> ready |-- Target A Pod 2 -> ready
|-- IAD Pod 1 -> ready |-- Target B Pod 1 -> ready
`-- IAD Pod 2 -> ready `-- Target B Pod 2 -> ready
``` ```
Se ORD perder saúde/capacidade xAI, os slots começam a falhar e a quantidade de conexões saudáveis/livres cai. Quando a readiness cruza o threshold, os Pods ORD saem dos endpoints e novas chamadas passam a ser atendidas pelos Pods IAD disponíveis. Se qualquer target perder saúde/capacidade xAI, seus slots começam a falhar e a quantidade de conexões saudáveis/livres cai. Quando a readiness cruza o threshold, os Pods desse target saem dos endpoints e novas chamadas passam a ser atendidas pelos demais targets Ready.
Não há necessidade de alterar o cliente ou o LiveKit para escolher a região. Não há necessidade de alterar o cliente ou o LiveKit para escolher a região.
@@ -324,16 +335,16 @@ capacidade regional de sockets = replicas_region * XAI_POOL_SIZE
Exemplo: Exemplo:
```text ```text
ORD: 2 pods x 25 sockets = 50 Target A: 2 pods x 25 sockets = 50
IAD: 2 pods x 25 sockets = 50 Target B: 2 pods x 25 sockets = 50
TOTAL = 100 sockets prewarmed TOTAL = 100 sockets prewarmed
``` ```
ou, se a OCI conceder capacidade independente suficiente: ou, se a OCI conceder capacidade independente suficiente:
```text ```text
ORD: 2 pods x 50 = 100 Target A: 2 pods x 50 = 100
IAD: 2 pods x 50 = 100 Target B: 2 pods x 50 = 100
TOTAL = 200 TOTAL = 200
``` ```
@@ -355,24 +366,24 @@ e deve ser comparado com a **capacidade efetivamente negociada/provisionada** pa
Carga baixa Carga baixa
========= =========
LB LB
|-- ORD-1 [pool 50: 10 leased / 40 free] |-- target-a-1 [pool 50: 10 leased / 40 free]
`-- IAD-1 [pool 50: 8 leased / 42 free] `-- target-b-1 [pool 50: 8 leased / 42 free]
Carga aumenta Carga aumenta
============= =============
LB LB
|-- ORD-1 [47 leased / 3 free] READY |-- target-a-1 [47 leased / 3 free] READY
`-- IAD-1 [30 leased /20 free] READY `-- target-b-1 [30 leased /20 free] READY
ORD satura Target A satura
=========== ===========
LB LB
|-- ORD-1 [48 leased /2 free] NOT READY -> sem novas chamadas |-- target-a-1 [48 leased /2 free] NOT READY -> sem novas chamadas
`-- IAD-1 [31 leased /19 free] READY -> recebe novas chamadas `-- target-b-1 [31 leased /19 free] READY -> recebe novas chamadas
ORD recupera Target A recupera
============= =============
ORD-1 chega a 45 leased /5 free target-a-1 chega a 45 leased /5 free
/readyz volta a 200 /readyz volta a 200
LB volta a considerá-lo para novas conexões LB volta a considerá-lo para novas conexões
``` ```
@@ -445,7 +456,7 @@ Estas métricas devem ser correlacionadas com as métricas já existentes no TIA
| conexão presa a uma chamada | lease somente durante utterance | | conexão presa a uma chamada | lease somente durante utterance |
| LB envia tráfego a Pod sem capacidade TTS | `/readyz` baseado no pool | | LB envia tráfego a Pod sem capacidade TTS | `/readyz` baseado no pool |
| flapping de health | histerese unavailable/recover | | flapping de health | histerese unavailable/recover |
| indisponibilidade regional | Deployments ORD/IAD no mesmo Service | | indisponibilidade regional | N deployments/targets no mesmo Service |
| rollout derruba novas sessões | drain + RollingUpdate | | rollout derruba novas sessões | drain + RollingUpdate |
| chave xAI em todos os processos | credencial real somente no sidecar | | chave xAI em todos os processos | credencial real somente no sidecar |
| observabilidade de capacidade | `/pool/status` + `/metrics` | | observabilidade de capacidade | `/pool/status` + `/metrics` |

View File

@@ -1,3 +1,12 @@
## Correção multi-target / N regiões
- removido hardcode `render_region ORD` / `render_region IAD`;
- nova variável `XAI_TARGETS` define qualquer quantidade de pools/endpoints;
- `XAI_REGIONS` mantida somente como alias de compatibilidade;
- deployment e rollout descobrem dinamicamente todos os manifests renderizados;
- suporte a múltiplos targets na mesma região OCI usando `REGION_ID` distintos;
- documentação e plano de testes generalizados para N targets.
# Changelog — TIA Regional / xAI Pool # Changelog — TIA Regional / xAI Pool
## Implementação adicionada ## Implementação adicionada

View File

@@ -1,5 +1,8 @@
# Deployment — TIA Regional com Pool xAI # Deployment — TIA Regional com Pool xAI
> **Modelo multi-target:** a lista de regiões/pools não é fixa. Declare `XAI_TARGETS="TARGET_A TARGET_B ..."` em `k8s/regional/regions.env`. Cada target define `<TARGET>_REGION_ID`, `<TARGET>_REGION_REPLICAS`, `<TARGET>_XAI_UPSTREAM_URL` e `<TARGET>_XAI_SECRET_NAME`. Para adicionar uma região ou um segundo pool na mesma região, altere somente configuração; os scripts e manifests não precisam ser modificados. `XAI_REGIONS` é aceito apenas como alias de compatibilidade.
## 1. Pré-requisitos ## 1. Pré-requisitos
- cluster Kubernetes/OKE funcional; - cluster Kubernetes/OKE funcional;
@@ -312,20 +315,21 @@ Um Pod NotReady não deve ser usado para novas conexões do Service.
## 15. Testar failover regional ## 15. Testar failover regional
### Teste controlado de ORD ### Teste controlado de qualquer target
Coloque ORD em drain: Escolha um `TARGET_REGION_ID` e coloque esse target em drain:
```bash ```bash
ORD_POD=$(kubectl -n "$K8S_NAMESPACE" get pod \ TARGET_REGION_ID=${TARGET_REGION_ID:-ord}
-l app=${APP_NAME}-regional,tia-region=ord \ TARGET_POD=$(kubectl -n "$K8S_NAMESPACE" get pod \
-l app=${APP_NAME}-regional,tia-region=${TARGET_REGION_ID} \
-o jsonpath='{.items[0].metadata.name}') -o jsonpath='{.items[0].metadata.name}')
kubectl -n "$K8S_NAMESPACE" exec "$ORD_POD" -c xai-pool -- \ kubectl -n "$K8S_NAMESPACE" exec "$TARGET_POD" -c xai-pool -- \
python -c "import urllib.request; urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:18100/drain', method='POST')).read()" python -c "import urllib.request; urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:18100/drain', method='POST')).read()"
``` ```
Confirme `/readyz=503` e gere **nova chamada**. Ela deve ser entregue a um Pod IAD ainda Ready. Confirme `/readyz=503` e gere **nova chamada**. Ela deve ser entregue a qualquer outro target que permaneça Ready.
Esse teste não deve ser interpretado como migração de uma chamada existente; somente novas conexões são rebalanceadas. Esse teste não deve ser interpretado como migração de uma chamada existente; somente novas conexões são rebalanceadas.
@@ -334,7 +338,7 @@ Esse teste não deve ser interpretado como migração de uma chamada existente;
O drain manual é intencional e não é revertido. Para voltar o Pod, reinicie-o: O drain manual é intencional e não é revertido. Para voltar o Pod, reinicie-o:
```bash ```bash
kubectl -n "$K8S_NAMESPACE" delete pod "$ORD_POD" kubectl -n "$K8S_NAMESPACE" delete pod "$TARGET_POD"
``` ```
O novo Pod deve: O novo Pod deve:
@@ -378,7 +382,7 @@ A alteração não remove suporte ao modo anterior.
## 20. Checklist de produção ## 20. Checklist de produção
- [ ] confirmar quota real de WebSockets por endpoint/região/tenancy; - [ ] confirmar quota real de WebSockets por endpoint/região/tenancy;
- [ ] confirmar se ORD e IAD têm pools de capacidade independentes; - [ ] confirmar capacidade independente/adequada para cada target configurado;
- [ ] validar `XAI_POOL_SIZE * replicas` por região; - [ ] validar `XAI_POOL_SIZE * replicas` por região;
- [ ] validar Secret/Vault; - [ ] validar Secret/Vault;
- [ ] medir tempo de prewarm dos 50 sockets; - [ ] medir tempo de prewarm dos 50 sockets;
@@ -388,7 +392,7 @@ A alteração não remove suporte ao modo anterior.
- [ ] testar recovery -> `/readyz=200`; - [ ] testar recovery -> `/readyz=200`;
- [ ] testar drain durante chamada ativa; - [ ] testar drain durante chamada ativa;
- [ ] testar rollout sem perda de novas chamadas; - [ ] testar rollout sem perda de novas chamadas;
- [ ] testar perda total de ORD e entrada em IAD; - [ ] testar perda total de cada target e redistribuição para os demais targets Ready;
- [ ] correlacionar TTFB/gap/underflow com `pool_free`; - [ ] correlacionar TTFB/gap/underflow com `pool_free`;
- [ ] validar LB/Service com WebSocket real; - [ ] validar LB/Service com WebSocket real;
- [ ] executar stress test com perfil semelhante ao tráfego de produção. - [ ] executar stress test com perfil semelhante ao tráfego de produção.

View File

@@ -6,7 +6,7 @@ Validar separadamente capacidade do pool, comportamento do Kubernetes, failover
## Camada 1 — Smoke ## Camada 1 — Smoke
1. subir 1 Pod ORD com pool pequeno (`XAI_POOL_SIZE=3`); 1. subir 1 Pod de um target de teste com pool pequeno (`XAI_POOL_SIZE=3`);
2. confirmar 3 conexões healthy; 2. confirmar 3 conexões healthy;
3. realizar uma chamada e uma síntese; 3. realizar uma chamada e uma síntese;
4. confirmar que `leased` vai 0 -> 1 -> 0; 4. confirmar que `leased` vai 0 -> 1 -> 0;
@@ -91,15 +91,15 @@ Critérios:
## Camada 7 — Falha upstream ## Camada 7 — Falha upstream
Bloqueie ORD ou aponte temporariamente para endpoint inválido. Bloqueie um target (por exemplo TARGET_A) ou aponte temporariamente seu upstream para endpoint inválido.
Esperado: Esperado:
- slots ORD tornam-se unhealthy; - slots do TARGET_A tornam-se unhealthy;
- maintenance tenta recuperação; - maintenance tenta recuperação;
- readiness ORD cai; - readiness do TARGET_A cai;
- Service deixa de enviar novas chamadas a ORD; - Service deixa de enviar novas chamadas ao TARGET_A;
- IAD continua Ready. - os demais targets continuam Ready.
## Camada 8 — Latência ## Camada 8 — Latência
@@ -141,7 +141,7 @@ Rodar pelo menos:
Com chamadas ativas: Com chamadas ativas:
```bash ```bash
kubectl rollout restart deployment/<tia-ord> kubectl rollout restart deployment/<tia-target>
``` ```
Validar: Validar:
@@ -153,11 +153,11 @@ Validar:
## Camada 10 — Failover regional ## Camada 10 — Failover regional
1. ORD e IAD Ready; 1. configurar pelo menos dois targets Ready;
2. iniciar tráfego contínuo; 2. iniciar tráfego contínuo;
3. tornar ORD NotReady; 3. tornar um target NotReady;
4. verificar novas chamadas em IAD; 4. verificar novas chamadas nos demais targets Ready;
5. recuperar ORD; 5. recuperar o target;
6. verificar reentrada progressiva. 6. verificar reentrada progressiva.
## Evidências a guardar ## Evidências a guardar

View File

@@ -8,6 +8,11 @@ TERMINATION_GRACE_SECONDS=600
DRAIN_SECONDS=30 DRAIN_SECONDS=30
PDB_MIN_AVAILABLE=2 PDB_MIN_AVAILABLE=2
# Logical xAI targets. Add/remove targets here without modifying scripts/YAML.
# A target represents one pool/upstream endpoint and does not need to be 1:1
# with an OCI region. This also supports multiple pools in the same region.
XAI_TARGETS="ORD IAD"
# Existing TIA resources # Existing TIA resources
CPU_TIA_BRIDGE_REQ=250m CPU_TIA_BRIDGE_REQ=250m
MEM_TIA_BRIDGE_REQ=512Mi MEM_TIA_BRIDGE_REQ=512Mi
@@ -22,9 +27,8 @@ MEM_XAI_POOL_REQ=256Mi
CPU_XAI_POOL_LIM=1000m CPU_XAI_POOL_LIM=1000m
MEM_XAI_POOL_LIM=768Mi MEM_XAI_POOL_LIM=768Mi
# Pool profile - 50 means 50 prewarmed upstream WebSockets PER POD. # Pool profile - XAI_POOL_SIZE means prewarmed upstream WebSockets PER POD.
# Tamanho do pool por réplica TIA. NÃO representa limite do OCI/xAI. # It is NOT the OCI/xAI service limit; tune it to the negotiated capacity.
# Ajuste conforme capacidade negociada para o ambiente.
XAI_POOL_SIZE=50 XAI_POOL_SIZE=50
XAI_POOL_UNAVAILABLE_FREE=2 XAI_POOL_UNAVAILABLE_FREE=2
XAI_POOL_RECOVER_FREE=5 XAI_POOL_RECOVER_FREE=5
@@ -36,19 +40,37 @@ XAI_TTS_VOICE=c8x2ieiocufs
XAI_TTS_LANGUAGE=pt-BR XAI_TTS_LANGUAGE=pt-BR
XAI_UPSTREAM_AUTH_METHOD=API_KEY XAI_UPSTREAM_AUTH_METHOD=API_KEY
# HPA. replicas * XAI_POOL_SIZE deve respeitar a capacidade efetivamente negociada/provisionada para cada região/endpoint. # HPA. replicas * XAI_POOL_SIZE must fit each target/endpoint capacity.
HPA_MIN_REPLICAS=1 HPA_MIN_REPLICAS=1
HPA_MAX_REPLICAS=3 HPA_MAX_REPLICAS=3
HPA_CPU_TARGET=65 HPA_CPU_TARGET=65
# ORD # Example target ORD
ORD_REGION_ID=ord ORD_REGION_ID=ord
ORD_REGION_REPLICAS=1 ORD_REGION_REPLICAS=1
ORD_XAI_UPSTREAM_URL=wss://peordagnt002prd.pe.inference.generativeai.us-chicago-1.oci.oraclecloud.com/xai/v1/tts ORD_XAI_UPSTREAM_URL=wss://peordagnt002prd.pe.inference.generativeai.us-chicago-1.oci.oraclecloud.com/xai/v1/tts
ORD_XAI_SECRET_NAME=xai-ord-credentials ORD_XAI_SECRET_NAME=xai-ord-credentials
# IAD # Example target IAD
IAD_REGION_ID=iad IAD_REGION_ID=iad
IAD_REGION_REPLICAS=1 IAD_REGION_REPLICAS=1
IAD_XAI_UPSTREAM_URL=wss://peiadagnt003prd.pe.inference.generativeai.us-ashburn-1.oci.oraclecloud.com/xai/v1/tts IAD_XAI_UPSTREAM_URL=wss://peiadagnt003prd.pe.inference.generativeai.us-ashburn-1.oci.oraclecloud.com/xai/v1/tts
IAD_XAI_SECRET_NAME=xai-iad-credentials IAD_XAI_SECRET_NAME=xai-iad-credentials
# Add another region without changing code:
# XAI_TARGETS="ORD IAD GRU"
# GRU_REGION_ID=gru
# GRU_REGION_REPLICAS=1
# GRU_XAI_UPSTREAM_URL=wss://<endpoint>/xai/v1/tts
# GRU_XAI_SECRET_NAME=xai-gru-credentials
# Multiple independent pools in one OCI region are also supported:
# XAI_TARGETS="ORD_PRIMARY ORD_SECONDARY IAD"
# ORD_PRIMARY_REGION_ID=ord-primary
# ORD_PRIMARY_REGION_REPLICAS=1
# ORD_PRIMARY_XAI_UPSTREAM_URL=wss://<endpoint-a>/xai/v1/tts
# ORD_PRIMARY_XAI_SECRET_NAME=xai-ord-primary-credentials
# ORD_SECONDARY_REGION_ID=ord-secondary
# ORD_SECONDARY_REGION_REPLICAS=1
# ORD_SECONDARY_XAI_UPSTREAM_URL=wss://<endpoint-b>/xai/v1/tts
# ORD_SECONDARY_XAI_SECRET_NAME=xai-ord-secondary-credentials

View File

@@ -3,13 +3,23 @@ set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${1:-$ROOT/k8s/regional/regions.env}" ENV_FILE="${1:-$ROOT/k8s/regional/regions.env}"
OUT="${2:-$ROOT/k8s/regional/rendered}" OUT="${2:-$ROOT/k8s/regional/rendered}"
"$ROOT/scripts/render-regional-k8s.sh" "$ENV_FILE" "$OUT" "$ROOT/scripts/render-regional-k8s.sh" "$ENV_FILE" "$OUT"
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
kubectl apply -f "$OUT/service.yaml" kubectl apply -f "$OUT/service.yaml"
kubectl apply -f "$OUT/pdb.yaml" kubectl apply -f "$OUT/pdb.yaml"
kubectl apply -f "$OUT/deployment-ord.yaml"
kubectl apply -f "$OUT/deployment-iad.yaml" shopt -s nullglob
kubectl apply -f "$OUT/hpa-ord.yaml" deployments=("$OUT"/deployment-*.yaml)
kubectl apply -f "$OUT/hpa-iad.yaml" hpas=("$OUT"/hpa-*.yaml)
kubectl rollout status -f "$OUT/deployment-ord.yaml" --timeout=10m ((${#deployments[@]} > 0)) || { echo "No regional deployments rendered" >&2; exit 2; }
kubectl rollout status -f "$OUT/deployment-iad.yaml" --timeout=10m
kubectl get pods -l app="$(grep '^APP_NAME=' "$ENV_FILE" | cut -d= -f2)-regional" -o wide for manifest in "${deployments[@]}"; do kubectl apply -f "$manifest"; done
for manifest in "${hpas[@]}"; do kubectl apply -f "$manifest"; done
for manifest in "${deployments[@]}"; do kubectl rollout status -f "$manifest" --timeout=10m; done
kubectl -n "$K8S_NAMESPACE" get pods -l "app=${APP_NAME}-regional" -o wide

View File

@@ -4,21 +4,61 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${1:-$ROOT/k8s/regional/regions.env}" ENV_FILE="${1:-$ROOT/k8s/regional/regions.env}"
OUT="${2:-$ROOT/k8s/regional/rendered}" OUT="${2:-$ROOT/k8s/regional/rendered}"
[[ -f "$ENV_FILE" ]] || { echo "Missing $ENV_FILE (copy regions.env.example)" >&2; exit 2; } [[ -f "$ENV_FILE" ]] || { echo "Missing $ENV_FILE (copy regions.env.example)" >&2; exit 2; }
set -a; source "$ENV_FILE"; set +a set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
command -v envsubst >/dev/null || { echo "envsubst is required (gettext package)" >&2; exit 2; } command -v envsubst >/dev/null || { echo "envsubst is required (gettext package)" >&2; exit 2; }
rm -rf "$OUT"; mkdir -p "$OUT"
render_region() { XAI_TARGETS="${XAI_TARGETS:-${XAI_REGIONS:-}}"
local prefix="$1" [[ -n "${XAI_TARGETS// }" ]] || {
echo 'XAI_TARGETS is required. Example: XAI_TARGETS="ORD IAD GRU"' >&2
exit 2
}
rm -rf "$OUT"
mkdir -p "$OUT"
required_target_var() {
local target="$1" suffix="$2" var value
var="${target}_${suffix}"
value="${!var:-}"
[[ -n "$value" ]] || { echo "Missing required configuration: $var" >&2; exit 2; }
printf '%s' "$value"
}
render_target() {
local target="$1"
[[ "$target" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || {
echo "Invalid XAI target '$target'. Use letters, numbers and underscores only." >&2
exit 2
}
export REGION_ID REGION_REPLICAS XAI_UPSTREAM_URL XAI_SECRET_NAME export REGION_ID REGION_REPLICAS XAI_UPSTREAM_URL XAI_SECRET_NAME
REGION_ID="$(eval echo \"\${${prefix}_REGION_ID}\")" REGION_ID="$(required_target_var "$target" REGION_ID)"
REGION_REPLICAS="$(eval echo \"\${${prefix}_REGION_REPLICAS}\")" REGION_REPLICAS="$(required_target_var "$target" REGION_REPLICAS)"
XAI_UPSTREAM_URL="$(eval echo \"\${${prefix}_XAI_UPSTREAM_URL}\")" XAI_UPSTREAM_URL="$(required_target_var "$target" XAI_UPSTREAM_URL)"
XAI_SECRET_NAME="$(eval echo \"\${${prefix}_XAI_SECRET_NAME}\")" XAI_SECRET_NAME="$(required_target_var "$target" XAI_SECRET_NAME)"
[[ "$REGION_ID" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]] || {
echo "Invalid ${target}_REGION_ID='$REGION_ID'. Use a lowercase Kubernetes label." >&2
exit 2
}
[[ ! -e "$OUT/deployment-${REGION_ID}.yaml" ]] || {
echo "Duplicate REGION_ID '$REGION_ID'. Each target must have a unique REGION_ID." >&2
echo "For multiple pools in one OCI region use ids such as ord-primary and ord-secondary." >&2
exit 2
}
envsubst < "$ROOT/k8s/regional/deployment-region.yaml" > "$OUT/deployment-${REGION_ID}.yaml" envsubst < "$ROOT/k8s/regional/deployment-region.yaml" > "$OUT/deployment-${REGION_ID}.yaml"
envsubst < "$ROOT/k8s/regional/hpa-region.yaml" > "$OUT/hpa-${REGION_ID}.yaml" envsubst < "$ROOT/k8s/regional/hpa-region.yaml" > "$OUT/hpa-${REGION_ID}.yaml"
echo "Rendered target=$target region_id=$REGION_ID"
} }
render_region ORD
render_region IAD for target in $XAI_TARGETS; do
render_target "$target"
done
envsubst < "$ROOT/k8s/regional/service.yaml" > "$OUT/service.yaml" envsubst < "$ROOT/k8s/regional/service.yaml" > "$OUT/service.yaml"
envsubst < "$ROOT/k8s/regional/pdb.yaml" > "$OUT/pdb.yaml" envsubst < "$ROOT/k8s/regional/pdb.yaml" > "$OUT/pdb.yaml"
echo "Rendered manifests in $OUT" echo "Rendered manifests in $OUT"

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
mkdir -p "$TMP/repo/scripts" "$TMP/repo/k8s/regional" "$TMP/bin"
cp "$ROOT/scripts/render-regional-k8s.sh" "$TMP/repo/scripts/"
cat > "$TMP/bin/envsubst" <<'PY'
#!/usr/bin/env python3
import os,re,sys
s=sys.stdin.read()
s=re.sub(r'\$\{([A-Za-z_][A-Za-z0-9_]*)\}',lambda m:os.environ.get(m.group(1),m.group(0)),s)
sys.stdout.write(s)
PY
chmod +x "$TMP/bin/envsubst"
cat > "$TMP/repo/k8s/regional/deployment-region.yaml" <<'EOF2'
id=${REGION_ID};replicas=${REGION_REPLICAS};url=${XAI_UPSTREAM_URL};secret=${XAI_SECRET_NAME}
EOF2
cat > "$TMP/repo/k8s/regional/hpa-region.yaml" <<'EOF2'
hpa=${REGION_ID}
EOF2
cat > "$TMP/repo/k8s/regional/service.yaml" <<'EOF2'
service=${APP_NAME}
EOF2
cat > "$TMP/repo/k8s/regional/pdb.yaml" <<'EOF2'
pdb=${APP_NAME}
EOF2
cat > "$TMP/regions.env" <<'EOF2'
APP_NAME=tia
XAI_TARGETS="ORD IAD GRU"
ORD_REGION_ID=ord
ORD_REGION_REPLICAS=1
ORD_XAI_UPSTREAM_URL=wss://ord
ORD_XAI_SECRET_NAME=secret-ord
IAD_REGION_ID=iad
IAD_REGION_REPLICAS=2
IAD_XAI_UPSTREAM_URL=wss://iad
IAD_XAI_SECRET_NAME=secret-iad
GRU_REGION_ID=gru
GRU_REGION_REPLICAS=3
GRU_XAI_UPSTREAM_URL=wss://gru
GRU_XAI_SECRET_NAME=secret-gru
EOF2
PATH="$TMP/bin:$PATH" "$TMP/repo/scripts/render-regional-k8s.sh" "$TMP/regions.env" "$TMP/out"
test -f "$TMP/out/deployment-ord.yaml"
test -f "$TMP/out/deployment-iad.yaml"
test -f "$TMP/out/deployment-gru.yaml"
grep -q 'replicas=3' "$TMP/out/deployment-gru.yaml"
echo 'test_render_regional_multi_target: PASS'