用 SkyPilot 一键在云上部署 Kubernetes 集群:从 k3s 安装、GPU 使能到 AI 任务调度实战
【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot
本文基于仓库 examples/k8s_cloud_deploy 目录下的完整示例,讲解如何借助 SkyPilot 在云厂商的裸 VM 上「一键」部署一个自带 GPU 支持的 Kubernetes 集群,并让本地的kubectl与sky命令直接对接该集群。读完本文,你将掌握cloud_k8s.yaml中每个配置项的真实作用、launch_k8s.sh的端到端执行链路,以及如何在部署好的集群上运行交互式开发环境、托管作业(Jobs)并完成队列调度与销毁。
一、方案概览:为什么要用 SkyPilot 在云上部署 Kubernetes
在云端手动搭建一套可用的 GPU Kubernetes 集群,通常需要依次完成:购买多台带 GPU 的 VM → 安装容器运行时与 kubelet → 部署控制平面 → 打通网络与证书 → 安装 NVIDIA GPU Operator → 配置 RuntimeClass → 拉取并改写 kubeconfig。这一流程冗长且极易出错。
SkyPilot 提供了一条捷径:你只需要写一个描述「集群长什么样」的 YAML(cloud_k8s.yaml),再执行一个脚本(launch_k8s.sh),SkyPilot 就会替你完成 VM 采购、集群部署、GPU 使能、kubeconfig 抓取与本地kubectl对接的全部工作。官方文档 kubernetes-deployment.rst 将这种模式称为 "Deploying on cloud VMs",并明确说明仓库中提供了"负责采购 VM、安装 Kubernetes、配置 GPU 支持以及配置本地 kubeconfig 的脚本"。
本文示例默认以 Lambda Cloud 作为云厂商(infra: lambda),但通过修改 YAML 中的infra字段即可切换到其他云。
二、前置条件
1. 安装 SkyPilot nightly 版本
示例要求使用最新的 SkyPilot nightly 发行版,并同时启用lambda(Lambda Cloud 支持)与kubernetes(Kubernetes 支持)两个 extra:
pip install "skypilot-nightly[lambda,kubernetes]"kubernetesextra 会引入kubernetesPython 客户端依赖,这是 SkyPilot 以 Kubernetes 为后端(cloud)与集群 API Server 交互所必需的。
2. 开放 API Server 所需端口
SkyPilot 会在被采购的 VM 上安装 k3s,k3s 的 API Server 监听6443端口,而后续kubectl/sky通过 HTTPS 访问时也会用到443。因此需要满足以下二选一:
- 使用支持在 SkyPilot 层面直接打开端口的云(在 YAML 中配置
ports); - 或者手动在云控制台的防火墙/安全组中放行 VM 的
6443与443入站流量。
以 Lambda Cloud 为例,需要在其 Dashboard 的防火墙配置中允许443和6443端口的入站连接。
说明:示例 YAML 中的
ports: 6443默认被注释掉,是因为 Lambda Cloud 会在 SkyPilot 层面对 6443 做端口映射;而如果改用 GCP、AWS 或 Azure,则需取消注释ports: 6443行,让 SkyPilot 在 VM 上打开对应端口(详见下文配置文件分析)。
三、核心配置文件 cloud_k8s.yaml 详解
完整文件位于 cloud_k8s.yaml,它同时承担两项职责:一是描述 SkyPilot 集群的资源配置(VM 规格、节点数),二是通过run命令段定义 k3s 集群的完整安装脚本。逐项拆解如下。
1. 资源与集群规模
resources: infra: lambda accelerators: A10:1 # Uncomment the following line to expose ports on a different cloud # ports: 6443 num_nodes: 2| 配置项 | 含义 | 说明 |
|---|---|---|
infra: lambda | 指定底层基础设施为 Lambda Cloud | 可替换为其他云,见下文infra字段解析 |
accelerators: A10:1 | 每台节点配备 1 块 NVIDIA A10 | 示例共 2 节点,即 2 块 A10 |
ports: 6443 | 在 VM 上打开 6443 端口 | 默认注释;切换到 GCP/AWS/Azure 等云时需要取消注释 |
num_nodes: 2 | 集群节点总数 | 1 个作为控制平面(head),其余作为 worker 节点加入 |
关于infra字段,从源码 sky/resources.py 可以看到它是资源规格的顶层字段,格式为cloud、cloud/region或cloud/region/zone;同时该文件中明确注释cloud/region/zone为已弃用字段,推荐统一使用infra,且二者不能同时指定(否则抛出ValueError)。解析逻辑位于 sky/utils/infra_utils.py:
- 对
k8s/kubernetes前缀有特殊处理:k8s/之后的整段字符串会被当作 Kubernetescontext 名称(即源码注释里说的sky.Resources(infra='k8s/my-cluster-ctx', accelerators='V100')用法),且k8s会被归一化为kubernetes; - 对
*通配符会归一化为None,表示「任意值均可」; - 其他云则按
/最多切分成 cloud/region/zone 三段,格式非法时抛出ValueError。
2. 集群加入令牌
envs: SKY_K3S_TOKEN: mytoken # Can be any string, used to join worker nodes to the clusterSKY_K3S_TOKEN是一个任意字符串,用于 k3s worker 节点向控制平面注册时进行身份验证。它会被注入到所有节点的环境中,供后续run脚本读取。
3. run 命令:k3s 集群的完整安装逻辑
run段是一个 Shell 脚本,SkyPilot 会将它分发到每个节点执行。脚本通过 SkyPilot 自动注入的SKYPILOT_NODE_RANK与SKYPILOT_NODE_IPS两个环境变量来区分节点角色(这两个变量由 sky/backends/task_codegen.py 按节点 rank 与实际 IP 列表生成并注入):
run: | # ...(wait_for_gpu_operator_installation 辅助函数,见下文) if [ ${SKYPILOT_NODE_RANK} -ne 0 ]; then # Worker nodes MASTER_ADDR=`echo "$SKYPILOT_NODE_IPS" | head -n1` echo "Worker joining k3s cluster @ ${MASTER_ADDR}" curl -sfL https://get.k3s.io | K3S_URL=https://${MASTER_ADDR}:6443 K3S_TOKEN=${SKY_K3S_TOKEN} sh - exit 0 fiWorker 节点分支:SKYPILOT_NODE_RANK不为 0 的节点即为 worker,它们从SKYPILOT_NODE_IPS中取出第一个 IP(head 节点)作为 master 地址,通过K3S_URL与K3S_TOKEN环境变量执行 k3s 官方安装脚本,把自己注册进集群后立即退出。
Head 节点分支(SKYPILOT_NODE_RANK为 0,即控制平面):
# Head node curl -sfL https://get.k3s.io | K3S_TOKEN=${SKY_K3S_TOKEN} sh - # Copy over kubeconfig file echo "Copying kubeconfig file" mkdir -p $HOME/.kube sudo cp /etc/rancher/k3s/k3s.yaml $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # Wait for k3s to be ready echo "Waiting for k3s to be ready" sleep 5 kubectl wait --for=condition=ready node --all --timeout=5m --kubeconfig ~/.kube/config- 以
K3S_TOKEN方式初始化 k3s 控制平面; - 将
/etc/rancher/k3s/k3s.yaml复制到~/.kube/config并修正属主,方便本节点使用kubectl; - 用
kubectl wait等待所有节点进入 Ready 状态(5 分钟超时)。
GPU 支持:安装 NVIDIA GPU Operator。接下来 head 节点负责为集群安装 GPU 驱动与容器运行时支持:
# =========== GPU support =========== echo "Installing helm" 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 echo "Creating namespace gpu-operator" kubectl create namespace gpu-operator --kubeconfig ~/.kube/config || true # Patch ldconfig echo "Patching ldconfig" sudo ln -s /sbin/ldconfig /sbin/ldconfig.real echo "Installing GPU operator" helm install gpu-operator -n gpu-operator --create-namespace \ nvidia/gpu-operator $HELM_OPTIONS \ --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' wait_for_gpu_operator_installation这段脚本的关键点:
- 通过
helm repo add nvidia添加 NVIDIA 官方 Helm 仓库; - 通过环境变量注入k3s 特有路径:
CONTAINERD_CONFIG指向/var/lib/rancher/k3s/agent/etc/containerd/config.toml(k3s 的 containerd 配置),CONTAINERD_SOCKET指向/run/k3s/containerd/containerd.sock,CONTAINERD_RUNTIME_CLASS设为nvidia。这套 k3s 专用的参数与官方部署文档 kubernetes-deployment.rst 中 "K3s" 一节的安装说明完全一致; sudo ln -s /sbin/ldconfig /sbin/ldconfig.real是官方文档里提到的 nvidia-docker 已知问题的变通修复(K3s 场景下该符号链接路径同样适用);- 安装完成后进入等待循环:每 5 秒检查一次
kubectl describe nodes输出中是否出现nvidia.com/gpu:资源标记,最多等待 600 秒(10 分钟),超时则exit 1。官方文档提示 GPU Operator 安装需要几分钟,期间出现部分 CrashLoopBackOff 属正常现象,可通过kubectl get pods -n gpu-operator观察安装进度。
创建 RuntimeClass。最后为 k3s 创建名为nvidia的 RuntimeClass:
# Create RuntimeClass sleep 5 echo "Creating RuntimeClass" kubectl apply --kubeconfig ~/.kube/config -f - <<EOF apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: nvidia handler: nvidia EOF该 RuntimeClass 会被 SkyPilot 自动用于调度 GPU Pod(见 kubernetes-deployment.rst)。
四、launch_k8s.sh:一键脚本的完整执行链路
launch_k8s.sh 将上述 YAML 的部署、kubeconfig 抓取与本地对接封装为一个命令:
./launch_k8s.sh其执行流程可拆解为四步,理解它对排查部署问题很有帮助。
1. 发起 SkyPilot launch
CLUSTER_NAME=${CLUSTER_NAME:-k8s} sky launch -y -c ${CLUSTER_NAME} cloud_k8s.yaml- 集群名默认取
k8s,可通过环境变量CLUSTER_NAME覆盖(例如CLUSTER_NAME=myk8s ./launch_k8s.sh); -y跳过确认,-c k8s指定集群名,随后 SkyPilot 完成 VM 采购、环境初始化与run脚本的分发执行。
2. 获取 API Server 端点
PRIMARY_ENDPOINT="" SKY_STATUS_OUTPUT=$(SKYPILOT_DEBUG=0 sky status --endpoint 6443 ${CLUSTER_NAME} 2>&1) || true if [[ "$SKY_STATUS_OUTPUT" != *"ValueError"* ]]; then PRIMARY_ENDPOINT="$SKY_STATUS_OUTPUT" else echo "Primary endpoint retrieval failed or unsupported. Falling back to alternate method..." fi脚本优先通过sky status --endpoint 6443 <cluster>获取主端点;若结果中包含ValueError(说明当前云不支持该方式),则回退到备用方案——从 SkyPilot 生成的 SSH 配置~/.sky/generated/ssh/${CLUSTER_NAME}中解析HostName字段并拼上:6443:
ENDPOINT=$(awk '/^ *HostName / { print $2; exit}' "$SSH_CONFIG_FILE") ENDPOINT="${ENDPOINT}:6443"3. 拉取并改写本地 kubeconfig
mkdir -p ~/.kube rsync -av ${CLUSTER_NAME}:'~/.kube/config' ~/.kube/config- 通过
rsync把 head 节点上(run脚本第 4 节生成的)~/.kube/config拉到本地; - 本地已有 kubeconfig 时先备份为
~/.kube/config.bak; - 随后用
awk处理:删除集群段的certificate-authority-data,并把server替换为https://<ENDPOINT>,同时加入insecure-skip-tls-verify: true(因为本地没有集群 CA 证书,且对外访问的是公网端点)。这样本地的kubectl便能直连云端 k3s 集群。
4. 校验 Kubernetes 后端
sleep 5 # Wait for the cluster to be ready sky check kubernetessky check kubernetes会让 SkyPilot 重新探测并确认 Kubernetes 后端的连通性,将集群注册为可用的 SkyPilot 云后端。脚本成功后会输出提示:可以用sky gpus list --cloud kubernetes查看集群可用 GPU,用sky launch --cloud kubernetes --gpus <GPU> -- nvidia-smi提交测试任务。
五、验证集群:kubectl 与 sky 双视角
部署完成后,README 给出了两套并行的验证手段。kubectl确认集群本身正常(注意节点名即云上 VM 的 IP,版本为 k3s 的 v1.30.4+k3s1):
$ kubectl get nodes NAME STATUS ROLES AGE VERSION 129-80-133-44 Ready <none> 14m v1.30.4+k3s1 150-230-191-161 Ready control-plane,master 14m v1.30.4+k3s1sky gpus list --cloud kubernetes则从 SkyPilot 视角确认 GPU 已被 GPU Operator 正确暴露、并可由 SkyPilot 调度:
$ sky gpus list --cloud kubernetes Kubernetes GPUs GPU REQUESTABLE_QTY_PER_NODE UTILIZATION A10 1 2 of 2 free Kubernetes per node GPU availability NODE GPU UTILIZATION 129-80-133-44 A10 1 of 1 free 150-230-191-161 A10 1 of 1 free输出显示 2 块 A10 均处于空闲状态,且 SkyPilot 能按节点粒度报告 GPU 占用,说明从驱动、容器运行时到调度器的整条链路已经打通。
六、在集群上运行 AI 工作负载
1. 交互式开发集群
想获得一个可直接 SSH / 远程开发的 GPU 环境,在云上 Kubernetes 之上再叠一层 SkyPilot 开发集群即可:
sky launch -c mycluster --cloud kubernetes --gpus A10:1SkyPilot 会同时为你配置好 SSH:
- SSH 直连:
ssh mycluster - VSCode 远程开发:
code --remote ssh-remote+mycluster "/home"
这正是 SkyPilot 交互式开发(interactive development)模式在自建 Kubernetes 上的落地用法,开发者无需关心 Pod 与端口映射细节。
2. 托管作业与自动排队
提交一次性作业:
sky jobs launch --gpus A10:1 --cloud kubernetes -- 'nvidia-smi; sleep 600'当集群资源不足时,可以持续提交多个作业,让 SkyPilot 的托管作业队列自动处理排队。README 给出了一个真实的排队观察结果:
$ sky jobs queue Fetching managed job statuses... Managed jobs In progress tasks: 2 RUNNING, 1 STARTING ID TASK NAME RESOURCES SUBMITTED TOT. DURATION JOB DURATION #RECOVERIES STATUS 3 - finetune 1x[A10:1] 24 secs ago 24s - 0 STARTING 2 - qlora 1x[A10:1] 2 min ago 2m 18s 12s 0 RUNNING 1 - sky-cmd 1x[A10:1] 4 mins ago 4m 27s 3m 12s 0 RUNNING可以看到finetune作业处于 STARTING(等待资源)、qlora与sky-cmd正在 RUNNING,这正是 SkyPilot 在集群资源不足时的排队调度的直观体现。
3. 用 kubectl 观察底层 Pod
SkyPilot 作业在 Kubernetes 上以 Pod 形式运行,可以在kubectl侧验证:
$ kubectl get pods NAME READY STATUS RESTARTS AGE qlora-2-2ea4-head 1/1 Running 0 5m31s sky-cmd-1-2ea4-head 1/1 Running 0 8m36s sky-jobs-controller-2ea485ea-2ea4-head 1/1 Running 0 10m输出中除了用户作业对应的 Pod,还能看到 SkyPilot 部署的sky-jobs-controller控制器 Pod——它是 SkyPilot 托管作业系统在集群内的执行载体,也是理解「SkyPilot 作业如何映射到 Kubernetes」的关键观察点。
七、销毁集群
不再使用时,一条命令即可回收全部云资源:
sky down k8sSkyPilot 会销毁对应集群名下所有 VM 及其上部署的 k3s 集群。
八、排障与注意事项
- GPU Operator 安装较慢:官方文档指出安装需要数分钟,期间出现部分 Pod CrashLoopBackOff 属正常现象,可通过
kubectl get pods -n gpu-operator观察;若长期卡死,可检查 head 节点上是否已正确创建/sbin/ldconfig.real符号链接(对应cloud_k8s.yaml中的sudo ln -s步骤); - 端口不通导致 kubeconfig 失效:
kubectl/sky无法连接时,优先检查云防火墙是否放行了6443与443;若在 GCP/AWS/Azure 上部署,需确认已取消注释ports: 6443; - infra 字段格式:
infra支持cloud、cloud/region、cloud/region/zone三种粒度,Kubernetes context 用k8s/<context>表达(源码见 sky/utils/infra_utils.py);它与旧的cloud/region/zone字段不能混用; sky down k8s默认删除名为k8s的集群:如果部署时通过CLUSTER_NAME指定了其他名称,销毁时需使用对应集群名。
通过这套「YAML 描述 + 一键脚本」的方案,SkyPilot 将「云端 GPU VM 采购、k3s 部署、GPU Operator 使能、kubeconfig 对接」压缩为两条命令,让开发团队可以像使用公有云 K8s 一样,把自建 GPU 集群直接当作 SkyPilot 的调度后端来跑训练、微调与推理负载。
【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考