Skip to content

feat: RFC-001 cloud-native optimization (M1+M2+M3+M4 + docs)#103

Merged
singchia merged 12 commits into
mainfrom
feat/rfc-001-m1
May 2, 2026
Merged

feat: RFC-001 cloud-native optimization (M1+M2+M3+M4 + docs)#103
singchia merged 12 commits into
mainfrom
feat/rfc-001-m1

Conversation

@singchia

@singchia singchia commented May 1, 2026

Copy link
Copy Markdown
Owner

Summary

启动 RFC-001 云原生部署优化专项 的第一个里程碑(M1)。本 PR 包含三块独立 commit:

  1. chore(repo): AGENTS.md 引入 gospec 项目规范作为 priority="0" 块,原有 openskills 索引保留。
  2. docs(rfc): 落档 docs/rfc/RFC-001-cloud-native-optimization.md,提出 M1-M4 四阶段计划。
  3. fix(operator): 修复 4 个 P0 数据正确性 bug。

M1 修复的 4 个 bug

# Bug 位置
1 TLS Cert/Key volume 挂的是 CA Secret,导致 mTLS Pod 取不到证书 pkg/operator/internal/controller/frontiercluster_deployment.go:114
2 getEBCAFromSecret 把 Secret 读取错误吞成 ("", nil),apiserver / RBAC 失败被掩盖成"CA 不存在" pkg/operator/internal/controller/frontiercluster_tls.go
3 Operator 自管 TLS Secret 命名缺连字符(<fc>edgebound-...<fc>-edgebound-... pkg/operator/api/v1alpha1/frontiercluster_fields.go
4 Frontlas frontier-plane 端口硬编码 40012,CRD 改不动 — 新增 ControlPlane.FrontierPlanePort + FRONTLAS_FRONTIERPLANE_PORT env,下沉到 frontlas config types.go / fields.go / deployment.go / pkg/frontlas/config/config.go

Test plan

  • go build ./...(root + operator 模块)
  • go vet ./...
  • 7 条新单元测试 PASS:
    • TestEBTLSOperatorSecretNames_HaveDashSeparator
    • TestFrontlasServicePort_FrontierPlanePort(4 子测试)
    • TestGetEBCAFromSecret_PropagatesReadError / _MissingCAKey / _Happy
  • 评审者本地执行 cd pkg/operator && go test ./... 全绿
  • mTLS e2e 场景验证证书目录内容正确(建议在 stage 集群跑一次 kubectl apply 验证)
  • CRD schema 变更(新增 frontierPlanePort 字段)对 v1alpha1 老用户向后兼容(optional 字段,不填走默认 40012)

兼容性说明

  • 修复 Clientbound #3 改了 Operator 自管 Secret 的名字。老集群升级 operator 后会创建新名 Secret,旧名 Secret 因仍有 OwnerReference 不会被立即 GC,但不再被引用。可在 kubectl get fc 删除时被清理;也可手动删除。建议在 CHANGELOG 提示。
  • CRD schema 仅添加 optional 字段,无破坏性变更。

关联

  • RFC: docs/rfc/RFC-001-cloud-native-optimization.md
  • 后续里程碑:M2(生产可用底座)/ M3(可观测性达标)/ M4(交付与发布规范)

🤖 Generated with Claude Code

singchia and others added 3 commits May 1, 2026 19:42
Add a priority="0" project_spec block referencing the gospec Go
backend SDLC standard (architecture, coding, API, observability,
delivery, ops red lines). Existing openskills index preserved at
priority="1". Also list gospec in the available skills table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four-milestone proposal scoping the Frontier / Frontlas cloud-native
hardening: M1 P0 bug fixes, M2 production baseline (CRD v1alpha2 +
graceful shutdown + non-root), M3 observability (/healthz /readyz
/metrics + Prometheus + slog), M4 delivery (Makefile single entry +
frontlas Helm + image signing + Conditions status).

Status: draft. Each milestone ships as its own PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. TLS cert/key volume mounted the CA secret instead of the cert/key
   secret, so mTLS pods received an empty certificate path.
   (frontiercluster_deployment.go:114)

2. getEBCAFromSecret silently swallowed read errors as ("", nil),
   masking apiserver / RBAC failures behind a misleading "CA not
   found" path. Now wraps with %w and propagates.
   (frontiercluster_tls.go)

3. Operator-managed TLS secret names lacked a separator
   ("<fc>edgebound-..." → "<fc>-edgebound-..."), producing names
   that clashed with project naming style.
   (frontiercluster_fields.go)

4. Frontlas frontier-plane port was hard-coded to 40012 with no
   way to override it via CRD. Add ControlPlane.FrontierPlanePort
   (default 40012) and wire it through a new FRONTLAS_FRONTIERPLANE_PORT
   env var into pkg/frontlas/config so the listen addr follows.
   (types.go, fields.go, deployment.go, frontlas/config/config.go)

Add 7 unit tests covering all four fixes (api/v1alpha1 +
internal/controller). go.mod toolchain version directive realigned
by `go mod tidy` to satisfy local Go 1.24; no dependency changes.

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment May 2, 2026 1:29am

singchia and others added 5 commits May 2, 2026 00:09
…wordSecret

Add a generic PodOverrides struct that lets users tune the most common
production-grade Pod-level fields without breaking v1alpha1 — Resources,
NodeSelector, Tolerations, TopologySpreadConstraints, Affinity,
PriorityClassName, ServiceAccountName, ImagePullSecrets, ImagePullPolicy,
Annotations, Labels, PodSecurityContext, ContainerSecurityContext,
TerminationGracePeriodSeconds, LivenessProbe, ReadinessProbe, Lifecycle.

Embed PodOverrides as Frontier.Pod and Frontlas.Pod. The legacy
NodeAffinity field stays for backwards compat but Pod.Affinity, when set,
fully replaces it.

Add Redis.PasswordSecret (corev1.SecretKeySelector). Takes precedence over
the existing Redis.Password plaintext when both are set; the plaintext
field is kept for backwards compat but marked deprecated. Resolves the
gospec security red line "密钥禁止进代码仓库 / 镜像 / 日志" — operator
no longer needs to materialize the password into spec env.

zz_generated.deepcopy.go and CRD schema regenerated by controller-gen.
v1alpha1 → v1alpha1: existing CRs keep working (all new fields optional).
Version bump to v1beta1 deferred to M4 to bundle with Status conditions.

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire PodOverrides through container_builder + podtemplatespec_builder so
the Reconciler can render every covered field. The builders gain typed
setters for Resources, LivenessProbe, Lifecycle, SecurityContext
(container), TopologySpreadConstraints, PriorityClassName,
ServiceAccountName, ImagePullSecrets, PodSecurityContext, full Affinity,
plus annotation and label merge helpers.

Add internal/controller/podoverrides.go: per-component default tables
(frontierDefaults / frontlasDefaults) plus pickXxx helpers that resolve
override-or-default for each field. The defaults encode several gospec
red lines and operational best practices:

- ImagePullPolicy: IfNotPresent (was PullAlways)
- Pod / Container SecurityContext: runAsNonRoot=true, RunAsUser=65532,
  drop ALL capabilities, no privilege escalation, RuntimeDefault seccomp
- TerminationGracePeriodSeconds: 60 for frontier (long-lived edge
  connections), 30 for frontlas
- Lifecycle.PreStop: sleep 10, gives kube-proxy time to remove the pod
  from Service Endpoints before SIGTERM
- LivenessProbe and ReadinessProbe: TCP socket on listener ports for
  frontier (HTTP /readyz endpoint comes in M3); Frontlas keeps its
  /cluster/v1/health readiness and gains a TCP liveness
- PodAntiAffinity: PreferredDuringScheduling (was Required, which made
  small clusters fail to schedule when replicas exceeded node count)

Redis password env switches to corev1.EnvVarSource SecretKeyRef when
Redis.PasswordSecret is set; the legacy plaintext path still works when
only Redis.Password is provided.

Frontier deployment also gains FRONTIER_DRAIN_SECONDS env, computed as
terminationGracePeriodSeconds - 10, to be consumed by the binary's
shutdown handler (next commit).

12 unit tests cover the picker logic, default safety properties (nonRoot,
drop ALL caps, preferred not required affinity), and Redis env routing.

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When SIGTERM arrives, sleep for FRONTIER_DRAIN_SECONDS (default 30s,
operator sets it dynamically based on terminationGracePeriodSeconds)
before invoking frontier.Close().

This is the minimum-useful step toward graceful shutdown for long-lived
edge connections: it gives kube-proxy time to fully propagate the
endpoint removal that the preStop sleep started, and lets in-flight
edge sessions finish naturally instead of being torn down the instant
the signal hits.

Full deregister-from-Frontlas + close-listeners-but-keep-existing flow
is deferred to M3, where it will compose with the upcoming /readyz
endpoint (readiness flips to 503 -> kube removes endpoint -> drain).

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a system user (UID 65532) to both Alpine images and switch USER to
it. UID 65532 matches the operator's default PodSecurityContext.RunAsUser,
so runAsNonRoot=true now passes for stock images without users having to
override SecurityContext.

Resolves the gospec security red line "容器以非 root 用户运行".

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two amendments to RFC-001 captured in the changelog table:

- M2 stays on v1alpha1 with optional fields rather than introducing
  v1alpha2. Multi-version CRDs require a conversion strategy; None
  needs schema parity (defeating the new fields), Webhook adds an
  in-cluster service + cert lifecycle to M2 scope. Version bump moved
  to M4 alongside Status conditions, going straight to v1beta1.

- M2 through M4 ship on a single feat/rfc-001-m1 branch instead of
  one PR per milestone, per user direction after M1 verification.

Refs: docs/rfc/RFC-001-cloud-native-optimization.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
singchia and others added 4 commits May 2, 2026 09:25
…ns schema

CRD-level changes that improve day-to-day kubectl experience and lay
the groundwork for modern Kubernetes-style status reporting (M4.3).

- shortNames: fc, fcs (kubectl get fc just works)
- categories: frontier (kubectl get all -l frontier picks it up)
- printcolumns: Phase, FrontierReady, FrontlasReady, Age, Message
  (Message is priority=1 so it only shows in -o wide)
- Status now exposes:
    * conditions []metav1.Condition (Available/Progressing/Degraded)
    * observedGeneration
    * frontierReadyReplicas / frontlasReadyReplicas
- Phase + Message kept for backwards compat and the printcolumn,
  but tagged Deprecated in favor of Conditions

zz_generated.deepcopy.go and CRD YAML regenerated by controller-gen.

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M4.3)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…icas + Events

