From cb3226ead6bf8f97ac6577fbe8c2397ac3fb1ddf Mon Sep 17 00:00:00 2001 From: Li Jiashu Date: Wed, 4 Nov 2026 08:33:45 +0000 Subject: [PATCH] feat(sglang,vllm): run the router's wait-upstreams on debian:12-slim wait-upstreams only does DNS lookups, but it ran on the router image, so an override lacking bash, getent or awk would leave the router stuck in init. Move it to debian:12-slim in sglang 0.9.31 and vllm 0.9.3, independent of pd.router.imageOverride. Add pd.router.waitUpstreamsImage so mirrored and air-gapped sites have a way to change it; an emptied value falls back to debian. Routers roll once on upgrade, and sites without Docker Hub access must mirror the image. Verified: helm lint --strict on both charts, helm unittest (sglang 33/33, vllm 42/42), and new tests fail when the change is reverted. Render changes only the init container's image line in pd-ds; colocate is unchanged. Image tools were checked from its published layers. Not yet run on a cluster. Co-Authored-By: Claude Opus 5.5 --- charts/sglang/Chart.yaml | 2 +- charts/sglang/templates/_helpers.tpl | 8 +++++ charts/sglang/templates/pd-ds.yaml | 2 +- charts/sglang/tests/pd_router_smg_test.yaml | 40 ++++++++++++++++++--- charts/sglang/values.schema.json | 9 +++++ charts/sglang/values.yaml | 5 +++ charts/vllm/Chart.yaml | 2 +- charts/vllm/templates/_helpers.tpl | 8 +++++ charts/vllm/templates/pd-ds.yaml | 2 +- charts/vllm/tests/pd_router_test.yaml | 40 ++++++++++++++++++--- charts/vllm/values.schema.json | 9 +++++ charts/vllm/values.yaml | 5 +++ 12 files changed, 120 insertions(+), 12 deletions(-) diff --git a/charts/sglang/Chart.yaml b/charts/sglang/Chart.yaml index 56659b1..fae69f4 100644 --- a/charts/sglang/Chart.yaml +++ b/charts/sglang/Chart.yaml @@ -5,7 +5,7 @@ home: https://github.com/modelsphere/helm-charts sources: - https://github.com/modelsphere/helm-charts type: application -version: 0.9.30 +version: 0.9.31 appVersion: "1.0.0" # CART is vendored under charts/cart -- already present, so no `helm dependency diff --git a/charts/sglang/templates/_helpers.tpl b/charts/sglang/templates/_helpers.tpl index 814759e..144f937 100644 --- a/charts/sglang/templates/_helpers.tpl +++ b/charts/sglang/templates/_helpers.tpl @@ -280,3 +280,11 @@ {{- $img := .Values.pd.router.imageOverride | default dict -}} {{ $img.repository | default "lightseekorg/smg" }}:{{ $img.tag | default "1.14.0" }} {{- end -}} + +{{/* + The PD router's wait-upstreams init container image. Takes the root context. +*/}} +{{- define "sglang.pdWaitUpstreamsImage" -}} +{{- $img := .Values.pd.router.waitUpstreamsImage | default dict -}} +{{ $img.repository | default "debian" }}:{{ $img.tag | default "12-slim" }} +{{- end -}} diff --git a/charts/sglang/templates/pd-ds.yaml b/charts/sglang/templates/pd-ds.yaml index 81a5029..303d6df 100644 --- a/charts/sglang/templates/pd-ds.yaml +++ b/charts/sglang/templates/pd-ds.yaml @@ -181,7 +181,7 @@ spec: loop just blocks until the full set shows up. */}} initContainers: - name: wait-upstreams - image: "{{ include "sglang.pdRouterImage" $root }}" + image: "{{ include "sglang.pdWaitUpstreamsImage" $root }}" command: ["bash", "-lc"] args: - | diff --git a/charts/sglang/tests/pd_router_smg_test.yaml b/charts/sglang/tests/pd_router_smg_test.yaml index f730769..2434fae 100644 --- a/charts/sglang/tests/pd_router_smg_test.yaml +++ b/charts/sglang/tests/pd_router_smg_test.yaml @@ -17,7 +17,7 @@ set: pd.decode.gpus: 8 model.localPath: /models/x tests: - - it: pd-ds router and its init container default to the pinned smg image + - it: pd-ds router defaults to the pinned smg image; its init container to debian template: templates/pd-ds.yaml documentSelector: path: metadata.name @@ -28,7 +28,7 @@ tests: value: lightseekorg/smg:1.14.0 - equal: path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image - value: lightseekorg/smg:1.14.0 + value: debian:12-slim - it: pd-ds router execs smg and keeps its state dir where non-root can write template: templates/pd-ds.yaml @@ -59,7 +59,7 @@ tests: path: spec.template.spec.containers[?(@.name == "router")].livenessProbe.exec.command[2] pattern: 'file="/tmp/pd-router/\$3"' - - it: pd-ds override wins over the default + - it: pd-ds router override wins over the default and leaves the init container alone template: templates/pd-ds.yaml documentSelector: path: metadata.name @@ -71,6 +71,9 @@ tests: - equal: path: spec.template.spec.containers[?(@.name == "router")].image value: registry.example.com/mirror/smg:1.14.1 + - equal: + path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image + value: debian:12-slim - it: colocate router runs smg while its engines stay on the engine image template: templates/pd-colocate.yaml @@ -110,7 +113,7 @@ tests: value: lightseekorg/smg:1.14.0 - equal: path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image - value: lightseekorg/smg:1.14.0 + value: debian:12-slim - it: colocate emptied override falls back to smg, not the engine image template: templates/pd-colocate.yaml @@ -125,3 +128,32 @@ tests: - equal: path: spec.template.spec.containers[?(@.name == "router")].image value: lightseekorg/smg:1.14.0 + + - it: wait-upstreams image can be mirrored without touching the router + template: templates/pd-ds.yaml + documentSelector: + path: metadata.name + value: ci-router-s0 + set: + pd.router.waitUpstreamsImage.repository: registry.example.com/mirror/debian + pd.router.waitUpstreamsImage.tag: 12-slim-pinned + asserts: + - equal: + path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image + value: registry.example.com/mirror/debian:12-slim-pinned + - equal: + path: spec.template.spec.containers[?(@.name == "router")].image + value: lightseekorg/smg:1.14.0 + + - it: emptied wait-upstreams image falls back to debian + template: templates/pd-ds.yaml + documentSelector: + path: metadata.name + value: ci-router-s0 + set: + pd.router.waitUpstreamsImage.repository: "" + pd.router.waitUpstreamsImage.tag: "" + asserts: + - equal: + path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image + value: debian:12-slim diff --git a/charts/sglang/values.schema.json b/charts/sglang/values.schema.json index 746fb4a..0ce4d02 100644 --- a/charts/sglang/values.schema.json +++ b/charts/sglang/values.schema.json @@ -192,6 +192,15 @@ "tag": { "type": "string" } } }, + "waitUpstreamsImage": { + "description": "Image of the router's wait-upstreams init container. Needs bash, getent, awk, sort, wc and sleep. Independent of imageOverride.", + "type": "object", + "additionalProperties": false, + "properties": { + "repository": { "type": "string" }, + "tag": { "type": "string" } + } + }, "affinityReplace": { "type": ["object", "null"] }, "tolerationsReplace": { "type": ["array", "null"] } } diff --git a/charts/sglang/values.yaml b/charts/sglang/values.yaml index c77e572..4a7c697 100644 --- a/charts/sglang/values.yaml +++ b/charts/sglang/values.yaml @@ -238,6 +238,11 @@ pd: imageOverride: repository: "lightseekorg/smg" tag: "1.14.0" + # The wait-upstreams init container only resolves DNS, so it stays on a + # plain base image whatever the router runs. Set repository/tag to mirror it. + waitUpstreamsImage: + repository: "debian" + tag: "12-slim" # The router always gets a copy of the model mounted read-only at # model.mountPath (as /model for the tokenizer's HF lookup, the path # cache_aware's prefix-hashing needs). Its default scheduling is diff --git a/charts/vllm/Chart.yaml b/charts/vllm/Chart.yaml index 162ba5f..f141a8a 100644 --- a/charts/vllm/Chart.yaml +++ b/charts/vllm/Chart.yaml @@ -5,7 +5,7 @@ home: https://github.com/modelsphere/helm-charts sources: - https://github.com/modelsphere/helm-charts type: application -version: 0.9.2 +version: 0.9.3 appVersion: "1.0.0" # CART is vendored under charts/cart -- already present, so no `helm dependency diff --git a/charts/vllm/templates/_helpers.tpl b/charts/vllm/templates/_helpers.tpl index 59f9480..8334971 100644 --- a/charts/vllm/templates/_helpers.tpl +++ b/charts/vllm/templates/_helpers.tpl @@ -301,3 +301,11 @@ {{- $img := .Values.pd.router.imageOverride | default dict -}} {{ $img.repository | default "vllm/vllm-router" }}:{{ $img.tag | default "v0.1.16.1" }} {{- end -}} + +{{/* + The PD router's wait-upstreams init container image. Takes the root context. +*/}} +{{- define "vllm.pdWaitUpstreamsImage" -}} +{{- $img := .Values.pd.router.waitUpstreamsImage | default dict -}} +{{ $img.repository | default "debian" }}:{{ $img.tag | default "12-slim" }} +{{- end -}} diff --git a/charts/vllm/templates/pd-ds.yaml b/charts/vllm/templates/pd-ds.yaml index 14df7c1..b1bda22 100644 --- a/charts/vllm/templates/pd-ds.yaml +++ b/charts/vllm/templates/pd-ds.yaml @@ -215,7 +215,7 @@ spec: loop just blocks until the full set shows up. */}} initContainers: - name: wait-upstreams - image: "{{ include "vllm.pdRouterImage" $root }}" + image: "{{ include "vllm.pdWaitUpstreamsImage" $root }}" command: ["bash", "-lc"] args: - | diff --git a/charts/vllm/tests/pd_router_test.yaml b/charts/vllm/tests/pd_router_test.yaml index dc62223..82c0c5f 100644 --- a/charts/vllm/tests/pd_router_test.yaml +++ b/charts/vllm/tests/pd_router_test.yaml @@ -18,7 +18,7 @@ set: pd.decode.gpus: 8 model.localPath: /models/x tests: - - it: pd-ds router and its init container default to the pinned vllm-router image + - it: pd-ds router defaults to the pinned vllm-router image; its init container to debian template: templates/pd-ds.yaml documentSelector: path: metadata.name @@ -29,7 +29,7 @@ tests: value: vllm/vllm-router:v0.1.16.1 - equal: path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image - value: vllm/vllm-router:v0.1.16.1 + value: debian:12-slim - it: pd-ds router execs vllm-router and keeps its state dir where non-root can write template: templates/pd-ds.yaml @@ -60,7 +60,7 @@ tests: path: spec.template.spec.containers[?(@.name == "router")].livenessProbe.exec.command[2] pattern: 'file="/tmp/pd-router/\$3"' - - it: pd-ds override wins over the default + - it: pd-ds router override wins over the default and leaves the init container alone template: templates/pd-ds.yaml documentSelector: path: metadata.name @@ -72,6 +72,9 @@ tests: - equal: path: spec.template.spec.containers[?(@.name == "router")].image value: registry.example.com/mirror/vllm-router:v0.1.17 + - equal: + path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image + value: debian:12-slim - it: colocate router runs vllm-router while its engines stay on the engine image template: templates/pd-colocate.yaml @@ -108,7 +111,7 @@ tests: value: vllm/vllm-router:v0.1.16.1 - equal: path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image - value: vllm/vllm-router:v0.1.16.1 + value: debian:12-slim - it: colocate emptied override falls back to vllm-router, not the engine image template: templates/pd-colocate.yaml @@ -123,3 +126,32 @@ tests: - equal: path: spec.template.spec.containers[?(@.name == "router")].image value: vllm/vllm-router:v0.1.16.1 + + - it: wait-upstreams image can be mirrored without touching the router + template: templates/pd-ds.yaml + documentSelector: + path: metadata.name + value: ci-router-s0 + set: + pd.router.waitUpstreamsImage.repository: registry.example.com/mirror/debian + pd.router.waitUpstreamsImage.tag: 12-slim-pinned + asserts: + - equal: + path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image + value: registry.example.com/mirror/debian:12-slim-pinned + - equal: + path: spec.template.spec.containers[?(@.name == "router")].image + value: vllm/vllm-router:v0.1.16.1 + + - it: emptied wait-upstreams image falls back to debian + template: templates/pd-ds.yaml + documentSelector: + path: metadata.name + value: ci-router-s0 + set: + pd.router.waitUpstreamsImage.repository: "" + pd.router.waitUpstreamsImage.tag: "" + asserts: + - equal: + path: spec.template.spec.initContainers[?(@.name == "wait-upstreams")].image + value: debian:12-slim diff --git a/charts/vllm/values.schema.json b/charts/vllm/values.schema.json index 68a0f66..977f6ef 100644 --- a/charts/vllm/values.schema.json +++ b/charts/vllm/values.schema.json @@ -231,6 +231,15 @@ "tag": { "type": "string" } } }, + "waitUpstreamsImage": { + "description": "Image of the router's wait-upstreams init container. Needs bash, getent, awk, sort, wc and sleep. Independent of imageOverride.", + "type": "object", + "additionalProperties": false, + "properties": { + "repository": { "type": "string" }, + "tag": { "type": "string" } + } + }, "affinityReplace": { "type": ["object", "null"] }, "tolerationsReplace": { "type": ["array", "null"] } } diff --git a/charts/vllm/values.yaml b/charts/vllm/values.yaml index 4bc9cce..3106eb0 100644 --- a/charts/vllm/values.yaml +++ b/charts/vllm/values.yaml @@ -312,6 +312,11 @@ pd: imageOverride: repository: "vllm/vllm-router" tag: "v0.1.16.1" + # The wait-upstreams init container only resolves DNS, so it stays on a + # plain base image whatever the router runs. Set repository/tag to mirror it. + waitUpstreamsImage: + repository: "debian" + tag: "12-slim" # The router always gets a copy of the model mounted read-only at # model.mountPath (used by the tokenizer's HF lookup). Its default # scheduling is pinned: required podAffinity to THIS slice's prefill