Skip to main content
Version: v1.1

Installing Kubernetes Operators

Overview

Welcome to Nova Control Plane's guide on managing Kubernetes Operators! Nova provides a simplified way to manage Kubernetes Operators across different clusters. This guide will walk you through the steps to install and manage Kubernetes Operators using Nova. We'll use the Prometheus Operator as an example, but the principles can be applied to other operators as well. This guide is based on the official Prometheus Operator documentation.

Note: The examples provided are for illustrative purposes. In a real-world setup, make sure to customize the SchedulePolicy, deployment, CRDs, and values according to your specific needs.

Prerequisites

Before proceeding, you'll need to set labels on the resources you're applying. We've created a Python helper script for this purpose. Save it as add_labels.py and make it executable with chmod +x add_labels.py.

Click here to view the script
#!/usr/bin/env python3
"""
add_labels.py

This script adds labels to Kubernetes resource YAML files. It supports three modes:
- "cluster": Adds labels only to cluster-scoped resources.
- "namespace": Adds labels only to namespace-scoped resources.
- "all": Adds labels to all resources.

Usage:
add_labels.py mode label_key label_value < input.yaml > output.yaml

Dependencies:
- PyYAML: A YAML parser for Python.

Author: Elotl Inc.
Date: 28.08.2023
"""

import sys
import yaml
import subprocess
import re
import argparse
from typing import List, Set, Union, Dict

CMD_FETCH_CLUSTER_RESOURCES = "kubectl api-resources --namespaced=false | awk '{print $NF}' | tail -n +2"

def preprocess_yaml(yaml_str: str) -> str:
"""Escape special characters in a YAML string."""
lines = yaml_str.split("\n")
for i, line in enumerate(lines):
if re.match(r"^\s*-\s*=\s*$", line):
lines[i] = line.replace("=", "\"=\"")
elif re.match(r"^\s*-\s*=~\s*$", line):
lines[i] = line.replace("=~", "\"=~\"")
return "\n".join(lines)

def get_cluster_resources() -> Set[str]:
"""Fetch the list of cluster-scoped resources from the Kubernetes cluster."""
try:
cluster_resources = subprocess.check_output(CMD_FETCH_CLUSTER_RESOURCES, shell=True, text=True)
return set(cluster_resources.strip().split("\n"))
except subprocess.CalledProcessError:
sys.stderr.write("Error fetching cluster-scoped resources. Make sure kubectl is installed and configured.\n")
sys.exit(1)

def should_add_label(doc_kind: str, mode: str, cluster_resources: Set[str]) -> bool:
"""Determine if a label should be added based on the mode and kind of the resource."""
return (
mode == "all" or
(mode == "cluster" and doc_kind in cluster_resources) or
(mode == "namespace" and doc_kind not in cluster_resources)
)

def add_labels(documents: List[Union[None, Dict]], mode: str, label_key: str, label_value: str) -> List[Union[None, Dict]]:
"""Add labels to Kubernetes YAML documents."""
cluster_resources = get_cluster_resources() if mode != "all" else set()

for doc in documents:
if doc is None or 'kind' not in doc:
continue
doc.setdefault('metadata', {}).setdefault('labels', {})

if should_add_label(doc['kind'], mode, cluster_resources):
doc['metadata']['labels'][label_key] = label_value

return documents

def main() -> None:
"""Main function."""
parser = argparse.ArgumentParser(description='Add labels to Kubernetes YAML files.')
parser.add_argument('mode', choices=['cluster', 'namespace', 'all'], help='Mode to apply labels. Can be "cluster", "namespace", or "all".')
parser.add_argument('label_key', help='The key of the label to add.')
parser.add_argument('label_value', help='The value of the label to add.')

args = parser.parse_args()

try:
yaml_str = preprocess_yaml(sys.stdin.read())
documents = list(yaml.safe_load_all(yaml_str))
documents = add_labels(documents, args.mode, args.label_key, args.label_value)

for doc in documents:
if doc is not None:
yaml.safe_dump(doc, sys.stdout, default_flow_style=False)
print('---')
except yaml.YAMLError as e:
sys.stderr.write(f"YAML Error: {e}\n")
sys.exit(1)
except Exception as e:
sys.stderr.write(f"Unexpected Error: {e}\n")
sys.exit(1)

if __name__ == "__main__":
main()

Steps to Install Operators

1. Define Your SchedulePolicy

Start by defining a SchedulePolicy tailored to your specific clusters and namespaces. This sets the stage for deploying your Operator.

apiVersion: policy.elotl.co/v1alpha1
kind: SchedulePolicy
metadata:
name: prometheus-operator-policy
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: default
clusterSelector:
matchLabels:
kubernetes.io/metadata.name: wlc-1
resourceSelectors:
labelSelectors:
- matchLabels:
nova.elotl.co/policy: prometheus-operator-policy

Apply the policy:

kubectl create -f example-operator-policy.yaml

2. Install Prometheus Operator and Resources

Use the following one-liner to download, label, and apply all the necessary Prometheus Operator resources. This command uses a helper script, add_labels.sh, to add the appropriate labels for Nova management:

LATEST=$(curl -s https://api.github.com/repos/prometheus-operator/prometheus-operator/releases/latest | jq -cr .tag_name)
curl -sL https://github.com/prometheus-operator/prometheus-operator/releases/download/${LATEST}/bundle.yaml | \
./add_labels.py all nova.elotl.co/policy prometheus-operator-policy | \
kubectl create -f -

3. Verify the Operator is Running

After applying the one-liner, you can verify that the Prometheus Operator is running successfully:

kubectl get deploy

Wait for the Prometheus Operator's to show running pods.

Congratulations! You've successfully installed the Prometheus Operator and its resources in your wlc-1 cluster using Nova, and all of this was done with a convenient one-liner.

4. Deploying Prometheus

Now we need to setup RBAC for our Prometheus and deploy Prometheus itself. First, save this as prom-rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
labels:
nova.elotl.co/policy: prometheus-operator-policy
name: prometheus
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
nova.elotl.co/policy: prometheus-operator-policy
name: prometheus
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/metrics
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources:
- configmaps
verbs: ["get"]
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
nova.elotl.co/policy: prometheus-operator-policy
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: default

And run:

kubectl create -f prom-rbac.yaml

This will setup RBAC needed by Prometheus deployment.

With that out of the way, we can finally deploy Prometheus itself!

Save this as prometheus.yaml:

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
labels:
nova.elotl.co/policy: prometheus-operator-policy
name: prometheus
spec:
serviceAccountName: prometheus
serviceMonitorSelector:
matchLabels:
team: frontend
resources:
requests:
memory: 400Mi
enableAdminAPI: false

Run:

kubectl create -f prometheus.yaml

And thats it! You now have a running Prometheus stack inside your wlc-1 cluster!

Learn more about our SchedulePolicies and how you can deploy operators across multiple clusters in our Tutorials section.

Reminder: Customize According to Your Needs

As mentioned before, the examples provided here are solely for illustrative purposes. For your specific needs, make sure to adapt the SchedulePolicy, Operator deployment, CRDs, and custom resources.