# Deployment — TIA Regional com Pool xAI

## 1. Pré-requisitos

- cluster Kubernetes/OKE funcional;
- `kubectl` configurado;
- `envsubst` (`gettext`) instalado na máquina de deployment;
- imagem TIA construída a partir desta versão;
- ConfigMap `${APP_NAME}-config` já existente;
- Secrets `${APP_NAME}-api-secrets`, `${APP_NAME}-google-sa-secret` e `shared-tls-secret` já existentes conforme deployment atual;
- endpoint e credencial xAI de cada região;
- quota xAI validada para o número total de WebSockets configurado.

## 2. Arquivos adicionados

```text
src/app/livekit/adapters/xai_pool_proxy.py

k8s/regional/
  deployment-region.yaml
  service.yaml
  pdb.yaml
  hpa-region.yaml
  xai-secret.example.yaml
  regions.env.example

scripts/
  render-regional-k8s.sh
  validate-regional-k8s.sh
  deploy-regional-k8s.sh
```

## 3. Construir a imagem

Use o pipeline existente ou Dockerfile atual do TIA. O novo sidecar utiliza a mesma imagem e apenas muda o módulo executado:

```text
python -m app.livekit.adapters.xai_pool_proxy
```

Exemplo local:

```bash
docker build -f k8s/tia/Dockerfile \
  -t iad.ocir.io/<namespace>/tia:regional-xai-pool-v1 .
```

Faça push para o registry usado pelo cluster.

## 4. Criar arquivo de ambiente de deployment

```bash
cp k8s/regional/regions.env.example k8s/regional/regions.env
```

Edite no mínimo:

```bash
APP_NAME=tim-ai-atend-agnt-integ-tia
K8S_NAMESPACE=...
IMAGE_REPOSITORY=...
IMAGE_TAG=...

ORD_XAI_UPSTREAM_URL=wss://...us-chicago-1.../xai/v1/tts
ORD_XAI_SECRET_NAME=xai-ord-credentials

IAD_XAI_UPSTREAM_URL=wss://...us-ashburn-1.../xai/v1/tts
IAD_XAI_SECRET_NAME=xai-iad-credentials
```

## 5. Definir tamanho do pool

`XAI_POOL_SIZE` é o número de conexões xAI pré-aquecidas mantidas por réplica TIA; **não é um limite do serviço OCI/xAI**. Ajuste-o conforme a capacidade negociada/provisionada. Exemplo com 50 sockets:

```bash
XAI_POOL_SIZE=50
```

Readiness recomendada:

```bash
XAI_POOL_UNAVAILABLE_FREE=2
XAI_POOL_RECOVER_FREE=5
```

Se desejar exatamente o comportamento "só sair quando os 50 estiverem ocupados":

```bash
XAI_POOL_UNAVAILABLE_FREE=0
XAI_POOL_RECOVER_FREE=5
```


Para barge-in, configure também:

```bash
XAI_POOL_BARGE_IN_CLEAR_TIMEOUT_S=1.0
```

Esse timeout limita quanto tempo o sidecar espera pelo `audio.clear` que confirma que o WebSocket está limpo após uma interrupção. Se não houver confirmação, o socket é descartado e substituído.

### Importante

Se houver `N` réplicas apontando para o mesmo endpoint:

```text
sockets máximos = N * XAI_POOL_SIZE
```

Dimensione esse total de acordo com a capacidade efetivamente negociada/provisionada para a região/endpoint.

## 6. Criar Secrets regionais

Não versione chaves reais.

Exemplo por linha de comando:

```bash
kubectl -n "$K8S_NAMESPACE" create secret generic xai-ord-credentials \
  --from-literal=XAI_API_KEY='<ORD_KEY>' \
  --from-literal=OCI_COMPARTMENT_ID='<COMPARTMENT_OCID>'

kubectl -n "$K8S_NAMESPACE" create secret generic xai-iad-credentials \
  --from-literal=XAI_API_KEY='<IAD_KEY>' \
  --from-literal=OCI_COMPARTMENT_ID='<COMPARTMENT_OCID>'
```

