Skip to main content
O ChatCLI Operator roda servidores ChatCLI no Kubernetes como recursos Instance e, em cima deles, um pipeline de AIOps que transforma alertas do watcher em incidentes, pede ao LLM a causa raiz e executa a remediação. Esta página é a jornada de quem opera: instalar, subir a primeira Instance de ponta a ponta, conectar, abrir o dashboard e endurecer tudo para produção.
Os detalhes internos do pipeline de AIOps (correlação, análise, ações de remediação, post-mortems) estão em AIOps Platform e Ciclo de vida do incidente. Esta página cobre tudo o que você precisa para operá-lo.

Como as peças se encaixam

O que você precisa saber antes de começar:
  • Uma Instance é um servidor gRPC do ChatCLI (chatcli server) que o operator implanta e mantém em ordem: um Deployment, um Service, ConfigMaps, uma ServiceAccount e, opcionalmente, um PVC e RBAC do watcher.
  • O operator sempre conecta no servidor com TLS 1.3. Não existe caminho em texto puro do operator até uma Instance. O endereço discado é <name>.<namespace>.svc.cluster.local:<port>, então o certificado do servidor precisa valer para esse nome.
  • Toda Instance precisa de uma credencial. Dentro do cluster o servidor escuta em todas as interfaces e se recusa a subir sem token compartilhado, material JWT ou CA de cliente. O operator confere isso antes e não cria o Deployment quando o spec não tem nenhum.
  • Uma Instance por cluster conduz o AIOps. O WatcherBridge lista todas as Instances do cluster e se conecta à primeira pronta que encontrar. Você pode rodar outras Instances para chat, mas só uma alimenta o pipeline de incidentes, e qual delas não é algo em que se possa confiar.
  • A API REST e o dashboard web são servidos pelo operator, não pela Instance: Service chatcli-operator, porta 8090, autenticados por API key.

API group e CRDs

Todos os recursos são namespaced e ficam em platform.chatcli.io/v1alpha1. O operator traz 17 CRDs: Cada kind de AIOps tem sua página em AIOps Platform na barra lateral, por exemplo Notificações e escalação, SLOs e SLAs e Fluxo de aprovação.

Pré-requisitos

Instalar o operator

O chart é publicado como artefato OCI no GHCR; não é preciso clonar o repositório:
O que é instalado: os 17 CRDs, o Deployment do operator (imagem ghcr.io/diillson/chatcli-operator, tag = appVersion do chart), o Service chatcli-operator (portas metrics 8080, health 8081, api 8090), a ClusterRole do operator com seu binding e as ClusterRoles pré-provisionadas chatcli-watcher e chatcli-role-{viewer,operator,admin,superadmin}.CRDs no upgrade. O Helm só instala os arquivos de crds/ na primeira instalação e nunca os atualiza. Por isso o chart roda um Job de hook pre-install/pre-upgrade (crdUpgrade.enabled: true, imagem registry.k8s.io/kubectl:v1.31.10) que reaplica todos os CRDs, mantendo o schema sempre alinhado com o controller. Desligue só se você gerencia CRDs por fora; nesse caso aplique o crds/ do chart novo antes do upgrade.
O nome do Service segue o nome do release do Helm: com o release chatcli-operator ele é chatcli-operator. Outro nome, por exemplo aiops, gera aiops-chatcli-operator. Os comandos desta página assumem o release chatcli-operator em chatcli-system.

Verificar a instalação

O pod do operator fica Ready quando o /readyz (porta 8081) responde. Até você criar API keys, o log mostra SECURITY: no API keys ConfigMap found and CHATCLI_OPERATOR_DEV_MODE is not set e toda chamada ao dashboard é recusada: é o esperado.

Sua primeira Instance

O passo a passo cria uma Instance chamada chatcli no namespace chatcli. O operator vai discar em chatcli.chatcli.svc.cluster.local:50051; se escolher outros nomes, troque em todos os lugares, inclusive no certificado.
1

Crie o namespace

2

Guarde a API key do LLM

O Secret inteiro é carregado no container do servidor (envFrom), então suas chaves são as variáveis do provedor:
Os outros provedores leem OPENAI_API_KEY, GOOGLEAI_API_KEY, XAI_API_KEY, ZAI_API_KEY, MINIMAX_API_KEY, MOONSHOT_API_KEY, OPENROUTER_API_KEY ou GITHUB_COPILOT_TOKEN. O Bedrock usa AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (e, opcionalmente, AWS_SESSION_TOKEN) mais AWS_REGION ou BEDROCK_REGION, ou nenhuma chave com IRSA (veja serviceAccount.annotations). Coloque neste Secret a chave de todos os provedores de uma cadeia de fallback.
Este não é o Secret de keys do dashboard. O chatcli-api-keys (qualquer nome, referenciado por spec.apiKeys.name) fica ao lado da Instance e guarda chaves de LLM; o chatcli-operator-secrets fica no namespace do operator e guarda as API keys do dashboard.
3