Reconciler now writes a full modern K8s status alongside the legacy Phase:

- Available / Progressing / Degraded Conditions, set in lockstep with Phase
  transitions. Status changes refresh LastTransitionTime; no-op writes
  preserve it (idiomatic K8s condition behavior).
- ObservedGeneration written on every status update so consumers can detect
  stale status.
- FrontierReadyReplicas / FrontlasReadyReplicas pulled from the underlying
  Deployment.Status.ReadyReplicas. ensureDeployment now returns a
  deploymentReadiness struct instead of a bare bool so the controller can
  feed both numbers into status regardless of pending/failed/running path.

EventRecorder ("frontier-operator") emits Kubernetes Events at meaningful
state transitions:

- ServiceEnsureFailed (warning) — Service reconcile error
- TLSEnsureFailed (warning) — TLS Secret reconcile error
- DeploymentEnsureFailed (warning) — Deployment apply error
- Available (normal) — fired once when the cluster first becomes Running,
  not on every successful reconcile

RBAC: + events:create;patch (the standard EventRecorder verbs).

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M4.1, M4.2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves the gospec red line "all external services must expose
/healthz, /readyz, /metrics" for both frontier and frontlas (M3.1, M3.2).

New shared package pkg/observability:

- Server with three endpoints on a single configurable port.
  * /healthz — liveness; always 200 if process responds
  * /readyz  — readiness; calls a pluggable ReadinessFn, returns 503 with
    a one-line reason when not ready
  * /metrics — Prometheus default registry (Go runtime + process collectors
    auto-included; business code can add custom collectors via promauto)
