From dacf27348ae1446c3c93d0ee2fc57702c5366eac Mon Sep 17 00:00:00 2001 From: Romil Bhardwaj Date: Fri, 27 Sep 2024 13:42:22 -0700 Subject: [PATCH] [Docs] Deployment on existing infra (#3926) * add onprem guide and script * add onprem guide and script * wip * updates * Updates and add figure * update show-gpus * Add deploy_remote_cluster.sh * Add deploy_remote_cluster.sh * Add deploy_remote_cluster.sh * Add deploy_remote_cluster.sh * Add light/dark images * updates * fix light dark images * updates * comments * comments * lint * existing clusters -> existing machines * Update images * Update images * comments and type hints * newline * comments --- docs/source/_static/custom.js | 1 + docs/source/docs/index.rst | 1 + .../sky-existing-infra-workflow-dark.png | Bin 0 -> 46000 bytes .../sky-existing-infra-workflow-light.png | Bin 0 -> 43221 bytes .../kubernetes/kubernetes-deployment.rst | 5 +- .../source/reservations/existing-machines.rst | 153 +++++++++++ docs/source/reservations/reservations.rst | 2 +- sky/cli.py | 128 ++++++++- sky/utils/kubernetes/deploy_remote_cluster.sh | 243 ++++++++++++++++++ sky/utils/log_utils.py | 98 ++++++- 10 files changed, 610 insertions(+), 21 deletions(-) create mode 100644 docs/source/images/sky-existing-infra-workflow-dark.png create mode 100644 docs/source/images/sky-existing-infra-workflow-light.png create mode 100644 docs/source/reservations/existing-machines.rst create mode 100755 sky/utils/kubernetes/deploy_remote_cluster.sh diff --git a/docs/source/_static/custom.js b/docs/source/_static/custom.js index 3e5653295e0..1fa28105186 100644 --- a/docs/source/_static/custom.js +++ b/docs/source/_static/custom.js @@ -32,6 +32,7 @@ document.addEventListener('DOMContentLoaded', () => { { selector: '.toctree-l1 > a', text: 'Reserved, Capacity Blocks, DWS' }, { selector: '.toctree-l1 > a', text: 'Llama 3.2 (Meta)' }, { selector: '.toctree-l1 > a', text: 'Admin Policy Enforcement' }, + { selector: '.toctree-l1 > a', text: 'Using Existing Machines' }, ]; newItems.forEach(({ selector, text }) => { document.querySelectorAll(selector).forEach((el) => { diff --git a/docs/source/docs/index.rst b/docs/source/docs/index.rst index 6bf2d889582..d83bf7821c3 100644 --- a/docs/source/docs/index.rst +++ b/docs/source/docs/index.rst @@ -149,6 +149,7 @@ Read the research: :caption: Reserved & Existing Clusters ../reservations/reservations + Using Existing Machines <../reservations/existing-machines> ../reference/kubernetes/index .. toctree:: diff --git a/docs/source/images/sky-existing-infra-workflow-dark.png b/docs/source/images/sky-existing-infra-workflow-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..aaf4245bcf38a7f13f07a53c4eec68556b76e613 GIT binary patch literal 46000 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 v{{Q>;>)Pa-TgbF-?jtaHmnj>)ZbZVU0Wj literal 0 HcmV?d00001 diff --git a/docs/source/images/sky-existing-infra-workflow-light.png b/docs/source/images/sky-existing-infra-workflow-light.png new file mode 100644 index 0000000000000000000000000000000000000000..b2cf42b48787d47b738ea5aea41ad8f94b721afa GIT binary patch literal 43221 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%0D6A)L9Y5d-R-6DUe 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 guide to set up SkyPilot on your on-prem cluster. + +Alternatively, you can also deploy Kubernetes on your on-prem clusters using off-the-shelf tools, such as `kubeadm `_, `k3s `_ or `Rancher `_. diff --git a/docs/source/reservations/existing-machines.rst b/docs/source/reservations/existing-machines.rst new file mode 100644 index 00000000000..2f9ac2a2441 --- /dev/null +++ b/docs/source/reservations/existing-machines.rst @@ -0,0 +1,153 @@ +.. _existing-machines: + +Deploy SkyPilot on existing machines +==================================== + +This guide will help you deploy SkyPilot on your existing machines — whether they are on-premises or reserved instances on a cloud provider. + +**Given a list of IP addresses and SSH credentials,** +SkyPilot will install necessary dependencies on the remote machines and configure itself to run jobs and services on the cluster. + +.. + Figure v1 (for deploy.sh): https://docs.google.com/drawings/d/1Jp1tTu1kxF-bIrS6LRMqoJ1dnxlFvn-iobVsXElXfAg/edit?usp=sharing + Figure v2: https://docs.google.com/drawings/d/1hMvOe1HX0ESoUbCvUowla2zO5YBacsdruo0dFqML9vo/edit?usp=sharing + Figure v2 Dark: https://docs.google.com/drawings/d/1AEdf9i3SO6MVnD7d-hwRumIfVndzNDqQmrFvRwwVEiU/edit + +.. figure:: ../images/sky-existing-infra-workflow-light.png + :width: 85% + :align: center + :alt: Deploying SkyPilot on existing machines + :class: no-scaled-link, only-light + + Given a list of IP addresses and SSH keys, ``sky local up`` will install necessary dependencies on the remote machines and configure SkyPilot to run jobs and services on the cluster. + +.. figure:: ../images/sky-existing-infra-workflow-dark.png + :width: 85% + :align: center + :alt: Deploying SkyPilot on existing machines + :class: no-scaled-link, only-dark + + Given a list of IP addresses and SSH keys, ``sky local up`` will install necessary dependencies on the remote machines and configure SkyPilot to run jobs and services on the cluster. + + +.. note:: + + Behind the scenes, SkyPilot deploys a lightweight Kubernetes cluster on the remote machines using `k3s `_. + + **Note that no Kubernetes knowledge is required for running this guide.** SkyPilot abstracts away the complexity of Kubernetes and provides a simple interface to run your jobs and services. + +Prerequisites +------------- + +**Local machine (typically your laptop):** + +* `kubectl `_ +* `SkyPilot `_ + +**Remote machines (your cluster, optionally with GPUs):** + +* Debian-based OS (tested on Debian 11) +* SSH access from local machine to all remote machines with key-based authentication and passwordless sudo +* All machines must use the same SSH key and username +* All machines must have network access to each other +* Port 6443 must be accessible on at least one node from your local machine + +Deploying SkyPilot +------------------ + +1. Create a file ``ips.txt`` with the IP addresses of your machines with one IP per line. + The first node will be used as the head node — this node must have port 6443 accessible from your local machine. + + Here is an example ``ips.txt`` file: + + .. code-block:: text + + 192.168.1.1 + 192.168.1.2 + 192.168.1.3 + + In this example, the first node (``192.168.1.1``) has port 6443 open and will be used as the head node. + +2. Run ``sky local up`` and pass the ``ips.txt`` file, SSH username, and SSH key as arguments: + + .. code-block:: bash + + IP_FILE=ips.txt + SSH_USER=username + SSH_KEY=path/to/ssh/key + sky local up --ips $IP_FILE --ssh-user SSH_USER --ssh-key-path $SSH_KEY + + SkyPilot will deploy a Kubernetes cluster on the remote machines, set up GPU support, configure Kubernetes credentials on your local machine, and set up SkyPilot to operate with the new cluster. + + Example output of ``sky local up``: + + .. code-block:: console + + $ sky local up --ips ips.txt --ssh-user gcpuser --ssh-key-path ~/.ssh/id_rsa + Found existing kube config. It will be backed up to ~/.kube/config.bak. + To view detailed progress: tail -n100 -f ~/sky_logs/sky-2024-09-23-18-53-14-165534/local_up.log + ✔ K3s successfully deployed on head node. + ✔ K3s successfully deployed on worker node. + ✔ kubectl configured for the remote cluster. + ✔ Remote k3s is running. + ✔ Nvidia GPU Operator installed successfully. + Cluster deployment done. You can now run tasks on this cluster. + E.g., run a task with: sky launch --cloud kubernetes -- echo hello world. + 🎉 Remote cluster deployed successfully. + + +4. To verify that the cluster is running, run: + + .. code-block:: bash + + sky check kubernetes + + You can now use SkyPilot to launch your :ref:`development clusters ` and :ref:`training jobs ` on your own infrastructure. + + .. code-block:: console + + $ sky show-gpus --cloud kubernetes + Kubernetes GPUs + GPU QTY_PER_NODE TOTAL_GPUS TOTAL_FREE_GPUS + L4 1, 2, 4 12 12 + H100 1, 2, 4, 8 16 16 + + Kubernetes per node GPU availability + NODE_NAME GPU_NAME TOTAL_GPUS FREE_GPUS + my-cluster-0 L4 4 4 + my-cluster-1 L4 4 4 + my-cluster-2 L4 2 2 + my-cluster-3 L4 2 2 + my-cluster-4 H100 8 8 + my-cluster-5 H100 8 8 + + $ sky launch --cloud kubernetes --gpus H100:1 -- nvidia-smi + + .. tip:: + + You can also use ``kubectl`` to interact and perform administrative operations on the cluster. + +What happens behind the scenes? +------------------------------- + +When you run ``sky local up``, SkyPilot runs the following operations: + +1. Install and run `k3s `_ Kubernetes distribution as a systemd service on the remote machines. +2. [If GPUs are present] Install `Nvidia GPU Operator `_ on the newly provisioned k3s cluster. Note that this step does not modify your local nvidia driver/cuda installation, and only runs inside the cluster. +3. Expose the Kubernetes API server on the head node over port 6443. API calls are on this port are secured with a key pair generated by the cluster. +4. Configure ``kubectl`` on your local machine to connect to the remote cluster. + + +Cleanup +------- + +To clean up all state created by SkyPilot on your machines, use the ``--cleanup`` flag: + +.. code-block:: bash + + IP_FILE=ips.txt + SSH_USER=username + SSH_KEY=path/to/ssh/key + sky local up --ip $IP_FILE --ssh-user SSH_USER --ssh-key-path $SSH_KEY --cleanup + +This will stop all Kubernetes services on the remote machines. diff --git a/docs/source/reservations/reservations.rst b/docs/source/reservations/reservations.rst index 8d0625846f7..800a34c802c 100644 --- a/docs/source/reservations/reservations.rst +++ b/docs/source/reservations/reservations.rst @@ -204,5 +204,5 @@ Unlike short-term reservations above, long-term reservations are typically more SkyPilot supports long-term reservations and on-premise clusters through Kubernetes, i.e., you can set up a Kubernetes cluster on top of your reserved resources and interact with them through SkyPilot. -See the simple steps to set up a Kubernetes cluster on existing machines in :ref:`kubernetes-overview`. +See the simple steps to set up a Kubernetes cluster on existing machines in :ref:`Using Existing Machines ` or :ref:`bring your existing Kubernetes cluster `. diff --git a/sky/cli.py b/sky/cli.py index f334a4181b8..c538c99aeb3 100644 --- a/sky/cli.py +++ b/sky/cli.py @@ -5072,15 +5072,7 @@ def local(): pass -@click.option('--gpus/--no-gpus', - default=True, - is_flag=True, - help='Launch cluster without GPU support even ' - 'if GPUs are detected on the host.') -@local.command('up', cls=_DocumentedCodeCommand) -@usage_lib.entrypoint -def local_up(gpus: bool): - """Creates a local cluster.""" +def _deploy_local_cluster(gpus: bool): cluster_created = False # Check if GPUs are available on the host @@ -5206,6 +5198,124 @@ def local_up(gpus: bool): f'{gpu_hint}') +def _deploy_remote_cluster(ip_file: str, ssh_user: str, ssh_key_path: str, + cleanup: bool): + success = False + path_to_package = os.path.dirname(os.path.dirname(__file__)) + up_script_path = os.path.join(path_to_package, 'sky/utils/kubernetes', + 'deploy_remote_cluster.sh') + # Get directory of script and run it from there + cwd = os.path.dirname(os.path.abspath(up_script_path)) + + deploy_command = f'{up_script_path} {ip_file} {ssh_user} {ssh_key_path}' + if cleanup: + deploy_command += ' --cleanup' + + # Convert the command to a format suitable for subprocess + deploy_command = shlex.split(deploy_command) + + # Setup logging paths + run_timestamp = backend_utils.get_run_timestamp() + log_path = os.path.join(constants.SKY_LOGS_DIRECTORY, run_timestamp, + 'local_up.log') + tail_cmd = 'tail -n100 -f ' + log_path + + # Check if ~/.kube/config exists: + if os.path.exists(os.path.expanduser('~/.kube/config')): + click.echo('Found existing kube config. ' + 'It will be backed up to ~/.kube/config.bak.') + style = colorama.Style + click.echo('To view detailed progress: ' + f'{style.BRIGHT}{tail_cmd}{style.RESET_ALL}') + if cleanup: + msg_str = 'Cleaning up remote cluster...' + else: + msg_str = 'Deploying remote cluster...' + with rich_utils.safe_status(f'[bold cyan]{msg_str}'): + returncode, _, stderr = log_lib.run_with_log( + cmd=deploy_command, + log_path=log_path, + require_outputs=True, + stream_logs=False, + line_processor=log_utils.SkyRemoteUpLineProcessor(), + cwd=cwd) + if returncode == 0: + success = True + else: + with ux_utils.print_exception_no_traceback(): + raise RuntimeError( + 'Failed to deploy remote cluster. ' + f'Full log: {log_path}' + f'\nError: {style.BRIGHT}{stderr}{style.RESET_ALL}') + + if success: + if cleanup: + click.echo(f'{colorama.Fore.GREEN}' + '🎉 Remote cluster cleaned up successfully.' + f'{style.RESET_ALL}') + else: + click.echo('Cluster deployment done. You can now run tasks on ' + 'this cluster.\nE.g., run a task with: ' + 'sky launch --cloud kubernetes -- echo hello world.' + f'\n{colorama.Fore.GREEN}🎉 Remote cluster deployed ' + f'successfully. {style.RESET_ALL}') + + +@click.option('--gpus/--no-gpus', + default=True, + is_flag=True, + help='Launch cluster without GPU support even ' + 'if GPUs are detected on the host.') +@click.option( + '--ips', + type=str, + required=False, + help='Path to the file containing IP addresses of remote machines.') +@click.option('--ssh-user', + type=str, + required=False, + help='SSH username for accessing remote machines.') +@click.option('--ssh-key-path', + type=str, + required=False, + help='Path to the SSH private key.') +@click.option('--cleanup', + is_flag=True, + help='Clean up the remote cluster instead of deploying it.') +@local.command('up', cls=_DocumentedCodeCommand) +@usage_lib.entrypoint +def local_up(gpus: bool, ips: str, ssh_user: str, ssh_key_path: str, + cleanup: bool): + """Creates a local or remote cluster.""" + + def _validate_args(ips, ssh_user, ssh_key_path, cleanup): + # If any of --ips, --ssh-user, or --ssh-key-path is specified, + # all must be specified + if bool(ips) or bool(ssh_user) or bool(ssh_key_path): + if not (ips and ssh_user and ssh_key_path): + raise click.BadParameter( + 'All --ips, --ssh-user, and --ssh-key-path ' + 'must be specified together.') + + # --cleanup can only be used if --ips, --ssh-user and --ssh-key-path + # are all provided + if cleanup and not (ips and ssh_user and ssh_key_path): + raise click.BadParameter('--cleanup can only be used with ' + '--ips, --ssh-user and --ssh-key-path.') + + _validate_args(ips, ssh_user, ssh_key_path, cleanup) + + # If remote deployment arguments are specified, run remote up script + if ips and ssh_user and ssh_key_path: + # Convert ips and ssh_key_path to absolute paths + ips = os.path.abspath(ips) + ssh_key_path = os.path.abspath(ssh_key_path) + _deploy_remote_cluster(ips, ssh_user, ssh_key_path, cleanup) + else: + # Run local deployment (kind) if no remote args are specified + _deploy_local_cluster(gpus) + + @local.command('down', cls=_DocumentedCodeCommand) @usage_lib.entrypoint def local_down(): diff --git a/sky/utils/kubernetes/deploy_remote_cluster.sh b/sky/utils/kubernetes/deploy_remote_cluster.sh new file mode 100755 index 00000000000..94736474289 --- /dev/null +++ b/sky/utils/kubernetes/deploy_remote_cluster.sh @@ -0,0 +1,243 @@ +#!/bin/bash +# Refer to https://skypilot.readthedocs.io/en/latest/reservations/existing-machines.html for details on how to use this script. +set -e + +# Colors for nicer UX +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No color + +# Variables +IPS_FILE=$1 +USER=$2 +SSH_KEY=$3 +K3S_TOKEN=mytoken # Any string can be used as the token +CLEANUP=false +INSTALL_GPU=false + +if [[ "$4" == "--cleanup" ]]; then + CLEANUP=true +fi + +# Basic argument checks +if [ -z "$IPS_FILE" ] || [ -z "$USER" ] || [ -z "$SSH_KEY" ]; then + >&2 echo -e "${RED}Error: Missing required arguments.${NC}" + >&2 echo "Usage: ./deploy_remote_cluster.sh ips.txt username path/to/ssh/key [--cleanup]" + exit 1 +fi + +# Check if SSH key exists +if [ ! -f "$SSH_KEY" ]; then + >&2 echo -e "${RED}Error: SSH key not found: $SSH_KEY${NC}" + exit 1 +fi + +# Check if IPs file exists +if [ ! -f "$IPS_FILE" ]; then + >&2 echo -e "${RED}Error: IPs file not found: $IPS_FILE${NC}" + exit 1 +fi + +# Get head node and worker nodes from the IPs file +HEAD_NODE=$(head -n 1 "$IPS_FILE") +WORKER_NODES=$(tail -n +2 "$IPS_FILE") + +# Check if the IPs file is empty or not formatted correctly +if [ -z "$HEAD_NODE" ]; then + >&2 echo -e "${RED}Error: IPs file is empty or not formatted correctly.${NC}" + exit 1 +fi + +# Function to show a progress message +progress_message() { + echo -e "${YELLOW}➜ $1${NC}" +} + +# Step to display success +success_message() { + echo -e "${GREEN}✔ $1${NC}" +} + +# Function to run a command on a remote machine via SSH +run_remote() { + local NODE_IP=$1 + local CMD=$2 + # echo -e "${YELLOW}Running command on $NODE_IP...${NC}" + ssh -o StrictHostKeyChecking=no -i "$SSH_KEY" "$USER@$NODE_IP" "$CMD" +} + +# Function to uninstall k3s and clean up the state on a remote machine +cleanup_server_node() { + local NODE_IP=$1 + echo -e "${YELLOW}Cleaning up head node $NODE_IP...${NC}" + run_remote "$NODE_IP" " + echo 'Uninstalling k3s...' && + /usr/local/bin/k3s-uninstall.sh || true && + sudo rm -rf /etc/rancher /var/lib/rancher /var/lib/kubelet /etc/kubernetes ~/.kube + " + echo -e "${GREEN}Node $NODE_IP cleaned up successfully.${NC}" +} + +# Function to uninstall k3s and clean up the state on a remote machine +cleanup_agent_node() { + local NODE_IP=$1 + echo -e "${YELLOW}Cleaning up node $NODE_IP...${NC}" + run_remote "$NODE_IP" " + echo 'Uninstalling k3s...' && + /usr/local/bin/k3s-agent-uninstall.sh || true && + sudo rm -rf /etc/rancher /var/lib/rancher /var/lib/kubelet /etc/kubernetes ~/.kube + " + echo -e "${GREEN}Node $NODE_IP cleaned up successfully.${NC}" +} + +check_gpu() { + local NODE_IP=$1 + run_remote "$NODE_IP" " + if command -v nvidia-smi &> /dev/null; then + nvidia-smi --list-gpus | grep 'GPU 0' + fi + " +} + +# Pre-flight checks +run_remote "$HEAD_NODE" "echo 'SSH connection successful'" +# TODO: Add more pre-flight checks here, including checking if port 6443 is accessible + +# If --cleanup flag is set, uninstall k3s and exit +if [ "$CLEANUP" == "true" ]; then + echo -e "${YELLOW}Starting cleanup...${NC}" + + # Clean up head node + cleanup_server_node "$HEAD_NODE" + + # Clean up worker nodes + for NODE in $WORKER_NODES; do + cleanup_agent_node "$NODE" + done + + echo -e "${GREEN}Cleanup completed successfully.${NC}" + exit 0 +fi + +# Step 1: Install k3s on the head node +progress_message "Deploying Kubernetes on head node ($HEAD_NODE)..." +run_remote "$HEAD_NODE" " + curl -sfL https://get.k3s.io | K3S_TOKEN=$K3S_TOKEN sh - && + mkdir -p ~/.kube && + sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config && + sudo chown \$(id -u):\$(id -g) ~/.kube/config && + for i in {1..3}; do + if kubectl wait --for=condition=ready node --all --timeout=2m --kubeconfig ~/.kube/config; then + break + else + echo 'Waiting for nodes to be ready...' + sleep 5 + fi + done + if [ $i -eq 3 ]; then + echo 'Failed to wait for nodes to be ready after 3 attempts' + exit 1 + fi" +success_message "K3s deployed on head node." + +# Check if head node has a GPU +if check_gpu "$HEAD_NODE"; then + echo -e "${YELLOW}GPU detected on head node ($HEAD_NODE).${NC}" + INSTALL_GPU=true +fi + +# Fetch the head node's internal IP (this will be passed to worker nodes) +MASTER_ADDR=$(run_remote "$HEAD_NODE" "hostname -I | awk '{print \$1}'") + +echo -e "${GREEN}Master node internal IP: $MASTER_ADDR${NC}" + +# Step 2: Install k3s on worker nodes and join them to the master node +for NODE in $WORKER_NODES; do + progress_message "Deploying Kubernetes on worker node ($NODE)..." + run_remote "$NODE" " + curl -sfL https://get.k3s.io | K3S_URL=https://$MASTER_ADDR:6443 K3S_TOKEN=$K3S_TOKEN sh -" + success_message "Kubernetes deployed on worker node ($NODE)." + + # Check if worker node has a GPU + if check_gpu "$NODE"; then + echo -e "${YELLOW}GPU detected on worker node ($NODE).${NC}" + INSTALL_GPU=true + fi +done +# Step 3: Configure local kubectl to connect to the cluster +progress_message "Configuring local kubectl to connect to the cluster..." +scp -o StrictHostKeyChecking=no -i "$SSH_KEY" "$USER@$HEAD_NODE":~/.kube/config ~/.kube/config + +# Back up the original kubeconfig file if it exists +KUBECONFIG_FILE="$HOME/.kube/config" +if [[ -f "$KUBECONFIG_FILE" ]]; then + echo "Backing up existing kubeconfig to $KUBECONFIG_FILE.bak" + cp "$KUBECONFIG_FILE" "$KUBECONFIG_FILE.bak" +fi + +# Update kubeconfig for the local machine to use the master node's IP +# Temporary file to hold the modified kubeconfig +TEMP_FILE=$(mktemp) + +# Remove the certificate-authority-data, and replace the server with the master address +awk ' + BEGIN { in_cluster = 0 } + /^clusters:/ { in_cluster = 1 } + /^users:/ { in_cluster = 0 } + in_cluster && /^ *certificate-authority-data:/ { next } + in_cluster && /^ *server:/ { + print " server: https://'${HEAD_NODE}:6443'" + print " insecure-skip-tls-verify: true" + next + } + { print } +' "$KUBECONFIG_FILE" > "$TEMP_FILE" + +# Replace the original kubeconfig with the modified one +mv "$TEMP_FILE" "$KUBECONFIG_FILE" + +success_message "kubectl configured to connect to the cluster." + +echo "Cluster deployment completed. You can now run 'kubectl get nodes' to verify the setup." + +# Install GPU operator if a GPU was detected on any node +if [ "$INSTALL_GPU" == "true" ]; then + echo -e "${YELLOW}GPU detected in the cluster. Installing Nvidia GPU Operator...${NC}" + run_remote "$HEAD_NODE" " + curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 && + chmod 700 get_helm.sh && + ./get_helm.sh && + helm repo add nvidia https://helm.ngc.nvidia.com/nvidia && helm repo update && + kubectl create namespace gpu-operator --kubeconfig ~/.kube/config || true && + sudo ln -s /sbin/ldconfig /sbin/ldconfig.real || true && + helm install gpu-operator -n gpu-operator --create-namespace nvidia/gpu-operator \ + --set 'toolkit.env[0].name=CONTAINERD_CONFIG' \ + --set 'toolkit.env[0].value=/var/lib/rancher/k3s/agent/etc/containerd/config.toml' \ + --set 'toolkit.env[1].name=CONTAINERD_SOCKET' \ + --set 'toolkit.env[1].value=/run/k3s/containerd/containerd.sock' \ + --set 'toolkit.env[2].name=CONTAINERD_RUNTIME_CLASS' \ + --set 'toolkit.env[2].value=nvidia' && + echo 'Waiting for GPU operator installation...' && + while ! kubectl describe nodes --kubeconfig ~/.kube/config | grep -q 'nvidia.com/gpu:'; do + echo 'Waiting for GPU operator...' + sleep 5 + done + echo 'GPU operator installed successfully.'" + success_message "GPU Operator installed." +else + echo -e "${YELLOW}No GPUs detected. Skipping GPU Operator installation.${NC}" +fi + +# Configure SkyPilot +progress_message "Configuring SkyPilot..." +sky check kubernetes +success_message "SkyPilot configured successfully." + +# Display final success message +echo -e "${GREEN}==== 🎉 Kubernetes cluster deployment completed successfully 🎉 ====${NC}" +echo "You can now interact with your Kubernetes cluster through SkyPilot: " +echo " • List available GPUs: sky show-gpus --cloud kubernetes" +echo " • Launch a GPU development pod: sky launch -c devbox --cloud kubernetes --gpus A100:1" +echo " • Connect to pod with SSH: ssh devbox" +echo " • Connect to pod with VSCode: code --remote ssh-remote+devbox '/'" diff --git a/sky/utils/log_utils.py b/sky/utils/log_utils.py index 90928b8014d..8f7a152392e 100644 --- a/sky/utils/log_utils.py +++ b/sky/utils/log_utils.py @@ -1,6 +1,7 @@ """Logging utils.""" import enum -from typing import List, Optional +import types +from typing import List, Optional, Type import colorama import pendulum @@ -15,13 +16,15 @@ class LineProcessor(object): """A processor for log lines.""" - def __enter__(self): + def __enter__(self) -> None: pass - def process_line(self, log_line): + def process_line(self, log_line: str) -> None: pass - def __exit__(self, except_type, except_value, traceback): + def __exit__(self, except_type: Optional[Type[BaseException]], + except_value: Optional[BaseException], + traceback: Optional[types.TracebackType]) -> None: del except_type, except_value, traceback # unused pass @@ -34,12 +37,12 @@ class ProvisionStatus(enum.Enum): RUNTIME_SETUP = 1 PULLING_DOCKER_IMAGES = 2 - def __enter__(self): + def __enter__(self) -> None: self.state = self.ProvisionStatus.LAUNCH self.status_display = rich_utils.safe_status('[bold cyan]Launching') self.status_display.start() - def process_line(self, log_line): + def process_line(self, log_line: str) -> None: if ('Success.' in log_line and self.state == self.ProvisionStatus.LAUNCH): logger.info(f'{colorama.Fore.GREEN}Head node is up.' @@ -60,7 +63,9 @@ def process_line(self, log_line): '[bold cyan]Launching - Preparing SkyPilot runtime') self.state = self.ProvisionStatus.RUNTIME_SETUP - def __exit__(self, except_type, except_value, traceback): + def __exit__(self, except_type: Optional[Type[BaseException]], + except_value: Optional[BaseException], + traceback: Optional[types.TracebackType]) -> None: del except_type, except_value, traceback # unused self.status_display.stop() @@ -68,13 +73,13 @@ def __exit__(self, except_type, except_value, traceback): class SkyLocalUpLineProcessor(LineProcessor): """A processor for `sky local up` log lines.""" - def __enter__(self): + def __enter__(self) -> None: status = rich_utils.safe_status('[bold cyan]Creating local cluster - ' 'initializing Kubernetes') self.status_display = status self.status_display.start() - def process_line(self, log_line): + def process_line(self, log_line: str) -> None: if 'Kind cluster created.' in log_line: logger.info(f'{colorama.Fore.GREEN}Kubernetes is running.' f'{colorama.Style.RESET_ALL}') @@ -124,7 +129,80 @@ def process_line(self, log_line): f'{colorama.Fore.GREEN}Nginx Ingress Controller installed.' f'{colorama.Style.RESET_ALL}') - def __exit__(self, except_type, except_value, traceback): + def __exit__(self, except_type: Optional[Type[BaseException]], + except_value: Optional[BaseException], + traceback: Optional[types.TracebackType]) -> None: + del except_type, except_value, traceback # unused + self.status_display.stop() + + +class SkyRemoteUpLineProcessor(LineProcessor): + """A processor for deploy_remote_cluster.sh log lines.""" + + def __enter__(self) -> None: + status = rich_utils.safe_status('[bold cyan]Creating remote cluster') + self.status_display = status + self.status_display.start() + + def process_line(self, log_line: str) -> None: + # Pre-flight checks + if 'SSH connection successful' in log_line: + logger.info(f'{colorama.Fore.GREEN}SSH connection established.' + f'{colorama.Style.RESET_ALL}') + + # Kubernetes installation steps + if 'Deploying Kubernetes on head node' in log_line: + self.status_display.update('[bold cyan]Creating remote cluster - ' + 'deploying Kubernetes on head node') + if 'K3s deployed on head node.' in log_line: + logger.info(f'{colorama.Fore.GREEN}' + '✔ K3s successfully deployed on head node.' + f'{colorama.Style.RESET_ALL}') + + # Worker nodes + if 'Deploying Kubernetes on worker node' in log_line: + self.status_display.update('[bold cyan]Creating remote cluster - ' + 'deploying Kubernetes on worker nodes') + if 'Kubernetes deployed on worker node' in log_line: + logger.info(f'{colorama.Fore.GREEN}' + '✔ K3s successfully deployed on worker node.' + f'{colorama.Style.RESET_ALL}') + + # Cluster configuration + if 'Configuring local kubectl to connect to the cluster...' in log_line: + self.status_display.update('[bold cyan]Creating remote cluster - ' + 'configuring local kubectl') + if 'kubectl configured to connect to the cluster.' in log_line: + logger.info(f'{colorama.Fore.GREEN}' + '✔ kubectl configured for the remote cluster.' + f'{colorama.Style.RESET_ALL}') + + # GPU operator installation + if 'Installing Nvidia GPU Operator...' in log_line: + self.status_display.update('[bold cyan]Creating remote cluster - ' + 'installing Nvidia GPU Operator') + if 'GPU Operator installed.' in log_line: + logger.info(f'{colorama.Fore.GREEN}' + '✔ Nvidia GPU Operator installed successfully.' + f'{colorama.Style.RESET_ALL}') + + # Cleanup steps + if 'Cleaning up head node' in log_line: + self.status_display.update('[bold cyan]Cleaning up head node') + if 'Cleaning up node' in log_line: + self.status_display.update('[bold cyan]Cleaning up worker node') + if 'cleaned up successfully' in log_line: + logger.info(f'{colorama.Fore.GREEN}' + f'{log_line.strip()}{colorama.Style.RESET_ALL}') + + # Final status + if 'Cluster deployment completed.' in log_line: + logger.info(f'{colorama.Fore.GREEN}✔ Remote k3s is running.' + f'{colorama.Style.RESET_ALL}') + + def __exit__(self, except_type: Optional[Type[BaseException]], + except_value: Optional[BaseException], + traceback: Optional[types.TracebackType]) -> None: del except_type, except_value, traceback # unused self.status_display.stop()