SkyPilot 在 Kubernetes 上暴露服务端口:LoadBalancer 与 Nginx Ingress 双模式配置实战
SkyPilot 在 Kubernetes 上暴露服务端口LoadBalancer 与 Nginx Ingress 双模式配置实战【免费下载链接】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本文是一份面向已有 Kubernetes 集群管理员的实操指南讲解 SkyPilot 如何在 Kubernetes 上通过LoadBalancer Service默认与Nginx Ingress两种模式对外暴露任务端口并深入分析集群前置条件、常见坑位EKS 健康检查、子网标签与底层实现原理。读完本文你将能够为自建或云托管的 Kubernetes 集群正确配置端口暴露并使用sky status --endpoints获取可访问的外部端点。背景SkyPilot 的 Kubernetes 端口暴露机制SkyPilot 的 SkyServe 服务和普通集群任务都可以通过任务定义中的ports字段开放端口从而把运行在 Pod 中的服务如 Jupyter、TensorBoard、vLLM API 等暴露到集群外部。在 Kubernetes 后端上SkyPilot 支持以下两种端口暴露模式LoadBalancer Service 模式默认通过 Kubernetes 的 LoadBalancer Service 暴露端口Nginx Ingress 模式通过已有的 Nginx Ingress Controller 创建 Ingress 暴露端口。从源码上看端口模式在 sky/utils/kubernetes_enums.py 中被定义为一个枚举实际上存在三种取值除了文档主讲的两种外还包含不开放端口、直接使用 Pod IP 访问的podip模式class KubernetesPortMode(enum.Enum): Enum for the different types of modes supported for opening ports on Kubernetes. INGRESS ingress LOADBALANCER loadbalancer PODIP podip也就是说端口模式的选择、端口的创建与清理、端点查询等全部由 sky/provision/kubernetes/network.py 中的open_ports()/cleanup_ports()/query_ports()三个入口函数根据当前port_mode分发到不同实现。如果你只是 SkyPilot 的普通用户并且集群管理员已经完成了端口暴露的配置那么任务侧只需在 YAML 中声明ports即可相关用法见 docs/source/examples/ports.rstOpening Ports 指南。本文聚焦的是集群侧的配置工作。模式一LoadBalancer Service默认模式这是 SkyPilot 在 Kubernetes 上的默认端口暴露方式为整个集群暴露的所有端口创建一个单个LoadBalancer Service每个端口通过该负载均衡器的外部 IP 地址加端口号访问。使用前提该模式要求 Kubernetes 集群支持 LoadBalancer ServiceGKE、EKS 等云托管 Kubernetes 服务开箱即用无需额外配置裸金属、自建 Kubernetes 集群可以使用 MetalLB 等方案来提供 LoadBalancer Service 能力。工作原理与端点查询当使用该模式时SkyPilot 会为集群上所有已开放端口创建一个LoadBalancer Service从源码看其命名规则为{cluster_name_on_cloud}--skypilot-lb见 sky/provision/kubernetes/network.py。每个端口都可以通过负载均衡器的外部 IP 与对应端口号访问使用以下命令查看集群所有端口的外部端点sky status --endpoints cluster在云托管的 Kubernetes 集群中创建 LoadBalancer Service 会自动创建云厂商的外部负载均衡器GKE 创建Pass-through Load Balancer直通负载均衡器AWS EKS 创建Network Load BalancerNLB。这些云负载均衡器会在 SkyPilot 集群被删除时自动清理释放。重要限制与坑位kind 集群不支持 LoadBalancer使用sky local up创建的 kind 集群不支持LoadBalancer Service。从 sky/provision/kubernetes/network_utils.py 的get_port_mode()实现可以看到当检测到当前 kubeconfig 上下文是 kind 时SkyPilot 会自动切换到 Ingress 模式curr_kube_config kubernetes_utils.get_current_kube_config_context_name() running_kind curr_kube_config kubernetes_utils.KIND_CONTEXT_NAME if running_kind: # If running in kind (sky local up), use ingress mode return kubernetes_enums.KubernetesPortMode.INGRESSEKS 健康检查随机选端口问题EKS 默认的 LoadBalancer 实现会从已开放的端口列表中随机选择一个端口作为负载均衡器的健康检查端口。如果该端口后面没有实际运行的服务健康检查就会失败导致负载均衡器无法获得外部 IP 地址。例如一个 SkyPilot 任务开放了 5 个端口但只有 2 个端口后面有服务在运行那么 EKS 有可能选中一个没有服务运行的端口做健康检查导致整个服务拿不到外部 IP。解决方案确保所有已开放的端口后面都有服务在运行即每个端口都真实监听。EKS 子网标签要求对于 EKS 集群子网必须带有正确的 Kubernetes 集群标签AWS Load Balancer Controller 才能为 LoadBalancer Service 创建 Elastic Load BalancerELB。如果 LoadBalancer Service 一直拿不到外部 IP请检查子网是否已按如下方式打标签# 将 CLUSTER_NAME 替换为你的 EKS 集群名REGION 替换为 AWS 区域 # 将 SUBNET_ID 替换为你的子网 ID每个子网重复执行 aws ec2 create-tags --region REGION \ --resources SUBNET_ID \ --tags Keykubernetes.io/cluster/CLUSTER_NAME,Valueshared \ Keykubernetes.io/role/elb,Value1例如集群名为my-eks-cluster、区域为us-east-2、子网为subnet-abc123和subnet-def456aws ec2 create-tags --region us-east-2 \ --resources subnet-abc123 subnet-def456 \ --tags Keykubernetes.io/cluster/my-eks-cluster,Valueshared \ Keykubernetes.io/role/elb,Value1也可以使用以下脚本自动为 EKS 集群的所有子网打标签# 从 kubeconfig 获取集群名和区域然后为子网打标签 CLUSTER_NAME$(kubectl config view --minify -o jsonpath{.clusters[0].name} | sed s/.*\///) REGION$(kubectl config view --minify -o jsonpath{.clusters[0].cluster.server} | sed -n s/.*\.eks\.\([^.]*\)\.amazonaws\.com.*/\1/p) for SUBNET_ID in $(aws eks describe-cluster --name $CLUSTER_NAME --region $REGION --query cluster.resourcesVpcConfig.subnetIds --output text); do aws ec2 create-tags --region $REGION --resources $SUBNET_ID \ --tags Keykubernetes.io/cluster/$CLUSTER_NAME,Valueshared \ Keykubernetes.io/role/elb,Value1 done这一要求同时适用于使用 LoadBalancer 模式的普通 SkyPilot 集群和 SkyServe 服务。内部负载均衡器仅集群内可访问如果希望服务只能在集群内部访问不暴露到公网可以在 SkyPilot 配置文件~/.sky/config.yaml中为所有服务统一设置内部负载均衡器注解。根据你的云厂商选择对应的注解# ~/.sky/config.yaml kubernetes: custom_metadata: annotations: # 适用于 GCP/GKE networking.gke.io/load-balancer-type: Internal # 适用于 AWS/EKS service.beta.kubernetes.io/aws-load-balancer-internal: true # 适用于 Azure/AKS service.beta.kubernetes.io/azure-load-balancer-internal: true从源码看这些custom_metadata下的 annotations 和 labels 会由 sky/provision/kubernetes/network_utils.py 中的fill_loadbalancer_template()读取并注入到 LoadBalancer Service 模板 sky/templates/kubernetes-loadbalancer.yml.j2 生成的 Service 元数据中。模板生成的 Service 采用type: LoadBalancer为每个端口生成一个 TCP 协议端口条目selector 指向带有 SkyPilot 集群标签的 Podservice_spec: apiVersion: v1 kind: Service metadata: name: {{ service_name }} labels: parent: skypilot # ... 用户自定义 labels annotations: # ... 用户自定义 annotations # 注云特定注解会被其他云忽略 service.beta.kubernetes.io/coreweave-load-balancer-type: public spec: type: LoadBalancer selector: {{ selector_key }}: {{ selector_value }} ports: - port: {{ port | int }} targetPort: {{ port | int }} protocol: TCP name: port{{ port }}模式二Nginx Ingress当集群不支持 LoadBalancer Service 时SkyPilot 可以改用Nginx Ingress模式创建 Kubernetes Ingress 资源由集群中已有的 Nginx Ingress Controller 将外部流量路由到各服务的端口。启用步骤步骤 1安装 Nginx Ingress Controller先在 Kubernetes 集群上安装 Nginx Ingress Controller请参照其官方部署文档中针对你所在环境的安装说明进行安装。步骤 2验证 controller 具有有效的外部 IPkubectl get service ingress-nginx-controller -n ingress-nginx示例输出NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) ingress-nginx-controller LoadBalancer 10.24.4.254 35.202.58.117 80:31253/TCP,443:32699/TCP关于 EXTERNAL-IP 为none的情况如果EXTERNAL-IP字段为none可以通过在ingress-nginx-controllerService 上手动添加skypilot.co/external-ip注解来指定 Ingress 的外部 IP 或主机名此时不再要求EXTERNAL-IP字段有效。例如当你的ingress-nginx-controllerService 类型为NodePort时# 为 nginx ingress 服务添加 skypilot.co/external-ip 注解。 # 将命令中的 IP 替换为你选择的 IP。 # 如果使用 NodePort 类型服务可以是任意节点的 IP。 kubectl annotate service ingress-nginx-controller skypilot.co/external-ipIP -n ingress-nginx注意如果EXTERNAL-IP为none且不存在skypilot.co/external-ip注解SkyPilot 会使用localhost作为 Ingress 的外部 IP此时集群外可能无法访问该端点。这一回退逻辑可以在源码 sky/provision/kubernetes/network_utils.py 的get_ingress_external_ip_and_ports()中看到解析优先级依次为「已分配的外部 IP/主机名 →spec.externalIPs→skypilot.co/external-ip注解 →localhost」。步骤 3更新 SkyPilot 配置启用 Ingress 模式在~/.sky/config.yaml中设置kubernetes: ports: ingress提示对于 RKE2 和 K3s其预装的 Nginx ingress 默认配置并不正确请按照 Nginx Ingress Controller 官方「裸金属集群」安装说明重新正确部署。访问方式使用 Ingress 模式时SkyPilot 会为每个端口创建一个 Ingress 资源和一个ClusterIPService。外部可以通过 Ingress URL 加上/skypilot/{pod_name}/{port}形式的前缀路径来访问对应端口其中{pod_name}为 Pod 名{port}为端口号。使用sky status --endpoints cluster查看所有端口的完整端点 URL$ sky status --endpoints mycluster 8888: http://34.173.152.251/skypilot/test-2ea4/8888需要说明的是从当前仓库源码 sky/provision/kubernetes/network.py 的实现看路径前缀实际定义为_PATH_PREFIX /skypilot/{namespace}/{cluster_name_on_cloud}/{port}即实际生成路径时使用的是namespace 与集群名cluster name on cloud文档示例中的test-2ea4即对应集群名这与上文示例输出一致可供排查 404 路径问题时参考。子路径访问的限制通过 Ingress 的路径前缀暴露端口时期望以根路径访问的服务例如 Jupyter Notebook可能遇到问题。解决办法将服务配置为在不同的 base URL 下运行。例如 Jupyter 启动时增加--NotebookApp.base_url参数或者改用 LoadBalancer 模式。Ingress 模式的实现细节从源码看Ingress 模式主要由 sky/provision/kubernetes/network.py 的_open_ports_using_ingress()实现先通过ingress_controller_exists()见 network_utils.py检查集群中是否存在名为nginx的 IngressClass不存在则直接报错提示先安装 Nginx Ingress Controller为每个端口生成{cluster_name_on_cloud}--skypilot-svc--{port}命名的 ClusterIP Service 规格并把这些规则合并到一个 Ingress 对象中创建。这样做的原因是每条 Ingress 规则都会触发 Nginx Controller 一次热重载若逐条顺序创建短时间内多次重载会让 Nginx Controller Pod 生成过多子进程触发 Kubernetes 的podPidsLimit默认约 1024而被杀掉重启最终 Ingress 名为{cluster_name_on_cloud}-skypilot-ingress采用ingressClassName: nginx。Ingress 与 Service 的规格由模板 sky/templates/kubernetes-ingress.yml.j2 渲染生成其中关键注解为nginx.ingress.kubernetes.io/use-regex: true启用正则路径匹配nginx.ingress.kubernetes.io/rewrite-target: /$2配合形如/skypilot/{namespace}/{cluster}/{port}(/|$)(.*)的路径规则把路径前缀剥离后转发给后端 Service。端口清理逻辑_cleanup_ports_for_ingress()见 network.py会逐个删除每个端口的 ClusterIP Service再删除单个共享的 Ingress 对象。集群引导时的 Ingress 权限当端口模式为ingress时SkyPilot 在集群引导阶段bootstrap_instances见 sky/provision/kubernetes/config.py会额外为自动扩缩器创建 Ingress 相关的 Role 与 RoleBindingautoscaler_ingress_role/autoscaler_ingress_role_binding。如果返回 404说明集群中未安装 Nginx IngressSkyPilot 会打印提示并跳过该项设置。如何选择端口暴露模式对比维度LoadBalancer 模式默认Nginx Ingress 模式集群要求支持 LoadBalancer Service云托管集群开箱即用裸金属需 MetalLB需要预先安装 Nginx Ingress Controller创建的 K8s 资源每集群一个 LoadBalancer Service每端口一个 Ingress 规则 ClusterIP Service聚合为单个 Ingress访问方式http://LB-IP:porthttp://Ingress-IP/skypilot/namespace/cluster/port适用场景云上集群、期望端口直通无法提供 LoadBalancer 的集群如自建、kind特殊限制EKS 健康检查端口随机选择、子网需打标签根路径服务如 Jupyter需配置 base URL从源码的get_port_mode()可以看到端口模式的具体取值遵循「kind 强制 Ingress → 配置文件中kubernetes.ports的值 → 默认loadbalancer」的解析顺序因此你只需在~/.sky/config.yaml中写入kubernetes.ports即可完成全局切换。配置生效后SkyPilot 会自动完成 LoadBalancer Service 或 Ingress 及配套 ClusterIP Service 的创建、更新与清理并通过sky status --endpoints cluster向你报告最终可访问的外部端点。完整的 Kubernetes 集群部署与入门流程可进一步参考 docs/source/reference/kubernetes/kubernetes-setup.rst 与 docs/source/reference/kubernetes/kubernetes-getting-started.rst。【免费下载链接】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),仅供参考