- Atomic readiness function pointer so business code can flip readiness
  state at any point in the lifecycle (start, register-with-Frontlas,
  shutdown).
- Graceful Shutdown(timeout) wired into the parent component's Close().

Wiring:

- Frontier: default port 0.0.0.0:9091. Configurable via the new
  spec.observability block in frontier.yaml. Default ReadinessFn is "always
  ready"; future iterations can plug in Frontlas registration state.
- Frontlas: default port 0.0.0.0:9092. ReadinessFn pings Redis with a 2s
  timeout — if Redis is unreachable, /readyz returns 503 (kube removes
  the pod from Endpoints, frontier nodes route around it).
- Frontlas Dao gains a Ping(ctx) helper that wraps RDS.Ping.

Existing /cluster/v1/health gRPC-gateway endpoint stays for backwards
compat with the operator's frontlas readinessProbe.

Adds github.com/prometheus/client_golang v1.23.2.

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M3)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the 68-line stub at /docs/operator with a full reference covering
the M1+M2+M3+M4 deliverables.

Twelve sections:

  1.  Overview — two-tier architecture, what one CR gets you
  2.  Installation — install.yaml + RBAC walkthrough
  3.  Quick start — minimal CR + verification commands
  4.  CRD field reference — three tables (Frontier, Frontlas, PodOverrides)
      documenting every spec field, type, default, and intent
  5.  Common scenarios — mTLS, production resources/scheduling, private
      registry, Prometheus + cert-manager annotations, SecurityContext
      override for legacy images
  6.  Status & Conditions — Available/Progressing/Degraded semantics
  7.  Observability endpoints — /healthz, /readyz, /metrics ports for
      both frontier and frontlas
  8.  Common kubectl operations — short name fc, patches, JSON path tricks
  9.  Operator behavior — reconcile order, owner references, graceful
      shutdown env, event types
  10. Troubleshooting — six failure modes mapped to root cause + remediation
  11. Known limitations — no kubectl scale, alpha API, Helm gaps,
      no validation webhook
  12. Roadmap — pointer to RFC-001 in the repo

