# External integration CI

rs-zero 的默认验证保持离线：`cargo test --workspace` 不连接 Redis、etcd、Kubernetes、OTLP collector、Pyroscope、MySQL 或 PostgreSQL。生产 adapter 的真实依赖验证由独立 external integration 流程负责。

## CI 触发方式

GitHub Actions workflow 位于 `.github/workflows/external-integrations.yml`，支持：

- `workflow_dispatch`：手动选择 `all` 或单个目标。
- `schedule`：按周定时运行完整 matrix。

每个目标独立 job 运行，失败时能区分依赖启动、健康检查、测试和清理问题。workflow 不使用真实云凭据、持久 kubeconfig 或生产 endpoint。

## 本地复现

本地入口与 CI 使用同一套脚本：

```bash
scripts/external-integration.sh redis
scripts/external-integration.sh redis-fault
scripts/external-integration.sh redis-recovery
RS_ZERO_TEST_REDIS_CLUSTER_URL=redis://127.0.0.1:7000,redis://127.0.0.1:7001 scripts/external-integration.sh redis-cluster
scripts/external-integration.sh linux-cpu
scripts/external-integration.sh etcd
scripts/external-integration.sh kubernetes
scripts/external-integration.sh otlp
scripts/external-integration.sh pyroscope
scripts/external-integration.sh mysql
scripts/external-integration.sh postgres
scripts/external-integration.sh all
```

脚本默认启动 `examples/production-adapters/docker-compose.external.yml` 中需要的服务，并在退出时清理容器和 KinD 集群。需要保留资源排障时设置：

```bash
RS_ZERO_SKIP_CLEANUP=1 scripts/external-integration.sh redis
```

Kubernetes 目标需要本机安装 Docker 和 kubectl。CI 会安装 KinD 并创建或复用 `rs-zero-external` 集群，测试使用 `kind-rs-zero-external` context；本地可用 `RS_ZERO_KUBE_CONTEXT` 指定现有 context；未安装 KinD 时脚本会先检查当前 context，再尝试常见本地 context（如 `docker-desktop`）。Compose 默认使用 `12379`、`16379`、`14317`、`14318`、`14040`、`13306`、`15432` 等宿主端口，避免和本机已运行的 etcd、Redis 或数据库冲突；如需改端口，设置 `RS_ZERO_EXTERNAL_*_PORT`。

`redis-recovery` 会启动脚本管理的 Redis 容器，并在测试中执行 `docker stop` / `docker start` 验证 token limiter 从 Redis 切到本地 rescue limiter，再由后台 ping 恢复到 Redis。`linux-cpu` 是 Linux 专用压力验证；非 Linux 平台只会明确跳过，不会伪造 CPU 采样结果。

## 外部依赖与变量

| 目标 | 默认依赖 | 环境变量 | 默认值 |
| --- | --- | --- | --- |
| Redis | `redis:7-alpine` | `RS_ZERO_TEST_REDIS_URL` | `redis://127.0.0.1:16379` |
| Redis fault | 不需要外部服务 | 无 | 本地错误端口和 breaker 测试 |
| Redis recovery | `redis:7-alpine` | `RS_ZERO_TEST_REDIS_URL` / `RS_ZERO_TEST_REDIS_CONTAINER` | URL 默认 `redis://127.0.0.1:16379`，容器由脚本注入 |
| Redis Cluster | 现有 Redis Cluster | `RS_ZERO_TEST_REDIS_CLUSTER_URL` | 无默认值 |
| Linux CPU pressure | Linux 主机 | `RS_ZERO_CPU_PRESSURE_SECONDS` / `RS_ZERO_CPU_PRESSURE_TASKS` / `RS_ZERO_CPU_SHEDDER_IN_FLIGHT` | `3` / `4` / `4` |
| etcd | `quay.io/coreos/etcd:v3.5.15` | `RS_ZERO_ETCD_ENDPOINT` | `http://127.0.0.1:12379` |
| Kubernetes | KinD | `RS_ZERO_KUBE_EXTERNAL` / `RS_ZERO_KUBE_CONTEXT` | `1` / `kind-rs-zero-external` |
| OTLP | `otel/opentelemetry-collector:0.104.0` | `RS_ZERO_OTLP_ENDPOINT` | `http://127.0.0.1:14318` |
| Pyroscope | `grafana/pyroscope:1.14.0` | `RS_ZERO_PYROSCOPE_ENDPOINT` | `http://127.0.0.1:14040` |
| MySQL | `mysql:8.4` | `RS_ZERO_MYSQL_URL` | `mysql://rs_zero:rs_zero@127.0.0.1:13306/rs_zero` |
| PostgreSQL | `postgres:16-alpine` | `RS_ZERO_POSTGRES_URL` | `postgres://rs_zero:rs_zero@127.0.0.1:15432/rs_zero` |

