-
Notifications
You must be signed in to change notification settings - Fork 156
feat(dev): add lightweight KFP local dev cluster via k3d #753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 \ | ||
|
|
@@ -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)" | ||
| @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)" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also here: |
||
| @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 | ||
|
|
@@ -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)" | ||
|
|
||
| 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}" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. before this we should switch kubectl context - something like |
||
| 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 | ||
There was a problem hiding this comment.
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