Skip to content

Commit 6461675

Browse files
chore(CLI): Add option for namespaced layout such as we have for multigroup
1 parent 15eb316 commit 6461675

File tree

31 files changed

+1722
-94
lines changed

31 files changed

+1722
-94
lines changed

docs/book/src/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
- [Step 2: Discovery Commands](./migration/discovery-commands.md)
5454
- [Step 3: Port Code](./migration/port-code.md)
5555
- [Single Group to Multi-Group](./migration/multi-group.md)
56+
- [Cluster-Scoped to Namespace-Scoped](./migration/namespace-scoped.md)
5657

5758
- [Alpha Commands](./reference/alpha_commands.md)
5859

@@ -92,6 +93,8 @@
9293
- [Monitoring with Pprof](./reference/pprof-tutorial.md)
9394

9495
- [Manager and CRDs Scope](./reference/scopes.md)
96+
- [Operator Scope](./reference/operator-scope.md)
97+
- [CRD Scope](./reference/crd-scope.md)
9598

9699
- [Sub-Module Layouts](./reference/submodule-layouts.md)
97100
- [Using an external Resource / API](./reference/using_an_external_resource.md)
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Migrating to Namespace-Scoped Operator
2+
3+
By default, Kubebuilder scaffolds cluster-scoped operators that watch and manage resources across all namespaces. You can convert an existing project to namespace-scoped deployment, limiting the operator to watch only specific namespace(s).
4+
5+
## When to Use Namespace-Scoped
6+
7+
**Use namespace-scoped when:**
8+
- Building tenant-specific operators in multi-tenant clusters
9+
- Security policies require least-privilege (no cluster-wide permissions)
10+
- Need multiple operator instances in different namespaces
11+
- Managing only namespace-scoped resources (Deployments, Services, ConfigMaps, etc.)
12+
13+
**Use cluster-scoped (default) when:**
14+
- Managing cluster-scoped resources (Nodes, ClusterRoles, Namespaces, etc.)
15+
- Single operator instance managing resources across all namespaces
16+
17+
## Migration Steps
18+
19+
### 1. Enable namespace-scoped mode
20+
21+
```bash
22+
kubebuilder edit --namespaced=true
23+
```
24+
25+
This command:
26+
- Sets `namespaced: true` in your PROJECT file
27+
- Scaffolds RBAC patch files to convert ClusterRole/ClusterRoleBinding to Role/RoleBinding
28+
- Updates `config/rbac/kustomization.yaml` to apply patches
29+
- Updates `config/manager/manager.yaml` to include `WATCH_NAMESPACE` environment variable
30+
31+
<aside class="warning">
32+
<h1>Manual Step Required</h1>
33+
34+
The `edit` command cannot automatically update `cmd/main.go`. You must manually add the namespace watching logic.
35+
</aside>
36+
37+
### 2. Update cmd/main.go (Required Manual Step)
38+
39+
The operator needs to read the `WATCH_NAMESPACE` environment variable to determine which namespace(s) to watch. Add the following changes to `cmd/main.go`:
40+
41+
**Add imports:**
42+
```go
43+
import (
44+
// ... existing imports ...
45+
"fmt"
46+
"strings"
47+
"sigs.k8s.io/controller-runtime/pkg/cache"
48+
)
49+
```
50+
51+
**Add helper function after `init()` and before `main()`:**
52+
```go
53+
// getWatchNamespace returns the namespace(s) the operator should watch.
54+
func getWatchNamespace() (string, error) {
55+
watchNamespaceEnvVar := "WATCH_NAMESPACE"
56+
ns, found := os.LookupEnv(watchNamespaceEnvVar)
57+
if !found {
58+
return "", fmt.Errorf("%s must be set", watchNamespaceEnvVar)
59+
}
60+
return ns, nil
61+
}
62+
```
63+
64+
**In `main()` function, add namespace retrieval before creating the manager:**
65+
```go
66+
func main() {
67+
// ... existing flag parsing and setup ...
68+
69+
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
70+
71+
// Get the namespace(s) to watch from WATCH_NAMESPACE environment variable
72+
watchNamespace, err := getWatchNamespace()
73+
if err != nil {
74+
setupLog.Error(err, "Unable to get WATCH_NAMESPACE")
75+
os.Exit(1)
76+
}
77+
78+
// ... continue with manager creation ...
79+
}
80+
```
81+
82+
**Update manager creation to configure namespace watching:**
83+
```go
84+
mgrOptions := ctrl.Options{
85+
Scheme: scheme,
86+
Metrics: metricsServerOptions,
87+
WebhookServer: webhookServer,
88+
HealthProbeBindAddress: probeAddr,
89+
LeaderElection: enableLeaderElection,
90+
}
91+
92+
// Configure cache based on WATCH_NAMESPACE
93+
if strings.Contains(watchNamespace, ",") {
94+
// Multi-namespace mode: watch multiple specific namespaces
95+
setupLog.Info("Manager configured for multi-namespace mode", "namespaces", watchNamespace)
96+
mgrOptions.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(watchNamespace, ","))
97+
} else {
98+
// Single namespace mode: watch only one specific namespace
99+
setupLog.Info("Manager configured for namespace-scoped mode", "namespace", watchNamespace)
100+
mgrOptions.Cache = cache.Options{
101+
DefaultNamespaces: map[string]cache.Config{
102+
watchNamespace: {},
103+
},
104+
}
105+
}
106+
107+
mgrOptions.LeaderElectionID = "your-leader-election-id"
108+
109+
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), mgrOptions)
110+
if err != nil {
111+
setupLog.Error(err, "Failed to start manager")
112+
os.Exit(1)
113+
}
114+
```
115+
116+
### 3. Verify the migration
117+
118+
```bash
119+
make manifests # Regenerate CRDs and RBAC
120+
make generate # Regenerate code
121+
make test # Run tests
122+
```
123+
124+
### 4. Deploy and test
125+
126+
```bash
127+
make deploy IMG=<your-image>
128+
129+
# Verify RBAC is namespace-scoped (not cluster-scoped)
130+
kubectl get role,rolebinding -n <operator-namespace>
131+
132+
# Test: Create a resource in the operator's namespace - should be reconciled
133+
kubectl apply -f config/samples/ -n <operator-namespace>
134+
135+
# Test: Create a resource in a different namespace - should NOT be reconciled
136+
kubectl apply -f config/samples/ -n other-namespace
137+
```
138+
139+
## Multi-Namespace Support
140+
141+
The `WATCH_NAMESPACE` environment variable supports comma-separated values to watch multiple specific namespaces:
142+
143+
```yaml
144+
env:
145+
- name: WATCH_NAMESPACE
146+
value: "namespace-1,namespace-2,namespace-3"
147+
```
148+
149+
Note: You'll need to create Role/RoleBinding in each namespace for proper RBAC.
150+
151+
## Reverting to Cluster-Scoped
152+
153+
To revert:
154+
155+
```bash
156+
kubebuilder edit --namespaced=false
157+
```
158+
159+
Then manually remove the namespace-scoped code from `cmd/main.go`:
160+
- Remove the `getWatchNamespace()` function
161+
- Remove the namespace retrieval and cache configuration
162+
- Remove the added imports (`fmt`, `strings`, `cache`) if not used elsewhere
163+
164+
## Important Notes
165+
166+
- **RBAC markers unchanged**: Your controller RBAC markers (`+kubebuilder:rbac`) remain the same. The scope is determined by deployment configuration (Role vs ClusterRole).
167+
- **Controller-gen always generates ClusterRole**: This is normal. Kustomize patches convert it to Role at deployment time.
168+
- **Metrics auth role stays cluster-scoped**: The `metrics-auth-role` uses cluster-scoped APIs (TokenReview, SubjectAccessReview) and correctly remains a ClusterRole.
169+
170+
## See Also
171+
172+
- [Operator Scope](../reference/operator-scope.md) - Detailed explanation of operator scope concepts
173+
- [Project Config](../reference/project-config.md) - PROJECT file configuration reference

0 commit comments

Comments
 (0)