Crie o token do servidor

Clientes e o operator o apresentam como authorization: Bearer <token>; quem tem o token compartilhado é administrador no servidor. As outras opções de credencial estão em Opções de autenticação.
4

Emita o certificado TLS

O certificado precisa valer para o nome que o operator disca. Inclua os nomes curtos para clientes dentro do cluster e localhost / 127.0.0.1 para o chatcli connect funcionar via kubectl port-forward (a CLI não tem como sobrescrever o nome do servidor). Acrescente seu nome DNS externo se for expor o servidor para fora do cluster.
O ca.crt é a raiz de confiança que o operator usa para esta Instance. Sem ele o operator recorre às CAs do sistema (ou a security.grpcTLS.caFile), o que só funciona com certificado de uma CA publicamente confiável.
5

Crie a Instance

O spec.image.tag fica de fora de propósito: o operator roda a imagem de servidor da mesma release que ele (ghcr.io/diillson/chatcli:1.212.1).
6

Confira se subiu e está alcançável

READY significa que o Deployment está com todas as réplicas prontas. VERSION é o que o servidor em execução informou à sonda do próprio operator: só aparece depois que o operator alcançou o servidor via TLS com a sua credencial. Leia todas as conditions de uma vez:

Condições da Instance

Uma Instance pronta é sondada de novo a cada cinco minutos, então ServerReachable e VERSION acompanham um servidor que foi atualizado, reiniciado ou perdeu a credencial. O status.serverProbeTime muda quando o resultado muda e, fora isso, no máximo uma vez por intervalo (sem loop de escrita de status). Uma Instance pronta cuja última sondagem falhou é sondada de novo em 30 segundos, então um False gravado enquanto os pods eram trocados some logo depois do rollout. Qualquer mudança na Instance, inclusive uma anotação, também dispara a reconciliação e a sondagem na hora, sem reiniciar os pods:

Logs que valem a leitura

Conectar a CLI

O Service da Instance é ClusterIP (headless com mais de uma réplica). De uma estação de trabalho, faça o port-forward e conecte com TLS:
  • Certificado de cliente (mTLS): não há flag. Defina CHATCLI_TLS_CLIENT_CERT e CHATCLI_TLS_CLIENT_KEY; elas só são usadas junto com --tls.
  • Texto puro: sem --tls a CLI continua discando TLS com as CAs do sistema, a menos que CHATCLI_ALLOW_INSECURE=true esteja definida. Servidor em texto puro só serve para desenvolvimento local; o operator não o alcança.
  • A referência completa do cliente está em Remote Connect.

Expor o servidor fora do cluster

O operator cria só o Service ClusterIP. Para expor uma Instance, coloque um recurso seu na frente e inclua o nome DNS externo nos SANs do certificado:
  • Load balancer L4 (por exemplo um NLB da AWS): crie um Service do tipo LoadBalancer selecionando app.kubernetes.io/name: chatcli e app.kubernetes.io/instance: chatcli, porta 50051 para targetPort: grpc, em passthrough TCP. O TLS fica de ponta a ponta, que também é o único jeito de certificados de cliente (mTLS) chegarem ao servidor.
  • ingress-nginx: o backend fala TLS, então use nginx.ingress.kubernetes.io/backend-protocol: "GRPCS", ou TLS passthrough (nginx.ingress.kubernetes.io/ssl-passthrough: "true", que exige a flag --enable-ssl-passthrough no controller). Um ingress que termina o TLS não consegue levar certificados de cliente mTLS até o servidor.
  • Os clientes então conectam com chatcli connect chatcli.example.com:443 --tls --token "$TOKEN" (acrescente --ca-cert para uma CA privada).

Dashboard e API REST

