How to write an Operator
========================

Pykube can be used to implement Kubernetes Operators. Here is how to write a very simple operator which adds a label ``foo`` with value ``bar`` to every deployment object which has the ``pykube-test-operator`` annotation:

.. code-block:: python

    # simplified example script, no error handling!
    import pykube, time

    while True:
        # loads in-cluster auth or local ~/.kube/config for testing
        config = pykube.KubeConfig.from_env()
        api = pykube.HTTPClient(config)
        for deploy in pykube.Deployment.objects(api, namespace=pykube.all):
            if 'pykube-test-operator' in deploy.annotations:
                print(f'Updating deployment {deploy.namespace}/{deploy.name}..')
                deploy.labels['foo'] = 'bar'
                deploy.update()
        time.sleep(15)

Save the above Python script as ``main.py``.

Testing
-------

You can now test the script locally with Pipenv_ and Minikube_ (run ``minikube start`` first):

.. code-block:: bash

    pipenv install pykube-ng
    pipenv run python3 main.py

See the operator in action by creating a deployment with the right annotation:

.. code-block:: bash

    kubectl run nginx --image=nginx
    kubectl annotate deploy nginx pykube-test-operator=true

The operator should should now assign the ``foo`` label to the ``nginx`` deployment.

Building the Docker image
-------------------------

Create a ``Dockerfile`` in the same directory as ``main.py``:

.. code-block:: Dockerfile

    FROM python:3.7-alpine3.10

    WORKDIR /

    RUN pip3 install pykube-ng

    COPY main.py /

    ENTRYPOINT ["python3", "main.py"]

Now build it:

.. code-block:: bash

    docker build -t pykube-test-operator .

You need to push the Docker image to some Docker registry before you can deploy it.


Deployment
----------

Now deploy the Docker image to your Kubernetes cluster using a service account with the necessary permissions (in this case to list and update deployments).
To create such an service account with the necessary RBAC rights create ``rbac.yaml`` with these contents:

.. code-block:: yaml

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: pykube-test-operator
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: pykube-test-operator
    rules:
    - apiGroups:
      - apps
      resources:
      - deployments
      verbs:
      - get
      - watch
      - list
      - update
      - patch
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: pykube-test-operator
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: pykube-test-operator
    subjects:
    - kind: ServiceAccount
      name: pykube-test-operator
      namespace: default

Apply the RBAC role via ``kubectl apply -f rbac.yaml``.

Finally, the deployment of the operator would then look like (``deployment.yaml``):

.. code-block:: yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: pykube-test-operator
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: pykube-test-operator
      template:
        metadata:
          labels:
            app: pykube-test-operator
        spec:
          serviceAccountName: pykube-test-operator
          containers:
          - name: operator
            # this image needs have been pushed to some Docker registry!
            image: pykube-test-operator
            resources:
              limits:
                memory: 50Mi
              requests:
                cpu: 5m
                memory: 50Mi
            securityContext:
              readOnlyRootFilesystem: true
              runAsNonRoot: true
              runAsUser: 1000

Create the deployment via ``kubectl apply -f deployment.yaml``.

You should now have a working operator deployment in your cluster.

.. _Pipenv: https://pipenv.readthedocs.io/en/latest/
.. _Minikube: https://github.com/kubernetes/minikube
