- o operator em
chatcli-system, com API REST, dashboard web, métricas e uma NetworkPolicy opcional; - um servidor ChatCLI (
Instance) emchatcli, com credencial, TLS, recursos explícitos e um watcher nas suas cargas de produção; - políticas de notificação, escalação, SLO, SLA e aprovação;
- dashboards no Grafana e coleta pelo Prometheus;
- um drill de chaos que mostra o pipeline detectando uma falha.
Pré-requisitos
- Um cluster Kubernetes e permissão de cluster-admin para a instalação (o chart cria CRDs, ClusterRoles e ClusterRoleBindings).
kubectl, Helm 3.8 ou mais recente (para charts OCI; Helm 4 incluído) eopenssl.- Pelo menos uma chave de provedor de LLM (os exemplos usam Anthropic e OpenAI).
- Para observabilidade: o Prometheus Operator (para
ServiceMonitor) e o Grafana com o sidecar de dashboards. Os dois são opcionais. - A CLI
chatclina sua máquina, para o teste de ponta a ponta do passo 13.
Onde cada recurso fica
O operator procura os recursos em namespaces específicos. Um recurso no namespace errado não dá erro: ele é simplesmente ignorado.1. Instalar o operator
Escreva um arquivo de values. Todas as chaves abaixo existem no schema do chart, e o schema recusa chaves desconhecidas.chatcli-operator, portas metrics 8080, health 8081, api 8090), o RBAC de cluster, a ClusterRole chatcli-watcher pré-provisionada e um hook de pre-install/pre-upgrade que reaplica os CRDs a cada helm upgrade. Ele fixa a imagem de servidor usada pelas Instances na mesma release, a menos que a Instance defina spec.image.tag.
Verificação:
O chart do operator não tem template de PodDisruptionBudget. Com
replicaCount: 2, crie um você mesmo se um drain de nó precisar manter uma réplica no ar (seletor app.kubernetes.io/name: chatcli-operator).2. Criar os Secrets da Instance
Todos ficam no namespace da Instance.Certificado TLS
O operator sempre se conecta ao servidor com TLS 1.3. Não existe modo em texto puro: uma Instance sem TLS sobe, mas o operator não consegue alcançá-la (ServerReachable=False), e alertas, análise de IA e remediação param aí.
O operator disca <instance>.<namespace>.svc.cluster.local, aqui chatcli-prod.chatcli.svc.cluster.local. O certificado precisa ser válido para esse nome. Se não for assinado por uma CA pública, o Secret também precisa trazer a CA como ca.crt, que o operator usa como raiz de confiança para essa Instance.
- cert-manager (recomendado)
- openssl (autoassinado)
Um issuer de CA interna grava
tls.crt, tls.key e ca.crt no Secret, e renova o certificado. Cada renovação recria os pods da Instance.Não existe campo na Instance para o endereço que o operator disca. É sempre o nome do Service da Instance acima, na porta
spec.server.port. Há overrides para o operator inteiro, da raiz de confiança e de um certificado de cliente (chart security.grpcTLS.*), mas um ca.crt no próprio Secret da Instance é mais simples e tem prioridade sobre eles.3. Criar a Instance de produção
- Um Deployment
chatcli-prodcujos pods rodam sem root, com sistema de arquivos raiz somente leitura, sem nenhuma capability, e atendem ao Pod Security Standardrestricted. As probes de startup, readiness e liveness chamamGET /healthzna porta de métricas. - Um Service
chatcli-prodcom as portasgrpcemetrics. Comreplicas > 1ele é headless, e o operator balanceia entre os pods. - Uma ServiceAccount e, para alvos do watcher em outros namespaces, um ClusterRoleBinding para
chatcli-watcher, para o servidor conseguir ler essas cargas. - Nenhum PodDisruptionBudget, HPA ou NetworkPolicy. Crie você mesmo se precisar (há um exemplo de NetworkPolicy abaixo).
Fallback. O servidor só monta a cadeia de fallback quando pelo menos dois dos provedores listados têm credencial, e só a usa em requisições que não trazem credencial nem provedor próprios. As requisições do pipeline de AIOps se encaixam.
monitoring (acrescente os namespaces de quem usa chatcli connect):
Na maioria dos CNIs as probes do kubelet não passam pela NetworkPolicy, mas não em todos. Se os pods falharem no readiness depois de aplicar a política, libere também a porta 9090 a partir do CIDR dos nós.
True e ServerReachable mostra Serving:
VERSION de kubectl get instance mostra a versão do servidor que o operator leu por último. Uma Instance pronta é sondada de novo a cada cinco minutos, então espere até cinco minutos para ServerReachable refletir uma mudança; depois de uma sondagem que falha, ela é repetida a cada 30 segundos.
4. Vincular repositórios de código (opcional)
UmSourceRepository entrega à IA os commits recentes e o código em volta dos stack traces de uma carga. Crie o repositório, e o Secret do git, no namespace da carga. O operator o clona no emptyDir gravável /tmp dele (valor tmpVolume.sizeLimit do chart, passo 1).
authType: token para token, username e password para basic, ssh-key e known_hosts para ssh. As credenciais HTTPS chegam ao git por um helper GIT_ASKPASS a cada comando e nunca são gravadas no .git/config do clone; um clone feito por uma versão anterior, que guardava o token na URL do origin, tem o token removido do origin e o reflog expirado na próxima sincronização. A imagem do operator traz o openssh-client, e um clone por SSH confere a chave do host do servidor contra o known_hosts do Secret com StrictHostKeyChecking=yes:
kubectl -n production get sourcerepositories mostra o repositório pronto depois da primeira sincronização.
5. Notificações
Coloque URLs de webhook, routing keys e senhas num Secret. Todas as chaves do Secret dosecretRef de um canal são mescladas no config dele. Não as coloque no config em si: GET /api/v1/policies/notification devolve o spec inteiro para qualquer chave com papel viewer.
deduplicationWindow curto. Uma notificação dentro da janela é descartada, não enfileirada, então com o padrão de 5m uma Issue que se resolve rápido pode nunca mandar o Resolved para o Slack ou o e-mail. O Resolved para PagerDuty e OpsGenie nunca é limitado, nem pela janela nem pelo maxPerHour, então o page sempre é fechado. Contained significa que a plataforma conteve o impacto (por exemplo, escalou a carga para zero) sem corrigir a causa, e alguém precisa agir: mande esse estado para o pager.
Veja Notificações para as chaves de cada canal e os templates.
6. Escalação
Uma escalação começa quando a Issue chega aEscalated, o que acontece quando as tentativas de remediação acabam. Ela para quando a Issue fica Resolved ou quando alguém a reconhece (acknowledge). O operator usa a primeira política habilitada, de qualquer namespace, cujo severities inclua a severidade da Issue.
targets são só informativos: entram no texto da mensagem, e a mensagem vai para os notifyChannels de cada nível, procurados pelo nome nas NotificationPolicies habilitadas.
Como uma escalação se comporta:
- O nível atual fica salvo na Issue (
platform.chatcli.io/escalation-level, comescalation-notified-atpara o último acionamento). CadatimeoutMinutessobe um nível, até o último, onde ela fica.chatcli_operator_escalation_level_reachedsobe um a cada nível alcançado. repeatIntervalMinutesreenvia a mensagem do nível atual nesse intervalo; no último nível, continua repetindo.0(o padrão) nunca repete.- O acknowledge (dashboard, ou
POST /api/v1/incidents/<name>/acknowledge) para a escalação onde ela está: nenhum nível a mais, nenhuma repetição. A EscalationPolicy registraacknowledgedAteacknowledgedBy(o role da API key de quem chamou) emstatus.activeEscalations. - O snooze (
POST /api/v1/incidents/<name>/snoozecom umdurationpositivo, como"30m") segura todas as notificações da Issue, menos oResolved, e segura a escalação. O acionamento de nível que cai dentro do snooze é marcado comescalation-pending-notifye sai quando o snooze termina; o timer do nível recomeça nesse momento. - Issues de um drill de chaos nunca escalam.
7. SLOs e SLAs
SLO
Os SLOs são calculados a partir das próprias Issues e Anomalies do operator para o recurso, não a partir do Prometheus. Crie o SLO no namespace da carga.signalType: slo_violation, que a regra slo-violations do passo 5 encaminha.
SLA
Vale aIncidentSLA do namespace da Issue. Se não houver, vale uma com a mesma severidade de qualquer namespace, então um conjunto em chatcli-system funciona como padrão do cluster inteiro.
platform.chatcli.io/sla-violated, mais platform.chatcli.io/sla-name com a SLA que a contou) e na trilha de auditoria. status.activeViolations volta a cair quando a Issue é resolvida. Uma duração inválida deixa a condição Ready da SLA em False com o motivo InvalidDuration. Uma violação não envia notificação: escalationPolicyRef e notificationPolicyRef são reservados e não são lidos. Alerte com chatcli_operator_sla_violations_total.
Veja SLOs e SLAs para o cálculo e os campos de status.
8. Dashboard e chaves da API REST
A API REST e o dashboard web dividem a porta 8090 do Service do operator. Toda chamada em/api/ precisa do header X-API-Key. As chaves vêm do Secret chatcli-operator-secrets (chave api-keys) no namespace do operator. O nome é fixo. Sem ele, toda chamada em /api/ devolve 401.
Qualquer outro nome de papel não dá acesso nenhum. O operator relê o Secret a cada 30 segundos, então mudar chaves não exige restart. Editar o Secret para tirar uma chave revoga essa chave, e uma lista vazia não deixa nenhuma chave válida. Um YAML que não faz parse mantém em vigor o último conjunto de chaves válido (o operator registra isso no log uma vez por versão), então corrija o erro em vez de contar com ele para revogar. Um Secret sem a entrada
api-keys cai para o ConfigMap; apagar o Secret e também o ConfigMap chatcli-operator-config revoga todas as chaves em cerca de 30 segundos (401 a partir daí). Um erro de leitura que não seja “não encontrado” mantém as chaves em vigor. A inicialização aplica as mesmas regras da consulta periódica.
Cada entrada também aceita um name opcional, a identidade registrada nas aprovações feitas com aquela chave (na falta dele vale o description, e depois uma impressão digital key-<hash>). Um quorum conta cada chave uma vez, então dê a cada aprovador a sua própria chave.
Em vez do kubectl, o chart pode gerar o mesmo Secret (--set apiKeys.create=true com apiKeys.entries), mas aí as chaves ficam guardadas na release do Helm.
Abra o dashboard:
/api/v1/... com caminho absoluto, então um sub-path com rewrite-target carrega a página mas quebra todas as chamadas da API.
O rate limit da API REST conta as requisições sem chave válida por host de origem (30 por minuto) e cada chave válida separadamente (600 por minuto). Atrás de um ingress, o host de origem é o ingress controller, então todo o tráfego sem autenticação divide um único bucket. Para TLS na própria porta 8090, monte um certificado com
extraVolumes/extraVolumeMounts e defina security.apiTLS.certFile/keyFile. Não existe login por SSO ou OIDC: o acesso é só por chave de API.9. Aprovações
UmaApprovalPolicy controla os RemediationPlans do próprio namespace. Dentro de uma política, vale a primeira regra que casar. Os actionTypes de uma regra casam quando qualquer ação do plano está na lista. Coloque as regras mais rígidas primeiro e termine com uma regra que casa com tudo.
autosemautoApproveConditionssignifica que a política não segura o plano. Uma regraautocomautoApproveConditionssegura o plano e só o aprova automaticamente quando todas as condições valem (maxSeverityé comparado com a severidade da Issue; uma severidade desconhecida não atende); caso contrário, um humano decide. Um plano com um restart e uma ação que nenhuma regra anterior lista também roda pelalow-risk-auto.defaultModeé informativo: okubectl get apo mostra, mas o operator não o aplica. Sem regra que case, a política não segura o plano, e é por isso que a última regra (match: {}) é um pega-tudo que segura todo o resto.DrainNodefaz cordon do nó e despeja os pods pela Eviction APIpolicy/v1com 30 segundos de grace period, então os PodDisruptionBudgets são respeitados. Despejos recusados com429ou erro de servidor são refeitos a cada 5 segundos até o param opcionaltimeout(padrão2m, no máximo10m); depois disso a ação falha, citando os pods que não conseguiu despejar. Pods de DaemonSet e mirror pods são pulados. Mesmo assim, deixe essa ação atrás de uma regra manual.- O gate falha fechado. Se a Issue, o AIInsight ou as ApprovalPolicies não puderem ser lidos, ou o decision engine der erro, o plano fica em
Pending, um Event de WarningApprovalGateUnavailableé registrado nele e ele é tentado de novo com backoff. Um plano cuja Issue pai sumiu falha (“Parent issue not found…”). Um AIInsight ausente conta como confiança 0, o que só deixa o gate mais rígido.
ApprovalRequest (chamado approval-<plano>, no namespace do plano). Restrinja update/patch em approvalrequests com RBAC a quem pode aprovar, porque o nome de quem aprova é texto livre:
O dashboard e o
POST /api/v1/approvals/{name}/approve (ou /reject) registram uma decisão num pedido Pending, do mesmo jeito que a anotação. Quem aprovou fica gravado como <nome digitado> (api-key: <identidade da chave>), e o ApprovalReconciler avalia então o requiredApprovers e a janela de mudança. Pela REST, um quorum conta API keys distintas, então uma chave compartilhada não satisfaz requiredApprovers: 2; aprovadores por anotação são contados pelo nome, sem diferenciar maiúsculas. Qualquer rejeição rejeita o pedido. Um pedido que não está Pending, ou uma segunda decisão da mesma chave, responde 409.Approved quando a janela abre, com a condition ChangeWindow=False (reason OutsideChangeWindow) enquanto isso. Uma janela que nunca abre bloqueia a aprovação e expira pelo relógio de parede.
Mais dois portões podem segurar um plano sem política nenhuma: o tier do cluster (chart clusterName, com o nome de um ClusterRegistration) e o decision engine (chart decisionEngine.enabled). Veja Fluxo de Aprovação e Decision Engine.
10. Prometheus
São duas integrações separadas: Coleta. O operator expõe/metrics na porta 8080 (chatcli_operator_*). Cada Instance expõe /metrics na porta de métricas (chatcli_grpc_*, chatcli_llm_*, chatcli_watcher_*, chatcli_server_*). As duas são HTTP puro sem autenticação, então restrinja quem alcança essas portas com NetworkPolicy (passos 1 e 3). O serviceMonitor.enabled do chart cobre o operator. Para a Instance:
prometheusUrl definido no chart do operator (variável PROMETHEUS_URL), o operator consulta o Prometheus no intervalo do incidente e junta o resultado à análise de IA: CPU, memória, restarts e rede por pod, taxas de requisição e de erro, percentis de latência e réplicas do HPA. Ele chama /api/v1/query_range sem autenticação, então a URL precisa chegar a uma API HTTP do Prometheus sem autenticação, normalmente o Service interno do cluster. As métricas de container vêm do cAdvisor e os restarts do kube-state-metrics. Sem essa URL a análise roda do mesmo jeito, só que sem essas métricas.
11. Dashboards do Grafana
O repositório traz quatro dashboards emdeploy/grafana/. Os painéis referenciam o datasource ${DS_PROMETHEUS}, que a tela de importação do Grafana resolve, mas o provisionamento pelo sidecar não. Para o sidecar, troque antes pelo UID do seu datasource do Prometheus (prometheus no kube-prometheus-stack; confira em Connections → Data sources no Grafana):
12. Validar com um drill de chaos
Um drill prova o ciclo de ponta a ponta: a falha é injetada, o watcher percebe, uma Issue é aberta e marcada como drill, e a carga se recupera.pod_killepod_failurefuncionam com o RBAC que o chart instala.cpu_stress,memory_stressedisk_stresscriam um pod de stress, que o RBAC do chart permite; o pod passa no Pod Security Standardrestricted(UID/GID 65534, não-root, seccompRuntimeDefault, sem escalada de privilégio, todas as capabilities removidas, root filesystem somente leitura com umemptyDirem/tmp, sem token de ServiceAccount).network_delayenetwork_lossnão são suportados: o experimento falha emPendingcom uma mensagem dizendo que nada foi injetado.requireApproval: truecria um ApprovalRequestchaos-<experimento>(ação pedidaCustom, timeout de 30 minutos). O experimento roda depois de aprovado; uma rejeição ou expiração faz o experimento falhar.schedulenão é suportado: um experimento que o define falha emPendinge nada roda. Crie um ChaosExperiment por execução.recoveryTimeé medido do fim da injeção até o primeiro health check que passa (os checks rodam a cada 5 segundos);recoveryVerifiedé o sinal de passou/falhou. As Issues do drill ficam fora do MTTD e do MTTR.
13. Verificar a instalação
1
Operator e CRDs
2
Condições da Instance
True, como no passo 3.3
A ponte de alertas está conectada
Connected to Instance seguido de Alert stream open no líder. Connected to Instance sozinho não prova nada, porque a conexão é aberta de forma preguiçosa. Erros de TLS e de credencial aparecem em ServerReachable e nos erros do stream que vêm depois.4
gRPC de ponta a ponta, com TLS e o token
localhost do passo 2.5
API REST e dashboard
6
Métricas
No Prometheus,
up{namespace="chatcli-system"} e up{namespace="chatcli"} estão em 1, e chatcli_operator_instance_ready e chatcli_server_info retornam séries.7
As políticas foram carregadas
8
O pipeline reage
Rode o drill do passo 12 e acompanhe uma Issue aparecer em
production e no dashboard.14. Troubleshooting
Checklist de produção
- Operator instalado com
--versionfixado, 17 CRDs presentes,replicaCount: 2 - NetworkPolicy do operator ligada, com o ingress da API e das métricas restrito
- Secrets da Instance criados: chaves de LLM, token, TLS (
tls.crt,tls.key,ca.crt), chave de criptografia - Certificado válido para
chatcli-prod.chatcli.svc.cluster.local(maislocalhostpara os testes), com renovação configurada - Instance com
resourcesexplícitos, anti-affinity e persistência coerente comreplicas - As cinco condições da Instance em
True, eServerReachableemServing - Só uma Instance com watcher no cluster
- NetworkPolicy da Instance e, se preciso, um PodDisruptionBudget criados por você
- Chaves do dashboard em
chatcli-operator-secrets, uma por time e papel, com poucas chaves admin - Dashboard publicado só com TLS, num host próprio
- NotificationPolicy com credenciais em Secrets e
deduplicationWindowcurto - EscalationPolicy com os níveis e, onde um acionamento precisa repetir,
repeatIntervalMinutes - SLOs e ApprovalPolicies nos namespaces das cargas
- ApprovalPolicy com ações de nó e mudanças arriscadas primeiro, e uma regra que casa com tudo no fim
- RBAC em
approvalrequestslimitado a quem aprova - ServiceMonitors coletando o operator e a Instance
- Dashboards do Grafana provisionados com o UID do datasource trocado
- Um drill de chaos rodado primeiro em dry-run e depois de verdade, com a recuperação verificada
- Values do operator guardados num arquivo (
helm get values chatcli-operator -n chatcli-system -o yaml) para o próximo upgrade
Próximos passos
Hardening de segurança
Credenciais, TLS, auditoria e o que a plataforma não protege.
K8s Operator
Todos os campos da Instance, o que o operator cria e como fazer upgrade.
Fluxo de Aprovação
Modos, janelas de mudança e pedidos de aprovação.
API REST
Endpoints, papéis e rate limits.