Skip to content

Instantly share code, notes, and snippets.

@WalBeh
Created May 22, 2024 09:26
Show Gist options
  • Select an option

  • Save WalBeh/2f9f2115961a7fce8ec5f57ecf3ccb65 to your computer and use it in GitHub Desktop.

Select an option

Save WalBeh/2f9f2115961a7fce8ec5f57ecf3ccb65 to your computer and use it in GitHub Desktop.
k8s update deployment and cronjob
#!/usr/bin/env python3
""" Update the backup image in the cratedDB namespaces.
The script iterates over all cratedbs in a k8s context and updates tag on the deployment and cronjob
using the backup image.
The script can be run standlone/locally, or on-demand in the cluster via CI/CD. Runtime
behaviour and configuration is controlled via environment variables.
The k8s context need to match the cluster names in your kubeconfig file.
"""
# https://pip.wtf
def pip_wtf(command):
"""Setup python module."""
import os, os.path, sys
t = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".pip_wtf." + os.path.basename(__file__))
sys.path = [p for p in sys.path if "-packages" not in p] + [t]
os.environ["PATH"] += os.pathsep + t + os.path.sep + "bin"
os.environ["PYTHONPATH"] = os.pathsep.join(sys.path)
if os.path.exists(t): return
os.system(" ".join([sys.executable, "-m", "pip", "install", "-t", t, command]))
import sys
pip_wtf('kubernetes structlog')
from kubernetes import client, config, utils
from kubernetes.client.rest import ApiException
from distutils.util import strtobool
import os
import logging
import structlog
# Optional environment settings
CONTEXTS = os.getenv('KUBE_CONTEXTS', 'aks1-eastus-dev')
DRY_RUN = strtobool(os.getenv('DRY_RUN', True))
IMAGE_NAME = os.getenv('IMAGE_NAME', 'cloud.registry.cr8.net/crate/cloud-cluster-backup')
IGNORED_NAMESPACES = os.getenv('IGNORED_NAMESPACES', 'templates, cloud-app')
LOGLEVEL = os.environ.get("LOG_LEVEL", "INFO").upper()
ONLY_NAMESPACE = os.getenv('ONLY_NAMESPACE', "06410cd8-1e4c-462f-92ca-9a0a5864b6b0")
TAG = os.getenv('TAG', '0.30.1')
# Constants
API_GROUP = "cloud.crate.io"
CJ_NAME = "create-snapshot-"
DEPLOYMENT_NAME = "backup-metrics-"
RESOURCE_CRATEDB = "cratedbs"
# Logging Setup
LOG_LEVEL = getattr(logging, LOGLEVEL)
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(LOG_LEVEL))
logger = structlog.get_logger()
# Log relevant environment settings during startup
logger.info("Updating backup image to tag: %s", TAG)
if DRY_RUN: logger.info("Dry Run: %s", DRY_RUN)
if IGNORED_NAMESPACES: logger.info("IGNORED_NAMESPACES: %s", IGNORED_NAMESPACES)
if ONLY_NAMESPACE: logger.info("ONLY_NAMESPACE: %s", ONLY_NAMESPACE)
def update_deployment(api, deployment, image_name, tag, dry_run):
old_tag = deployment.spec.template.spec.containers[0].image
deployment.spec.template.spec.containers[0].image = f"{image_name}:{tag}"
if old_tag == f"{image_name}:{tag}":
logger.info(f" Deployment Image already up-to-date: {old_tag}")
else:
if not dry_run:
api.replace_namespaced_deployment(bkp_depl, namespace.metadata.name, deployment)
logger.info(f" Updated Deployment - {deployment.metadata.name}")
else:
logger.info(f" Dry Run: Deployment would be updated - {deployment.metadata.name}")
def update_cronjob(cj, cronjob, image_name, tag, dry_run):
old_tag = cronjob.spec.job_template.spec.template.spec.containers[0].image
if old_tag == f"{image_name}:{tag}":
logger.info(f" Cronjob Image already up-to-date: {old_tag}")
else:
cronjob.spec.job_template.spec.template.spec.containers[0].image = f"{image_name}:{tag}"
if not dry_run:
cj.replace_namespaced_cron_job(bkp_cj, namespace.metadata.name, cronjob)
logger.info(f" Updated Cronjob updated - {cronjob.metadata.name}")
else:
logger.info(f" Dry Run: Cronjob would be updated - {cronjob.metadata.name}")
# switch to a specific k8s context provided by an environment variable
# multiple contexts can be provided as a space separated list
for context in CONTEXTS.split(" "):
logger.warning("Kubernetes Context: %s", context)
config.load_kube_config(context=context)
# Setup the k8s client
crd = client.CustomObjectsApi()
core_api = client.CoreV1Api()
api = client.AppsV1Api()
cj = client.BatchV1Api()
cratedbs = crd.list_cluster_custom_object(API_GROUP, "v1", RESOURCE_CRATEDB)
# Loop over all cratedbs in the k8s context
for cratedb in cratedbs['items']:
# Mostly a safety measure, this will ignore the specified namespaces
# but they should not have any crateDB CRDs
if cratedb['metadata']['namespace'] in IGNORED_NAMESPACES:
logger.warning("Ignoring namespace: %s", cratedb['metadata']['namespace'])
continue
# Mostly for testing purposes, this will only update the specified namespace
if ONLY_NAMESPACE:
if cratedb['metadata']['namespace'] != ONLY_NAMESPACE:
logger.warning("Skipping namespace: %s", cratedb['metadata']['namespace'])
continue
logger.info("// namespace: %s",cratedb["metadata"]["namespace"])
try:
logger.warning(" cratedb: %s %s", cratedb["metadata"]["name"], cratedb["spec"]["cluster"]["name"])
# Get the current deployment
namespace = core_api.read_namespace(cratedb['metadata']['namespace'])
bkp_depl = DEPLOYMENT_NAME + cratedb["metadata"]["name"]
deployment = api.read_namespaced_deployment(bkp_depl , namespace.metadata.name)
logger.info(" current Deployment: %s, %s", deployment.metadata.name, deployment.spec.template.spec.containers[0].image.split(":")[-1])
update_deployment(api, deployment, IMAGE_NAME, TAG, DRY_RUN)
# Now also update the responding cronjob
bkp_cj = CJ_NAME + cratedb["metadata"]["name"]
cronjob = cj.read_namespaced_cron_job(bkp_cj, namespace.metadata.name)
logger.info(" current Cronjob: %s %s", cronjob.metadata.name, cronjob.spec.job_template.spec.template.spec.containers[0].image.split(":")[-1])
#ic(cronjob.spec.job_template.spec.template.spec.containers[0].image)
update_cronjob(cj, cronjob, IMAGE_NAME, TAG, DRY_RUN)
except ApiException as e:
if e.status == 404:
logger.error(" backup-metrics deployment not found!")
else:
raise
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment