From 482255a7f16aa6c4c70e3a32c29dbb082ee4c584 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Fri, 20 Mar 2026 11:15:50 +0300 Subject: [PATCH] [cozystack-scheduler] Publish SchedulingClass demo ## What this PR does Publish the scripts used for running the Cozystack scheduler demo at the community meetup on 19.03.2026. ### Release note ```release-note [cozystack-scheduler] Add demo scripts to the cozystack-scheduler system packages. ``` --- .../examples/demo/00-helpers.sh | 121 ++++++++++++++++ .../examples/demo/01-scheduling-classes.sh | 133 ++++++++++++++++++ .../examples/demo/02-create-tenants.sh | 121 ++++++++++++++++ .../examples/demo/03-colocate-demo.sh | 69 +++++++++ .../examples/demo/04-colocate-second-redis.sh | 61 ++++++++ .../examples/demo/05-colocate-cleanup.sh | 24 ++++ .../examples/demo/06-onepernode-demo.sh | 92 ++++++++++++ .../demo/07-onepernode-second-redis.sh | 68 +++++++++ .../examples/demo/08-onepernode-cleanup.sh | 24 ++++ .../examples/demo/09-spread-demo.sh | 73 ++++++++++ .../examples/demo/10-spread-cleanup.sh | 24 ++++ .../examples/demo/11-node2-demo.sh | 94 +++++++++++++ .../examples/demo/12-node2-cleanup.sh | 23 +++ .../examples/demo/13-teardown.sh | 24 ++++ 14 files changed, 951 insertions(+) create mode 100755 packages/system/cozystack-scheduler/examples/demo/00-helpers.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/01-scheduling-classes.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/02-create-tenants.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/03-colocate-demo.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/04-colocate-second-redis.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/05-colocate-cleanup.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/06-onepernode-demo.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/07-onepernode-second-redis.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/08-onepernode-cleanup.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/09-spread-demo.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/10-spread-cleanup.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/11-node2-demo.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/12-node2-cleanup.sh create mode 100755 packages/system/cozystack-scheduler/examples/demo/13-teardown.sh diff --git a/packages/system/cozystack-scheduler/examples/demo/00-helpers.sh b/packages/system/cozystack-scheduler/examples/demo/00-helpers.sh new file mode 100755 index 00000000..1cd66446 --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/00-helpers.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# Helper functions for the scheduling classes demo +# Source this file in other scripts: source "$(dirname "$0")/00-helpers.sh" + +# ANSI color codes +export RED='\033[0;31m' +export GREEN='\033[0;32m' +export YELLOW='\033[1;33m' +export BLUE='\033[0;34m' +export MAGENTA='\033[0;35m' +export CYAN='\033[0;36m' +export WHITE='\033[1;37m' +export NC='\033[0m' # No Color +export BOLD='\033[1m' + +log_info() { + echo -e "${BLUE}ℹ${NC} $*" >&2 +} + +log_success() { + echo -e "${GREEN}✔${NC} $*" >&2 +} + +log_warning() { + echo -e "${YELLOW}⚠${NC} $*" >&2 +} + +log_error() { + echo -e "${RED}✖${NC} $*" >&2 +} + +log_step() { + echo -e "\n${MAGENTA}${BOLD}▶ $*${NC}" >&2 +} + +log_command() { + echo -e "${WHITE} \$ $*${NC}" >&2 +} + +separator() { + echo -e "\n${CYAN}────────────────────────────────────────────────────────────${NC}\n" >&2 +} + +print_header() { + local title="$1" + echo -e "\n${MAGENTA}${BOLD}╔════════════════════════════════════════════════════════════╗${NC}" >&2 + echo -e "${MAGENTA}${BOLD}║${NC} ${WHITE}${BOLD}$title${NC}" >&2 + echo -e "${MAGENTA}${BOLD}╚════════════════════════════════════════════════════════════╝${NC}\n" >&2 +} + +# Display a YAML manifest with syntax highlighting (just colored output) +show_manifest() { + echo -e "${YELLOW}$1${NC}" >&2 +} + +# Wait for user to press any key +pause() { + echo -e "\n${CYAN}Press any key to continue...${NC}" >&2 + read -rsn1 +} + +# Wait for pods to appear and stabilize +wait_for_pods() { + local namespace="$1" + local label="${2:-}" + local timeout="${3:-120}" + local elapsed=0 + + log_info "Waiting for pods in $namespace..." + while true; do + local count + if [[ -n "$label" ]]; then + count=$(kubectl get pods -n "$namespace" -l "$label" --no-headers 2>/dev/null | wc -l) + else + count=$(kubectl get pods -n "$namespace" --no-headers 2>/dev/null | wc -l) + fi + if [[ "$count" -gt 0 ]]; then + break + fi + if [[ $elapsed -ge $timeout ]]; then + log_warning "Timed out waiting for pods" + return 1 + fi + sleep 2 + elapsed=$((elapsed + 2)) + done + # Give pods a moment to get node assignments + sleep 3 +} + +# Show pods with node placement +show_pods() { + local namespace="$1" + echo "" >&2 + kubectl get pods -n "$namespace" -o wide 2>&1 >&2 + echo "" >&2 +} + +# Wait for a Redis resource to be ready (HelmRelease applied) +wait_for_redis_ready() { + local namespace="$1" + local name="$2" + local timeout="${3:-180}" + local elapsed=0 + + log_info "Waiting for Redis $name in $namespace to become ready..." + while true; do + local phase + phase=$(kubectl get redis "$name" -n "$namespace" -o jsonpath='{.status.conditions[0].status}' 2>/dev/null || echo "") + if [[ "$phase" == "True" ]]; then + log_success "Redis $name is ready" + break + fi + if [[ $elapsed -ge $timeout ]]; then + log_warning "Timed out waiting for Redis to be ready, continuing anyway..." + break + fi + sleep 5 + elapsed=$((elapsed + 5)) + done +} diff --git a/packages/system/cozystack-scheduler/examples/demo/01-scheduling-classes.sh b/packages/system/cozystack-scheduler/examples/demo/01-scheduling-classes.sh new file mode 100755 index 00000000..2ef6e977 --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/01-scheduling-classes.sh @@ -0,0 +1,133 @@ +#!/bin/bash +# Script 01: Create SchedulingClasses +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +print_header "Step 1: Create SchedulingClasses" + +# --- pin-to-node2 --- +log_step "SchedulingClass: pin-to-node2" +log_info "All pods will always be scheduled to node2." + +show_manifest "apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: pin-to-node2 +spec: + nodeSelector: + kubernetes.io/hostname: node2" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: pin-to-node2 +spec: + nodeSelector: + kubernetes.io/hostname: node2 +EOF + +log_success "Created SchedulingClass pin-to-node2" + +separator + +# --- one-per-node --- +log_step "SchedulingClass: one-per-node" +log_info "Pods of the same application will never share a node." + +show_manifest "apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: one-per-node +spec: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - topologyKey: kubernetes.io/hostname" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: one-per-node +spec: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - topologyKey: kubernetes.io/hostname +EOF + +log_success "Created SchedulingClass one-per-node" + +separator + +# --- spread-evenly --- +log_step "SchedulingClass: spread-evenly" +log_info "Pods will be spread evenly across nodes (maxSkew=1)." + +show_manifest "apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: spread-evenly +spec: + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: spread-evenly +spec: + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule +EOF + +log_success "Created SchedulingClass spread-evenly" + +separator + +# --- colocate --- +log_step "SchedulingClass: colocate" +log_info "All pods of the same application will land on the same node." + +show_manifest "apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: colocate +spec: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - topologyKey: kubernetes.io/hostname" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: cozystack.io/v1alpha1 +kind: SchedulingClass +metadata: + name: colocate +spec: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - topologyKey: kubernetes.io/hostname +EOF + +log_success "Created SchedulingClass colocate" + +separator +log_success "All SchedulingClasses created." +log_command "kubectl get schedulingclasses" +kubectl get schedulingclasses >&2 + +echo -e "\n${GREEN}${BOLD}Next step:${NC} ./02-create-tenants.sh" diff --git a/packages/system/cozystack-scheduler/examples/demo/02-create-tenants.sh b/packages/system/cozystack-scheduler/examples/demo/02-create-tenants.sh new file mode 100755 index 00000000..7a9d6b9d --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/02-create-tenants.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# Script 02: Create tenants, one per scheduling class +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +print_header "Step 2: Create Tenants" + +# --- tenant colocate --- +log_step "Tenant: colocate (schedulingClass: colocate)" + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: colocate + namespace: tenant-root +spec: + schedulingClass: colocate" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: colocate + namespace: tenant-root +spec: + schedulingClass: colocate +EOF + +log_success "Created tenant colocate" + +separator + +# --- tenant onepernode --- +log_step "Tenant: onepernode (schedulingClass: one-per-node)" + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: onepernode + namespace: tenant-root +spec: + schedulingClass: one-per-node" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: onepernode + namespace: tenant-root +spec: + schedulingClass: one-per-node +EOF + +log_success "Created tenant onepernode" + +separator + +# --- tenant spread --- +log_step "Tenant: spread (schedulingClass: spread-evenly)" + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: spread + namespace: tenant-root +spec: + schedulingClass: spread-evenly" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: spread + namespace: tenant-root +spec: + schedulingClass: spread-evenly +EOF + +log_success "Created tenant spread" + +separator + +# --- tenant node2 --- +log_step "Tenant: node2 (schedulingClass: pin-to-node2)" + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: node2 + namespace: tenant-root +spec: + schedulingClass: pin-to-node2" + +pause + +kubectl apply -f - <<'EOF' +apiVersion: apps.cozystack.io/v1alpha1 +kind: Tenant +metadata: + name: node2 + namespace: tenant-root +spec: + schedulingClass: pin-to-node2 +EOF + +log_success "Created tenant node2" + +separator +log_success "All tenants created." +log_command "kubectl get tenants -n tenant-root" +kubectl get tenants -n tenant-root >&2 + +echo -e "\n${GREEN}${BOLD}Next step:${NC} ./03-colocate-demo.sh" diff --git a/packages/system/cozystack-scheduler/examples/demo/03-colocate-demo.sh b/packages/system/cozystack-scheduler/examples/demo/03-colocate-demo.sh new file mode 100755 index 00000000..9019a00a --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/03-colocate-demo.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# Script 03: Colocate demo - all pods of a Redis land on the same node +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +NAMESPACE="tenant-colocate" +REDIS_NAME="redis-demo" + +print_header "Step 3: Colocate Demo" + +log_step "Creating Redis in $NAMESPACE (1 replica + 3 sentinels)" + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Redis +metadata: + name: $REDIS_NAME + namespace: $NAMESPACE +spec: + replicas: 1 + storageClass: local + resourcesPreset: nano + authEnabled: false" + +pause + +kubectl apply -f - <&2 +sleep 3 + +separator + +log_step "Creating a second Redis in $NAMESPACE" +log_info "This Redis will colocate its own pods together," +log_info "but may land on a DIFFERENT node than the first Redis." + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Redis +metadata: + name: $REDIS_NAME + namespace: $NAMESPACE +spec: + replicas: 1 + storageClass: local + resourcesPreset: nano + authEnabled: false" + +pause + +kubectl apply -f - <&2 + +log_info "Waiting for pods to terminate..." +sleep 10 + +log_step "Remaining pods:" +show_pods "$NAMESPACE" + +separator +log_success "Cleanup complete." +echo -e "\n${GREEN}${BOLD}Next step:${NC} ./06-onepernode-demo.sh" diff --git a/packages/system/cozystack-scheduler/examples/demo/06-onepernode-demo.sh b/packages/system/cozystack-scheduler/examples/demo/06-onepernode-demo.sh new file mode 100755 index 00000000..f656422a --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/06-onepernode-demo.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# Script 06: One-per-node demo - anti-affinity prevents pods on the same node +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +NAMESPACE="tenant-onepernode" +REDIS_NAME="redis-demo" + +print_header "Step 6: One-per-node Demo" + +log_step "Creating Redis in $NAMESPACE (1 replica)" +log_info "We'll also reduce sentinels to 2 (1 storage + 2 sentinels = 3 pods for 3 nodes)." + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Redis +metadata: + name: $REDIS_NAME + namespace: $NAMESPACE +spec: + replicas: 1 + storageClass: local + resourcesPreset: nano + authEnabled: false" + +pause + +kubectl apply -f - </dev/null || echo "") + +if [[ -n "$PENDING_POD" ]]; then + log_command "kubectl describe pod $PENDING_POD -n $NAMESPACE | tail -10" + kubectl describe pod "$PENDING_POD" -n "$NAMESPACE" 2>&1 | tail -10 >&2 +else + log_warning "No Pending pods found (pods may still be initializing)." + log_command "kubectl get pods -n $NAMESPACE -o wide" +fi + +separator +log_success "One-per-node demo complete. Anti-affinity prevents co-scheduling." +echo -e "\n${GREEN}${BOLD}Next step:${NC} ./07-onepernode-second-redis.sh" diff --git a/packages/system/cozystack-scheduler/examples/demo/07-onepernode-second-redis.sh b/packages/system/cozystack-scheduler/examples/demo/07-onepernode-second-redis.sh new file mode 100755 index 00000000..e86a07e8 --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/07-onepernode-second-redis.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# Script 07: Second Redis in one-per-node tenant - shows cross-app independence +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +NAMESPACE="tenant-onepernode" +REDIS_NAME="redis-demo2" + +print_header "Step 7: One-per-node - Second Redis (cross-app independence)" + +log_step "Cleaning up $REDIS_NAME if it already exists..." +kubectl delete redis "$REDIS_NAME" -n "$NAMESPACE" --ignore-not-found >&2 +sleep 3 + +separator + +log_step "Creating a second Redis in $NAMESPACE" +log_info "Anti-affinity only applies WITHIN the same application." +log_info "This second Redis has no anti-affinity against the first one." + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Redis +metadata: + name: $REDIS_NAME + namespace: $NAMESPACE +spec: + replicas: 1 + storageClass: local + resourcesPreset: nano + authEnabled: false" + +pause + +kubectl apply -f - <&2 + +log_info "Waiting for pods to terminate..." +sleep 10 + +log_step "Remaining pods:" +show_pods "$NAMESPACE" + +separator +log_success "Cleanup complete." +echo -e "\n${GREEN}${BOLD}Next step:${NC} ./09-spread-demo.sh" diff --git a/packages/system/cozystack-scheduler/examples/demo/09-spread-demo.sh b/packages/system/cozystack-scheduler/examples/demo/09-spread-demo.sh new file mode 100755 index 00000000..91d3b35f --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/09-spread-demo.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Script 09: Spread-evenly demo - topology spread constraints +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +NAMESPACE="tenant-spread" +REDIS_NAME="redis-demo" + +print_header "Step 9: Spread-evenly Demo" + +log_step "Creating Redis in $NAMESPACE (1 replica)" + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Redis +metadata: + name: $REDIS_NAME + namespace: $NAMESPACE +spec: + replicas: 1 + storageClass: local + resourcesPreset: nano + authEnabled: false" + +pause + +kubectl apply -f - </dev/null \ + | awk '{print $7}' | sort | uniq -c | sort -rn >&2 + +separator +log_success "Spread-evenly demo complete. Pods are balanced across nodes." +echo -e "\n${GREEN}${BOLD}Next step:${NC} ./10-spread-cleanup.sh" diff --git a/packages/system/cozystack-scheduler/examples/demo/10-spread-cleanup.sh b/packages/system/cozystack-scheduler/examples/demo/10-spread-cleanup.sh new file mode 100755 index 00000000..9049aff5 --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/10-spread-cleanup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Script 10: Clean up Redis instances in tenant-spread +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +NAMESPACE="tenant-spread" + +print_header "Step 10: Clean up tenant-spread" + +log_step "Deleting all Redis instances in $NAMESPACE..." + +kubectl delete redis --all -n "$NAMESPACE" --ignore-not-found >&2 + +log_info "Waiting for pods to terminate..." +sleep 10 + +log_step "Remaining pods:" +show_pods "$NAMESPACE" + +separator +log_success "Cleanup complete." +echo -e "\n${GREEN}${BOLD}Next step:${NC} ./11-node2-demo.sh" diff --git a/packages/system/cozystack-scheduler/examples/demo/11-node2-demo.sh b/packages/system/cozystack-scheduler/examples/demo/11-node2-demo.sh new file mode 100755 index 00000000..a3ae32ec --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/11-node2-demo.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# Script 11: Pin-to-node2 demo - all pods on a specific node +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +NAMESPACE="tenant-node2" +REDIS1="redis-demo" +REDIS2="redis-demo2" + +print_header "Step 11: Pin-to-node2 Demo" + +log_step "Creating first Redis in $NAMESPACE" + +show_manifest "apiVersion: apps.cozystack.io/v1alpha1 +kind: Redis +metadata: + name: $REDIS1 + namespace: $NAMESPACE +spec: + replicas: 1 + storageClass: local + resourcesPreset: nano + authEnabled: false" + +pause + +kubectl apply -f - <&2 + +log_info "Waiting for pods to terminate..." +sleep 10 + +log_step "Remaining pods:" +show_pods "$NAMESPACE" + +separator +log_success "Cleanup complete. Demo finished!" diff --git a/packages/system/cozystack-scheduler/examples/demo/13-teardown.sh b/packages/system/cozystack-scheduler/examples/demo/13-teardown.sh new file mode 100755 index 00000000..529df7b2 --- /dev/null +++ b/packages/system/cozystack-scheduler/examples/demo/13-teardown.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Script 13: Tear down all tenants and scheduling classes +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/00-helpers.sh" + +print_header "Teardown: Remove tenants and scheduling classes" + +log_step "Deleting tenants..." +for t in colocate onepernode spread node2; do + kubectl delete tenant "$t" -n tenant-root --ignore-not-found >&2 +done + +log_info "Waiting for tenant namespaces to be cleaned up..." +sleep 15 + +log_step "Deleting scheduling classes..." +for sc in colocate one-per-node spread-evenly pin-to-node2; do + kubectl delete schedulingclass "$sc" --ignore-not-found >&2 +done + +separator +log_success "Teardown complete."