Para `API_KEY`, `OCI_COMPARTMENT_ID` pode ficar vazio se o fluxo upstream não o exigir.

Para produção, prefira OCI Vault/External Secrets/Workload Identity em vez de gravar chaves no repositório.

## 7. Renderizar manifests

```bash
./scripts/render-regional-k8s.sh
```

Saída:

```text
k8s/regional/rendered/
  deployment-ord.yaml
  deployment-iad.yaml
  hpa-ord.yaml
  hpa-iad.yaml
  service.yaml
  pdb.yaml
```

É possível escolher outro arquivo e diretório:

```bash
./scripts/render-regional-k8s.sh ./minha-config.env /tmp/tia-regional
```

## 8. Validar sem implantar

```bash
./scripts/validate-regional-k8s.sh
```

O script usa:

```bash
kubectl apply --dry-run=client
```

Revise também:

```bash
kubectl diff -f k8s/regional/rendered/
```

## 9. Implantar

```bash
./scripts/deploy-regional-k8s.sh
```

Ou manualmente:

```bash
kubectl apply -f k8s/regional/rendered/service.yaml
kubectl apply -f k8s/regional/rendered/pdb.yaml
kubectl apply -f k8s/regional/rendered/deployment-ord.yaml
kubectl apply -f k8s/regional/rendered/deployment-iad.yaml
kubectl apply -f k8s/regional/rendered/hpa-ord.yaml
kubectl apply -f k8s/regional/rendered/hpa-iad.yaml
```

## 10. Verificar rollout

```bash
kubectl -n "$K8S_NAMESPACE" get pods -l app=${APP_NAME}-regional -o wide
```

Todos os containers devem ficar Ready:

```text
bridge   1/1
agent    1/1
xai-pool 1/1
```

Confira os deployments:

```bash
kubectl -n "$K8S_NAMESPACE" rollout status deployment/${APP_NAME}-ord
kubectl -n "$K8S_NAMESPACE" rollout status deployment/${APP_NAME}-iad
```

## 11. Validar o pool

Escolha um Pod:

```bash
POD=$(kubectl -n "$K8S_NAMESPACE" get pod \
  -l app=${APP_NAME}-regional,tia-region=ord \
  -o jsonpath='{.items[0].metadata.name}')
```

Port-forward:

```bash
kubectl -n "$K8S_NAMESPACE" port-forward "$POD" 18100:18100
```

Em outro terminal:

```bash
curl -s http://127.0.0.1:18100/healthz | jq
curl -s http://127.0.0.1:18100/readyz | jq
curl -s http://127.0.0.1:18100/pool/status | jq
curl -s http://127.0.0.1:18100/metrics
```

Resultado esperado após prewarm:

```json
{
  "region": "ord",
  "configured": 50,
  "healthy": 50,
  "leased": 0,
  "free": 50,
  "status": "ready"
}
```

## 12. Logs

```bash
kubectl -n "$K8S_NAMESPACE" logs "$POD" -c xai-pool -f
```

Eventos relevantes:

```text
XAI_POOL_SLOT_OPENED
XAI_POOL_PREWARM_FAILED
XAI_POOL_SLOT_RECOVERY_FAILED
XAI_POOL_STARTED
```

## 13. Testar saturação/readiness

Objetivo: provar que o Pod sai da rotação para novas sessões quando o pool atinge o limite.

1. Gere sínteses concorrentes suficientes para ocupar o pool.
2. Observe:

```bash
watch -n 1 'curl -s http://127.0.0.1:18100/pool/status | jq'
```

3. Quando `free <= XAI_POOL_UNAVAILABLE_FREE`, espere:

```bash
curl -i http://127.0.0.1:18100/readyz
```

Resultado:

```text
HTTP/1.1 503 Service Unavailable
```

4. Verifique o Pod:

```bash
kubectl get pod "$POD"
```

