Skip to content

Commit bcc5a36

Browse files
Codex Automationkimpenhaus
andcommitted
feat: add Aspire integration for KubeOps operators
Add Aspire service defaults, AppHost hosting integration, Kubernetes run/publish support, examples, tests, and docs for local, Azure, and standalone publish scenarios. Co-authored-by: Marcus Kimpenhaus <kimpenhaus@devil-engineering.de>
1 parent cbaaac5 commit bcc5a36

28 files changed

Lines changed: 2383 additions & 7 deletions

KubeOps.slnx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<Solution>
22
<Folder Name="/examples/">
3+
<Project Path="examples\AspireAppHost\AspireAppHost.csproj" />
34
<Project Path="examples\ConversionWebhookOperator\ConversionWebhookOperator.csproj" />
45
<Project Path="examples\Operator\Operator.csproj" />
56
<Project Path="examples\WebhookOperator\WebhookOperator.csproj" />
@@ -29,6 +30,8 @@
2930
</Folder>
3031
<Folder Name="/src/">
3132
<Project Path="src\KubeOps.Abstractions\KubeOps.Abstractions.csproj" />
33+
<Project Path="src\KubeOps.Aspire.Hosting\KubeOps.Aspire.Hosting.csproj" />
34+
<Project Path="src\KubeOps.Aspire\KubeOps.Aspire.csproj" />
3235
<Project Path="src\KubeOps.Cli\KubeOps.Cli.csproj" />
3336
<Project Path="src\KubeOps.Generator\KubeOps.Generator.csproj" />
3437
<Project Path="src\KubeOps.KubernetesClient\KubeOps.KubernetesClient.csproj" />
@@ -41,6 +44,8 @@
4144
<Folder Name="/test/">
4245
<Project Path="test/KubeOps.Generator.Test.Entities/KubeOps.Generator.Test.Entities.csproj" />
4346
<Project Path="test\KubeOps.Abstractions.Test\KubeOps.Abstractions.Test.csproj" />
47+
<Project Path="test\KubeOps.Aspire.Hosting.Test\KubeOps.Aspire.Hosting.Test.csproj" />
48+
<Project Path="test\KubeOps.Aspire.Test\KubeOps.Aspire.Test.csproj" />
4449
<Project Path="test\KubeOps.Cli.Test\KubeOps.Cli.Test.csproj" />
4550
<Project Path="test\KubeOps.Generator.Test\KubeOps.Generator.Test.csproj" />
4651
<Project Path="test\KubeOps.KubernetesClient.Test\KubeOps.KubernetesClient.Test.csproj" />

