Skip to content

Latest commit

 

History

History
67 lines (53 loc) · 5.66 KB

File metadata and controls

67 lines (53 loc) · 5.66 KB

sei-k8s-controller

Kubernetes operator for managing Sei blockchain nodes. Single binary, two controllers: SeiNodeDeployment (fleet orchestration, genesis ceremonies, deployments) and SeiNode (individual node lifecycle).

Architecture

  • API group: sei.io/v1alpha1
  • CRD types: SeiNodeDeployment, SeiNode (defined in api/v1alpha1/)
  • Controllers: internal/controller/nodedeployment/, internal/controller/node/
  • Entry point: cmd/main.go — thin binary that creates a manager.Manager and registers both controllers
  • Framework: controller-runtime v0.23.1 / kubebuilder v4.12.0

Subagents

Always use the available subagents for relevant work:

  • kubernetes-specialist: Use for all Kubernetes design, deployment, troubleshooting, and operational decisions. Consult before making changes to CRDs, RBAC, kustomize configs, StatefulSet specs, or any cluster-facing resource definitions.
  • platform-engineer: Use for architectural decisions about platform composability, developer experience, CI/CD workflows, and infrastructure abstraction patterns.

Code Standards

Go

  • Follow idiomatic Go. No unnecessary abstractions — three similar lines are better than a premature helper.
  • Use controller-runtime patterns: every controller exports a reconciler struct with SetupWithManager(mgr).
  • All code must pass golangci-lint (config in .golangci.yml). Fix lint issues, don't suppress them.
  • Imports must be grouped: stdlib, external, then github.com/sei-protocol/sei-k8s-controller (enforced by goimports).
  • No panic in controller code. Return errors and let the reconciler retry.
  • Keep reconcile loops idempotent — every reconcile should converge toward desired state regardless of current state.

Testing

  • Tests use testing + gomega for assertions.
  • Test fixtures for platform config live in internal/platform/platformtest/.
  • Run tests with make test before submitting changes.

CRD Changes

  • Edit types in api/v1alpha1/ (e.g., seinode_types.go, seinodedeployment_types.go, validator_types.go).
  • After any type change, run make manifests generate to regenerate CRD YAML and DeepCopy methods.
  • Never hand-edit files in manifests/ or zz_generated.deepcopy.go.

RBAC

  • RBAC is generated from // +kubebuilder:rbac: markers on controller files.
  • After changing markers, run make manifests to regenerate manifests/role.yaml.

Build & Deploy

make build                    # Build the manager binary
make test                     # Run unit tests
make lint                     # Run golangci-lint
make manifests generate       # Regenerate CRDs, RBAC, DeepCopy after type changes
make docker-build IMG=<image> # Build container image
make docker-push IMG=<image>  # Push container image

Key Patterns

  • SeiNodeDeployment creates and owns SeiNode resources. Groups orchestrate genesis ceremonies, manage deployments, and coordinate networking/monitoring.
  • SeiNode creates StatefulSets (replicas=1), headless Services, and PVCs via server-side apply (fieldOwner: seinode-controller).
  • Plan-driven reconciliation — Both controllers use ordered task plans (stored in .status.plan) to drive lifecycle. Plans are built by internal/planner/ (ResolvePlan for nodes, ForGroup for deployments), executed by planner.Executor, with individual tasks in internal/task/. The reconcile loop is: ResolvePlan → persist plan → ExecutePlan. See internal/planner/doc.go for the full plan lifecycle.
  • Init plans transition nodes from Pending → Running. They include infrastructure tasks (ensure-data-pvc, apply-statefulset, apply-service) followed by sidecar tasks (configure-genesis, config-apply, etc.).
  • NodeUpdate plans roll out image changes on Running nodes. Built when spec.image != status.currentImage. Tasks: apply-statefulset, apply-service, observe-image (polls StatefulSet rollout, stamps currentImage), mark-ready (sidecar re-init). The planner sets NodeUpdateInProgress condition on creation and clears it on completion/failure. When no drift is detected, no plan is built — the node sits in steady state.
  • Atomic plan creation — New plans are persisted before any tasks execute. The reconciler flushes the plan, then requeues. Execution starts on the next reconcile. This guarantees external observers see the plan before side effects occur.
  • Condition ownership — The planner owns all condition management on the owning resource. It sets conditions when creating plans (e.g., NodeUpdateInProgress=True) and when observing terminal plans (e.g., NodeUpdateInProgress=False). The executor does not set conditions — it only mutates plan/task state and phase transitions.
  • Single-patch model — All status mutations (plan state, conditions, phase, currentImage) accumulate in-memory during a reconcile and are flushed in a single Status().Patch() at the end. Tasks mutate owned resources (StatefulSets, Services, PVCs); the executor mutates plan state in-memory; the reconciler flushes once.
  • Resource generators live in internal/noderesource/ — pure functions that produce StatefulSets, Services, and PVCs from a SeiNode spec. Used by both the controller and plan tasks.
  • Platform config is fully environment-driven — all fields in platform.Config must be set via env vars (no defaults). See internal/platform/platform.go for the full list.
  • Genesis resolution is handled by the sidecar autonomously: embedded sei-config for well-known chains, S3 fallback at {SEI_GENESIS_BUCKET}/{chainID}/genesis.json for custom chains.
  • Config keys in seid's config.toml use hyphens (e.g., persistent-peers, trust-height), not underscores.