O operator serve o dashboard web em / e a API REST em /api/v1/ na porta 8090 de toda réplica. A API exige o header X-API-Key. As keys são lidas do Secret chatcli-operator-secrets (chave api-keys) no namespace do operator, com o ConfigMap chatcli-operator-config (mesma chave) como alternativa, e relidas a cada 30 segundos: incluir, trocar ou remover uma key não exige restart.
Papéis: viewer (lê tudo), operator (reconhece, adia, resolve, aprova, revisa post-mortems, escreve runbooks), admin (tudo, inclusive apagar runbooks). Qualquer outro nome de papel não dá acesso a nada. Um name opcional numa entrada é a identidade registrada nas decisões de aprovação tomadas com aquela chave; um quorum conta cada chave uma vez, então dê a cada aprovador a sua própria chave. Como o operator aplica uma mudança, na inicialização e em cada consulta de 30 segundos igualmente: um Secret sem a entrada api-keys cai para o ConfigMap; quando nenhum dos dois objetos a fornece (ambos apagados, ou nenhum tem a entrada), todas as chaves são revogadas em cerca de 30 segundos (401 a partir daí). Uma entrada api-keys que não é YAML válido mantém em vigor o último conjunto de chaves válido e é registrada no log uma vez por versão, para que um erro de digitação não tranque todo mundo do lado de fora; revogue uma chave removendo a entrada dela, não quebrando o YAML. Um erro de leitura que não seja “não encontrado” também mantém as chaves em vigor.
  • Rate limit: 30 requisições por minuto por host de cliente para requisições sem key válida (o que limita adivinhação de keys) e 600 por minuto por key válida. Passar disso devolve 429 com Retry-After.
  • Publicar: coloque um Ingress na frente do Service na raiz de um host só dele. A página chama /api/v1/... com caminho absoluto, então um sub-path (/chatcli, com rewrite ou strip-path) carrega a página e quebra todos os painéis. Exemplos e notas por controller: Web Dashboard: Acessar e publicar.
  • TLS: defina security.apiTLS.certFile/keyFile e monte o Secret com extraVolumes/extraVolumeMounts; a API passa a servir HTTPS só com TLS 1.3.
  • Dev mode: security.devMode: true (CHATCLI_OPERATOR_DEV_MODE, lida como booleano: true, TRUE, 1 ou t) aceita toda chamada como admin quando não há keys configuradas; o log de startup informa o mesmo modo que a API aplica. Existe para experimentos locais; nunca ligue em cluster compartilhado.
  • Endpoints, códigos de erro e paginação: Referência da API REST. Tour do dashboard: Web Dashboard.

Ligar o AIOps

O watcher roda dentro do pod da Instance: coleta status dos pods, eventos, logs e, opcionalmente, métricas Prometheus dos alvos que você listar, e gera alertas. O WatcherBridge do operator transforma esses alertas no pipeline de incidentes.
O que acontece em seguida:
  1. Detecção. O WatcherBridge (no líder do operator) mantém aberto o stream StreamAlerts do servidor (heartbeat a cada 15s; um servidor sem essa RPC, anterior à 1.211.0, é consultado com GetAlerts a cada 30s, e o stream é tentado de novo a cada 10 minutos ou assim que esse servidor sai; alertTransport: poll força o polling) e cria recursos Anomaly, deduplicados por aiops.dedupTTLMinutes (padrão 30).
  2. Correlação. Anomalias no mesmo recurso são agrupadas numa Issue com risk score e severidade.
  3. Análise. Um AIInsight é criado e a RPC AnalyzeIssue do servidor pede ao LLM a causa raiz, enriquecida com contexto do Kubernetes, logs, métricas, estado de GitOps e código-fonte vinculado.
  4. Remediação. Um RemediationPlan executa um Runbook compatível, um runbook gerado pela IA ou um loop agêntico observar-decidir-agir (RPC AgenticStep), com 54 ações tipadas (mais Custom, que as verificações de segurança rejeitam), snapshot antes das ações e rollback automático. ApprovalPolicies, o decision engine e o tier do cluster podem segurar um plano para um humano.
  5. Resolução. A Issue é resolvida ou escalada depois de aiops.maxRemediationAttempts, e um PostMortem é gerado. Notificação e escalação ficam por conta de NotificationPolicy e EscalationPolicy.
A máquina de estados completa, o catálogo de ações e as regras de rollback estão em Ciclo de vida do incidente e AIOps Platform. Detalhes da coleta do watcher estão em K8s Watcher.
Prefira targets. A forma legada de alvo único (watcher.deployment + watcher.namespace) continua funcionando: no namespace da Instance ela recebe a Role namespaced do watcher e, em outro namespace, recebe o mesmo ClusterRoleBinding <namespace>-<name>-watcher para a ClusterRole compartilhada chatcli-watcher que os targets em outros namespaces recebem. targets é a única forma que observa vários recursos ou kinds além de Deployment.

ExecDiagnostic Allowlist

