Skip to main content
Este runbook leva um cluster vazio até uma plataforma AIOps do ChatCLI funcionando. Todos os manifestos abaixo são válidos contra os CRDs atuais. Siga os passos na ordem. A maioria termina com uma verificação para rodar antes de seguir. No final você terá:
  • o operator em chatcli-system, com API REST, dashboard web, métricas e uma NetworkPolicy opcional;
  • um servidor ChatCLI (Instance) em chatcli, 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) e openssl.
  • 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 chatcli na 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.
Só uma Instance por cluster conduz o AIOps. A ponte de alertas do operator se conecta à primeira Instance pronta que encontra em todos os namespaces, e a análise de IA e a remediação usam essa mesma conexão. Com várias Instances prontas, não é definido qual delas é escolhida. Rode uma única Instance com watcher por cluster.

1. Instalar o operator

Escreva um arquivo de values. Todas as chaves abaixo existem no schema do chart, e o schema recusa chaves desconhecidas.
O chart instala os 17 CRDs, o Deployment do operator, o Service dele (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.
O operator observa todos os Secrets que uma Instance referencia. Quando você rotaciona um deles, os pods são recriados automaticamente, porque um hash das chaves referenciadas vai no template do pod.

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.
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

O que o operator cria, e o que não cria:
  • Um Deployment chatcli-prod cujos pods rodam sem root, com sistema de arquivos raiz somente leitura, sem nenhuma capability, e atendem ao Pod Security Standard restricted. As probes de startup, readiness e liveness chamam GET /healthz na porta de métricas.
  • Um Service chatcli-prod com as portas grpc e metrics. Com replicas > 1 ele é 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).
Persistência e réplicas. persistence.enabled: true cria um único PVC ReadWriteOnce (chatcli-prod-sessions) que todas as réplicas montam, e o modo de acesso não pode ser alterado. Com replicas > 1, pods agendados em nós diferentes ficam em ContainerCreating com erro Multi-Attach. Ou mantenha replicas: 1 com persistência, ou rode várias réplicas sem ela, como acima. Com persistência o operator define a estratégia do Deployment como Recreate, então o pod antigo libera o volume antes de o novo subir: cada rollout tem uma breve indisponibilidade. Sem ela a estratégia é RollingUpdate. O pipeline de AIOps não precisa do volume de sessões. Sem ele, as sessões de quem usa chatcli connect duram só enquanto o pod existir.
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.
Uma NetworkPolicy para a Instance, que libera gRPC a partir do namespace do operator e a coleta a partir de 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.
Verificação: todas as condições estão True e ServerReachable mostra Serving:
A coluna 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)

Um SourceRepository 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).
As chaves do Secret dependem do 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:
Sem known_hosts a sincronização por SSH falha (secret ... has no "known_hosts" key), a menos que o SourceRepository defina spec.sshHostKeyPolicy: acceptNew, que confia na chave que o host apresenta no primeiro contato e rejeita uma chave diferente depois (lembrada enquanto durar o pod do operator). O padrão, strict, é a escolha segura: fixe as chaves do host no Secret.
Verificação: 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 do secretRef 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.
Mantenha 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 a Escalated, 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.
Os 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, com escalation-notified-at para o último acionamento). Cada timeoutMinutes sobe um nível, até o último, onde ela fica. chatcli_operator_escalation_level_reached sobe um a cada nível alcançado.
  • repeatIntervalMinutes reenvia 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 registra acknowledgedAt e acknowledgedBy (o role da API key de quem chamou) em status.activeEscalations.
  • O snooze (POST /api/v1/incidents/<name>/snooze com um duration positivo, como "30m") segura todas as notificações da Issue, menos o Resolved, e segura a escalação. O acionamento de nível que cai dentro do snooze é marcado com escalation-pending-notify e 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.
