Skip to content

Commit aff7df4

Browse files
authored
Merge pull request #9 from numtide/plan-child-resource-crds
Add planning doc for defining CRDs
2 parents 7d240e9 + 8cdb2c2 commit aff7df4

File tree

1 file changed

+393
-0
lines changed

1 file changed

+393
-0
lines changed
Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
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

Comments
 (0)