Webhooks
Note: Using KubeOps webhooks requires your operator to be hosted within an ASP.NET Core application. The KubeOps.Operator.Web
NuGet package provides the necessary integration and middleware.
KubeOps leverages ASP.NET Core to host webhook endpoints, allowing your operator to interact with the Kubernetes API server during crucial lifecycle events. This requires the KubeOps.Operator.Web
NuGet package.
There are two main categories of webhooks supported:
- Admission Webhooks: Intercept requests to the Kubernetes API server before an object is persisted (created, updated, or deleted). They can either validate the request or mutate (modify) the object.
- Conversion Webhooks: Handle the conversion of custom resources between different versions defined in your CRD.
Admission Webhooks
Admission Webhooks act as gatekeepers for API requests.
Implementation Steps
- Add Package: Ensure your operator project references
KubeOps.Operator.Web
. - Create Implementation:
- For validation, create a class inheriting from
ValidationWebhook<TEntity>
(found inKubeOps.Operator.Web.Webhooks.Admission.Validation
). - For mutation, create a class inheriting from
MutationWebhook<TEntity>
(found inKubeOps.Operator.Web.Webhooks.Admission.Mutation
). TEntity
is the Kubernetes resource type the webhook applies to (e.g.,V1Pod
, or your ownV1MyCustomResource
).
- For validation, create a class inheriting from
- Add Attribute: Decorate your implementation class with
[ValidationWebhook(typeof(TEntity))]
or[MutationWebhook(typeof(TEntity))]
respectively. - Implement Methods: Override the relevant methods (
Create
,Update
,Delete
) to contain your logic.- Validation methods return
ValidationResult
, typicallySuccess()
orFail("reason", [optional httpStatusCode])
. - Mutation methods return
MutationResult<TEntity>
, typicallyNoChanges()
orModified(updatedEntity)
.
- Validation methods return
- Register: In your
Program.cs
or other startup code, register the webhook implementation using the standard .NET dependency injection container:// Example registration in Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddKubernetesOperator(); // Base KubeOps registration // Register specific webhook implementations builder.Services.AddWebhook<MyValidationWebhook>(); builder.Services.AddWebhook<MyMutationWebhook>(); // ... other service registrations ... var app = builder.Build(); app.UseKubernetesOperator(); // Enable KubeOps endpoints // ... other middleware ... app.Run();
- Configure Kubernetes: Create
ValidatingWebhookConfiguration
orMutatingWebhookConfiguration
Kubernetes resources. These tell the API server to send admission requests for specific resources/operations to your operator's webhook service endpoint (usually/mutate/{webhook-name}
or/validate/{webhook-name}
).- Important: Manually creating these YAML configurations can be complex. The KubeOps CLI Tool is highly recommended for generating these based on your webhook attributes:
dotnet kubeops generate webhooks --assembly /path/to/your/operator.dll --output-path ./deployment
. - Additionally, the command
dotnet kubeops generate operator
(which also generates RBAC) creates the necessary KubernetesService
manifest required to expose your webhook endpoints within the cluster so the API server can reach them.
- Important: Manually creating these YAML configurations can be complex. The KubeOps CLI Tool is highly recommended for generating these based on your webhook attributes:
Validation Webhook Example
This example prevents creating or updating a V1TestEntity
if its spec.username
is "forbidden".
// From examples/WebhookOperator/Webhooks/TestValidationWebhook.cs
using KubeOps.Operator.Web.Webhooks.Admission.Validation;
using WebhookOperator.Entities; // Assuming V1TestEntity is defined here
namespace WebhookOperator.Webhooks;
[ValidationWebhook(typeof(V1TestEntity))]
public class TestValidationWebhook : ValidationWebhook<V1TestEntity>
{
// Only implement methods for operations you want to validate
public override ValidationResult Create(V1TestEntity entity, bool dryRun)
{
if (entity.Spec.Username == "forbidden")
{
// Reject the request with a reason and HTTP status code 422
return Fail("name may not be 'forbidden'.", 422);
}
return Success(); // Allow the request
}
public override ValidationResult Update(V1TestEntity oldEntity, V1TestEntity newEntity, bool dryRun)
{
if (newEntity.Spec.Username == "forbidden")
{
// Reject the request with a reason (defaults to HTTP 400)
return Fail("name may not be 'forbidden'.");
}
return Success(); // Allow the request
}
}
Find the full example code here: https://github.com/ewassef/dotnet-operator-sdk/tree/main/examples/WebhookOperator/.
Mutation Webhook Example
This example changes spec.username
to "random overwritten" if it's initially set to "overwrite" during creation.
// From examples/WebhookOperator/Webhooks/TestMutationWebhook.cs
using KubeOps.Operator.Web.Webhooks.Admission.Mutation;
using WebhookOperator.Entities; // Assuming V1TestEntity is defined here
namespace WebhookOperator.Webhooks;
[MutationWebhook(typeof(V1TestEntity))]
public class TestMutationWebhook : MutationWebhook<V1TestEntity>
{
// Only implement methods for operations you want to mutate
public override MutationResult<V1TestEntity> Create(V1TestEntity entity, bool dryRun)
{
if (entity.Spec.Username == "overwrite")
{
entity.Spec.Username = "random overwritten";
// Return the modified entity
return Modified(entity);
}
// Indicate no changes were made
return NoChanges();
}
}
Conversion Webhooks
When you manage multiple versions of a Custom Resource Definition (CRD), Kubernetes needs a way to convert resources between these versions. This is essential for allowing users to interact with different API versions while maintaining a single storage version internally. KubeOps uses Conversion Webhooks for this.
See: Kubernetes API Versioning
Implementation Steps
- Add Package: Ensure your operator project references
KubeOps.Operator.Web
. - Define Versions: Define your different entity versions as separate C# classes (e.g.,
V1Entity
,V2Entity
,V3Entity
), each marked with[KubernetesEntity]
. Ensure your CRD definition in Kubernetes lists all supported versions and specifies one as thestorage
version. - Create Implementation: Create a class inheriting from
ConversionWebhook<TStorageEntity>
(found inKubeOps.Operator.Web.Webhooks.Conversion
), whereTStorageEntity
is the C# type corresponding to your designatedstorage
version (e.g.,V3Entity
). - Add Attribute: Decorate the implementation class with
[ConversionWebhook(typeof(TStorageEntity))]
. - Implement Converters: Override the
Converters
property. This property must return a collection of objects, each implementing the bidirectionalIEntityConverter<TNonStorage, TStorage>
interface (found inKubeOps.Operator.Web.Webhooks.Conversion
). Each implementation handles conversion between one specific non-storage version (TNonStorage
) and the storage version (TStorage
).- Implement the
Convert(TNonStorage from)
method to convert from the non-storage version to the storage version. - Implement the
Revert(TStorage storage)
method to convert from the storage version back to the non-storage version.
- Implement the
- Register: In your
Program.cs
or other startup code, register the webhook implementation using the dependency injection container:// Example registration builder.Services.AddWebhook<MyConversionWebhook>(); // Registered via DI like other webhooks
- Configure Kubernetes: Update your CRD manifest:
- Define all supported versions under
spec.versions
. - Set
spec.conversion.strategy
toWebhook
. - Configure
spec.conversion.webhook.clientConfig
to point to your operator's conversion webhook service endpoint (usually/convert/{webhook-name}
). - Specify the
spec.conversion.webhook.conversionReviewVersions
your webhook supports (e.g.,["v1", "v1beta1"]
). - Note: The KubeOps CLI Tool can help generate CRDs with conversion settings (
dotnet kubeops generate crds ...
). This is the recommended approach to ensure theconversion
stanza is correctly configured.
- Define all supported versions under
Conversion Webhook Example
This example handles conversion between V1TestEntity
, V2TestEntity
, and V3TestEntity
, where V3TestEntity
is the storage version.
// From examples/ConversionWebhookOperator/Webhooks/TestConversionWebhook.cs
using ConversionWebhookOperator.Entities; // v1, v2, v3 entities defined here
using KubeOps.Operator.Web.Webhooks.Conversion;
namespace ConversionWebhookOperator.Webhooks;
// Attribute specifies the STORAGE version
[ConversionWebhook(typeof(V3TestEntity))]
public class TestConversionWebhook : ConversionWebhook<V3TestEntity>
{
// Provide converters for V1<->V3 and V2<->V3
protected override IEnumerable<IEntityConverter<V3TestEntity>> Converters => new IEntityConverter<V3TestEntity>[]
{
new V1ToV3(), new V2ToV3(),
};
// Handles V1 <-> V3 conversion
private class V1ToV3 : IEntityConverter<V1TestEntity, V3TestEntity>
{
public V3TestEntity Convert(V1TestEntity from)
{
// Logic to convert V1 spec to V3 spec
var nameSplit = from.Spec.Name.Split(' ');
var result = new V3TestEntity { Metadata = from.Metadata };
result.Spec.Firstname = nameSplit[0];
result.Spec.Lastname = string.Join(' ', nameSplit[1..]);
return result;
}
public V1TestEntity Revert(V3TestEntity to)
{
// Logic to convert V3 spec back to V1 spec
var result = new V1TestEntity { Metadata = to.Metadata };
result.Spec.Name = $"{to.Spec.Firstname} {to.Spec.Lastname}";
return result;
}
}
// Handles V2 <-> V3 conversion
private class V2ToV3 : IEntityConverter<V2TestEntity, V3TestEntity>
{
public V3TestEntity Convert(V2TestEntity from)
{
// Logic to convert V2 spec to V3 spec
var result = new V3TestEntity { Metadata = from.Metadata };
result.Spec.Firstname = from.Spec.Firstname;
result.Spec.Lastname = from.Spec.Lastname;
return result;
}
public V2TestEntity Revert(V3TestEntity to)
{
// Logic to convert V3 spec back to V2 spec
var result = new V2TestEntity { Metadata = to.Metadata };
result.Spec.Firstname = to.Spec.Firstname;
result.Spec.Lastname = to.Spec.Lastname;
return result;
}
}
}
Find the full conversion webhook example code in the GitHub repository: https://github.com/ewassef/dotnet-operator-sdk/tree/main/examples/ConversionWebhookOperator/
Important Considerations
- TLS & Connectivity:
- Webhook endpoints must be served over HTTPS. The Kubernetes API server must be able to reach your operator's webhook service endpoint (usually via a Kubernetes
Service
of typeClusterIP
). - The API server must trust the TLS certificate presented by your operator's webhook endpoint.
- Production: Tools like cert-manager are commonly used to automate certificate provisioning and rotation within the cluster.
- Development: For local development (e.g., using
dotnet run
and port-forwarding or tools like ngrok), you might use self-signed certificates or disable TLS verification temporarily (not recommended for production!). KubeOps development templates often include helper scripts or configurations for local TLS setup. - The generated
ValidatingWebhookConfiguration
orMutatingWebhookConfiguration
includes aclientConfig.caBundle
field where the CA certificate used to sign the webhook server's certificate must be placed, allowing the API server to verify the connection.
- Webhook endpoints must be served over HTTPS. The Kubernetes API server must be able to reach your operator's webhook service endpoint (usually via a Kubernetes
- RBAC: Your operator's ServiceAccount needs appropriate RBAC permissions to get/list/watch the resources targeted by the webhooks, as well as permissions to manage
ValidatingWebhookConfiguration
,MutatingWebhookConfiguration
, and potentiallyCustomResourceDefinition
resources. - Availability: If your webhook service is down, the API server operations depending on it (creation, updates, reads of different versions) will fail. Ensure your operator deployment is highly available.
- Idempotency: Mutation webhooks might be called multiple times for the same event. Ensure your mutation logic is idempotent (applying it multiple times has the same effect as applying it once).