Ele deve ficar `NotReady` enquanto o sidecar estiver sem capacidade.

5. Quando conexões forem liberadas e `free >= XAI_POOL_RECOVER_FREE`, o `/readyz` volta a 200 e o Pod retorna aos endpoints.

## 14. Verificar endpoints do Service

```bash
kubectl -n "$K8S_NAMESPACE" get endpoints ${APP_NAME}-regional -o wide
```

ou, em clusters novos:

```bash
kubectl -n "$K8S_NAMESPACE" get endpointslice \
  -l kubernetes.io/service-name=${APP_NAME}-regional -o yaml
```

Um Pod NotReady não deve ser usado para novas conexões do Service.

## 15. Testar failover regional

### Teste controlado de ORD

Coloque ORD em drain:

```bash
ORD_POD=$(kubectl -n "$K8S_NAMESPACE" get pod \
  -l app=${APP_NAME}-regional,tia-region=ord \
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n "$K8S_NAMESPACE" exec "$ORD_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()"
```

Confirme `/readyz=503` e gere **nova chamada**. Ela deve ser entregue a um Pod IAD ainda Ready.

Esse teste não deve ser interpretado como migração de uma chamada existente; somente novas conexões são rebalanceadas.

## 16. Testar recuperação

O drain manual é intencional e não é revertido. Para voltar o Pod, reinicie-o:

```bash
kubectl -n "$K8S_NAMESPACE" delete pod "$ORD_POD"
```

O novo Pod deve:

1. iniciar sidecar;
2. pré-aquecer sockets;
3. atingir `recover_free_threshold`;
4. ficar Ready;
5. entrar novamente nos endpoints.

## 17. Load Balancer e WebSocket

WebSocket é suportado porque a conexão começa como HTTP Upgrade. O LB escolhe o backend no handshake e mantém a conexão naquele Pod.

Não use sticky session como requisito de correção. A conexão WebSocket em si já é persistente ao backend selecionado. Em caso de perda do Pod, o cliente precisa reconectar.

Uma política equivalente a least-connections pode ser útil quando o LB permite configuração, mas o mecanismo primário de proteção desta arquitetura é a readiness baseada em capacidade real do TTS.

## 18. HPA

Os manifests incluem HPA por CPU como proteção inicial.

Atenção: aumentar réplicas também aumenta o número de sockets xAI pré-aquecidos. Portanto, HPA só deve ter `maxReplicas` maior que 1 se a quota xAI permitir:

```text
HPA_MAX_REPLICAS * XAI_POOL_SIZE <= quota regional permitida
```

Para evolução futura, prefira uma métrica customizada que considere chamadas/TTS ativos e também um controlador que respeite orçamento global de sockets.

## 19. Rollback

Para voltar ao modelo anterior:

1. redirecione o Service/LB para o deployment antigo;
2. ou restaure o manifesto `k8s/tia/deployment.yaml` original;
3. o código antigo `OraclexAITTS` continua presente e compatível com endpoint xAI direto.

A alteração não remove suporte ao modo anterior.

## 20. Checklist de produção

- [ ] confirmar quota real de WebSockets por endpoint/região/tenancy;
- [ ] confirmar se ORD e IAD têm pools de capacidade independentes;
- [ ] validar `XAI_POOL_SIZE * replicas` por região;
- [ ] validar Secret/Vault;
- [ ] medir tempo de prewarm dos 50 sockets;
- [ ] observar taxa de erro de handshake;
- [ ] validar refresh escalonado em janela > 10 minutos;
- [ ] testar saturation -> `/readyz=503`;
- [ ] testar recovery -> `/readyz=200`;
- [ ] testar drain durante chamada ativa;
- [ ] testar rollout sem perda de novas chamadas;
- [ ] testar perda total de ORD e entrada em IAD;
- [ ] correlacionar TTFB/gap/underflow com `pool_free`;
- [ ] validar LB/Service com WebSocket real;
- [ ] executar stress test com perfil semelhante ao tráfego de produção.