O ExecDiagnostic só roda um comando dentro de um pod quando ele bate, caractere por caractere, com uma entrada de um allowlist somente leitura de 100 comandos embutidos (inspeção de processos e sistema de arquivos, arquivos de cgroup v1/v2, /proc, introspecção de rede e DNS, curl/wget contra endpoints de health, métricas e pprof em localhost nas portas comuns, nc -zv até o API server e o DNS). Qualquer outro é recusado com command "..." not in approved diagnostic commands whitelist. A lista é lida pelo processo do operator, então estenda no chart do operator, não na Instance. Vírgulas separam as entradas, então escape-as no --set, ou coloque o valor no seu arquivo de values (aqui operator-values.yaml):
A linha de log da subida mostra as contagens de padrão, customizados e total, e cada entrada customizada.

Repositórios de código

Um SourceRepository vincula uma carga ao repositório git dela, para que a análise de incidentes veja os commits recentes e o código em volta dos stack traces. Crie-o, e o Secret dele, no namespace da carga. O operator faz um clone raso no emptyDir gravável /tmp dele (valor tmpVolume.sizeLimit do chart, padrão 1Gi) e sincroniza de novo a cada syncIntervalMinutes (padrão 30). A imagem do operator traz git e openssh-client. As credenciais são entregues ao git a cada comando e nunca 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. Sem known_hosts uma sincronização por SSH falha, a menos que spec.sshHostKeyPolicy: acceptNew confie na chave que o host apresenta no primeiro contato (e rejeite uma chave diferente depois, enquanto durar o pod do operator); o padrão é strict.
Um exemplo completo está em Setup de AIOps em produção.

Acompanhar o pipeline

Opções de autenticação

O operator escolhe a própria credencial nesta ordem: server.token, depois security.operatorTokenRef, depois security.jwtSecretRef (tokens emitidos por ele), depois security.operatorClientCertSecretName. Um certificado de cliente, quando configurado, é apresentado em toda conexão além de qualquer credencial bearer.
A opção mais simples, usada no passo a passo. Quem tem o token é admin no servidor (subject legacy-token), e todos os chamadores por token dividem um único balde de rate limit. Para trocar, atualize o Secret: os pods reiniciam e o operator passa a usar o novo valor.
Os tokens precisam ter exp (sem ele o token é recusado), são conferidos com 30 segundos de tolerância de relógio e respeitam nbf; iss e aud são conferidos quando configurados. A claim role mapeia admin para admin, operator e user para user, viewer e readonly para somente leitura; sem a claim, user. O operator emite os próprios tokens (sub: chatcli-operator, role: operator, uma hora, renovados após 45 minutos) com jwtIssuer e jwtAudience. Como emitir tokens para pessoas: autenticação no Server Mode.
O operator não consegue assinar tokens RS256, então precisa de operatorTokenRef (ou de um certificado de cliente). Esse token também precisa de exp, então mantenha o Secret renovado antes de expirar (por exemplo com External Secrets); o operator reconecta quando o Secret muda, sem reiniciar os pods do servidor.
O servidor passa a exigir um certificado de cliente válido em toda conexão, inclusive dos usuários da CLI. Quem chega só com certificado é identificado pelo CN (ou pelo primeiro SAN URI/DNS) e recebe mtlsRole (viewer, readonly, user, operator ou admin; padrão do servidor user); um token bearer enviado junto tem precedência. Emita os certificados de cliente com extendedKeyUsage = clientAuth a partir da mesma CA do passo de TLS.
O JWT falha fechado. Quando há material JWT configurado mas ele não carrega (uma chave pública que não é um PEM RSA válido, ou um CHATCLI_JWT_SECRET que parece material de chave mas não pode ser lido):
  • se o JWT é a única credencial, o servidor se recusa a subir (refusing to start: CHATCLI_JWT_PUBLIC_KEY is set but no RSA public key could be loaded: ...) e o pod entra em crash loop até a chave ser corrigida;
  • se há um token compartilhado ou uma CA de cliente ao lado, o servidor sobe, o token e os certificados continuam funcionando e os chamadores JWT são recusados.
Ele nunca cai para servir sem autenticação.
Servidores só em loopback (security.bindAddress: "127.0.0.1") podem rodar sem credencial, mas aí nada fora do pod os alcança, nem o operator: ServerReachable fica False. Só faz sentido para um servidor acessado a partir de um sidecar.

Referência do spec da Instance

Só spec.provider é obrigatório. O schema do CRD é a única validação: não existe admission webhook.

Nível raiz

spec.server

spec.server.security

spec.watcher

