Skip to content

Operator Explained

In this tutorial, we will explain the code in the previous tutorila Operator

1. nginx.py

Initialize

Upon initialization, save the name, namespace and passed kwargs to instance attribute and set the deploy_name of the Nginx instance

from kubernetes import client


class Nginx:
    def __init__(self, name, namespace, **kwargs):
        self.name = name
        self.deploy_name = f'{name}-deploy'
        self.namespace = namespace
        self.kwargs = kwargs

Property

Return the AppsV1Api client instance. Here because the function may be called more than once, after the first call, the client instance is saved in the attribute _api_client.

@property
def api_client(self):
    if not getattr(self, '_api_client', None):
        self._api_client = client.AppsV1Api()
    return self._api_client

Here returns the label selector we will use to filter the deployments

@property
def label_selector(self):
    return f'underneathall-app = nginx-{self.name}'

Here returns the labels we will use to create deployments.

@property
def labels(self):
    return {
        'underneathall-app': f'nginx-{self.name}',
        'app': 'underneathall'
    }

Here returns the body we will send to api server to create or update deployments.

@property
def body(self):
    return {
        'kind': 'Deployment',
        'apiVersion': 'apps/v1',
        'metadata': {
            'labels': self.labels,
            'namespace': self.namespace,
            'name': self.deploy_name
        },
        'spec': {
            'selector': {
                'matchLabels': self.labels
            },
            'replicas': self.kwargs.get('replicas', 1),
            'template': {
                'metadata': {
                    'labels': self.labels
                },
                'spec': {
                    'containers': [
                        {
                            'image': 'nginx:1.7.9',
                            'name': 'nginx',
                            'ports': [
                                {'containerPort': 80}
                            ]
                        }
                    ]
                }
            }
        }
    }

Check if the deployment already exists in the cluster.

@property
def exists(self):
    if not getattr(self, '_deploy', None):
        deploys = self.api_client.list_namespaced_deployment(
            namespace=self.namespace,
            label_selector=self.label_selector)
        if len(deploys.items) == 0:
            return False
        self._deploy = deploys.items[0]
    return True

Create, Update and Delete

Depends on the event_type, the operator will perform different actions.

Here, because our codes are pretty simple, we implement the logics in just on function sync.

def sync(self, event_type="ADDED"):
    event_type = event_type.upper()
    if event_type not in ["ADDED", "MODIFIED", "DELETED"]:
        print(f'Invalid event type received: {event_type}')
        return False
    if event_type in ["ADDED", "MODIFIED"] and not self.exists:
        ret = self.api_client.create_namespaced_deployment(
            namespace=self.namespace,
            body=self.body
        )
    elif event_type == "MODIFIED":
        ret = self.api_client.replace_namespaced_deployment(
            name=self.deploy_name,
            namespace=self.namespace,
            body=self.body
        )
    elif event_type == "DELETED" and self.exists:
        ret = self.api_client.delete_namespaced_deployment(
            name=self.deploy_name,
            namespace=self.namespace
        )
    return True

2. operator.py

Handle Event

For each event,

def handle_event(event):
    nginx = Nginx(
        name=event['object']['metadata']['name'],
        namespace=event['object']['metadata']['namespace'],
        replicas=event['object']['spec']['replicas']
    )
    nginx.sync(event_type=event['type'])