> ## Documentation Index
> Fetch the complete documentation index at: https://chatcli.edilsonfreitas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Chaos Engineering

> Validação de resiliência com experimentos de chaos integrados à plataforma AIOps: 7 tipos de experimento, safety checks e verificação pós-experimento.

O módulo de **Chaos Engineering** permite injetar falhas em workloads Kubernetes e observar como a plataforma AIOps reage. Diferente de ferramentas de chaos standalone, os experimentos aqui são integrados ao pipeline de AIOps: Issues abertas enquanto um experimento roda são marcadas como simulações de chaos, então não acionam ninguém e não poluem o histograma de MTTR de produção.

<Info>
  Cada experimento é um custom resource `ChaosExperiment` reconciliado pelo
  controller de chaos do operator. As proteções (listas de namespaces
  permitidos/bloqueados, mínimo de pods saudáveis, limite de concorrência,
  abortar ao surgir uma nova Issue) são campos declarativos do CR. Nada vem
  protegido por padrão: toda proteção é opt-in.
</Info>

<Warning>
  **O que funciona sem ajustes.** O RBAC distribuído pelo Helm chart do operator e por
  `operator/config/rbac/role.yaml` deixa o operator fazer `get`, `list`, `watch`, `create`, `update` e `delete` em pods, então todos os tipos de experimento suportados podem rodar:

  * `pod_kill` e `pod_failure` deletam pods do alvo.
  * `cpu_stress`, `memory_stress` e `disk_stress` criam um pod de stress que passa no Pod Security Standard `restricted`.
  * `network_delay` e `network_loss` **não são suportados**. O CRD os aceita, mas um experimento de qualquer um dos dois falha na hora, em `Pending`, com um result dizendo que nada foi injetado: falhas de rede precisam de um injetor tc/netem privilegiado que o operator não instala.

  Se você gerencia o RBAC do operator por conta própria, veja [Concedendo permissões em pods](#concedendo-permissões-em-pods) abaixo.
</Warning>

## Chaos Engineering no Contexto AIOps

```mermaid theme={"system"}
flowchart LR
    A[ChaosExperiment CR] --> B[Safety checks]
    B --> C[Injeta falha]
    C --> D[AIOps detecta anomalia]
    D --> E[Issue marcada como simulação]
    E --> F[Pipeline normal de remediação]
    F --> G[Verificação de recuperação após a duração]

    style E fill:#89dceb,color:#000
    style G fill:#a6e3a1,color:#000
```

<CardGroup cols={2}>
  <Card title="Validação de Remediações" icon="flask-vial">
    Depois de corrigir um incidente, recrie a falha com um novo experimento e
    veja se a plataforma detecta e remedia de novo.
  </Card>

  <Card title="Testes de Resiliência" icon="shield-halved">
    Confira se os workloads voltam à prontidão total depois que pods são
    derrubados.
  </Card>

  <Card title="Game Days" icon="calendar-check">
    Rode experimentos sob demanda. Agendamentos recorrentes não são
    suportados (um experimento com `schedule` falha); use um agendador
    externo para criar experimentos periodicamente.
  </Card>

  <Card title="Analytics que Reconhece Simulações" icon="stopwatch">
    Issues induzidas por chaos são contadas à parte, então simulações não
    inflam os números de incidentes de produção.
  </Card>
</CardGroup>

## Correlação Automática com Issues

Quando o controller de anomalias cria uma Issue, ele procura um `ChaosExperiment` **no mesmo namespace do recurso afetado** cujo `spec.target` tenha o mesmo `kind`, `name` e `namespace`, e que esteja `Running` ou tenha terminado (`Completed`/`Aborted`) há menos de **2 minutos**. Se encontrar, a Issue recebe dois labels:

```yaml theme={"system"}
metadata:
  labels:
    platform.chatcli.io/source: chaos-experiment
    platform.chatcli.io/chaos-experiment: <nome-do-experimento>
```

A Issue então segue o pipeline normal de AIOps, com estas diferenças:

| Comportamento | Issue de produção | Issue induzida por chaos |
| - | - | - |
| Pipeline AIOps (análise, remediação, PostMortem) | Sim | Sim |
| Regras de NotificationPolicy | Sim | Sim (não são suprimidas) |
| Escalação quando a Issue chega a `Escalated` | Sim | Ignorada |
| Histograma `chatcli_operator_issue_resolution_duration_seconds` | Registrado | Não registrado |
| Labels do PostMortem | - | Os mesmos dois labels são copiados |
| Itens de `incidents` / `postmortems` na REST | - | `chaosInduced: true`, `chaosExperiment: <nome>` |
| `analytics/summary` na REST | - | Contada em `chaosInducedIssues` |
| Listas de incidentes do dashboard | Visível | Oculta por padrão (filtro "Ocultar simulações de chaos") |

<Note>
  Os endpoints REST `analytics/mttd` e `analytics/mttr` deixam as simulações
  de chaos de fora, como o histograma de resolução do Prometheus. As
  regras de NotificationPolicy não conseguem casar por label, então as
  simulações ainda chegam a todo canal cuja regra case com a severidade,
  namespace, kind ou estado da Issue. Rode as simulações num namespace
  dedicado se quiser roteá-las separadamente.
</Note>

<Tip>
  Crie o `ChaosExperiment` **no mesmo namespace do alvo**. A busca de
  correlação só olha o namespace do alvo, enquanto `linkedIssueRef` e o pedido
  de aprovação são resolvidos no namespace do experimento.
</Tip>

## ChaosExperiment CRD

Short name: `chaos`. Colunas do `kubectl get`: `Type`, `Target`, `State`, `Duration`, `Age`.

```bash theme={"system"}
kubectl get chaos -n staging
kubectl get chaos validate-api-server-recovery -n staging -o jsonpath='{.status.result}'
```

### Especificação Completa

```yaml theme={"system"}
apiVersion: platform.chatcli.io/v1alpha1
kind: ChaosExperiment
metadata:
  name: validate-api-server-recovery
  namespace: staging
spec:
  # Tipo do experimento (obrigatório)
  experimentType: pod_kill

  # Alvo (obrigatório). kind, name e namespace são todos obrigatórios.
  # Só Deployment e StatefulSet são suportados.
  target:
    kind: Deployment
    name: api-server
    namespace: staging

  # Parâmetros específicos do tipo. O campo é map[string]string:
  # os valores PRECISAM ser strings entre aspas.
  parameters:
    count: "2"

  # Obrigatório. Sintaxe de duração do Go: 30s, 5m, 1h30m (sem "d").
  duration: 5m

  # Só roda os safety checks e registra o que aconteceria
  dryRun: false

  # Reservado: um valor não vazio faz o experimento falhar (execuções recorrentes não são suportadas)
  schedule: ""

  # Link opcional para uma Issue no namespace do experimento (só o nome)
  linkedIssueRef:
    name: issue-api-server-crashloop

  # Proteções (todas opcionais)
  safetyChecks:
    minHealthyPods: 2
    maxConcurrentExperiments: 1   # padrão 1
    abortOnIssueDetected: true
    requireApproval: false
    allowedNamespaces:
      - staging
      - chaos-testing
    blockedNamespaces:
      - production
      - kube-system
      - chatcli-system

  # Verificação pós-experimento
  postExperiment:
    verifyRecovery: true
    recoveryTimeout: 3m           # padrão 5m
    runRemediationTest: false

  # Padrão true. Experimentos desabilitados são ignorados pelo controller.
  enabled: true

status:
  state: Completed               # Pending | Running | Completed | Failed | Aborted
  startedAt: "2026-03-19T03:00:00Z"
  completedAt: "2026-03-19T03:05:02Z"
  result: "completed: pod_kill experiment finished; recovery verified in 2.004s"
  podsAffected: 2
  recoveryVerified: true
  recoveryTime: "2.004s"
  preExperimentSnapshot: "Deployment staging/api-server: replicas=5, ready=5, available=5, updated=5"
  postExperimentSnapshot: "Deployment staging/api-server: replicas=5, ready=5, available=5, updated=5"
```

<Note>
  O controller nunca preenche `status.conditions`. Acompanhe o experimento por
  `status.state` e `status.result` (um resumo legível).
</Note>

## 7 Tipos de Experimento

A falha é injetada **uma única vez**, no primeiro reconcile depois que o experimento entra em `Running`. `duration` é a janela de observação: o controller espera ela passar (verificando a cada 10 segundos no máximo) e então executa os passos pós-experimento. Os pods do alvo são os pods do Deployment (via seus ReplicaSets) ou do StatefulSet. Qualquer outro `target.kind` faz o experimento falhar.

Parâmetros ausentes ou que não são inteiros válidos caem no padrão sem aviso.

### 1. Pod Kill

Deleta pods do alvo escolhidos aleatoriamente, com `gracePeriodSeconds: 0`. A seleção aleatória é um embaralhamento Fisher-Yates com `crypto/rand`.

| Parâmetro | Tipo | Padrão | Descrição |
| - | - | - | - |
| `count` | string (inteiro) | `"1"` | Número de pods a deletar. Se for maior ou igual ao número de pods, **todos** os pods do alvo são deletados. |

<Warning>
  Pod kill simula uma falha abrupta (ex.: queda de node). Use
  `safetyChecks.minHealthyPods` para não derrubar todas as réplicas.
</Warning>

### 2. Pod Failure

Mesma seleção do `pod_kill`, mas deleta com um grace period definido no experimento (ele sobrepõe o `terminationGracePeriodSeconds` do próprio pod).

| Parâmetro | Tipo | Padrão | Descrição |
| - | - | - | - |
| `count` | string (inteiro) | `"1"` | Número de pods a deletar |
| `gracePeriodSeconds` | string (inteiro) | `"30"` | Grace period usado na deleção |

```yaml theme={"system"}
spec:
  experimentType: pod_failure
  target:
    kind: Deployment
    name: payment-service
    namespace: staging
  parameters:
    count: "1"
    gracePeriodSeconds: "10"
  duration: 2m
```

### 3. CPU Stress

Cria um pod `stress-ng` fixado no **node** do primeiro pod do alvo. Ele estressa o node, não o container do alvo.

```yaml theme={"system"}
spec:
  experimentType: cpu_stress
  target:
    kind: Deployment
    name: api-server
    namespace: staging
  parameters:
    cores: "2"
    loadPercent: "80"
  duration: 2m
```

**Pod de stress gerado** (mesmo formato para os três tipos de stress; só mudam o prefixo do nome e o comando):

```yaml theme={"system"}
apiVersion: v1
kind: Pod
metadata:
  name: chaos-cpu-<nome-do-experimento>     # chaos-mem-… / chaos-disk-…
  namespace: <namespace do alvo>
  labels:
    platform.chatcli.io/chaos-experiment: <nome-do-experimento>
    platform.chatcli.io/chaos-role: stress
spec:
  nodeName: <node do primeiro pod do alvo>  # não passa pelo scheduler
  restartPolicy: Never
  automountServiceAccountToken: false
  securityContext:                          # Pod Security Standard restricted
    runAsNonRoot: true
    runAsUser: 65534
    runAsGroup: 65534
    seccompProfile: { type: RuntimeDefault }
  tolerations:
    - operator: Exists                      # tolera qualquer taint
  volumes:
    - name: scratch
      emptyDir: {}
  containers:
    - name: stress
      image: alexeiled/stress-ng:latest
      command: ["stress-ng", "--cpu", "2", "--cpu-load", "80", "--timeout", "2m"]
      workingDir: /tmp
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        runAsNonRoot: true
        capabilities: { drop: ["ALL"] }
      volumeMounts:
        - name: scratch
          mountPath: /tmp                   # o único caminho gravável
      resources:
        requests: { cpu: 100m, memory: 64Mi }
        limits:   { cpu: 500m, memory: 512Mi }
```

| Parâmetro | Tipo | Padrão | Descrição |
| - | - | - | - |
| `cores` | string | `"1"` | Workers de CPU do stress-ng (`--cpu`) |
| `loadPercent` | string | `"80"` | Carga por worker (`--cpu-load`) |

<Warning>
  Os recursos do pod de stress são fixos: ele usa no máximo **500m de CPU e
  512Mi de memória**, não importa o que `cores`, `loadPercent` ou `bytes`
  digam. A imagem (`alexeiled/stress-ng:latest`, do Docker Hub) não pode ser
  trocada. O pod passa no Pod Security Standard `restricted`: roda como
  UID/GID 65534, não-root, com o perfil seccomp `RuntimeDefault`, sem escalada
  de privilégio, com todas as capabilities removidas, root filesystem somente
  leitura (um `emptyDir` em `/tmp` é o único caminho gravável) e sem token de
  ServiceAccount.
</Warning>

### 4. Memory Stress

Cria um pod `stress-ng` que aloca memória no node do alvo: `stress-ng --vm <workers> --vm-bytes <bytes> --timeout <duration>`.

```yaml theme={"system"}
spec:
  experimentType: memory_stress
  target:
    kind: Deployment
    name: cache-service
    namespace: staging
  parameters:
    bytes: "256M"
    workers: "1"
  duration: 3m
```

| Parâmetro | Tipo | Padrão | Descrição |
| - | - | - | - |
| `bytes` | string | `"256M"` | Memória por worker, repassada como está para `--vm-bytes` (use a sintaxe do stress-ng, como `256M`, `1G`) |
| `workers` | string | `"1"` | Workers de VM do stress-ng (`--vm`) |

Por causa do limite de 512Mi, pedir mais memória faz o container de stress ser morto por OOM em vez de pressionar o node.

### 5. Network Delay

**Não suportado.** O tipo é aceito pelo CRD por compatibilidade, mas injetar latência exige um injetor tc/netem privilegiado que o operator não instala. O experimento falha assim que é pego, ainda em `Pending` e antes de qualquer safety check, aprovação ou dry run; nenhum pod é tocado:

```yaml theme={"system"}
status:
  state: Failed
  result: "network_delay is not supported: injecting network faults needs a privileged tc/netem injector the operator does not deploy; nothing was injected"
```

Use uma ferramenta dedicada de chaos de rede para testes de latência.

### 6. Network Loss

**Não suportado**, pelo mesmo motivo do `network_delay`. Um experimento desse tipo falha em `Pending` com `result: "network_loss is not supported: ... nothing was injected"`, e nenhum pacote é descartado.

### 7. Disk Stress

Cria um pod `stress-ng` que gera I/O de disco no node do alvo: `stress-ng --hdd <workers> --hdd-bytes <size> --timeout <duration>`. O I/O vai para o filesystem do próprio container de stress.

```yaml theme={"system"}
spec:
  experimentType: disk_stress
  target:
    kind: Deployment
    name: database-proxy
    namespace: staging
  parameters:
    workers: "2"
    size: "1G"
  duration: 3m
```

| Parâmetro | Tipo | Padrão | Descrição |
| - | - | - | - |
| `workers` | string | `"1"` | Workers de HDD do stress-ng (`--hdd`) |
| `size` | string | `"1G"` | Bytes por worker (`--hdd-bytes`) |

### Resumo dos Tipos

| Tipo | Mecanismo | Permissão em pods necessária | Limpeza |
| - | - | - | - |
| `pod_kill` | Deleção com grace period 0 | `delete` (concedida) | ReplicaSet/StatefulSet recria os pods |
| `pod_failure` | Deleção com `gracePeriodSeconds` | `delete` (concedida) | ReplicaSet/StatefulSet recria os pods |
| `cpu_stress` | Pod stress-ng no node do alvo | `create` (concedida) | O stress-ng sai no `--timeout`; o pod é deletado no fim |
| `memory_stress` | Pod stress-ng no node do alvo | `create` (concedida) | Igual ao anterior |
| `disk_stress` | Pod stress-ng no node do alvo | `create` (concedida) | Igual ao anterior |
| `network_delay` | Não suportado: falha em `Pending`, nada é injetado | - | - |
| `network_loss` | Não suportado: falha em `Pending`, nada é injetado | - | - |

### Concedendo Permissões em Pods

O chart (`rbac.create: true`) e o `operator/config/rbac/role.yaml` já concedem esses verbos. Se você roda o operator com um RBAC gerenciado por você (`rbac.create: false`), os tipos de stress precisam de `create` em pods; sem isso, os tipos de stress falham (`execution failed: creating ... stress pod: ... forbidden`). Por exemplo, no cluster inteiro:

```yaml theme={"system"}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: chatcli-operator-chaos-pods
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: chatcli-operator-chaos-pods
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: chatcli-operator-chaos-pods
subjects:
  - kind: ServiceAccount
    name: chatcli-operator        # a ServiceAccount do seu operator
    namespace: chatcli-system
```

Para limitar o raio de impacto, aplique as mesmas regras com `Role`/`RoleBinding` só nos namespaces onde você roda experimentos.

## Safety Checks

Os safety checks rodam nesta ordem enquanto o experimento está `Pending`. Todos são avaliados uma única vez, antes da injeção da falha, exceto `abortOnIssueDetected`, que é verificado durante a execução.

### AllowedNamespaces / BlockedNamespaces

Comparados com `spec.target.namespace`. Um namespace bloqueado, ou ausente de uma lista de permitidos não vazia, leva o experimento a `Failed` (`namespace "<ns>" is not allowed by safety checks`).

<Tabs>
  <Tab title="AllowedNamespaces">
    Lista de permitidos. Se estiver definida, **somente** esses namespaces
    podem ser alvo. Se estiver vazia, todo namespace não bloqueado é
    permitido.

    ```yaml theme={"system"}
    safetyChecks:
      allowedNamespaces:
        - staging
        - chaos-testing
        - development
    ```
  </Tab>

  <Tab title="BlockedNamespaces">
    Lista de bloqueados. Experimentos **nunca** são executados nesses
    namespaces, mesmo que também estejam na lista de permitidos.

    ```yaml theme={"system"}
    safetyChecks:
      blockedNamespaces:
        - production
        - kube-system
        - chatcli-system
        - monitoring
    ```
  </Tab>
</Tabs>

<Warning>
  `blockedNamespaces` tem precedência sobre `allowedNamespaces`. **Não existem
  namespaces bloqueados por padrão**: `kube-system` e `chatcli-system` só
  ficam protegidos se você os listar.
</Warning>

### MaxConcurrentExperiments

Conta os experimentos em estado `Running` **em todos os namespaces** e compara com o `maxConcurrentExperiments` do próprio experimento (padrão `1`; `0` ou menos é tratado como `1`). Quando o limite é atingido, o experimento continua `Pending` e é tentado de novo a cada 15 segundos. Ele não falha.

### MinHealthyPods

Se for maior que `0`, o controller conta os pods do alvo com condição `Ready` igual a `True`, subtrai o parâmetro `count` (padrão 1, seja qual for o tipo do experimento) e leva o experimento a `Failed` quando o resultado fica abaixo de `minHealthyPods`:

```
safety check failed: killing 2 pods would leave 1 healthy (min required: 2, total: 3)
```

Essa verificação roda uma vez, antes da injeção. Ela não acompanha a saúde dos pods durante o experimento.

### RequireApproval

Quando `true`, o controller procura um `ApprovalRequest` chamado `chaos-<nome-do-experimento>` no namespace do experimento, cria se não existir (pertencente ao experimento, então é apagado junto com ele) e verifica de novo a cada 30 segundos. O experimento só começa quando o `status.state` desse pedido for `Approved`. Se o pedido ficar `Rejected` ou `Expired`, o experimento vai para `Failed` (`approval rejected by <aprovador>: <motivo>` ou `approval request chaos-<nome-do-experimento> expired without a decision`).

O pedido que o controller monta é este:

```yaml theme={"system"}
apiVersion: platform.chatcli.io/v1alpha1
kind: ApprovalRequest
metadata:
  name: chaos-validate-api-server-recovery
  namespace: staging
  labels:
    platform.chatcli.io/chaos-experiment: validate-api-server-recovery
spec:
  issueRef:
    name: chaos-experiment-validate-api-server-recovery   # ou spec.linkedIssueRef
  remediationPlanRef: validate-api-server-recovery        # o nome do experimento
  policyRef: chaos-safety
  ruleName: chaos-experiment-approval
  requester: chaos-controller
  requestedActions:
    - type: Custom                     # chaos não é uma remediação
      params:
        chaosExperiment: validate-api-server-recovery
        experimentType: pod_kill
        target: Deployment/staging/api-server
        duration: 2m
  timeoutMinutes: 30
  requiredApprovers: 1
```

<Note>
  `policyRef: chaos-safety` não é uma policy embutida. Quando existe no namespace do experimento uma ApprovalPolicy chamada `chaos-safety` com uma regra chamada `chaos-experiment-approval`, é essa regra que decide o pedido (por exemplo, um quorum ou uma change window). Sem ela, o pedido é avaliado com as próprias configurações: uma aprovação humana, expirando depois de 30 minutos. Aprove ou rejeite como qualquer outro pedido, com a anotação `platform.chatcli.io/approve` / `reject`, a API REST ou o dashboard:

  ```bash theme={"system"}
  kubectl annotate approvalrequest chaos-validate-api-server-recovery -n staging \
    platform.chatcli.io/approve="alice:drill combinado com o time"
  ```
</Note>

### AbortOnIssueDetected

Enquanto o experimento está `Running` e a `duration` ainda não passou, cada reconcile (a cada 10 segundos no máximo) lista as Issues do namespace do alvo. Se alguma Issue tiver exatamente o mesmo kind, nome e namespace em `spec.resource` que o alvo e tiver sido criada depois de `status.startedAt`, o experimento vai para `Aborted` com `result: "aborted: new issue detected: <issue>"`. Em seguida os pods de stress são deletados.

<Warning>
  A verificação não distingue Issues causadas pelo próprio experimento. Se o
  AIOps detectar a falha que você injetou (o objetivo habitual de uma
  simulação), o experimento é abortado. Deixe `abortOnIssueDetected: false`
  quando você espera que o AIOps abra uma Issue para o alvo.
</Warning>

## Verificação Pós-Experimento

Quando a `duration` passa, o controller deleta os pods de stress, grava o `postExperimentSnapshot` e executa os passos abaixo. A partir daqui o experimento sempre termina em `Completed`, mesmo que a recuperação não tenha sido verificada.

### VerifyRecovery

O alvo é considerado saudável quando um Deployment tem `readyReplicas` e `availableReplicas` pelo menos iguais a `spec.replicas`, ou quando um StatefulSet tem `readyReplicas` pelo menos igual a `spec.replicas`. Se ainda não estiver saudável, o controller verifica de novo a cada 5 segundos.

### RecoveryTimeout

Por quanto tempo continuar verificando depois que a `duration` passou. Padrão `5m`. Um valor inválido cai em `5m` sem aviso. Quando o prazo estoura, o experimento é marcado `Completed` com `recoveryVerified: false`, `recoveryTime: "timeout"` e um result terminando em `recovery NOT verified (timeout)`. Ele não é marcado `Failed`.

O `recoveryTime` (e o histograma `chatcli_operator_chaos_recovery_time_seconds`) é medido do **fim da injeção** (`startedAt + duration`) até a primeira verificação de saúde que passa. As verificações rodam a cada 5 segundos, então o valor tem essa granularidade; um alvo que já está saudável quando o experimento termina registra os poucos segundos entre o fim da injeção e essa verificação.

### RunRemediationTest

Esta opção **não** reinjeta a falha. Ao concluir, o controller lê a Issue indicada em `linkedIssueRef` (no namespace do experimento) e só verifica se o `status.state` dela é `Resolved`:

* `Resolved`: `result: "completed: <type> experiment finished; remediation validation passed"`
* qualquer outro estado, ou sem `linkedIssueRef`: `result: "completed: <type> experiment finished; no remediation was applied during experiment"`

Se a Issue vinculada já estava `Resolved` antes do experimento começar, a verificação passa independentemente do que aconteceu durante o experimento. Com `runRemediationTest: true`, o texto do result não fala da recuperação; consulte `recoveryVerified` para isso.

```yaml theme={"system"}
spec:
  linkedIssueRef:
    name: issue-api-server-crashloop
  postExperiment:
    verifyRecovery: true
    recoveryTimeout: 3m
    runRemediationTest: true   # verifica se a Issue vinculada está Resolved
```

## Máquina de Estados

```mermaid theme={"system"}
stateDiagram-v2
    [*] --> Pending: CR criado

    Pending --> Pending: Limite de concorrência / aguardando aprovação
    Pending --> Failed: Tipo ou schedule não suportado / namespace não permitido / minHealthyPods / aprovação rejeitada ou expirada
    Pending --> Completed: dryRun
    Pending --> Running: Verificações OK

    Running --> Failed: Duração inválida / erro na injeção
    Running --> Aborted: abortOnIssueDetected
    Running --> Completed: Duração encerrada (+ verificação de recuperação)

    Completed --> [*]
    Failed --> [*]
    Aborted --> [*]
```

| Estado | Descrição | Transições |
| - | - | - |
| **Pending** | Safety checks, limite de concorrência e aprovação são avaliados | Running, Failed, Completed (dry run) |
| **Running** | Falha injetada uma vez; o controller espera a `duration` | Completed, Failed, Aborted |
| **Completed** | Duração encerrada e passos pós-experimento feitos (a recuperação pode não ter sido verificada), ou dry run concluído | Terminal |
| **Failed** | `network_delay`/`network_loss` ou um `schedule` (não suportados), namespace bloqueado, `minHealthyPods` violado, aprovação rejeitada ou expirada, `duration` inválida, nenhum pod no alvo (tipos de pod e de stress), `target.kind` não suportado ou erro na injeção | Terminal |
| **Aborted** | Uma nova Issue para o alvo foi detectada (`abortOnIssueDetected`) | Terminal |

Experimentos em estado terminal nunca rodam de novo. Para repetir um experimento, delete e crie de novo (redefinir o status manualmente não reinjeta a falha, porque o controller pula a injeção quando `podsAffected` já está preenchido).

Não existe campo para abortar um experimento em execução. Deletar o CR ou definir `enabled: false` faz o controller parar de tratá-lo, mas sem limpeza: os pods de stress continuam rodando até o `--timeout` do stress-ng.

<Note>
  A `duration` só é interpretada quando o experimento já está `Running`, então
  um valor inválido (por exemplo `1d`) só é detectado depois que os safety
  checks e a aprovação já passaram.
</Note>

## DryRun Mode

Com `dryRun: true`, o controller roda as verificações de `Pending` (namespaces, concorrência, `minHealthyPods` e aprovação, se exigida) e leva o experimento direto para `Completed`, sem tocar em nenhum pod. Ele não seleciona pods nem planeja ações individuais.

```yaml theme={"system"}
spec:
  experimentType: pod_kill
  dryRun: true
  target:
    kind: Deployment
    name: api-server
    namespace: staging
  parameters:
    count: "3"
  duration: 5m
```

**Resultado de um DryRun:**

```yaml theme={"system"}
status:
  state: Completed
  startedAt: "2026-03-19T03:00:00Z"
  completedAt: "2026-03-19T03:00:00Z"
  result: "dry-run: would execute pod_kill on staging/api-server for 5m"
  podsAffected: 0
  recoveryVerified: false
  preExperimentSnapshot: "Deployment staging/api-server: replicas=5, ready=5, available=5, updated=5"
  postExperimentSnapshot: "Deployment staging/api-server: replicas=5, ready=5, available=5, updated=5"
```

Um dry run incrementa `chatcli_operator_chaos_experiments_total{result="dry_run"}`.

<Tip>
  Rode um dry run primeiro para confirmar que as listas de namespaces e o
  `minHealthyPods` aceitam o alvo. Se um safety check falhar, o dry run
  termina em `Failed` com a mesma mensagem que uma execução real teria.
</Tip>

## Schedule (Experimentos Recorrentes)

<Warning>
  O campo `schedule` existe no CRD, mas experimentos recorrentes **não são
  suportados**. Um experimento que define `schedule` falha na hora, em
  `Pending`, sem injetar nada, com `result: "schedule is not supported: recurring experiments are not implemented; remove spec.schedule and create one ChaosExperiment per run"`.
</Warning>

Para game days recorrentes, crie um `ChaosExperiment` novo de forma agendada, fora do operator, por exemplo com um CronJob do Kubernetes que rode `kubectl create -f` num manifesto com `metadata.generateName` (assim cada execução ganha um nome e um status novos). Limpe os experimentos antigos por conta própria; o operator não os remove.

## LinkedIssueRef

`linkedIssueRef` aceita só um `name`; a Issue é buscada no namespace do experimento. Ele é usado em dois lugares:

1. **Aprovação:** quando `requireApproval` é `true`, vira o `spec.issueRef` do ApprovalRequest gerado.
2. **`runRemediationTest`:** ao concluir, o controller verifica se essa Issue está `Resolved` e grava o resultado em `status.result`.

O controller não altera a Issue nem o RemediationPlan dela, e não registra o vínculo em nenhum outro lugar do status do experimento.

```yaml theme={"system"}
spec:
  linkedIssueRef:
    name: issue-api-server-crashloop
  experimentType: pod_kill
  parameters:
    count: "2"
  postExperiment:
    verifyRecovery: true
    recoveryTimeout: 3m
    runRemediationTest: true
```

## Exemplos YAML Completos

<Accordion title="Pod Kill com Safety Checks">
  ```yaml theme={"system"}
  apiVersion: platform.chatcli.io/v1alpha1
  kind: ChaosExperiment
  metadata:
    name: validate-api-server-pod-kill
    namespace: staging
    labels:
      team: platform
      experiment-type: resilience
  spec:
    experimentType: pod_kill
    target:
      kind: Deployment
      name: api-server
      namespace: staging
    parameters:
      count: "2"
    duration: 5m
    dryRun: false
    safetyChecks:
      minHealthyPods: 2
      maxConcurrentExperiments: 1
      abortOnIssueDetected: false
      requireApproval: false
      allowedNamespaces: [staging, chaos-testing]
      blockedNamespaces: [production, kube-system]
    postExperiment:
      verifyRecovery: true
      recoveryTimeout: 2m
      runRemediationTest: false
  ```
</Accordion>

<Accordion title="CPU Stress (exige permissão de create em pods)">
  ```yaml theme={"system"}
  apiVersion: platform.chatcli.io/v1alpha1
  kind: ChaosExperiment
  metadata:
    name: cpu-stress-api
    namespace: staging
  spec:
    experimentType: cpu_stress
    target:
      kind: Deployment
      name: api-server
      namespace: staging
    parameters:
      cores: "2"
      loadPercent: "90"
    duration: 10m
    safetyChecks:
      maxConcurrentExperiments: 1
      abortOnIssueDetected: true
      blockedNamespaces: [production, kube-system]
    postExperiment:
      verifyRecovery: true
      recoveryTimeout: 5m
  ```
</Accordion>

<Accordion title="Validação Pós-Remediação">
  ```yaml theme={"system"}
  apiVersion: platform.chatcli.io/v1alpha1
  kind: ChaosExperiment
  metadata:
    name: validate-crashloop-fix
    namespace: staging
  spec:
    experimentType: pod_kill
    target:
      kind: Deployment
      name: payment-service
      namespace: staging
    parameters:
      count: "1"
    duration: 3m
    linkedIssueRef:
      name: issue-payment-crashloop
    safetyChecks:
      minHealthyPods: 1
      abortOnIssueDetected: false   # esperamos que o AIOps detecte a falha
    postExperiment:
      verifyRecovery: true
      recoveryTimeout: 3m
      runRemediationTest: true      # verifica se a Issue vinculada está Resolved
  ```
</Accordion>

<Accordion title="DryRun para Validação de Configuração">
  ```yaml theme={"system"}
  apiVersion: platform.chatcli.io/v1alpha1
  kind: ChaosExperiment
  metadata:
    name: dryrun-memory-stress
    namespace: staging
  spec:
    experimentType: memory_stress
    dryRun: true
    target:
      kind: Deployment
      name: cache-service
      namespace: staging
    parameters:
      bytes: "256M"
    duration: 5m
    safetyChecks:
      minHealthyPods: 2
      maxConcurrentExperiments: 1
      blockedNamespaces: [production]
  ```
</Accordion>

## Métricas

O controller de chaos registra três métricas no endpoint de métricas do operator (porta 8080 por padrão):

| Métrica | Tipo | Labels | Descrição |
| - | - | - | - |
| `chatcli_operator_chaos_experiments_total` | Counter | `type`, `result` | Experimentos por tipo e desfecho. `result` é `completed`, `failed`, `aborted` ou `dry_run` |
| `chatcli_operator_chaos_recovery_time_seconds` | Histogram | `type` | Só é registrado quando a recuperação é verificada: tempo do fim da injeção até a primeira verificação de saúde que passa |
| `chatcli_operator_chaos_pods_affected_total` | Counter | `type` | Pods deletados, ou pods de stress criados (1 por experimento) |

`result="failed"` significa que um safety check ou a injeção falhou. Um alvo que não se recuperou a tempo ainda conta como `completed`.

### Exemplo de Alertas

```yaml theme={"system"}
groups:
  - name: chaos-engineering
    rules:
      - alert: ChaosExperimentFailed
        expr: increase(chatcli_operator_chaos_experiments_total{result="failed"}[1h]) > 0
        labels:
          severity: warning
        annotations:
          summary: "Experimento de chaos falhou"
          description: >
            Um experimento {{ $labels.type }} falhou num safety check ou não
            conseguiu injetar a falha. Veja o status.result do ChaosExperiment.

      - alert: ChaosExperimentAborted
        expr: increase(chatcli_operator_chaos_experiments_total{result="aborted"}[1h]) > 0
        labels:
          severity: info
        annotations:
          summary: "Experimento de chaos abortado"
          description: >
            Um experimento {{ $labels.type }} foi abortado porque uma nova
            Issue foi aberta para o alvo.
```

Para alertar sobre recuperação não verificada, consulte os CRs (`status.recoveryVerified: false` em experimentos `Completed`); não existe métrica para isso.

## Boas Práticas

<Steps>
  <Step title="Comece com DryRun">
    Confirme que as listas de namespaces e o `minHealthyPods` aceitam o alvo
    antes de rodar um experimento de verdade.
  </Step>

  <Step title="Staging Primeiro">
    Rode experimentos em staging antes de ambientes de tier mais alto. Defina
    `allowedNamespaces` e `blockedNamespaces` explicitamente; nada é
    bloqueado por padrão.
  </Step>

  <Step title="Safety Checks Conservadores">
    Configure `minHealthyPods` com margem. Se o deployment tem 5 réplicas e
    precisa de 3 para operar, use `minHealthyPods: 3` e mantenha `count`
    baixo: um `count` igual ou maior que o número de réplicas deleta todos os
    pods.
  </Step>

  <Step title="Escolha o abortOnIssueDetected com Intenção">
    Ligue para parar a simulação assim que o AIOps reagir; desligue quando o
    objetivo da simulação é deixar o AIOps detectar e remediar.
  </Step>

  <Step title="Recrie a Cada Execução">
    Experimentos rodam uma vez. Use um agendador externo que crie um CR novo
    (com `generateName`) para game days recorrentes.
  </Step>
</Steps>

## Próximos Passos

<CardGroup cols={2}>
  <Card title="Fluxo de Aprovação" icon="user-check" href="/pt/kubernetes/aiops/approval-workflow">
    Como os ApprovalRequests são decididos, usados pelo `requireApproval`.
  </Card>

  <Card title="Ciclo de Vida de Incidentes" icon="arrows-spin" href="/pt/kubernetes/aiops/incident-lifecycle">
    O pipeline de Issues pelo qual as Issues induzidas por chaos passam.
  </Card>

  <Card title="Dashboard Web" icon="gauge" href="/pt/kubernetes/aiops/web-dashboard">
    Mostre ou oculte simulações de chaos nas telas de incidentes.
  </Card>

  <Card title="Plataforma AIOps" icon="brain" href="/pt/kubernetes/aiops-platform">
    Retorne à visão geral da plataforma AIOps.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.