targets[]: name (nome do recurso; deployment é o alias obsoleto), kind (Deployment, StatefulSet, DaemonSet, Job, CronJob; padrão Deployment), namespace (obrigatório), metricsPort (0 = sem métricas), metricsPath (padrão do servidor /metrics), metricsFilter (padrões glob).

spec.fallback

O operator coloca spec.provider (com spec.model) na frente, a menos que a lista já o cite, caso em que a sua ordem é mantida. O servidor só monta a cadeia quando pelo menos dois dos provedores têm credencial, e a usa em requisições que não trazem credencial nem provedor explícito próprios. Coloque a chave de todos os provedores no Secret de apiKeys. Detalhes do comportamento: Provider Fallback.

spec.aiops

O pipeline de AIOps lê essas configurações da Instance de onde o WatcherBridge recebe os alertas (labels platform.chatcli.io/instance e platform.chatcli.io/instance-namespace em cada Anomaly e Issue); na falta delas, vale a primeira Instance pronta.

spec.features e spec.pipeline

spec.mcp, spec.agents, spec.plugins

Status

O que o operator cria

Tudo recebe o nome da Instance, os labels app.kubernetes.io/name: chatcli, app.kubernetes.io/instance: <name>, app.kubernetes.io/managed-by: chatcli-operator, e pertence à Instance (é apagado junto com ela). O Deployment roda chatcli server --port <port> --metrics-port <metricsPort> --provider ... [--model ...] [--tls-cert ... --tls-key ... [--tls-client-ca ...]] [--mcp-config ...] [--watch-config ... | --watch-deployment ...] com:
  • estratégia Recreate quando persistence.enabled (o PVC de sessões é ReadWriteOnce, então o pod antigo o libera antes de o novo subir; cada rollout tem uma breve indisponibilidade), senão RollingUpdate;
  • probes em GET /healthz da porta metrics (HTTP puro, sem credencial): startup a cada 5s por até 5 minutos, readiness a cada 10s (sai do Service após 30s de falhas), liveness a cada 20s (reinicia após 2 minutos);
  • security context do pod runAsNonRoot, UID 1000, fsGroup: 1000 com fsGroupChangePolicy: OnRootMismatch (para que um volume de sessões novo seja gravável seja qual for o provisioner), seccompProfile: RuntimeDefault (a menos que haja spec.securityContext) e, em todo container, inclusive o init container plugin-loader, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true e todas as capabilities removidas: o pod passa no Pod Security Standard restricted;
  • emptyDirs graváveis /tmp (100Mi) e /home/chatcli/.chatcli (200Mi); tudo o que estiver fora do PVC de sessões se perde quando o pod reinicia;
  • HOME=/home/chatcli, o ConfigMap <name> e o Secret de apiKeys como envFrom, depois extraEnv, depois as variáveis tipadas de credencial e features. As referências a Secret são opcionais, então um Secret ausente não trava o pod: ele sobe sem o valor.

Gatilhos de rollout

O pod lê a configuração na subida, então o operator grava hashes no template do pod e qualquer mudança reinicia os pods: Mudanças no próprio template do pod (imagem, recursos, env, scheduling) reiniciam os pods como em qualquer Deployment. Todo Secret referenciado pela Instance é observado, então criar ou trocar um deles reconcilia a Instance na hora. O que atualiza só o operator: operatorTokenRef e operatorClientCertSecretName são lidos pelo operator, não pelo pod. Trocá-los não reinicia o servidor; o operator refaz a conexão com o material novo (a sonda de status disca do zero toda vez). O que não reinicia os pods: uma imagem de plugins nova com a mesma tag. Rode kubectl -n <ns> rollout restart deploy/<name> depois de publicá-la.

Rodando em produção

Checklist

  • O certificado TLS cobre <name>.<namespace>.svc.cluster.local (mais os nomes curtos e, se exposto, o nome externo), e o Secret tem ca.crt para CA privada.
  • TLSConfigured, AuthenticationConfigured, OperatorCredentialConfigured, Available e ServerReachable estão todas True.
  • Uma credencial por público: token compartilhado só para automações que você confia como admin; JWTs (HS256 ou RS256) ou mTLS para pessoas.
  • spec.resources com requests e limits em toda Instance.
  • As API keys do dashboard estão em chatcli-operator-secrets, uma por time com o menor papel necessário, e security.devMode está desligado.
  • NetworkPolicies restringem os pods da Instance e do operator, inclusive as portas de métricas em HTTP puro.
  • Imagens fixadas (versão do chart do operator; spec.image.tag só se quiser desacoplar o servidor da release do operator).
  • Só uma Instance no cluster deve conduzir o AIOps.
  • O allowlist do ExecDiagnostic cobre as portas de health dos seus workloads.
  • Recursos customizados e Secrets referenciados estão no backup.

