Controllers
Controllers are the heart of your Kubernetes operator. They implement the reconciliation logic that ensures your custom resources are in the desired state.
Creating a Controller
To create a controller, create a class that implements IEntityController<TEntity>
where TEntity
is your custom entity type:
public class V1DemoEntityController(
ILogger<V1DemoEntityController> logger,
IKubernetesClient client) : IEntityController<V1DemoEntity>
{
public async Task ReconcileAsync(V1DemoEntity entity, CancellationToken token)
{
logger.LogInformation("Reconciling entity {Entity}.", entity);
// Implement your reconciliation logic here
}
public async Task DeletedAsync(V1DemoEntity entity, CancellationToken token)
{
logger.LogInformation("Deleting entity {Entity}.", entity);
// Implement your cleanup logic here
}
}
Resource Watcher
When you create a controller, KubeOps automatically creates a resource watcher (informer) for your entity type. This watcher:
- Monitors the Kubernetes API for changes to your custom resources
- Triggers reconciliation when resources are added, modified, or deleted
- Maintains a local cache of resources to reduce API server load
Reconciliation Loop
The reconciliation loop is the core of your operator's functionality. It consists of two main methods:
ReconcileAsync
This method is called when:
- A new resource is created
- An existing resource is modified
- The operator starts up and discovers existing resources
public async Task ReconcileAsync(V1DemoEntity entity, CancellationToken token)
{
// Check if required resources exist
var deployment = await client.GetAsync<V1Deployment>(
entity.Spec.DeploymentName,
entity.Namespace());
if (deployment == null)
{
// Create the deployment if it doesn't exist
await client.CreateAsync(new V1Deployment
{
Metadata = new V1ObjectMeta
{
Name = entity.Spec.DeploymentName,
NamespaceProperty = entity.Namespace()
},
Spec = new V1DeploymentSpec
{
Replicas = entity.Spec.Replicas,
// ... other deployment configuration
}
});
}
// Update status to reflect current state
entity.Status.LastReconciled = DateTime.UtcNow;
await client.UpdateStatusAsync(entity);
}
DeletedAsync
The DeletedAsync
method is purely informative and "fire and forget". It is called when a resource is deleted, but it cannot guarantee proper cleanup of resources. For reliable resource cleanup, you must use Finalizers.
This method is called when a resource is deleted, but should only be used for:
- Logging deletion events
- Triggering non-critical cleanup tasks
- Updating external systems about the deletion
public async Task DeletedAsync(V1DemoEntity entity, CancellationToken token)
{
// Log the deletion event
logger.LogInformation("Entity {Entity} was deleted.", entity);
// Update external systems if needed
await NotifyExternalSystem(entity);
}
Important Considerations
Status Updates
- Status updates do not trigger new reconciliation cycles
- Only changes to the
Spec
section trigger reconciliation - Use status updates to track the current state of your resource
Race Conditions
- If a reconciliation is currently running for a resource, new reconciliation requests for the same resource will be queued
- This prevents race conditions and ensures consistent state management
- The queue is processed in order, maintaining the sequence of changes
RBAC Requirements
Controllers need appropriate RBAC permissions to function. Use the [EntityRbac]
attribute to specify required permissions:
[EntityRbac(typeof(V1DemoEntity), Verbs = RbacVerb.All)]
public class V1DemoEntityController(
ILogger<V1DemoEntityController> logger,
IKubernetesClient client) : IEntityController<V1DemoEntity>
{
// Controller implementation
}
For more details about RBAC configuration, see the RBAC documentation.
Best Practices
Idempotency
- Make your reconciliation logic idempotent
- The same reconciliation should be safe to run multiple times
- Always check the current state before making changes
public async Task ReconcileAsync(V1DemoEntity entity, CancellationToken token)
{
// Check if required resources exist
if (await IsDesiredState(entity))
{
return;
}
// Only make changes if needed
await ApplyDesiredState(entity);
}
Error Handling
- Handle errors gracefully
- Log errors with appropriate context
- Consider implementing retry logic for transient failures
public async Task ReconcileAsync(V1DemoEntity entity, CancellationToken token)
{
try
{
await ReconcileInternal(entity, token);
}
catch (Exception ex)
{
logger.LogError(ex, "Error reconciling entity {Entity}", entity);
// Update status to reflect the error
entity.Status.Error = ex.Message;
await client.UpdateStatusAsync(entity);
}
}
Resource Management
- Clean up resources when entities are deleted
- Use finalizers to ensure proper cleanup
- Monitor resource usage and implement limits
Performance
- Keep reconciliation logic efficient
- Avoid long-running operations in the reconciliation loop
- Use background tasks for time-consuming operations
Common Pitfalls
- Infinite Loops: Avoid creating reconciliation loops that trigger themselves
- Missing Error Handling: Always handle potential errors
- Resource Leaks: Ensure proper cleanup of resources
- Missing RBAC: Configure appropriate permissions
- Status Updates: Remember that status updates don't trigger reconciliation