|
| 1 | +--- |
| 2 | +title: Create CRDs for each resource to be deployed |
| 3 | +state: draft |
| 4 | +tags: |
| 5 | +- multigateway |
| 6 | +- multipooler |
| 7 | +- multiorch |
| 8 | +- toposerver |
| 9 | +- crd |
| 10 | +- kubebuilder |
| 11 | +--- |
| 12 | + |
| 13 | +# Summary |
| 14 | + |
| 15 | +Create individual CRDs for each Multigres component (`MultiGateway`, `MultiOrch`, `MultiPooler`, `Etcd`) and a parent `MultigresCluster` CRD. Each component CRD can be deployed independently or as part of a `MultigresCluster`. This provides flexibility for users to manage components individually while still supporting convenient cluster-level management. |
| 16 | + |
| 17 | +This document focuses on defining the API types and CRD structure. Actual controller implementation and `MultigresCluster` parent-child creation logic are covered in separate documents. |
| 18 | + |
| 19 | +# Motivation |
| 20 | + |
| 21 | +Creating individual CRDs for each component provides several benefits: |
| 22 | + |
| 23 | +1. **Flexibility**: Users can deploy only the components they need (e.g., just `MultiPooler` and `Etcd` without `MultiGateway`) |
| 24 | +2. **Resource Sharing**: Multiple `MultigresCluster` instances can share common infrastructure components like `Etcd` |
| 25 | +3. **Independent Lifecycle**: Components can be scaled, upgraded, or configured independently without affecting others |
| 26 | +4. **Composability**: Advanced users can compose custom Multigres deployments by mixing and matching components |
| 27 | +5. **Simplified Testing**: Each component can be tested in isolation during development |
| 28 | + |
| 29 | +The `MultigresCluster` CRD provides a convenient higher-level abstraction for users who want to deploy a full Multigres stack with sensible defaults, while component CRDs give power users fine-grained control. |
| 30 | + |
| 31 | +## Goals |
| 32 | +- Define API types for all Multigres component CRDs |
| 33 | +- Generate CRD manifests using kubebuilder |
| 34 | +- Establish consistent field naming and structure across all CRDs |
| 35 | +- Document resource relationships and ownership patterns |
| 36 | +- Add validation markers for field constraints |
| 37 | +- Create sample CR manifests for testing |
| 38 | + |
| 39 | +## Non-Goals |
| 40 | +- Controller implementation (covered in separate documents) |
| 41 | +- Parent-child resource creation logic for `MultigresCluster` (separate document) |
| 42 | +- Admission webhooks (using CRD validation only for now) |
| 43 | +- Resource builder functions (covered in controller implementation docs) |
| 44 | + |
| 45 | +# Proposal |
| 46 | + |
| 47 | +Create the following CRD types in the `multigres.com/v1alpha1` API group: |
| 48 | + |
| 49 | +1. **MultigresCluster** - Top-level resource representing a complete Multigres deployment |
| 50 | +2. **MultiGateway** - Deployment configuration for the gateway component |
| 51 | +3. **MultiOrch** - Deployment configuration for the orchestration component |
| 52 | +4. **MultiPooler** - StatefulSet configuration for the connection pooler component |
| 53 | +5. **Etcd** - StatefulSet configuration for the etcd cluster |
| 54 | + |
| 55 | +## Resource Relationships |
| 56 | + |
| 57 | +``` |
| 58 | +MultigresCluster (optional - creates others) |
| 59 | + ├── MultiGateway (can exist independently) |
| 60 | + ├── MultiOrch (can exist independently) |
| 61 | + ├── MultiPooler (can exist independently) |
| 62 | + └── Etcd (can exist independently) |
| 63 | +``` |
| 64 | + |
| 65 | +Each component CRD can be created and managed independently, or via a `MultigresCluster` parent resource (parent-child relationship to be defined in separate document). |
| 66 | + |
| 67 | +## Common Patterns |
| 68 | + |
| 69 | +All component CRDs should follow these conventions: |
| 70 | + |
| 71 | +- **Image Configuration**: `image` field with repository, tag, and pull policy |
| 72 | +- **Replica Configuration**: `replicas` field (int32, with kubebuilder validation) |
| 73 | +- **Resource Limits**: Standard Kubernetes `resources` field (ResourceRequirements) |
| 74 | +- **HPA Support**: Optional `hpa` field for Horizontal Pod Autoscaler configuration |
| 75 | +- **Scheduling Configuration**: Standard Kubernetes scheduling fields: |
| 76 | + - `affinity`: Pod affinity/anti-affinity and node affinity rules |
| 77 | + - `tolerations`: Tolerations for node taints |
| 78 | + - `nodeSelector`: Simple node selection by labels |
| 79 | + - `topologySpreadConstraints`: Control pod distribution across topology domains |
| 80 | +- **Labels and Annotations**: Custom labels/annotations that merge with operator-generated ones |
| 81 | + |
| 82 | +## Field Structure |
| 83 | + |
| 84 | +### MultigresCluster Spec |
| 85 | + |
| 86 | +```go |
| 87 | +type MultigresClusterSpec struct { |
| 88 | + // Gateway configuration (optional) |
| 89 | + Gateway *MultiGatewaySpec `json:"gateway,omitempty"` |
| 90 | + |
| 91 | + // Orchestration configuration (optional) |
| 92 | + Orch *MultiOrchSpec `json:"orch,omitempty"` |
| 93 | + |
| 94 | + // Pooler configuration (optional) |
| 95 | + Pooler *MultiPoolerSpec `json:"pooler,omitempty"` |
| 96 | + |
| 97 | + // Etcd configuration (optional) |
| 98 | + Etcd *EtcdSpec `json:"etcd,omitempty"` |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +### Component Specs |
| 103 | + |
| 104 | +Each component spec should include: |
| 105 | + |
| 106 | +- **Image**: Container image configuration |
| 107 | +- **Replicas**: Number of replicas (with validation: minimum 1, maximum configurable) |
| 108 | +- **Resources**: CPU/memory requests and limits |
| 109 | +- **HPA**: Optional horizontal pod autoscaler configuration |
| 110 | +- **Affinity**: Optional pod affinity, anti-affinity, and node affinity rules |
| 111 | +- **Tolerations**: Optional tolerations for node taints |
| 112 | +- **NodeSelector**: Optional node selector labels |
| 113 | +- **TopologySpreadConstraints**: Optional constraints for pod distribution across topology domains |
| 114 | +- **Storage**: (For StatefulSet-based components like Etcd, MultiPooler) |
| 115 | +- **Component-specific fields**: Any unique configuration needs |
| 116 | + |
| 117 | +**Example Affinity Use Cases**: |
| 118 | +- **Anti-affinity for HA**: Spread etcd replicas across different nodes/zones to survive node failures |
| 119 | +- **Node isolation**: Dedicate specific nodes for database workloads using node affinity |
| 120 | +- **Zone spreading**: Distribute replicas across availability zones using topology spread constraints |
| 121 | + |
| 122 | +### Status Structure |
| 123 | + |
| 124 | +Each CRD should have a status subresource with: |
| 125 | + |
| 126 | +```go |
| 127 | +type ComponentStatus struct { |
| 128 | + // Ready indicates if component is healthy and available |
| 129 | + Ready bool `json:"ready"` |
| 130 | + |
| 131 | + // Replicas is the desired number of replicas |
| 132 | + Replicas int32 `json:"replicas"` |
| 133 | + |
| 134 | + // ReadyReplicas is the number of ready replicas |
| 135 | + ReadyReplicas int32 `json:"readyReplicas"` |
| 136 | + |
| 137 | + // Conditions represent the latest observations of the component state |
| 138 | + Conditions []metav1.Condition `json:"conditions,omitempty"` |
| 139 | + |
| 140 | + // ObservedGeneration tracks which spec generation was reconciled |
| 141 | + ObservedGeneration int64 `json:"observedGeneration,omitempty"` |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +# Design Details |
| 146 | + |
| 147 | +## Directory Structure |
| 148 | + |
| 149 | +``` |
| 150 | +api/v1alpha1/ |
| 151 | +├── groupversion_info.go # API group registration |
| 152 | +├── multigrescluster_types.go # MultigresCluster CRD |
| 153 | +├── multigateway_types.go # MultiGateway CRD |
| 154 | +├── multiorch_types.go # MultiOrch CRD |
| 155 | +├── multipooler_types.go # MultiPooler CRD |
| 156 | +├── etcd_types.go # Etcd CRD |
| 157 | +└── zz_generated.deepcopy.go # Auto-generated (by kubebuilder) |
| 158 | +``` |
| 159 | + |
| 160 | +## Kubebuilder Markers |
| 161 | + |
| 162 | +Each CRD type file should include appropriate markers: |
| 163 | + |
| 164 | +**For CRD generation**: |
| 165 | +```go |
| 166 | +// +kubebuilder:object:root=true |
| 167 | +// +kubebuilder:subresource:status |
| 168 | +// +kubebuilder:resource:scope=Namespaced |
| 169 | +// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready` |
| 170 | +// +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=`.status.replicas` |
| 171 | +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` |
| 172 | +``` |
| 173 | + |
| 174 | +**For validation**: |
| 175 | +```go |
| 176 | +// +kubebuilder:validation:Minimum=1 |
| 177 | +// +kubebuilder:validation:Maximum=100 |
| 178 | +// +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer |
| 179 | +``` |
| 180 | + |
| 181 | +**For defaults**: |
| 182 | +```go |
| 183 | +// +kubebuilder:default=1 |
| 184 | +// +kubebuilder:default="postgres:16" |
| 185 | +``` |
| 186 | + |
| 187 | +## Label Conventions |
| 188 | + |
| 189 | +All CRDs and their managed resources should use consistent labels: |
| 190 | + |
| 191 | +```yaml |
| 192 | +labels: |
| 193 | + app.kubernetes.io/name: multigres |
| 194 | + app.kubernetes.io/instance: <cr-name> |
| 195 | + app.kubernetes.io/component: <gateway|orch|pooler|etcd> |
| 196 | + app.kubernetes.io/managed-by: multigres-operator |
| 197 | + multigres.com/cluster: <multigrescluster-name> # If created by MultigresCluster |
| 198 | +``` |
| 199 | +
|
| 200 | +## Validation Strategy |
| 201 | +
|
| 202 | +Use CRD validation markers only (no admission webhooks at this stage): |
| 203 | +
|
| 204 | +- OpenAPI v3 schema constraints in kubebuilder markers |
| 205 | +- Numeric ranges (min/max replicas, storage size) |
| 206 | +- Enum values for service types, storage classes |
| 207 | +- Pattern validation for image names |
| 208 | +- Required vs optional field marking |
| 209 | +
|
| 210 | +## Implementation Tasks |
| 211 | +
|
| 212 | +### Project Initialization (One Time) |
| 213 | +
|
| 214 | +```bash |
| 215 | +# Initialize kubebuilder project |
| 216 | +kubebuilder init --domain multigres.com --repo github.com/numtide/multigres-operator |
| 217 | +``` |
| 218 | + |
| 219 | +Generates project structure, `Makefile` with targets (`make manifests`, `make install`, etc.), and dependencies. |
| 220 | + |
| 221 | +### Create CRDs (Once Per Type) |
| 222 | + |
| 223 | +```bash |
| 224 | +kubebuilder create api --group multigres --version v1alpha1 --kind MultigresCluster --resource --controller |
| 225 | +kubebuilder create api --group multigres --version v1alpha1 --kind MultiGateway --resource --controller |
| 226 | +kubebuilder create api --group multigres --version v1alpha1 --kind MultiOrch --resource --controller |
| 227 | +kubebuilder create api --group multigres --version v1alpha1 --kind MultiPooler --resource --controller |
| 228 | +kubebuilder create api --group multigres --version v1alpha1 --kind Etcd --resource --controller |
| 229 | +``` |
| 230 | + |
| 231 | +Generates `api/v1alpha1/{kind}_types.go` and `internal/controller/{kind}_controller.go` scaffolds. |
| 232 | + |
| 233 | +### Define API Types |
| 234 | + |
| 235 | +Edit `api/v1alpha1/{kind}_types.go` to add fields with validation markers: |
| 236 | + |
| 237 | +```go |
| 238 | +type MultiGatewaySpec struct { |
| 239 | + // +kubebuilder:validation:Minimum=1 |
| 240 | + // +kubebuilder:default=1 |
| 241 | + Replicas int32 `json:"replicas,omitempty"` |
| 242 | + |
| 243 | + Resources corev1.ResourceRequirements `json:"resources,omitempty"` |
| 244 | + Affinity *corev1.Affinity `json:"affinity,omitempty"` |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +Add type-level markers for CRD configuration: |
| 249 | + |
| 250 | +```go |
| 251 | +// +kubebuilder:object:root=true |
| 252 | +// +kubebuilder:subresource:status |
| 253 | +// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready` |
| 254 | +type MultiGateway struct { ... } |
| 255 | +``` |
| 256 | + |
| 257 | +### Generate and Install CRDs |
| 258 | + |
| 259 | +```bash |
| 260 | +# Generate CRD YAML from Go types (runs controller-gen) |
| 261 | +make manifests |
| 262 | + |
| 263 | +# Apply CRDs to cluster (runs kubectl apply -f config/crd/bases/) |
| 264 | +make install |
| 265 | +``` |
| 266 | + |
| 267 | +### Create Sample CRs |
| 268 | + |
| 269 | +Create `config/samples/multigres_v1alpha1_{kind}.yaml`: |
| 270 | + |
| 271 | +```yaml |
| 272 | +apiVersion: multigres.com/v1alpha1 |
| 273 | +kind: MultiGateway |
| 274 | +metadata: |
| 275 | + name: multigateway-sample |
| 276 | +spec: |
| 277 | + replicas: 3 |
| 278 | +``` |
| 279 | +
|
| 280 | +### Test and Verify |
| 281 | +
|
| 282 | +```bash |
| 283 | +kubectl get crds | grep multigres.com |
| 284 | +kubectl apply -f config/samples/ |
| 285 | +kubectl explain multigateway.spec |
| 286 | +``` |
| 287 | + |
| 288 | +### When to Use Each Command |
| 289 | + |
| 290 | +| Scenario | Commands | |
| 291 | +|----------|----------| |
| 292 | +| First time setup | `kubebuilder init` | |
| 293 | +| Add new CRD type | `kubebuilder create api` | |
| 294 | +| Modify existing CRD fields | Edit `api/v1alpha1/*.go` → `make manifests` → `make install` | |
| 295 | +| Regular development | Edit Go types → `make manifests` → `make install` → test | |
| 296 | + |
| 297 | +## Test Plan |
| 298 | + |
| 299 | +1. **CRD Generation**: Verify `make manifests` generates valid CRDs in `config/crd/bases/` |
| 300 | +2. **Installation**: Test `kubectl apply -f config/crd/bases/` succeeds |
| 301 | +3. **Validation**: Test invalid CR specs are rejected by API server (e.g., negative replicas, invalid enum values) |
| 302 | +4. **kubectl Output**: Verify printer columns display correctly with sample CRs |
| 303 | +5. **Schema Documentation**: Ensure `kubectl explain` shows field documentation for all CRDs |
| 304 | +6. **Unit Tests**: Test that Go types have proper JSON tags and validation markers |
| 305 | + |
| 306 | +## Version Skew Strategy |
| 307 | + |
| 308 | +**Operator vs CRD Version**: |
| 309 | +- Operator version must match or be newer than CRD version |
| 310 | +- CRDs installed before operator deployment |
| 311 | +- Operator can handle older CR specs (within same API version) |
| 312 | + |
| 313 | +**Multiple Operators**: |
| 314 | +- Not supported - use single operator with leader election |
| 315 | +- Multiple replicas for HA use leader election (same version) |
| 316 | + |
| 317 | +**CRD Versioning**: |
| 318 | +- During alpha (v1alpha1), only one version supported at a time |
| 319 | +- Future versions (beta/GA) can coexist with conversion webhooks |
| 320 | + |
| 321 | +# Implementation History |
| 322 | + |
| 323 | +- 2025-10-08: Initial draft created |
| 324 | + |
| 325 | +# Drawbacks |
| 326 | + |
| 327 | +**API Complexity**: Having individual CRDs for each component adds more API surface area compared to a single monolithic CRD. |
| 328 | + |
| 329 | +**Learning Curve**: Users need to understand the relationship between `MultigresCluster` and component CRDs. |
| 330 | + |
| 331 | +**Coordination**: When components need to reference each other, users must manage those references manually if not using `MultigresCluster`. |
| 332 | + |
| 333 | +However, these drawbacks are outweighed by the flexibility and composability benefits for advanced use cases. |
| 334 | + |
| 335 | +# Alternatives |
| 336 | + |
| 337 | +## Alternative 1: Single Monolithic CRD |
| 338 | + |
| 339 | +Create only a `MultigresCluster` CRD with all component specs embedded. |
| 340 | + |
| 341 | +**Pros**: |
| 342 | +- Simpler API surface |
| 343 | +- Easier to understand for basic use cases |
| 344 | +- Single resource to manage |
| 345 | + |
| 346 | +**Cons**: |
| 347 | +- No component reuse between clusters |
| 348 | +- Cannot deploy components independently |
| 349 | +- All-or-nothing deployment model |
| 350 | +- Harder to scale/upgrade individual components |
| 351 | + |
| 352 | +**Rejected because**: Lacks flexibility for advanced deployment patterns. |
| 353 | + |
| 354 | +## Alternative 2: Component CRDs Only (No Parent) |
| 355 | + |
| 356 | +Create only individual component CRDs without a `MultigresCluster` parent. |
| 357 | + |
| 358 | +**Pros**: |
| 359 | +- Maximum flexibility |
| 360 | +- No abstraction layers |
| 361 | + |
| 362 | +**Cons**: |
| 363 | +- Verbose for simple deployments |
| 364 | +- Users must manually create all components |
| 365 | +- No convenience wrapper for common patterns |
| 366 | + |
| 367 | +**Rejected because**: Makes basic deployments unnecessarily complex. The parent CRD provides important convenience. |
| 368 | + |
| 369 | +## Alternative 3: Helm Charts Instead of CRDs |
| 370 | + |
| 371 | +Use Helm charts to deploy Multigres components without operator. |
| 372 | + |
| 373 | +**Pros**: |
| 374 | +- Familiar deployment model for users |
| 375 | +- No operator overhead |
| 376 | + |
| 377 | +**Cons**: |
| 378 | +- No automatic reconciliation |
| 379 | +- No custom status reporting |
| 380 | +- Limited to initial deployment |
| 381 | +- Cannot react to cluster changes |
| 382 | +- Helm templating syntax is difficult to maintain for complex logic |
| 383 | +- Hard to test and debug template expansions |
| 384 | +- Less type safety compared to Go structs |
| 385 | + |
| 386 | +**Rejected because**: Operator pattern provides better lifecycle management and observability. Additionally, maintaining complex Helm templates is significantly harder for operator developers compared to writing Go code with proper type checking and testing. |
| 387 | + |
| 388 | +# Infrastructure Needed |
| 389 | + |
| 390 | +- **Kubebuilder**: For scaffolding and CRD generation |
| 391 | +- **controller-gen**: For generating CRD manifests (included with kubebuilder) |
| 392 | +- **Go toolchain**: Go 1.24+ for building API types |
| 393 | +- **kubectl**: For testing CRD installation and validation |
0 commit comments