TLS e RBAC

  • TLS em tudo que o operator conversa. O operator disca nas Instances só com TLS 1.3, validando o certificado contra o ca.crt da Instance (ou as CAs do sistema, ou security.grpcTLS.caFile). O servidor também só aceita TLS 1.3. Faça a rotação com o cert-manager ou trocando o Secret: os pods reiniciam (chatcli.io/tls-hash) e o operator confia no novo ca.crt na próxima conexão.
  • RBAC do operator. A ClusterRole do operator é ampla por necessidade: acesso total aos seus 17 CRDs; Deployments, Services, ConfigMaps, ServiceAccounts e PVCs; leitura e escrita de Secrets em todo o cluster (ele lê os Secrets referenciados e executa RotateSecret); pods (inclusive create, para os pods de stress do chaos), eviction de pods (o DrainNode usa a Eviction API), logs, e eventos do core e de events.k8s.io; nodes (cordon/drain); create e update em todo kind que o allowlist do ApplyManifest admite (workloads, Jobs, CronJobs, Services, ConfigMaps, HPAs, PDBs, Ingresses e os kinds do Prometheus Operator e do Istio; regras para um API group não instalado ficam inertes), além de NetworkPolicies, para remediação; ReplicaSets só leitura; leases para o leader election.
  • Sem escalada de RBAC em tempo de execução. O operator nunca cria nem altera ClusterRoles. Ele só pode fazer bind das pré-provisionadas chatcli-watcher e chatcli-role-{viewer,operator,admin,superadmin} (restrito por resourceNames), então um operator comprometido não consegue vincular uma ClusterRole mais privilegiada. As ClusterRoles chatcli-role-* existem para você vincular a pessoas; o operator não vincula nada a usuários.
  • Barreiras da remediação. O ApplyManifest só cria ou atualiza os 16 kinds permitidos (Deployment, StatefulSet, DaemonSet, Service, ConfigMap, HorizontalPodAutoscaler, PodDisruptionBudget, Ingress, CronJob, Job, ServiceMonitor, PrometheusRule, PodMonitor, ServiceEntry, VirtualService, DestinationRule; estenda com security.allowedResourceTypes) no namespace da Issue. O RBAC do operator (chart e kustomize) concede create e update em cada um deles, inclusive os kinds ServiceMonitor, PodMonitor, PrometheusRule, ServiceEntry, VirtualService e DestinationRule; um kind que você acrescenta com security.allowedResourceTypes também precisa de uma regra de ClusterRole que você adicione. ReplicaSet não está na lista: o Deployment dono dele reverteria uma escrita direta. O ExecDiagnostic fica limitado ao allowlist. Os logs passam por 18 padrões de limpeza (chaves de nuvem e de API, JWTs, tokens bearer, senhas, strings de conexão, chaves privadas, endereços IPv4, e-mails, segredos longos em base64/hex) antes de chegarem ao LLM.

Credenciais e rotação

Trocar o token compartilhado reinicia o servidor e muda o operator ao mesmo tempo, mas todo usuário da CLI precisa do novo valor. Para rotação sem interrupção para usuários, prefira JWTs.

Recursos

O operator não aplica requests nem limits às Instances, o que deixa os pods como BestEffort: os primeiros a serem despejados sob pressão no nó. Comece com requests: {cpu: 250m, memory: 256Mi} e limits: {cpu: "1", memory: 1Gi} e ajuste ao seu tráfego; watcher, servidores MCP e as RPCs de pipeline aumentam o consumo. O próprio operator vem com requests 100m/128Mi e limits 500m/256Mi; aumente em clusters com muitas Issues.

Alta disponibilidade

  • Operator: rode replicaCount: 2 ou mais com leaderElect: true. Os controllers e o WatcherBridge rodam só no líder; a API REST e o dashboard são servidos por todas as réplicas, então o Service continua respondendo durante um failover. O chart não cria PodDisruptionBudget para o operator; crie um se você drena nós com frequência.
  • Instances: replicas > 1 torna o Service headless e o operator balanceia em round-robin entre os pods. Cada pod é um servidor independente (memória, watcher e emptyDir próprios), e o stream de alertas se prende a um pod por vez. Para uma Instance de AIOps, uma réplica costuma ser o certo.
  • Persistência e réplicas: o PVC de sessões é ReadWriteOnce, então com persistence.enabled o Deployment usa a estratégia Recreate: o pod antigo para e libera o volume antes de o novo subir, e cada rollout (imagem, configuração ou credencial) tem uma breve indisponibilidade. Sem persistência a estratégia é RollingUpdate. Uma segunda réplica agendada em outro nó continua sem conseguir anexar o volume (Multi-Attach error): mantenha uma réplica com persistência e fixe-a com scheduling.affinity se o storage for zonal, ou rode sem persistência quando escalar horizontalmente.
  • O operator não cria PodDisruptionBudget, HorizontalPodAutoscaler nem NetworkPolicy para Instances.

