![]() |
VOOZH | about |
dotnet add package CanSupportMe.Operator --version 1.0.0
NuGet\Install-Package CanSupportMe.Operator -Version 1.0.0
<PackageReference Include="CanSupportMe.Operator" Version="1.0.0" />
<PackageVersion Include="CanSupportMe.Operator" Version="1.0.0" />Directory.Packages.props
<PackageReference Include="CanSupportMe.Operator" />Project file
paket add CanSupportMe.Operator --version 1.0.0
#r "nuget: CanSupportMe.Operator, 1.0.0"
#:package CanSupportMe.Operator@1.0.0
#addin nuget:?package=CanSupportMe.Operator&version=1.0.0Install as a Cake Addin
#tool nuget:?package=CanSupportMe.Operator&version=1.0.0Install as a Cake Tool
This package will allow you to create a simple Kubernetes Operator in .NET.
It can pick up changes to CRDs, ConfigMaps or Secrets and say if it was added, modified or deleted.
It can check if a CRD exists as well as retrieve, create, replace and delete configmaps and secrets.
It can also update the status of any manifest that supports it.
It is not a full blown Kubernetes Operator SDK. These exist and, if desired, this package can be used in conjunction with them. It has been developed to do what the project required and nothing more however this will likely fit the requirements of many similar projects too.
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: test-secret-with-label
namespace: default
labels:
app.kubernetes.io/managed-by: DemoOperator
stringData:
notImportant: SomeValue
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
name: test-secret-without-label
namespace: default
stringData:
notImportant: SomeValue
type: Opaque
EOF
Create a new .NET 8 console application and call it CanSupportMe.Operator.Demo.
Add the following packages from NuGet:
Note that the Serilog.AspNetCore package is not required but is used in this demo to show how to produce nicer logs.
dotnet add package CanSupportMe.Operator
Replace the contents of your Program.cs with the following:
using CanSupportMe.Operator.Extensions;
using CanSupportMe.Operator.Options;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
// const string FAILOVER_TOKEN = "<YOUR_TOKEN_GOES_HERE>";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Application starting up");
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddOperator(options =>
{
options.Group = "";
options.Kind = "Secret";
options.Version = "v1";
options.Plural = "secrets";
options.Scope = ResourceScope.Namespaced;
options.LabelFilters.Add("app.kubernetes.io/managed-by", "SeqApiKeyOperator");
options.OnAdded = (kind, name, @namespace, item, dataObjectManager) =>
{
Log.Information("On {Kind} Add: {Name} to {Namespace} which is of type {Type} with {ItemCount} item(s)", kind, name, @namespace, item.Type, item.Data?.Count);
};
// options.FailoverToken = FAILOVER_TOKEN;
});
})
.UseSerilog()
.Build();
host.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start failed");
}
finally
{
Log.CloseAndFlush();
}
Running the project will show it connecting to the cluster and watching for changes to secrets. It will then show test-secret-with-label as being added and not mention test-secret-without-label.
If a options.FailoverToken is required, one can be generated using the following bash script:
export SERVICEACCOUNT="cansupportme-operator"
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: ${SERVICEACCOUNT}-token
namespace: default
annotations:
kubernetes.io/service-account.name: $SERVICEACCOUNT
type: kubernetes.io/service-account-token
EOF
export TOKEN=$(kubectl -n default get secret ${SERVICEACCOUNT}-token -o jsonpath='{.data.token}' | base64 --decode)
echo $TOKEN
Uncomment the two commented out lines of code in the above snippet and paste in the key generated above into the FAILOVER_TOKEN constant.
The more likely scenario for an operator is to listen out for CRD changes. The following example shows the simple file you need to create and how to configure the operator. To set up a CRD, apply the following CRD manifest to your cluster:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: apikeys.demo.cansupport.me
spec:
# group name to use for REST API: /apis/<group>/<version>
group: demo.cansupport.me
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1alpha1
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
secretName:
type: string
x-kubernetes-validations:
- rule: self == oldSelf
message: "The secret name cannot be changed once set"
required:
- secretName
status:
type: object
properties:
status:
type: string
default: "New"
enum:
- "Ready"
- "Reconciling"
- "Failed"
- "New"
- "Deleting"
additionalPrinterColumns:
- name: Secret Name
type: string
description: The name of the secret that contains the API Key
jsonPath: .spec.secretName
- name: Status
type: string
description: The status of the Seq API key being reconciled
jsonPath: .status.status
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: apikeys
# singular name to be used as an alias on the CLI and for display
singular: apikey
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: ApiKey
# shortNames allow shorter string to match your resource on the CLI
shortNames:
- ak
Create a new file called MyCrd.cs in the root of the project and add the following:
using CanSupportMe.Operator.Interfaces;
using CanSupportMe.Operator.Models;
using System.Text.Json.Serialization;
namespace CanSupportMe.Operator.Demo;
public class MyCrd : KubernetesResource<MySpec, MyStatus>
{
}
public class MySpec : IKubernetesSpec
{
[JsonPropertyName("secretName")]
public required string SecretName { get; set; }
}
public class MyStatus : IKubernetesStatus
{
[JsonPropertyName("status")]
public required string Status { get; set; }
}
Then, in Program.cs add the following:
services.AddOperator<MySpec, MyStatus>(options =>
{
options.OperatorName = "SeqApiKeyOperator";
options.Group = "demo.cansupport.me";
options.Kind = "ApiKey";
options.Version = "v1alpha1";
options.Plural = "apikeys";
options.Scope = ResourceScope.Namespaced;
options.OnReconcile = async (kind, name, @namespace, item, dataObjectManager) =>
{
Log.Information("On {kind} reconcile: {Name} to {Namespace} to create secret called {SecretName}", kind, name, @namespace, item.Spec.SecretName);
var existingSecret = await dataObjectManager.Get("Secret", item.Spec.SecretName, @namespace);
if (existingSecret.IsSuccess)
{
Log.Debug("Secret {SecretName} already exists in {Namespace} and has a value of {ExistingValue}",
existingSecret.Value.Metadata.Name,
existingSecret.Value.Metadata.Namespace,
existingSecret.Value.Data["myApiKey"]);
}
else
{
Log.Debug("Secret {SecretName} does not exist in {Namespace}", item.Spec.SecretName, @namespace);
await dataObjectManager.Create("Secret", item.Spec.SecretName, @namespace, new Dictionary<string, string>
{
{ "myApiKey", $"Some random string at {DateTime.UtcNow}" }
}, labels: new()
{
{ "cansupport.me/owned-by", name }
});
}
};
options.FailoverToken = FAILOVER_TOKEN;
});
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 307 | 2/7/2024 |
| 1.0.0-nightly.33 | 155 | 2/7/2024 |
| 1.0.0-nightly.32 | 171 | 2/7/2024 |
| 1.0.0-nightly.31 | 147 | 2/4/2024 |
| 1.0.0-nightly.30 | 167 | 2/4/2024 |
| 1.0.0-nightly.29 | 161 | 2/4/2024 |
| 1.0.0-nightly.28 | 139 | 2/3/2024 |
| 1.0.0-nightly.27 | 152 | 1/24/2024 |
| 1.0.0-nightly.26 | 154 | 1/23/2024 |
| 1.0.0-nightly.25 | 158 | 1/23/2024 |
| 1.0.0-nightly.24 | 164 | 1/22/2024 |
| 1.0.0-nightly.23 | 143 | 1/22/2024 |
| 1.0.0-nightly.22 | 139 | 1/22/2024 |
| 1.0.0-nightly.21 | 168 | 1/22/2024 |
| 1.0.0-nightly.20 | 132 | 1/21/2024 |
| 1.0.0-nightly.19 | 167 | 1/20/2024 |
| 1.0.0-nightly.18 | 165 | 1/20/2024 |
| 1.0.0-nightly.17 | 159 | 1/20/2024 |
| 1.0.0-nightly.16 | 136 | 1/20/2024 |
| 1.0.0-nightly.15 | 171 | 1/19/2024 |