数据库账号只用于本地和 CI 容器，不能复用于生产配置。

## 单项测试命令

```bash
RS_ZERO_TEST_REDIS_URL=redis://127.0.0.1:16379 \
  cargo test --features cache-redis --test cache_integration redis_store_talks_to_external_redis -- --ignored

cargo test --features cache-redis,observability --test cache_redis_degradation

RS_ZERO_TEST_REDIS_URL=redis://127.0.0.1:16379 \
RS_ZERO_TEST_REDIS_CONTAINER=<redis-container-id> \
  cargo test --features cache-redis,resil --test redis_limiter_recovery_external -- --ignored --test-threads=1

RS_ZERO_TEST_REDIS_CLUSTER_URL=redis://127.0.0.1:7000,redis://127.0.0.1:7001 \
  cargo test --features cache-redis --test cache_redis_external redis_cluster_env_is_available_for_manual_moved_ask_validation -- --ignored

RS_ZERO_CPU_PRESSURE_SECONDS=3 RS_ZERO_CPU_PRESSURE_TASKS=4 \
  cargo test --features resil --test resilience_cpu_linux_external -- --ignored --test-threads=1

RS_ZERO_ETCD_ENDPOINT=http://127.0.0.1:12379 \
  cargo test --no-default-features --features discovery-etcd --test discovery_etcd_external -- --ignored

RS_ZERO_KUBE_EXTERNAL=1 RS_ZERO_KUBE_CONTEXT=kind-rs-zero-external \
  cargo test --no-default-features --features discovery-kube --test discovery_kube_external -- --ignored

RS_ZERO_OTLP_ENDPOINT=http://127.0.0.1:14318 \
  cargo test --no-default-features --features observability,otlp --test observability_otlp_external -- --ignored

RS_ZERO_PYROSCOPE_ENDPOINT=http://127.0.0.1:14040 \
  cargo test --no-default-features --features profiling --test profiling_pyroscope_external -- --ignored

RS_ZERO_MYSQL_URL=mysql://rs_zero:rs_zero@127.0.0.1:13306/rs_zero \
  cargo test --no-default-features --features db-mysql --test db_external mysql_external_pool_lifecycle -- --ignored

RS_ZERO_POSTGRES_URL=postgres://rs_zero:rs_zero@127.0.0.1:15432/rs_zero \
  cargo test --no-default-features --features db-postgres --test db_external postgres_external_pool_lifecycle -- --ignored
```

## 排障

- Compose 配置检查：`docker compose -f examples/production-adapters/docker-compose.external.yml config`。
- 查看服务状态：`docker compose -f examples/production-adapters/docker-compose.external.yml ps`。
- 查看服务日志：`docker compose -f examples/production-adapters/docker-compose.external.yml logs --tail=200 <service>`。
- Kubernetes 状态：`kubectl cluster-info --context kind-rs-zero-external` 和 `kubectl get endpointslices -A --context kind-rs-zero-external`。
- 若端口被占用，优先用 `RS_ZERO_EXTERNAL_*_PORT` 调整 compose 宿主端口；也可以直接设置对应 `RS_ZERO_*` endpoint 运行单项 cargo test。