Políticas de rede

  • Operator: ligue networkPolicy.enabled no chart (ou make deploy-network-policy nos manifestos crus). A entrada é liberada na porta da API (restrinja com networkPolicy.apiIngressFrom), na de métricas (metricsIngressFrom) e na de health. Com egress: restricted, a saída fica limitada a DNS, 443, a porta da API do Kubernetes (kubernetesApiPort, 6443), a porta gRPC das Instances (instanceGrpcPort, 50051) e a porta do Prometheus tirada de prometheusUrl; acrescente egressExtraPorts para SMTP (587/465), git via SSH (22) ou webhooks em outras portas.
  • Instances: o operator não cria nenhuma. Um ponto de partida:
    Acrescente um from para os seus clientes na 50051, e saída para o metricsPort dos alvos do watcher quando coletar métricas deles. O tráfego das probes do kubelet fica isento de NetworkPolicy na maioria dos CNIs, mas não em todos, por isso o exemplo deixa a 9090 aberta para qualquer origem.

Pod Security

Os pods da Instance e do operator atendem ao Pod Security Standard restricted com os padrões, então você pode rotular os namespaces com pod-security.kubernetes.io/enforce: restricted. Se definir spec.securityContext, ele substitui o padrão inteiro: mantenha runAsNonRoot: true e seccompProfile: {type: RuntimeDefault}. O padrão também define fsGroup: 1000 com fsGroupChangePolicy: OnRootMismatch, para que um volume de sessões novo seja gravável seja qual for o provisioner; mantenha fsGroup: 1000 no seu próprio contexto quando ligar a persistência.

Métricas e monitoramento

  • O servidor expõe /metrics (e /healthz) na porta metrics do Service da Instance (9090); o operator expõe métricas de controllers e de AIOps (chatcli_operator_*) na 8080 (serviceMonitor.enabled: true no chart).
  • Os dois são HTTP puro sem autenticação, e o listener de métricas do servidor escuta em todas as interfaces independentemente de bindAddress. Restrinja com NetworkPolicy.
  • Para uma Instance, crie seu próprio ServiceMonitor:
  • Nomes de métricas, dashboards do Grafana e queries úteis: Web Dashboard. Alerte em chatcli_operator_instance_ready == 0 e na condition ServerReachable.

Imagens, versões e upgrades

  • As imagens do operator e do servidor são publicadas como ghcr.io/diillson/chatcli-operator:<version> e ghcr.io/diillson/chatcli:<version> (mais latest), multi-arch (amd64, arm64), com SBOM e provenance, assinadas keyless com cosign:
  • Fixação. O chart fixa a imagem do operator no seu appVersion e passa CHATCLI_OPERATOR_APP_VERSION, então uma Instance sem spec.image.tag roda a mesma release e um helm upgrade do operator atualiza esses servidores também. Defina spec.image.tag só para desacoplar uma Instance da release do operator. Evite latest em produção.
  • Upgrade e desinstalação: veja Upgrade e desinstalação.

Backup

  • Recursos customizados, por exemplo kubectl get instances,runbooks,notificationpolicies,escalationpolicies,approvalpolicies,incidentslas,servicelevelobjectives,sourcerepositories,clusterregistrations -A -o yaml; acrescente issues,postmortems,auditevents se guarda histórico de incidentes no cluster. Ferramentas como o Velero fazem backup de namespaces com seus recursos customizados.
  • Os Secrets referenciados pelas Instances, e o chatcli-operator-secrets, a partir do seu cofre de segredos.
  • O PVC de sessões guarda as sessões do servidor. Ele pertence à Instance: apagar a Instance apaga o claim (o volume segue a reclaim policy da StorageClass).
  • O gasto com LLM registrado pelo pipeline fica nos ConfigMaps chatcli-cost-ledger dos namespaces onde as Issues acontecem.