Uma janela de burn rate que dispara abre uma Issue com signalType: slo_violation, que a regra slo-violations do passo 5 encaminha.
  • pageOnBudgetExhausted: true abre uma Issue crítica quando o budget chega a zero. Ele só aciona de novo depois que o budget volta a ficar acima de zero e se esgota outra vez; enquanto essa Issue de esgotamento estiver aberta, nenhuma segunda é criada.
  • As Issues slo_violation do próprio SLO não contam como indisponibilidade, então um acionamento não aprofunda a violação que ele reporta.
  • metricSource: prometheus não é consultado: o SLI continua sendo calculado a partir de Issues e Anomalies, e o SLO reporta a condição MetricSourceSupported=False. prometheusQuery, latencyPercentile e latencyThresholdMs não são avaliados, e alertPolicy.notificationPolicyRef é reservado: as Issues do SLO são notificadas por toda NotificationPolicy cujas regras casem com elas.

SLA

Vale a IncidentSLA 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.
Uma violação fica registrada no status da IncidentSLA, na Issue (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:
Para publicar, sirva o dashboard na raiz de um host próprio. O dashboard chama /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.
Notas para outros ingress controllers, TLS servido pelo próprio operator e clusters locais estão em Web Dashboard: Acessar e publicar.

9. Aprovações

Uma ApprovalPolicy 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.
Como ler:
  • auto sem autoApproveConditions significa que a política não segura o plano. Uma regra auto com autoApproveConditions segura 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 pela low-risk-auto.
  • defaultMode é informativo: o kubectl get ap o 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.
  • DrainNode faz cordon do nó e despeja os pods pela Eviction API policy/v1 com 30 segundos de grace period, então os PodDisruptionBudgets são respeitados. Despejos recusados com 429 ou erro de servidor são refeitos a cada 5 segundos até o param opcional timeout (padrão 2m, no máximo 10m); 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 Warning ApprovalGateUnavailable é 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.
Aprove com uma anotação no 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.
A janela de mudança só controla quando uma decisão tem efeito: decisões são registradas a qualquer hora, e uma rejeição vale na hora. O relógio do timeout só corre com a janela aberta, e um pedido já aprovado que só espera a janela não expira; ele vira 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:
deploy/grafana/dashboards-configmap.yaml traz só dois ServiceMonitors, não os dashboards. O ServiceMonitor do servidor dele seleciona app.kubernetes.io/name: chatcli e a porta metrics, que os Services das Instances (operator) e o Helm chart do servidor têm, em qualquer namespace; aplique-o, ou fique com o ServiceMonitor mais restrito da Instance acima.
Consultas durante a análise. Com 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 em deploy/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):
Ou importe cada JSON em Dashboards → New → Import e escolha o datasource ali. Todo painel consulta uma métrica que o operator ou o servidor exporta; veja em Web Dashboard o que cada dashboard mostra.

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.
Rode drills só em cargas com réplicas suficientes. Nada é protegido por padrão: todas as proteções abaixo são opcionais, e nenhum namespace é bloqueado a menos que você o liste.
O que observar:
Limitações para saber antes de planejar drills:
  • pod_kill e pod_failure funcionam com o RBAC que o chart instala. cpu_stress, memory_stress e disk_stress criam um pod de stress, que o RBAC do chart permite; o pod passa no Pod Security Standard restricted (UID/GID 65534, não-root, seccomp RuntimeDefault, sem escalada de privilégio, todas as capabilities removidas, root filesystem somente leitura com um emptyDir em /tmp, sem token de ServiceAccount). network_delay e network_loss não são suportados: o experimento falha em Pending com uma mensagem dizendo que nada foi injetado.
  • requireApproval: true cria um ApprovalRequest chaos-<experimento> (ação pedida Custom, timeout de 30 minutos). O experimento roda depois de aprovado; uma rejeição ou expiração faz o experimento falhar.
  • schedule não é suportado: um experimento que o define falha em Pending e 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.
Veja Chaos Engineering para todos os tipos, parâmetros e estados.

13. Verificar a instalação

1

Operator e CRDs

2

Condições da Instance

As cinco condições precisam estar True, como no passo 3.
3

A ponte de alertas está conectada

Espere 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

Isso depende do SAN 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 --version fixado, 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 (mais localhost para os testes), com renovação configurada
  • Instance com resources explícitos, anti-affinity e persistência coerente com replicas
  • As cinco condições da Instance em True, e ServerReachable em Serving
  • 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 deduplicationWindow curto
  • 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 approvalrequests limitado 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.