diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d43d6f..bb15ab9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,13 @@ jobs: deployment: runs-on: ubuntu-latest env: + PROXY_POOL_GATEWAY_USERNAME: ci-gateway-user PROXY_POOL_GATEWAY_PASSWORD: ci-gateway-password PROXY_POOL_EXTRACT_TOKEN: ci-extract-token PROXY_POOL_ADMIN_TOKEN: ci-admin-token PROXY_POOL_CONFIG_FINGERPRINT_KEY: ci-config-fingerprint-key-at-least-32-bytes + PROXY_POOL_POSTGRES_URL: postgres://ci:ci@postgres.example:5432/proxy_pool?sslmode=verify-full + PROXY_POOL_REDIS_URL: rediss://:ci@redis.example:6379/0 PROVIDER_A_TOKEN: ci-provider-a-token PROVIDER_B_TOKEN: ci-provider-b-token steps: @@ -34,6 +37,10 @@ jobs: run: docker compose -f deploy/docker-compose.yml config --quiet - name: Render Kubernetes base run: kubectl kustomize deploy/kubernetes/base > /dev/null + - name: Render Kubernetes development mTLS overlay + run: kubectl kustomize deploy/kubernetes/overlays/development-mtls > /dev/null + - name: Validate development mTLS configuration + run: go run ./deploy/tools/configcheck deploy/kubernetes/overlays/development-mtls/config.yaml test: strategy: diff --git a/README.md b/README.md index f59fadc..6dabe78 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ go run ./deploy/tools/configcheck deploy/config/local.yaml ``` `package-docs.ps1` 默认生成被忽略的 `dist/proxy-pool-docs-v1.0.zip`;包内包含 README、 -`docs/`、图表、OpenAPI 和 Proto 契约,并以 `manifest.json` 记录 Git revision、文件大小和 +`docs/`、图表、OpenAPI、Proto 契约和部署手册,并以 `manifest.json` 记录 Git revision、文件大小和 SHA-256。可使用 `-Version vMAJOR.MINOR[.PATCH]` 与 `-OutputPath OUTPUT.zip` 生成指定交付物。 `benchmark-gateway.ps1` 固定执行一次 100k 索引调度、Routing Round Robin 和 Snapshot Apply 微基准,并把版本与原始输出写入 `dist/gateway-benchmarks.txt`;它用于回归比较,不构成 diff --git a/deploy/README.md b/deploy/README.md index dcdd437..34754fa 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -14,9 +14,11 @@ SPIFFE URI 证书,私钥只写入被忽略的 `deploy/.control-plane-tls/`。 docker compose -f deploy/docker-compose.yml up -d --build ``` -Kubernetes Base 仍保持 `controlPlane.enabled: false`,并保留 `checker.yaml` 作为 -环境 Overlay 模板。生产 Overlay 必须为每个弹性 Gateway/Checker 工作负载配置唯一 -身份与证书轮换,不能复用 Compose 的固定开发证书。 +Kubernetes Base 仍保持 `controlPlane.enabled: false`。`kubernetes/overlays/development-mtls` +启用单副本 Controller/Gateway/Checker mTLS 拓扑,挂载三组外部 TLS Secret,并固定 +`gateway-a`、`checker-a` 身份。具体创建 Secret 和部署步骤见 +[development mTLS overlay](kubernetes/overlays/development-mtls/README.md)。生产 Overlay +必须为每个弹性 Gateway/Checker 工作负载配置唯一身份与证书轮换,不能复用固定开发证书。 当前可执行验证: @@ -24,6 +26,7 @@ Kubernetes Base 仍保持 `controlPlane.enabled: false`,并保留 `checker.yam docker compose -f deploy/docker-compose.yml config --quiet docker compose -f deploy/docker-compose.test.yml config --quiet kubectl kustomize deploy/kubernetes/base | Out-Null +kubectl kustomize deploy/kubernetes/overlays/development-mtls | Out-Null go run ./deploy/tools/configcheck deploy/config/local.yaml ``` diff --git a/deploy/ci_test.go b/deploy/ci_test.go index 46b5b54..f75d679 100644 --- a/deploy/ci_test.go +++ b/deploy/ci_test.go @@ -31,6 +31,8 @@ func TestCIValidatesProtocolAndDeploymentAssets(t *testing.T) { "./scripts/verify-proto.ps1", "docker compose -f deploy/docker-compose.yml config --quiet", "kubectl kustomize deploy/kubernetes/base", + "kubectl kustomize deploy/kubernetes/overlays/development-mtls", + "go run ./deploy/tools/configcheck deploy/kubernetes/overlays/development-mtls/config.yaml", } { if !strings.Contains(workflow, required) { t.Errorf("ci workflow does not validate %q", required) diff --git a/deploy/kubernetes/overlays/development-mtls/README.md b/deploy/kubernetes/overlays/development-mtls/README.md new file mode 100644 index 0000000..e05b06c --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/README.md @@ -0,0 +1,56 @@ +# Kubernetes development mTLS overlay + +该 Overlay 在 base 资源之上启用 Controller 控制面 mTLS,并启动一个固定身份的 Gateway +和 Checker。它只用于开发或预发布的单副本控制面验证,不是生产弹性身份方案。 + +## Prepare certificates + +先生成未提交的开发证书。输出目录必须为空;生成器的 `controller` 证书包含 +`DNS:controller`,Overlay 中的客户端也只拨号 `controller:8443`。 + +```powershell +./scripts/generate-local-controlplane-certs.ps1 +kubectl apply -f deploy/kubernetes/base/namespace.yaml +``` + +将生成的三组证书写入 Kubernetes Secret。下面的命令可重复执行,只更新对应 Secret: + +```powershell +kubectl -n proxy-pool create secret generic proxy-pool-controlplane-server-tls ` + --from-file=tls.crt=deploy/.control-plane-tls/controller/tls.crt ` + --from-file=tls.key=deploy/.control-plane-tls/controller/tls.key ` + --from-file=ca.crt=deploy/.control-plane-tls/controller/ca.crt ` + --dry-run=client -o yaml | kubectl apply -f - + +kubectl -n proxy-pool create secret generic proxy-pool-controlplane-gateway-tls ` + --from-file=tls.crt=deploy/.control-plane-tls/gateway-a/tls.crt ` + --from-file=tls.key=deploy/.control-plane-tls/gateway-a/tls.key ` + --from-file=ca.crt=deploy/.control-plane-tls/gateway-a/ca.crt ` + --dry-run=client -o yaml | kubectl apply -f - + +kubectl -n proxy-pool create secret generic proxy-pool-controlplane-checker-tls ` + --from-file=tls.crt=deploy/.control-plane-tls/checker-a/tls.crt ` + --from-file=tls.key=deploy/.control-plane-tls/checker-a/tls.key ` + --from-file=ca.crt=deploy/.control-plane-tls/checker-a/ca.crt ` + --dry-run=client -o yaml | kubectl apply -f - +``` + +另行从私密配置系统创建 `proxy-pool-secrets`。可参考 base 目录的 +`secret.example.yaml`,但不要把实际数据库、Redis、Provider 或 API 凭据写入 Git。 + +## Deploy + +```powershell +kubectl apply -k deploy/kubernetes/overlays/development-mtls +kubectl -n proxy-pool rollout status deployment/proxy-controller --timeout=5m +kubectl -n proxy-pool rollout status deployment/proxy-gateway --timeout=5m +kubectl -n proxy-pool rollout status deployment/proxy-checker --timeout=5m +``` + +Overlay 的 Gateway 固定为 `worker_id=gateway-a`,Checker 固定为 +`checker_id=checker-a`,并把 Gateway HPA 限制为 `minReplicas=maxReplicas=1`。 +因此不得在此 Overlay 上增加副本数或放宽 HPA;重复使用同一 Worker 身份会破坏 +session、ownership 和证书角色边界。 + +生产环境需要由工作负载身份系统为每个副本签发独立、可轮换的证书,并将该副本的 +身份注入 `worker_id`、`instance_id`、Gateway Client TLS 或 Checker Client TLS。 diff --git a/deploy/kubernetes/overlays/development-mtls/checker-networkpolicy.yaml b/deploy/kubernetes/overlays/development-mtls/checker-networkpolicy.yaml new file mode 100644 index 0000000..398c9aa --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/checker-networkpolicy.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: checker-control-plane + namespace: proxy-pool +spec: + podSelector: + matchLabels: {app.kubernetes.io/name: proxy-checker} + policyTypes: [Ingress, Egress] + ingress: + - from: + - namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: monitoring}} + ports: [{port: 9090, protocol: TCP}] + egress: + - to: + - namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: kube-system}} + ports: [{port: 53, protocol: UDP}, {port: 53, protocol: TCP}] + - to: + - podSelector: {matchLabels: {app.kubernetes.io/name: proxy-controller}} + ports: [{port: 8443, protocol: TCP}] + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: [10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16] diff --git a/deploy/kubernetes/overlays/development-mtls/checker-patch.yaml b/deploy/kubernetes/overlays/development-mtls/checker-patch.yaml new file mode 100644 index 0000000..daa6772 --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/checker-patch.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: proxy-checker + namespace: proxy-pool +spec: + replicas: 1 + template: + spec: + containers: + - name: checker + env: + - {name: PROXY_POOL_CONTROL_PLANE_ADDRESS, value: controller:8443} + - {name: PROXY_POOL_CHECKER_ID, value: checker-a} + - {name: PROXY_POOL_CHECKER_INSTANCE_ID, value: checker-a-1} + - {name: PROXY_POOL_CHECKER_MAX_IN_FLIGHT, value: "200"} + volumeMounts: + - name: controlplane-checker-tls + mountPath: /run/proxy-pool-tls/client + readOnly: true + volumes: + - name: controlplane-checker-tls + secret: + secretName: proxy-pool-controlplane-checker-tls diff --git a/deploy/kubernetes/overlays/development-mtls/checker.yaml b/deploy/kubernetes/overlays/development-mtls/checker.yaml new file mode 100644 index 0000000..c5d893c --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/checker.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: proxy-checker + namespace: proxy-pool + labels: {app.kubernetes.io/name: proxy-checker, app.kubernetes.io/part-of: proxy-pool} +spec: + replicas: 1 + minReadySeconds: 5 + revisionHistoryLimit: 3 + strategy: + type: RollingUpdate + rollingUpdate: {maxUnavailable: 1, maxSurge: 1} + selector: + matchLabels: {app.kubernetes.io/name: proxy-checker} + template: + metadata: + labels: {app.kubernetes.io/name: proxy-checker, app.kubernetes.io/part-of: proxy-pool} + annotations: {prometheus.io/scrape: "true", prometheus.io/port: "9090", prometheus.io/path: /metrics} + spec: + serviceAccountName: proxy-pool + automountServiceAccountToken: false + terminationGracePeriodSeconds: 45 + securityContext: {runAsNonRoot: true, seccompProfile: {type: RuntimeDefault}} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: {matchLabels: {app.kubernetes.io/name: proxy-checker}} + containers: + - name: checker + image: REGISTRY/proxy-pool:VERSION + imagePullPolicy: IfNotPresent + command: [proxy-checker] + env: + - {name: PROXY_POOL_CONFIG, value: /etc/proxy-pool/config.yaml} + envFrom: + - secretRef: {name: proxy-pool-secrets} + ports: + - {name: metrics, containerPort: 9090} + readinessProbe: + httpGet: {path: /readyz, port: metrics} + periodSeconds: 5 + timeoutSeconds: 2 + livenessProbe: + httpGet: {path: /livez, port: metrics} + periodSeconds: 10 + timeoutSeconds: 2 + lifecycle: + preStop: {exec: {command: [sh, -c, "sleep 3"]}} + resources: + requests: {cpu: "1", memory: 512Mi} + limits: {cpu: "2", memory: 1Gi} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: {drop: [ALL]} + volumeMounts: + - {name: config, mountPath: /etc/proxy-pool, readOnly: true} + - {name: tmp, mountPath: /tmp} + volumes: + - name: config + configMap: {name: proxy-pool-config} + - name: tmp + emptyDir: {sizeLimit: 64Mi} diff --git a/deploy/kubernetes/overlays/development-mtls/config.yaml b/deploy/kubernetes/overlays/development-mtls/config.yaml new file mode 100644 index 0000000..e0db905 --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/config.yaml @@ -0,0 +1,216 @@ +version: 1 + +security: + requireProtectionOnPublicListen: true + +gateway: + enabled: true + listen: 0.0.0.0:8080 + access: + allowCIDRs: [0.0.0.0/0] + trustedProxies: [] + auth: + mode: usernamePassword + username: "${PROXY_POOL_GATEWAY_USERNAME}" + password: "${PROXY_POOL_GATEWAY_PASSWORD}" + limits: + maxConcurrentConnections: 20000 + requestsPerMinutePerClient: 60000 + transport: + maxIdleConns: 20000 + maxIdleConnsPerHost: 32 + maxConnsPerHost: 32 + tunnelBufferBytes: 32768 + retry: + maxAttempts: 2 + retryMethods: [GET, HEAD] + destinationPolicy: + denyPrivateNetworks: true + denyLoopback: true + denyLinkLocal: true + denyCIDRs: [169.254.169.254/32] + +distribution: + enabled: true + listen: 0.0.0.0:8081 + access: + allowCIDRs: [10.0.0.0/8] + trustedProxies: [10.0.0.0/8] + auth: + mode: apiKey + header: X-API-Key + token: "${PROXY_POOL_EXTRACT_TOKEN}" + limits: + requestsPerMinute: 6000 + requestsPerMinutePerClient: 600 + clientIdentification: + mode: sourceIP + extraction: + fulfillment: partial + maxCountPerRequest: 100 + minRemainingTTL: 30s + maxHealthCheckAge: 30s + reserveForGateway: 1000 + idempotencyTTL: 5m + +admin: + enabled: true + listen: 0.0.0.0:8082 + access: + allowCIDRs: [10.0.0.0/8] + auth: + mode: apiKey + header: X-Admin-Key + token: "${PROXY_POOL_ADMIN_TOKEN}" + +controlPlane: + enabled: true + listen: 0.0.0.0:8443 + protocolVersion: 1 + heartbeatInterval: 10s + sessionTTL: 30s + maxStaleAge: 30s + maxMessageBytes: 4194304 + maxRuntimeCounters: 100000 + maxConcurrentStreams: 1000 + tls: + mode: mtls + certFile: /run/proxy-pool-tls/server/tls.crt + keyFile: /run/proxy-pool-tls/server/tls.key + clientCAFile: /run/proxy-pool-tls/server/ca.crt + trustDomain: proxy-pool.local + environment: development + gatewayTLS: + certFile: /run/proxy-pool-tls/client/tls.crt + keyFile: /run/proxy-pool-tls/client/tls.key + serverCAFile: /run/proxy-pool-tls/client/ca.crt + checkerTLS: + certFile: /run/proxy-pool-tls/client/tls.crt + keyFile: /run/proxy-pool-tls/client/tls.key + serverCAFile: /run/proxy-pool-tls/client/ca.crt + +metrics: + enabled: true + listen: 0.0.0.0:9090 + +storage: + postgresURL: "${PROXY_POOL_POSTGRES_URL}" + redisURL: "${PROXY_POOL_REDIS_URL}" + +routing: + - name: gateway-default + enabled: true + purpose: gateway + upstreams: [provider-a, provider-b] + strategy: + type: sequential + switchAfterEmptyFetch: 5 + endBehavior: stayLast + onUnavailable: + action: reject + - name: extract-default + enabled: true + purpose: extract + upstreams: [provider-a, provider-b] + strategy: + type: sequential + switchAfterEmptyFetch: 5 + endBehavior: stayLast + onUnavailable: + action: reject + +upstreams: + provider-a: + enabled: true + exposure: [gateway, extract] + provider: + billingMode: fetch + protocols: [http] + api: + url: https://provider-a.invalid/api/proxies + method: GET + auth: + type: apiKey + location: header + name: Authorization + value: "${PROVIDER_A_TOKEN}" + template: '{{ . }}' + proxyAuth: + type: response + pool: + maxSize: 5000 + shrinkDelay: 30s + capacity: + maxConcurrencyPerProxy: 20 + refill: + reconcileInterval: 1s + minimumAvailableSlots: 8000 + targetAvailableSlots: 12000 + lifecycle: + ttl: 5m + allocationSafetyMargin: 20s + fetch: + estimatedIPsPerCall: 100 + requestInterval: 1s + timeout: 3s + maxAttempts: 3 + maxInFlight: 1 + maxTotal: 100000 + maxResponseBytes: 4194304 + templateTimeout: 100ms + retry: {initial: 500ms, max: 30s, jitter: 20} + check: + interval: 30s + jitter: 20 + maxInFlight: 200 + timeout: 3s + maxAttempts: 2 + maxConsecutiveFailures: 3 + urls: [https://example.com/] + provider-b: + enabled: true + exposure: [gateway, extract] + provider: + billingMode: fetch + protocols: [http] + api: + url: https://provider-b.invalid/api/proxies + method: GET + auth: + type: apiKey + location: header + name: Authorization + value: "${PROVIDER_B_TOKEN}" + template: '{{ . }}' + proxyAuth: + type: response + pool: + maxSize: 5000 + shrinkDelay: 30s + capacity: + maxConcurrencyPerProxy: 20 + refill: + reconcileInterval: 1s + minimumAvailableSlots: 8000 + targetAvailableSlots: 12000 + lifecycle: + ttl: 5m + allocationSafetyMargin: 20s + fetch: + estimatedIPsPerCall: 100 + requestInterval: 1s + timeout: 3s + maxAttempts: 3 + maxInFlight: 1 + maxTotal: 100000 + maxResponseBytes: 4194304 + templateTimeout: 100ms + retry: {initial: 500ms, max: 30s, jitter: 20} + check: + interval: 30s + jitter: 20 + maxInFlight: 200 + timeout: 3s + maxAttempts: 2 + maxConsecutiveFailures: 3 + urls: [https://example.com/] diff --git a/deploy/kubernetes/overlays/development-mtls/controller-patch.yaml b/deploy/kubernetes/overlays/development-mtls/controller-patch.yaml new file mode 100644 index 0000000..f5987b2 --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/controller-patch.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: proxy-controller + namespace: proxy-pool +spec: + replicas: 1 + template: + spec: + containers: + - name: controller + volumeMounts: + - name: controlplane-server-tls + mountPath: /run/proxy-pool-tls/server + readOnly: true + volumes: + - name: controlplane-server-tls + secret: + secretName: proxy-pool-controlplane-server-tls diff --git a/deploy/kubernetes/overlays/development-mtls/controller-service.yaml b/deploy/kubernetes/overlays/development-mtls/controller-service.yaml new file mode 100644 index 0000000..846e818 --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/controller-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: controller + namespace: proxy-pool + labels: {app.kubernetes.io/name: proxy-controller} +spec: + type: ClusterIP + selector: {app.kubernetes.io/name: proxy-controller} + ports: + - {name: control, port: 8443, targetPort: control} diff --git a/deploy/kubernetes/overlays/development-mtls/gateway-hpa-patch.yaml b/deploy/kubernetes/overlays/development-mtls/gateway-hpa-patch.yaml new file mode 100644 index 0000000..b197861 --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/gateway-hpa-patch.yaml @@ -0,0 +1,8 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: proxy-gateway + namespace: proxy-pool +spec: + minReplicas: 1 + maxReplicas: 1 diff --git a/deploy/kubernetes/overlays/development-mtls/gateway-patch.yaml b/deploy/kubernetes/overlays/development-mtls/gateway-patch.yaml new file mode 100644 index 0000000..10fffb4 --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/gateway-patch.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: proxy-gateway + namespace: proxy-pool +spec: + replicas: 1 + template: + spec: + containers: + - name: gateway + env: + - {name: PROXY_POOL_CONTROL_PLANE_ADDRESS, value: controller:8443} + - {name: PROXY_POOL_CLUSTER_ID, value: kubernetes-development} + - {name: PROXY_POOL_WORKER_ID, value: gateway-a} + - {name: PROXY_POOL_INSTANCE_ID, value: gateway-a-1} + - {name: PROXY_POOL_ZONE, value: kubernetes-development} + volumeMounts: + - name: controlplane-gateway-tls + mountPath: /run/proxy-pool-tls/client + readOnly: true + volumes: + - name: controlplane-gateway-tls + secret: + secretName: proxy-pool-controlplane-gateway-tls diff --git a/deploy/kubernetes/overlays/development-mtls/kustomization.yaml b/deploy/kubernetes/overlays/development-mtls/kustomization.yaml new file mode 100644 index 0000000..327a605 --- /dev/null +++ b/deploy/kubernetes/overlays/development-mtls/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ../../base + - checker.yaml + - controller-service.yaml + - checker-networkpolicy.yaml +configMapGenerator: + - name: proxy-pool-config + namespace: proxy-pool + behavior: replace + files: + - config.yaml +generatorOptions: + disableNameSuffixHash: true +patches: + - path: controller-patch.yaml + - path: gateway-patch.yaml + - path: checker-patch.yaml + - path: gateway-hpa-patch.yaml diff --git a/deploy/kubernetes_overlay_test.go b/deploy/kubernetes_overlay_test.go new file mode 100644 index 0000000..99438c8 --- /dev/null +++ b/deploy/kubernetes_overlay_test.go @@ -0,0 +1,39 @@ +package deploy + +import ( + "os/exec" + "strings" + "testing" +) + +func TestKubernetesDevelopmentMTLSOverlayRendersStaticWorkerIdentities(t *testing.T) { + kubectl, err := exec.LookPath("kubectl") + if err != nil { + t.Skip("kubectl is required to render the Kubernetes development mTLS overlay") + } + command := exec.Command(kubectl, "kustomize", "kubernetes/overlays/development-mtls") + command.Dir = "." + payload, err := command.CombinedOutput() + if err != nil { + t.Fatalf("render development mTLS overlay: %v\n%s", err, payload) + } + rendered := string(payload) + for _, required := range []string{ + "mode: mtls", + "trustDomain: proxy-pool.local", + "value: controller:8443", + "value: gateway-a", + "value: checker-a", + "secretName: proxy-pool-controlplane-server-tls", + "secretName: proxy-pool-controlplane-gateway-tls", + "secretName: proxy-pool-controlplane-checker-tls", + "name: proxy-checker", + "name: checker-control-plane", + "minReplicas: 1", + "maxReplicas: 1", + } { + if !strings.Contains(rendered, required) { + t.Errorf("development mTLS overlay does not render %q", required) + } + } +} diff --git a/docs/development/implementation-plan.md b/docs/development/implementation-plan.md index 98eeea8..3b0693e 100644 --- a/docs/development/implementation-plan.md +++ b/docs/development/implementation-plan.md @@ -374,7 +374,9 @@ OpenAPI 结构契约由 Go 测试在双平台 CI 执行。 Gateway 热路径。`platform/logging` 以 JSON `slog` 输出进程级致命错误,字段、URL 用户信息、查询 Secret 和错误对象均经过脱敏,且不在请求热路径逐条写日志。 CI 另有独立 Deployment job,在占位凭据下渲染 Compose,并使用 `kubectl kustomize` -渲染 Kubernetes base;它不启动容器、不访问真实存储或密钥。 +渲染 Kubernetes base 和单副本 development mTLS Overlay;它不启动容器、不访问真实存储或密钥。 +development Overlay 已挂载独立 Controller/Gateway/Checker TLS Secret、启用 Checker 和最小 +网络策略,但固定 `gateway-a`、`checker-a` 身份且 HPA 锁为单副本,不能代替生产证书轮换。 ## Task 14: Documentation, Examples, and Diagrams @@ -386,7 +388,7 @@ CI 另有独立 Deployment job,在占位凭据下渲染 Compose,并使用 `k - [x] Provide at least 30 Mermaid architecture, flow, sequence, state, and failure diagrams. - [x] Generate `proxy-pool-docs-v1.0.zip` from versioned documentation assets. -`scripts/package-docs.ps1` 会将 README、`docs/`、图表、OpenAPI 与 Proto 契约复制至临时目录, +`scripts/package-docs.ps1` 会将 README、`docs/`、图表、OpenAPI、Proto 契约与部署手册复制至临时目录, 生成包含 Git revision、文件大小和 SHA-256 的 `manifest.json`,再以临时 ZIP 原子替换目标。 默认输出为被 Git 忽略的 `dist/proxy-pool-docs-v1.0.zip`;Go 回归测试实际执行脚本并校验归档内容。 diff --git a/docs/package_test.go b/docs/package_test.go index f08610d..36be79e 100644 --- a/docs/package_test.go +++ b/docs/package_test.go @@ -76,6 +76,8 @@ func TestPackageDocsCreatesTraceableArchive(t *testing.T) { } for _, required := range []string{ "README.md", + "deploy/README.md", + "deploy/kubernetes/overlays/development-mtls/README.md", "docs/design/architecture.md", "docs/operations/runbook.md", "diagrams/README.md", diff --git a/docs/requirements/completion-audit.md b/docs/requirements/completion-audit.md index 0bb87fb..7c3e0a1 100644 --- a/docs/requirements/completion-audit.md +++ b/docs/requirements/completion-audit.md @@ -103,8 +103,10 @@ Windows 环境为 `CGO_ENABLED=0` 且没有 C 编译器,`go test -race` 在本 CI 已配置 Linux race job。PostgreSQL 18 和 Redis 8.2 的隔离 Adapter fixture 已经运行;本地 Compose 已声明 Controller、两个独立 Gateway 身份和一个 Checker 的 mTLS 运行拓扑,并由配置、证书、命令装配和部署契约测试覆盖。Kubernetes base 刻意不直接 -启用 Checker,因为生产环境必须通过 Overlay 为每个弹性 Worker 注入唯一身份与证书; -该 Overlay 和容器端到端启动仍待具备镜像网络的环境验证。 +启用 Checker;`development-mtls` Overlay 已为单副本 Controller、Gateway 和 Checker +提供固定开发身份、mTLS Secret 挂载、最小出站 NetworkPolicy 与渲染/配置契约验证。 +生产环境仍必须通过独立 Overlay 为每个弹性 Worker 注入唯一、可轮换的身份与证书,且 +容器端到端启动仍待具备镜像网络的环境验证。 ## 3. 后续实现范围 @@ -116,8 +118,9 @@ mTLS 运行拓扑,并由配置、证书、命令装配和部署契约测试覆 与 p99 告警;仍需代表性环境的流量压测和容量报告。 3. Provider 长期凭据回收与累计额度的生产运营策略仍需结合实际供应商合同、失效时间和 密钥平台完成演练。 -4. Kubernetes 需要按工作负载身份系统提供 mTLS Overlay、证书轮换、唯一 Worker ID 和 - Checker 出站 NetworkPolicy;base 清单不假定或伪造这些生产环境能力。 +4. Kubernetes 的开发 mTLS Overlay 已覆盖单副本固定身份和 Checker 出站 + NetworkPolicy;生产环境仍需要按工作负载身份系统提供证书轮换、每副本唯一 Worker ID + 与可弹性扩缩容的身份注入,base 清单不假定或伪造这些能力。 5. Redis/PostgreSQL 故障转移、控制面断线恢复、Snapshot 回滚、Drain 回收和 Extract 库存不足的完整故障演练。 6. 10k 稳态、100k 峰值、CONNECT 活跃连接和建连速率的代表性集群负载测试。 diff --git a/docs/requirements/traceability.md b/docs/requirements/traceability.md index ac3592d..0cac854 100644 --- a/docs/requirements/traceability.md +++ b/docs/requirements/traceability.md @@ -9,7 +9,7 @@ |---|---|---|---| | ARCH-001 | 数据面 Worker 与控制面 Controller 分离 | 1-70 | 包、协议和部署拓扑已分离;Controller 已运行 Worker Register/Watch/ACK/Runtime/Outcome 与 Checker Observation gRPC,并发布 Proxy/Gateway Routing/按引用去重凭据完整快照;Checker 任务流已具备有界领取、租约栅栏和任务期凭据契约。Gateway 已将快照编译为同版本动态 View,并由独立进程维护控制面会话。`proxy-checker` 与 Redis BASIC/EGRESS/TARGET 共享任务运行态、`proxy-loadgen` 有界 HTTP/CONNECT/Extract 校验场景均已实现 | | ARCH-002 | 热路径只做认证、本地路由和网络转发 | 1-70, 380-430 | Gateway bootstrap 集成测试验证启动期控制面会话与快照就绪,HTTP 请求只走本地 Snapshot/Dispatch;Outcome 仅写入有界非阻塞本地队列,代表性性能剖析待完成 | -| ARCH-003 | Gateway、Distribution、Admin、Metrics 独立入口 | 8904-8958 | Controller 命令已装配 Distribution/Admin/Metrics 三个独立监听及联动停机;Gateway 命令已装配代理与 Metrics 监听。本地 Compose 已声明 Controller/Gateway/Checker mTLS 拓扑;Kubernetes 的工作负载身份 Overlay 仍待环境实现 | +| ARCH-003 | Gateway、Distribution、Admin、Metrics 独立入口 | 8904-8958 | Controller 命令已装配 Distribution/Admin/Metrics 三个独立监听及联动停机;Gateway 命令已装配代理与 Metrics 监听。本地 Compose 已声明 Controller/Gateway/Checker mTLS 拓扑;Kubernetes `development-mtls` Overlay 已通过渲染与配置契约验证单副本固定身份链路,生产工作负载身份仍待环境实现 | | ARCH-004 | Controller 集中 Provider 获取与切换 | 1403-1580 | Redis Leader、动态 Provider Supervisor 与 Bootstrap 生产装配已完成;静态配置与 Admin 管理态共同决定 Provider 启停,停用策略以 Upstream revision 栅栏驱动既有 ownership Drain;Routing 切换会向本进程 Worker 快照流广播完整刷新,多副本按定时刷新与权威 HMAC 指纹/revision 栅栏收敛并拒绝旧配置换主 | | ARCH-005 | 100k QPS 峰值使用多 Worker 集群 | 当前会话 | 未验证设计目标;待代表性集群负载报告 | diff --git a/scripts/package-docs.ps1 b/scripts/package-docs.ps1 index db4fd29..7f87a1e 100644 --- a/scripts/package-docs.ps1 +++ b/scripts/package-docs.ps1 @@ -31,7 +31,9 @@ $sources = @( "docs", "diagrams", "api/openapi", - "api/proto" + "api/proto", + "deploy/README.md", + "deploy/kubernetes/overlays/development-mtls/README.md" ) $stagingRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("proxy-pool-docs-" + [Guid]::NewGuid().ToString("N")) $temporaryArchive = "" diff --git a/task_plan.md b/task_plan.md index 79f2897..403e7f1 100644 --- a/task_plan.md +++ b/task_plan.md @@ -75,8 +75,8 @@ 与 PostgreSQL 18 的隔离 Adapter fixture 已运行。Docker Desktop 缺少镜像 HTTPS 代理配置,未能在本机完成 Compose 容器端到端启动。 - Controller、Gateway、Checker、Loadgen、Provider Fleet、WorkerControlPlane、业务指标和 - Redis 故障边界均有实现与定向测试。真实容器故障转移、Kubernetes 工作负载身份 Overlay - 与代表性集群压测仍属于后续验证范围。 -- `implementation-plan.md` 当前按 74 个验收项统计;验收勾选数不等同于生产就绪度。 + Redis 故障边界均有实现与定向测试。Kubernetes `development-mtls` Overlay 已静态验证 + 单副本固定身份链路;真实容器故障转移、生产工作负载身份与代表性集群压测仍属于后续验证范围。 +- `implementation-plan.md` 当前按 75 个验收项统计;验收勾选数不等同于生产就绪度。 Compose 本地 mTLS 控制面运行链已实现并通过配置、证书、单元与构建验证;受本机 Docker Desktop 镜像 HTTPS 代理缺失影响,容器端到端启动仍待具备镜像网络的环境复核。