Verified with `npx next build` — page generates statically alongside the
existing /docs/* routes.

Refs: docs/rfc/RFC-001-cloud-native-optimization.md (user-facing
deliverable for the cloud-native optimization initiative)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@singchia singchia changed the title fix(operator): RFC-001 M1 — four P0 correctness bugs in CRD/Operator feat: RFC-001 cloud-native optimization (M1+M2+M3+M4 + docs) May 2, 2026
@singchia
singchia merged commit caefd67 into main May 2, 2026
3 checks passed
singchia added a commit that referenced this pull request May 2, 2026
First release after RFC-001 cloud-native optimization (PRs #103, #104, #105):

- M1 — operator P0 bug fixes (TLS volume, Secret naming, frontier-plane port)
- M2 — production baseline (PodOverrides, nonRoot images, Redis SecretRef,
       graceful shutdown, default probes / preStop / grace)
- M3 — observability HTTP server (/healthz, /readyz, /metrics) on
       frontier:9091 and frontlas:9092
- M4 — Status Conditions, EventRecorder, CRD shortName fc, printcolumns
- M5 — full-stack Helm chart (frontier + frontlas + bundled bitnami/redis),
       published at https://singchia.github.io/frontier

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@singchia
singchia deleted the feat/rfc-001-m1 branch May 2, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant