Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
lint lint-backend lint-labextension format-labextension format-backend \
build \
kfp-build kfp-serve kfp-compile kfp-run \
kfp-dev-setup kfp-dev-start kfp-dev-stop kfp-dev-delete kfp-dev-status kfp-dev-upgrade \
clean clean-venv lock lock-upgrade check-uv \
jupyter jupyter-kfp watch-labextension \
docker-build docker-run \
Expand Down Expand Up @@ -148,6 +149,63 @@ kfp-run: ## Compile and run on KFP with local wheel (usage: make kfp-run NB=...
KALE_PIP_TRUSTED_HOSTS="$(KFP_HOST_ADDR)" \
$(UV) run kale --nb $(NB) --kfp_host $(KFP_HOST) --run_pipeline

##@ KFP Local Cluster (k3d — lightweight alternative to minikube)

KFP_CLUSTER_NAME ?= kale-kfp
KFP_PIPELINE_VERSION ?= 2.16.0
KFP_LOCAL_PORT ?= 8080
KFP_PID_FILE := $(CURDIR)/.kfp-dev-pf.pid

kfp-dev-setup: ## Create k3d cluster + install KFP standalone (~5 min, first time only)
@bash scripts/kfp-dev-setup.sh setup "$(KFP_CLUSTER_NAME)" "$(KFP_PIPELINE_VERSION)" "$(KFP_LOCAL_PORT)" "$(KFP_PID_FILE)"

kfp-dev-upgrade: ## Upgrade KFP on existing cluster (usage: make kfp-dev-upgrade KFP_PIPELINE_VERSION=2.17.0)
@bash scripts/kfp-dev-setup.sh upgrade "$(KFP_CLUSTER_NAME)" "$(KFP_PIPELINE_VERSION)" "$(KFP_LOCAL_PORT)" "$(KFP_PID_FILE)"

kfp-dev-start: ## Start existing cluster and port-forward KFP UI to localhost:8080
@printf "$(BLUE)Starting k3d cluster '$(KFP_CLUSTER_NAME)'...\n$(NC)"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here: kubectl config use-context k3d-kale-kfp

@k3d cluster start $(KFP_CLUSTER_NAME) 2>/dev/null || { \
printf "$(YELLOW)Cluster '$(KFP_CLUSTER_NAME)' not found. Run 'make kfp-dev-setup' first.\n$(NC)"; exit 1; \
}
@printf "$(BLUE)Switching kubectl context to k3d-$(KFP_CLUSTER_NAME)...\n$(NC)"
@kubectl config use-context k3d-$(KFP_CLUSTER_NAME)
@if [ -f $(KFP_PID_FILE) ]; then \
kill $$(cat $(KFP_PID_FILE)) 2>/dev/null || true; \
rm -f $(KFP_PID_FILE); \
fi
@printf "$(BLUE)Waiting for KFP pods to be ready...\n$(NC)"
@kubectl wait pods -l "application-crd-id=kubeflow-pipelines" \
--for condition=Ready --timeout=180s -n kubeflow 2>/dev/null || \
printf "$(YELLOW)Some pods may still be starting — check with: make kfp-dev-status\n$(NC)"
@kubectl port-forward -n kubeflow svc/ml-pipeline-ui $(KFP_LOCAL_PORT):80 >/dev/null 2>&1 & echo $$! > $(KFP_PID_FILE)
@sleep 2
@printf "$(GREEN)KFP UI: http://localhost:$(KFP_LOCAL_PORT)\n$(NC)"
@printf "$(GREEN)Run: make kfp-run NB=... KFP_HOST=http://localhost:$(KFP_LOCAL_PORT)\n$(NC)"

kfp-dev-stop: ## Stop port-forward and pause cluster (preserves all data)
@if [ -f $(KFP_PID_FILE) ]; then \
kill $$(cat $(KFP_PID_FILE)) 2>/dev/null || true; \
rm -f $(KFP_PID_FILE); \
printf "$(GREEN)Port-forward stopped\n$(NC)"; \
fi
@k3d cluster stop $(KFP_CLUSTER_NAME) 2>/dev/null || true
@printf "$(GREEN)Cluster paused. Run 'make kfp-dev-start' to resume.\n$(NC)"

kfp-dev-delete: ## Delete cluster and free all resources (irreversible)
@printf "$(YELLOW)Deleting cluster '$(KFP_CLUSTER_NAME)' and all KFP data...\n$(NC)"
@if [ -f $(KFP_PID_FILE) ]; then \
kill $$(cat $(KFP_PID_FILE)) 2>/dev/null || true; \
rm -f $(KFP_PID_FILE); \
fi
@k3d cluster delete $(KFP_CLUSTER_NAME) 2>/dev/null || true
@printf "$(GREEN)Cluster deleted. Run 'make kfp-dev-setup' to start fresh.\n$(NC)"