README.md

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,113 @@ There are two ways to start building an operator with KubeOps:
4848
cd MyOperator
4949
```
5050

51-
The template approach (`dotnet new operator`) scaffolds a basic operator structure with a sample custom resource, controller, and finalizer, so it is the quickest way to get started. The plain console approach gives you an empty project that you build up manually by adding the `KubeOps.Operator` package. In either case, the [`kubeops` CLI tool](./src/KubeOps.Cli/README.md) provides additional commands for generating CRDs, RBAC rules, and more.
51+
Both methods generate a basic operator structure with a sample custom resource, controller, and finalizer. The template approach is simpler and more direct, while the CLI provides additional commands for generating CRDs, RBAC rules, and more.
5252

5353
For detailed tutorials and guides, visit the [KubeOps Documentation Site](https://dotnet.github.io/dotnet-operator-sdk/).
5454

55+
## Aspire Quickstart
56+
57+
KubeOps can be used as a first-class resource in a [.NET Aspire](https://learn.microsoft.com/dotnet/aspire/) AppHost. Add `KubeOps.Aspire.Hosting` to the AppHost and `KubeOps.Aspire` to the operator project.
58+
59+
In the operator project, register the service defaults after the operator:
60+
61+
```csharp
62+
using KubeOps.Aspire;
63+
using KubeOps.Operator;
64+
65+
var builder = Host.CreateApplicationBuilder(args);
66+
67+
builder.Services
68+
.AddKubernetesOperator()
69+
.RegisterComponents();
70+
71+
builder.AddKubeOpsServiceDefaults();
72+
73+
await builder.Build().RunAsync();
74+
```
75+
76+
In the AppHost, add the operator and choose whether it should run locally, publish to Kubernetes, or both:
77+
78+
```csharp
79+
var builder = DistributedApplication.CreateBuilder(args);
80+
81+
var k8s = builder.AddKubernetesEnvironment("k8s")
82+
.WithHelm(helm =>
83+
{
84+
helm.WithChartName("my-operator");
85+
helm.WithReleaseName("my-operator");
86+
helm.WithNamespace("operator-system");
87+
});
88+
89+
builder.AddKubeOps<Projects.Operator>("operator")
90+
.RunWithKubernetes(k8s)
91+
.PublishAsKubernetesOperator(k8s);
92+
93+
builder.Build().Run();
94+
```
95+
96+
Run and publish with the Aspire CLI:
97+
98+
```bash
99+
aspire run --project src/MyApp.AppHost/MyApp.AppHost.csproj
100+
aspire publish --project src/MyApp.AppHost/MyApp.AppHost.csproj --output-path ./artifacts/k8s
101+
```
102+
103+
Common AppHost shapes:
104+
105+
Local development only:
106+
107+
```csharp
108+
var dev = builder.AddKubernetesEnvironment("dev");
109+
110+
builder.AddKubeOps<Projects.Operator>("operator")
111+
.RunWithKubernetes(dev, run => run.WithPersistentCrds());
112+
```
113+
114+
Azure publish/deploy only:
115+
116+
```csharp
117+
var aks = builder.AddAzureKubernetesEnvironment("aks");
118+
119+
builder.AddKubeOps<Projects.Operator>("operator")
120+
.PublishAsKubernetesOperator(aks, publish => publish.WithServiceAccount("operator"));
121+
```
122+
123+
Local run and Azure deploy:
124+
125+
```csharp
126+
var dev = builder.AddKubernetesEnvironment("dev");
127+
var aks = builder.AddAzureKubernetesEnvironment("aks");
128+
129+
builder.AddKubeOps<Projects.Operator>("operator")
130+
.RunWithKubernetes(dev)
131+
.PublishAsKubernetesOperator(aks);
132+
```
133+
134+
Publish only without an Aspire Kubernetes environment:
135+
136+
```csharp
137+
builder.AddKubeOps<Projects.Operator>("operator")
138+
.PublishAsKubernetesOperator(publish =>
139+
{
140+
publish.Namespace = "operator-system";
141+
publish.WithServiceAccount("operator");
142+
});
143+
```
144+
145+
Without `RunWithKubernetes(...)`, `AddKubeOps<TProject>(...)` keeps the operator in explicit-start mode for local Aspire runs. Standalone manifest publish does not require `AddKubernetesEnvironment(...)`, Helm, or a live cluster; publishing with a Kubernetes environment generates an Aspire Helm chart, while `aspire deploy` installs that chart into the selected environment.
146+
55147
## Packages
56148
57-
The runtime libraries target [.NET 8.0](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8/overview), [.NET 9.0](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview), and [.NET 10.0](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview), leveraging modern C# features. The build-time packages (`KubeOps.Generator` and `KubeOps.Templates`) target [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard) for broad tooling compatibility. The underlying Kubernetes client library (`KubernetesClient`) is referenced for interacting with the Kubernetes API.
149+
All packages target [.NET 8.0](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8/overview) and [.NET 9.0](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview), leveraging modern C# features. The underlying Kubernetes client library (`KubernetesClient.Official`) also follows this versioning strategy.
58150
59151
The SDK is designed to be modular. You can include only the packages you need:
60152
61153
| Package | Description |
62154
| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63155
| [KubeOps.Abstractions](./src/KubeOps.Abstractions/README.md) | Defines core interfaces, attributes (like `[KubernetesEntity]`), and base classes used across the SDK. Essential for defining your custom resources and controllers. |
156+
| [KubeOps.Aspire](./src/KubeOps.Aspire/README.md) | [.NET Aspire](https://learn.microsoft.com/dotnet/aspire/) service defaults for an operator: a single `AddKubeOpsServiceDefaults()` call wiring up OpenTelemetry, service discovery, HTTP resilience, and health checks. |
157+
| [KubeOps.Aspire.Hosting](./src/KubeOps.Aspire.Hosting/README.md) | [.NET Aspire](https://learn.microsoft.com/dotnet/aspire/) hosting integration. Adds `AddKubeOps<TProject>(...)` so a KubeOps operator can be orchestrated as a resource inside an Aspire AppHost. |
64158
| [KubeOps.Cli](./src/KubeOps.Cli/README.md) | A [.NET Tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools) providing commands for scaffolding projects, generating [Custom Resource Definitions (CRDs)](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/), and more. |
65159
| [KubeOps.Generator](./src/KubeOps.Generator/README.md) | Contains [Roslyn Source Generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) to automate boilerplate code generation for CRDs and controllers based on your definitions. |
66160
| [KubeOps.KubernetesClient](./src/KubeOps.KubernetesClient/README.md) | Provides an enhanced client for interacting with the [Kubernetes API](https://kubernetes.io/docs/reference/kubernetes-api/), built on top of the official `KubernetesClient` library. Offers convenience methods for common operator tasks. |
@@ -71,11 +165,7 @@ The SDK is designed to be modular. You can include only the packages you need:
71165
72166
## Examples
73167
74-
You can find various example operators demonstrating different features in the [`examples/`](https://github.com/dotnet/dotnet-operator-sdk/tree/main/examples/) directory of this repository:
75-
76-
- [`Operator`](./examples/Operator) - A minimal operator with an entity, controller, and finalizer.
77-
- [`WebhookOperator`](./examples/WebhookOperator) - Demonstrates validating and mutating admission webhooks.
78-
- [`ConversionWebhookOperator`](./examples/ConversionWebhookOperator) - Demonstrates converting between entity versions with a conversion webhook.
168+
You can find various example operators demonstrating different features in the [`examples/`](https://github.com/dotnet/dotnet-operator-sdk/tree/main/examples/) directory of this repository.
79169
80170
## License
81171
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
---
2+
title: Aspire Kubernetes Operator Model
3+
description: Run, publish, and deploy KubeOps operators with Aspire Kubernetes environments.
4+
sidebar_position: 12
5+
---
6+
7+
# Aspire Kubernetes Operator Model
8+
9+
This document describes the implemented Aspire model for running, publishing, and deploying KubeOps operators.
10+
11+
Aspire has two relevant execution modes:
12+
13+
- **Run mode** starts the AppHost locally for development and debugging.
14+
- **Publish/deploy mode** generates and applies deployment artifacts for a target environment.
15+
16+
KubeOps operators need both modes, but they need different behavior in each mode.
17+
18+
## Design
19+
20+
- Keep `aspire run` as local process orchestration.
21+
- Keep `aspire publish` and `aspire deploy` as deployment artifact and cluster deployment flows.
22+
- Reuse Aspire Kubernetes and AKS compute environments instead of inventing a separate KubeOps target model.
23+
- Make local CRD handling explicit, safe, and predictable.
24+
- Avoid automatic destructive changes to a developer's current cluster.
25+
26+
## AppHost Shape
27+
28+
The AppHost can express a local run target and a publish target separately:
29+
30+
```csharp
31+
var builder = DistributedApplication.CreateBuilder(args);
32+
33+
var dev = builder.AddKubernetesEnvironment("dev")
34+
.WithHelm(helm => helm.WithNamespace("operator-dev"));
35+
36+
var aks = builder.AddAzureKubernetesEnvironment("aks")
37+
.WithHelm(helm => helm.WithNamespace("operator-system"));
38+
39+
builder.AddKubeOps<Projects.Operator>("operator")
40+
.RunWithKubernetes(dev, run =>
41+
{
42+
run.WithPersistentCrds();
43+
})
44+
.PublishAsKubernetesOperator(aks, publish =>
45+
{
46+
publish.WithServiceAccount("operator");
47+
});
48+
49+
builder.Build().Run();
50+
```
51+
52+
`RunWithKubernetes(...)` configures the local project process. The operator still runs on the developer machine under Aspire, but its Kubernetes client is configured for the selected Kubernetes target.
53+
54+
`PublishAsKubernetesOperator(...)` configures the Kubernetes deployment. The operator becomes a Kubernetes workload, and KubeOps-generated CRDs, RBAC, and service-account resources are incorporated into the generated Aspire chart.
55+
56+
If `RunWithKubernetes(...)` is not configured, `AddKubeOps<TProject>(...)` keeps the operator resource in Aspire's explicit-start mode during `aspire run`. The project remains in the AppHost model and publish graph, but the local operator process does not start by default. A KubeOps operator without a Kubernetes run target would otherwise fail against an accidental kube context or mutate the wrong cluster.
57+
58+
## Run Mode
59+
60+
Run mode defaults to a productive inner loop once you opt in:
61+
62+
```csharp
63+
var dev = builder.AddKubernetesEnvironment("dev");
64+
65+
builder.AddKubeOps<Projects.Operator>("operator")
66+
.RunWithKubernetes(dev);
67+
```
68+
69+
Default run behavior:
70+
71+
- If run mode is not configured, do not start the operator automatically.
72+
- Create missing CRDs before starting the local operator process.
73+
- Track which CRDs were created by this Aspire run.
74+
- Remove only the CRDs that this run created when the AppHost shuts down.
75+
- Leave pre-existing CRDs alone.
76+
- Configure the operator process with the target namespace and Kubernetes client hints.
77+
78+
Run mode exposes explicit CRD lifecycle choices:
79+
80+
```csharp
81+
run.WithEphemeralCrds(); // default: create missing CRDs, remove only those created by this run
82+
run.WithPersistentCrds(); // create or update CRDs, leave them after shutdown
83+
run.RequireExistingCrds(); // fail before operator start if CRDs are missing
84+
run.SkipCrds(); // do not check or manage CRDs
85+
```
86+
87+
RBAC and service accounts are not required for the default local process path because the process normally authenticates with the developer's kubeconfig credentials. They can be added later for parity scenarios, for example a `RunAsServiceAccount(...)` option that creates a service account, binds RBAC, obtains a token, and configures the local process to use it.
88+
89+
## Publish And Deploy Mode
90+
91+
Publish mode defaults to production-ready Kubernetes output:
92+
93+
```csharp
94+
var k8s = builder.AddKubernetesEnvironment("k8s")
95+
.WithHelm(helm =>
96+
{
97+
helm.WithChartName("my-operator");
98+
helm.WithReleaseName("my-operator");
99+
helm.WithNamespace("operator-system");
100+
});
101+
102+
builder.AddKubeOps<Projects.Operator>("operator")
103+
.PublishAsKubernetesOperator(k8s);
104+
```
105+
106+
Default publish behavior:
107+
108+
- Generate CRDs.
109+
- Generate RBAC.
110+
- Generate a service account using the operator resource name.
111+
- Bind generated RBAC to that service account.
112+
- Let Aspire own the operator `Deployment` so image publishing, Helm values, service discovery, configuration, and telemetry wiring remain part of the full Aspire model.
113+
- Merge KubeOps-generated deployment settings into the Aspire-owned deployment instead of emitting a duplicate deployment.
114+
115+
Publish options make each part explicit:
116+
117+
```csharp
118+
publish.GenerateCrds(); // default
119+
publish.GenerateRbac(); // default
120+
publish.WithServiceAccount("operator"); // default name: resource name
121+
publish.SkipCrds();
122+
publish.SkipRbac();
123+
```
124+
125+
With a Kubernetes environment, `aspire publish` writes the generated Helm chart. `aspire deploy` uses the same Kubernetes environment and deployment engine to install it. The KubeOps integration hooks into Aspire's Kubernetes resource customization path (`PublishAsKubernetesService`) rather than running a separate KubeOps deployment.
126+
127+
The integration filters KubeOps' generated `Deployment` out of the additional resource set because Aspire owns the workload. KubeOps deployment settings such as replicas, termination grace period, environment variables, resource requirements, `POD_NAMESPACE`, and the service account are merged into the Aspire-owned deployment instead. The chart therefore deploys one operator deployment, not a service-only placeholder and not a duplicate operator.
128+
129+
## Standalone Publish
130+
131+
You can also generate KubeOps manifests without an Aspire Kubernetes environment:
132+
133+
```csharp
134+
builder.AddKubeOps<Projects.Operator>("operator")
135+
.PublishAsKubernetesOperator(publish =>
136+
{
137+
publish.Namespace = "operator-system";
138+
publish.WithServiceAccount("operator");
139+
});
140+
```
141+
142+
This path is useful when the AppHost should participate in Aspire publish, but the Kubernetes installation is handled by another workflow. It does not call Aspire's Kubernetes publishing integration and does not require `AddKubernetesEnvironment(...)`, Helm, or a live cluster. `aspire publish` writes the raw KubeOps-generated YAML under the operator resource's output directory.
143+
144+
## AKS And Existing Resources
145+
146+
AKS follows Aspire's Azure resource conventions.
147+
148+
When the AppHost declares:
149+
150+
```csharp
151+
var aks = builder.AddAzureKubernetesEnvironment("aks");
152+
```
153+
154+
then `aspire deploy` provisions the AKS cluster, ACR, identity, and dependent Azure resources according to Aspire's AKS integration, then builds images, pushes them to ACR, generates Helm charts, and installs them into AKS.
155+
156+
For local run mode, Azure integrations may provision Azure resources unless configured as existing resources. Users who want local run mode to attach to an existing AKS cluster should use Aspire's existing-resource APIs:
157+
158+
```csharp
159+
var aks = builder.AddAzureKubernetesEnvironment("aks")
160+
.RunAsExisting(aksName, aksResourceGroup);
161+
162+
builder.AddKubeOps<Projects.Operator>("operator")
163+
.RunWithKubernetes(aks);
164+
```
165+
166+
This keeps KubeOps aligned with Aspire instead of inventing a KubeOps-specific `UseExistingAks` switch.
167+
168+
## Scenario Examples
169+
170+
### Local Only
171+
172+
Run the operator as a local process against the selected Kubernetes environment. The operator is not automatically published to Kubernetes unless you also call `PublishAsKubernetesOperator(...)`.
173+
174+
```csharp
175+
var dev = builder.AddKubernetesEnvironment("dev");
176+
177+
builder.AddKubeOps<Projects.Operator>("operator")
178+
.RunWithKubernetes(dev, run => run.WithPersistentCrds());
179+
```
180+
181+
### Azure Only
182+
183+
Deploy the operator into AKS without running it as a local process during `aspire run`.
184+
185+
```csharp
186+
var aks = builder.AddAzureKubernetesEnvironment("aks");
187+
188+
builder.AddKubeOps<Projects.Operator>("operator")
189+
.PublishAsKubernetesOperator(aks, publish =>
190+
{
191+
publish.Namespace = "operator-system";
192+
publish.WithServiceAccount("operator");
193+
});
194+
```
195+
196+
### Local Run And Azure Deploy
197+
198+
Use a local Kubernetes environment for the development loop and AKS for publish/deploy.
199+
200+
```csharp
201+
var dev = builder.AddKubernetesEnvironment("dev");
202+
var aks = builder.AddAzureKubernetesEnvironment("aks");
203+
204+
builder.AddKubeOps<Projects.Operator>("operator")
205+
.RunWithKubernetes(dev)
206+
.PublishAsKubernetesOperator(aks, publish => publish.WithServiceAccount("operator"));
207+
```
208+
209+
### Publish Only Without A Kubernetes Environment
210+
211+
Generate standalone KubeOps manifests without registering an Aspire Kubernetes environment.
212+
213+
```csharp
214+
builder.AddKubeOps<Projects.Operator>("operator")
215+
.PublishAsKubernetesOperator(publish =>
216+
{
217+
publish.Namespace = "operator-system";
218+
publish.WithServiceAccount("operator");
219+
});
220+
```
221+
222+
## Mock And Local Cluster Targets
223+
224+
KubeOps runtime watchers require a Kubernetes API server. Unit tests can replace `IKubernetesClient`, but the normal operator runtime is not an offline simulator.
225+
226+
Additional targets can be modeled as Kubernetes environments if they provide Kubernetes API semantics:
227+
228+
```csharp
229+
var k3s = builder.AddK3sCluster("k3s");
230+
var mock = builder.AddKubeOpsMockKubernetes("mock-kube");
231+
232+
builder.AddKubeOps<Projects.Operator>("operator")
233+
.RunWithKubernetes(k3s);
234+
```
235+
236+
A mock target would require operator-side runtime support to replace the Kubernetes client and watcher behavior. It should not be represented as a normal Kubernetes environment unless it provides Kubernetes API semantics.
237+
238+
## Non-Goals
239+
240+
- Do not hide cluster mutations behind `#if DEBUG`.
241+
- Do not deploy a second KubeOps-generated operator deployment beside Aspire's deployment.
242+
- Do not require users to use AKS; plain Kubernetes environments, kind, k3s, and existing kubeconfig targets should work.
243+
- Do not make local run mode require RBAC/service-account generation unless the user explicitly asks to run as a service account.

0 commit comments

Comments
 (0)