O que não existe

  • Nenhum admission ou validating webhook: o schema do CRD é a única validação, e o operator informa o resto como conditions.
  • Nenhuma conexão em texto puro do operator até uma Instance.
  • Nenhum gRPC reflection, a menos que você defina spec.server.security.enableReflection.
  • Nenhum PodDisruptionBudget, HPA, NetworkPolicy, ServiceMonitor, Ingress ou LoadBalancer criado para Instances; o Service é sempre ClusterIP ou headless.
  • Nenhum TLS ou autenticação nos endpoints de métricas.
  • Nenhum SSO/OIDC no dashboard: só API keys.
  • Nenhum arquivo de auditoria no operator: as ações dele são recursos AuditEvent. O servidor tem o próprio arquivo de auditoria (security.auditLogPath).
  • Nenhum RBAC automático para pessoas: vincule as ClusterRoles chatcli-role-* você mesmo.

Upgrade e desinstalação

Um upgrade troca o operator e, em toda Instance que segue a release do operator (sem spec.image.tag), o servidor também. Dá para ir direto de qualquer release anterior para esta: o hook de CRDs reaplica o schema completo e o operator novo renderiza de novo cada Instance, então não há versões intermediárias para instalar. Leia Atravessando estas releases antes, se você vem de uma release antiga.

Antes de começar

  1. Faça backup dos recursos customizados e dos Secrets (veja Backup).
  2. Salve os values com que a release roda e use esse arquivo como fonte das suas configurações daqui em diante:
    Uma release instalada sem overrides, como o comando de instalação desta página, imprime null; o arquivo funciona assim mesmo.
  3. Confira em Atravessando estas releases se algo precisa ser feito antes do upgrade.

Rodar o upgrade

Sem arquivo de values, o --reset-then-reuse-values (Helm 3.14+) parte dos padrões do chart novo e reaplica seus overrides anteriores. Não use o --reuse-values puro: ele ignora os padrões das chaves que o chart novo adicionou e pode falhar na renderização (nil pointer evaluating).

O que acontece, em ordem

  1. CRDs. O Job de hook pre-upgrade reaplica os 17 CRDs (kubectl apply --server-side) antes de qualquer outra mudança.
  2. Operator. O Deployment do operator faz rollout: o pod novo fica Ready antes de o antigo parar, então o dashboard e a API REST continuam no ar. Os controllers reiniciam no pod novo.
  3. Instances. O operator novo renderiza de novo cada Instance. Uma Instance sem spec.image.tag recebe a imagem nova do servidor, e qualquer mudança no template do pod reinicia os pods dela. Com persistence.enabled a estratégia é Recreate: os pods antigos param antes de os novos subirem, então cada Instance assim fica um breve período fora do ar.
  4. Sondagem. O operator sonda cada servidor de novo. Uma sondagem que cai no meio do rollout pode gravar ServerReachable=False / ProbeFailed com DeadlineExceeded ... while waiting for connections to become ready, connection refused ou i/o timeout, mesmo que os pods novos já sirvam normalmente logo depois. Uma sondagem que falha é repetida em 30 segundos, então a condição vira True logo depois do rollout.
  5. Alertas. O WatcherBridge reconecta. Se ele alcança um servidor anterior à 1.211.0, que não tem StreamAlerts (tipicamente um pod antigo ainda rodando), ele loga Server has no StreamAlerts RPC; polling GetAlerts e faz polling a cada 30 segundos. Quando esse pod antigo sai, o próximo polling falha com Unavailable, o bridge loga Server unavailable while polling; retrying the alert stream on reconnect e abre o stream no servidor novo. Nenhum alerta se perde nesse meio-tempo.

Verificar

As cinco condições devem estar True. Se o ServerReachable ainda mostra o False gravado durante o rollout, espere uns 30 segundos pela nova tentativa.

Atravessando estas releases

Desinstalar

Apague as Instances primeiro, com o operator ainda rodando: o finalizer delas (platform.chatcli.io/finalizer) é removido pelo operator, e uma Instance apagada depois que o operator já saiu fica em Terminating até você removê-lo à mão (kubectl patch instance <name> -n <ns> --type merge -p '{"metadata":{"finalizers":null}}'). Depois rode helm uninstall chatcli-operator -n chatcli-system (ou make undeploy nos manifestos crus, que também apaga os CRDs). O helm uninstall mantém os CRDs (o Helm nunca apaga crds/); apagá-los apaga todos os recursos customizados.

Solução de problemas

Desenvolvimento

O make manifests só imprime o comando do controller-gen; os CRDs gerados ficam versionados em config/crd/bases/ e copiados para o crds/ dos dois charts.

Próximos passos

AIOps Platform

Como o pipeline funciona por dentro

Ciclo de vida do incidente

Estados, ações de remediação e rollback

Server Mode

Flags, autenticação e as RPCs gRPC

Setup de produção

Receita: um setup de AIOps em produção