kfp-dev-status: ## Show cluster and KFP pod status
@printf "$(BLUE)k3d clusters:\n$(NC)"
@k3d cluster list 2>/dev/null || printf "$(YELLOW)k3d not installed\n$(NC)"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here: kubectl config use-context k3d-kale-kfp

@printf "\n$(BLUE)KFP pods (kubeflow namespace):\n$(NC)"
@kubectl get pods -n kubeflow 2>/dev/null || printf "$(YELLOW)Cluster not running or unreachable\n$(NC)"

##@ Cleanup

clean: ## Clean all build artifacts
Expand All @@ -157,6 +215,7 @@ clean: ## Clean all build artifacts
rm -rf labextension/lib labextension/node_modules
rm -rf jupyterlab_kubeflow_kale/labextension
rm -rf .kfp-wheels .kale
rm -f $(KFP_PID_FILE)
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
find . -type d -name .pytest_cache -exec rm -rf {} + 2>/dev/null || true
@printf "$(GREEN)Clean complete\n$(NC)"
Expand Down
218 changes: 218 additions & 0 deletions scripts/kfp-dev-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/usr/bin/env bash
# Copyright 2026 The Kubeflow Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Sets up a lightweight KFP standalone cluster using k3d (k3s in Docker).
# Requires: docker, kubectl. k3d is installed automatically if missing.
#
# Usage: bash scripts/kfp-dev-setup.sh [cluster-name] [kfp-version] [local-port] [pid-file]

set -euo pipefail

COMMAND="${1:-setup}" # setup | upgrade
CLUSTER_NAME="${2:-kale-kfp}"
KFP_VERSION="${3:-2.16.0}"
LOCAL_PORT="${4:-8080}"
PID_FILE="${5:-.kfp-dev-pf.pid}"

BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m'

info() { printf "${BLUE}%s${NC}\n" "$*"; }
ok() { printf "${GREEN}%s${NC}\n" "$*"; }
warn() { printf "${YELLOW}%s${NC}\n" "$*"; }
die() { printf "${RED}ERROR: %s${NC}\n" "$*" >&2; exit 1; }

# ---------------------------------------------------------------------------
# Prerequisites
# ---------------------------------------------------------------------------

