Kubernetes Operator Pythonic Framework (Kopf)

repository·main·Indexed 25 days ago

https://github.com/nolar/kopf

A framework and library for simplifying Kubernetes operator development using Python. Kopf allows developers to implement domain logic for Custom Resources and built-in Kubernetes resources using a declarative approach with decorators. It features support for child resource management, automatic retries for handler exceptions, per-field diff handlers, operator peering for development, dynamic sub-handlers, and lifecycle hooks for startup and cleanup.

Tokens
63.1K
Snippets
181
Records
290
Agent score
82%

What's inside kopf

  1. Understand Kopf performance and resource usage

    main

    Kopf is written in Python, which means it consumes more memory and is slower than Go-based operators. If your primary requirement is extremely low memory footprint (e.g., reducing usage from ~300 MB to ~30 MB), you should consider using Go instead.

    Key performance characteristics:

    • Memory usage: Most memory is consumed by imported Python modules (up to ~75% when using the official kubernetes client) and TCP/SSL contexts, rather than the Kopf runtime itself.
    • Runtime: Switching from CPython to PyPy does not provide significant benefits for Kopf.
    • Use case: Kopf is optimized for expressive power, ease of use, quick prototyping, and building minimally viable products (MVPs) or ad-hoc operators rapidly, rather than matching the raw resource efficiency of Go.
  2. Check Kopf development status and stability

    main
    Kopf is considered production-ready and stable (semantic v1). The project focuses on maintenance for new versions of Python and Kubernetes, rather than introducing new major functionality. Planned updates include internal optimizations like reduced memory footprint, high-load readiness, and agentic friendliness, all of which are intended to be backwards-compatible.
  3. Identify the public API for Kopf

    main

    When developing operators, you should only depend on the public interface. All other modules are implementation details and are prefixed with underscores to discourage use. The only modules with public guarantees on names and signatures are:

    • kopf: The main entry point.
    • kopf.on: Used for defining handlers and decorators.
    • kopf.testing: Used for testing your operators.
  4. Understand Kopf's persistence and state storage

    main

    Kopf does not use an external database. Instead, it stores all state information directly on the Kubernetes objects via the Kubernetes API (typically in etcd).

    By default, Kopf uses the following storage locations:

    • Cross-operator exchange: Performed via peering objects of type KopfPeering or ClusterKopfPeering (API versions kopf.dev/v1 or zalando.org/v1).
    • Last handled state: Stored in metadata.annotations using the key kopf.zalando.org/last-handled-configuration. This is used to calculate diffs when objects change.
    • Handler state (failures, successes, retries, delays): Stored in either metadata.annotations (using kopf.zalando.org/{id} keys) or in status.kopf.progress.{id} (where {id} is the handler's ID).

    You can configure these storage locations to use different keys if you need to run multiple independent operators on the same resources without them overlapping.

  5. Embed a Kopf operator into an arbitrary application

    main

    Instead of using the kopf run CLI command, you can embed a Kopf operator directly into your own Python application. This allows you to run the operator in a side thread while the main thread performs other application activities (such as a UI loop or orchestration logic).

    Note that when running in a side thread, OS signals (like SIGTERM) are ignored by the operator thread, as they are typically handled by the main thread.

    python example.py
  6. Run Kopf against a real Kubernetes cluster

    main

    When running against a real cluster, use the --dev flag. This sets the operator's priority to 666 and automatically pauses all other running operators (which default to priority 0) to prevent collisions and infinite loops.

    Alternatively, you can manually pause or resume all other operators using the kopf freeze and kopf resume commands.

  7. Configure RBAC for the operator

    main

    The operator's pod must have sufficient permissions via RBAC (Role-Based Access Control) to access and manipulate both built-in Kubernetes objects (like Pod, Job, or PersistentVolumeClaim) and your domain-specific custom resources.

    To apply these permissions, create a ServiceAccount, Role/ClusterRole, and RoleBinding/ClusterRoleBinding, then attach the ServiceAccount to the operator's pod in the Deployment specification.

  8. Handle multiple resources with multiple decorators

    main

    Kopf resource specifications do not support masks, globs, or multiple values in a single selector. To handle multiple independent resources, apply multiple @kopf.on.event decorators to the same handler function. Kopf automatically deduplicates these so the function is only triggered once per resource even if specifications overlap.

    import kopf
    from typing import Any
    
    @kopf.on.event('kopfexamples')
    @kopf.on.event('v1', 'pods')
    def fn(**_: Any) -> None:
        pass
  9. Understand the EphemeralVolumeClaim use case

    main

    The documentation uses the implementation of an EphemeralVolumeClaim as a running example to demonstrate Kopf features.

    The Problem: Standard Kubernetes storage options like Local Ephemeral Storage are often size-limited, and PersistentVolumeClaim (PVC) resources are persistent, meaning they require manual deletion and are not suitable for temporary workspaces in data-crunching jobs.

    The Solution (implemented via Kopf): A custom EphemeralVolumeClaim object kind that:

    1. Uses a PersistentVolumeClaim template internally.
    2. Uses a pod selector (labels) to designate which pods can use the volume.
    3. Automatically deletes the underlying PVC after a grace period once the designated pods are gone and not scheduled for restart.
    4. Includes an expiry period to delete the claim if it remains unused for a certain amount of time (to prevent stale claims if pods fail to start).
  10. Skip resources from indexing

    main

    You can prevent specific resources from being added to an index by having your indexing function return None (or by not returning a value at all). When None is returned, the index is not updated for that resource, and existing values for that resource are preserved.

    Note: If you return a dictionary containing None as a value (e.g., {'key': None}), the None value will be indexed as a placeholder. This is useful when you want to record the existence of a key without providing a specific value.

    import kopf
    from typing import Any
    
    # This resource will be skipped and existing index values preserved
    @kopf.index('pods')
    async def empty_index(**_: Any) -> Any:
        pass
    
    # This will index the key with a None value
    @kopf.index('pods')
    async def index_of_nones(**_: Any) -> Any:
        return {'key': None}
  11. Deploy the operator to a Kubernetes cluster

    main

    The recommended way to deploy Kopf is using a Kubernetes Deployment object.

    Critical Requirements:

    • Replicas: Set replicas: 1. Running multiple replicas for the same objects causes unpredictable collisions.
    • Update Strategy: Use .spec.strategy.type: Recreate to ensure that during pod restarts or upgrades, only one pod is running at a time.
    • Networking: No Services or Ingresses are required because the operator only makes outgoing calls to the Kubernetes API.
    kubectl apply -f deployment.yaml
  12. Configure Kubernetes liveness probes for the operator

    main

    Once a liveness endpoint is configured via the --liveness flag, you can use it in your Kubernetes Deployment specification. If the operator fails to respond to the probe, Kubernetes will restart the pod.

    Warning: Ensure that exactly one pod of your operator is running at a time, especially during restarts, to avoid conflicts.

    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          containers:
          - name: the-only-one
            image: ...
            livenessProbe:
              httpGet:
                path: /healthz
                port: 8080