check_prereqs() {
local missing=()
command -v docker >/dev/null 2>&1 || missing+=(docker)
command -v kubectl >/dev/null 2>&1 || missing+=(kubectl)

if [ ${#missing[@]} -gt 0 ]; then
die "Missing required tools: ${missing[*]}. Install them and re-run."
fi

if ! docker info >/dev/null 2>&1; then
die "Docker daemon is not running. Start Docker and re-run."
fi
}

ensure_k3d() {
if command -v k3d >/dev/null 2>&1; then
ok "k3d already installed ($(k3d version | head -1))"
return
fi

warn "k3d not found — installing via official install script..."
curl -fsSL https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
ok "k3d installed"
}

# ---------------------------------------------------------------------------
# Cluster
# ---------------------------------------------------------------------------

create_cluster() {
if k3d cluster list 2>/dev/null | grep -q "^${CLUSTER_NAME}[[:space:]]"; then
warn "Cluster '${CLUSTER_NAME}' already exists — skipping creation."
info "Starting cluster in case it was stopped..."
k3d cluster start "${CLUSTER_NAME}"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before this we should switch kubectl context - something like
kubectl config use-context k3d-kale-kfp

else
info "Creating k3d cluster '${CLUSTER_NAME}'..."
info "(Traefik ingress is disabled to save memory — KFP is accessed via port-forward)"
k3d cluster create "${CLUSTER_NAME}" \
--agents 1 \
--k3s-arg "--disable=traefik@server:0" \
--wait
ok "Cluster '${CLUSTER_NAME}' created"
fi

info "Switching kubectl context to k3d-${CLUSTER_NAME}..."
kubectl config use-context "k3d-${CLUSTER_NAME}"
ok "kubectl context set to k3d-${CLUSTER_NAME}"
}

# ---------------------------------------------------------------------------
# KFP deployment
# ---------------------------------------------------------------------------

_apply_kfp_manifests() {
info " [1/3] Applying cluster-scoped resources..."
kubectl apply -k \
"github.com/kubeflow/pipelines/manifests/kustomize/cluster-scoped-resources?ref=${KFP_VERSION}"

info " [2/3] Waiting for CRDs to be established..."
kubectl wait crd/applications.app.k8s.io \
--for condition=established \
--timeout=60s

info " [3/3] Applying platform-agnostic KFP manifest..."
kubectl apply -k \
"github.com/kubeflow/pipelines/manifests/kustomize/env/platform-agnostic?ref=${KFP_VERSION}"

info " Waiting for KFP pods to be Ready (up to 5 min — images are being pulled)..."
kubectl wait pods \
-l "application-crd-id=kubeflow-pipelines" \
--for condition=Ready \
--timeout=300s \
-n kubeflow
}

deploy_kfp() {
if kubectl get namespace kubeflow >/dev/null 2>&1 && \
kubectl get deploy -n kubeflow ml-pipeline >/dev/null 2>&1; then
warn "KFP already deployed in namespace 'kubeflow' — skipping."
warn "To upgrade KFP to v${KFP_VERSION}, run: make kfp-dev-upgrade"
return
fi

info "Deploying KFP v${KFP_VERSION} (platform-agnostic, no Argo/Docker executor)..."
_apply_kfp_manifests
ok "KFP v${KFP_VERSION} deployed"
}

upgrade_kfp() {
info "Upgrading KFP to v${KFP_VERSION} on cluster '${CLUSTER_NAME}'..."
info "Switching kubectl context to k3d-${CLUSTER_NAME}..."
kubectl config use-context "k3d-${CLUSTER_NAME}"
_apply_kfp_manifests
ok "KFP upgraded to v${KFP_VERSION}"
}

# ---------------------------------------------------------------------------
# Port-forward
# ---------------------------------------------------------------------------

start_port_forward() {
# Kill any existing port-forward for this session
if [ -f "${PID_FILE}" ]; then
kill "$(cat "${PID_FILE}")" 2>/dev/null || true
rm -f "${PID_FILE}"
fi

# Also kill any stale kubectl port-forward on the same port
pkill -f "kubectl port-forward.*ml-pipeline-ui.*${LOCAL_PORT}" 2>/dev/null || true

info "Starting port-forward: localhost:${LOCAL_PORT} → kubeflow/ml-pipeline-ui:80"
kubectl port-forward -n kubeflow svc/ml-pipeline-ui "${LOCAL_PORT}:80" >/dev/null 2>&1 &
echo $! > "${PID_FILE}"
sleep 2

# Smoke-test: the UI should respond
if curl -sf --max-time 5 "http://localhost:${LOCAL_PORT}" >/dev/null 2>&1; then
ok "Port-forward verified (HTTP 200)"
else
warn "Port-forward started but the UI did not respond yet — it may need another minute."
warn "Check status with: make kfp-dev-status"
fi
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

main() {
case "${COMMAND}" in
upgrade)
echo ""
info "======================================================="
info " Kale KFP upgrade"
info " Cluster: ${CLUSTER_NAME} | KFP: v${KFP_VERSION}"
info "======================================================="
echo ""
check_prereqs
upgrade_kfp
echo ""
ok "KFP upgraded to v${KFP_VERSION} on cluster '${CLUSTER_NAME}'."
echo ""
;;
setup|*)
echo ""
info "======================================================="
info " Kale KFP dev cluster setup"
info " Cluster: ${CLUSTER_NAME} | KFP: v${KFP_VERSION}"
info "======================================================="
echo ""
check_prereqs
ensure_k3d
create_cluster
deploy_kfp
start_port_forward
echo ""
ok "======================================================="
ok " KFP dev cluster is ready!"
ok "======================================================="
printf "${BLUE} UI: ${NC}http://localhost:${LOCAL_PORT}\n"
printf "${BLUE} Compile:${NC} make kfp-compile NB=notebook.ipynb\n"
printf "${BLUE} Run: ${NC} make kfp-run NB=notebook.ipynb KFP_HOST=http://localhost:${LOCAL_PORT}\n"
printf "${BLUE} Stop: ${NC} make kfp-dev-stop\n"
printf "${BLUE} Resume: ${NC} make kfp-dev-start\n"
printf "${BLUE} Upgrade:${NC} make kfp-dev-upgrade\n"
printf "${BLUE} Delete: ${NC} make kfp-dev-delete\n"
echo ""
warn "Memory tip: if Docker is slow, increase Docker Desktop memory in Settings → Resources."
warn "KFP requires ~2 GB. k3d overhead is ~512 MB on top of that."
echo ""
;;
esac
}

main
Loading