Skip to content

Commit 79bf2d0

Browse files
committed
fix(dev): harden local dev stack for Helm 4 and the webhook startup race
Addresses review feedback on the local dev path: - Gateway API CRDs: apply with --server-side and pass --force-conflicts to the eg chart install, gated on Helm major version (Helm 4 needs it; Helm 3 has no server-side apply, skips present crds/, and rejects the flag). Fixes the CRD ownership conflict that blocked `make setup` on Helm 4. - make setup: guard `kind create cluster` so a partial setup is re-runnable. - operator.yaml: add a readinessProbe on the webhook port (9443) so the `make deploy` rollout waits until the webhook is serving. - run-dev.sh: bounded retry around the webhook-gated PassthroughModel apply (covers the residual endpoint-propagation window), and a per-run mktemp file for the port-forward log.
1 parent 7367fb5 commit 79bf2d0

3 files changed

Lines changed: 46 additions & 7 deletions

File tree

dev/Makefile

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ GIE_VERSION ?= v1.4.0
1010
ENVOY_GATEWAY_VERSION ?= v1.6.7
1111
AI_GATEWAY_VERSION ?= v0.5.0
1212

13+
# Helm 4 applies the eg chart's bundled Gateway API CRDs (crds/) server-side,
14+
# which conflicts with the standalone install in `setup` (owned by a different
15+
# field manager). Force ownership so the chart wins. Helm 3 has no server-side
16+
# apply and skips already-present crds/, so it neither needs nor accepts this
17+
# flag - keep it empty there. Empty when helm is missing (setup fails later).
18+
HELM_MAJOR := $(shell helm version --template '{{.Version}}' 2>/dev/null | sed -E 's/^v?([0-9]+).*/\1/')
19+
HELM_FORCE_CONFLICTS := $(if $(filter-out 1 2 3,$(HELM_MAJOR)),--force-conflicts,)
20+
1321
.PHONY: setup teardown build-images load-images deploy deploy-operator deploy-key-manager apply-test-model apply-passthrough-model create-openrouter-secret run-dev ui logs-operator logs-key-manager clean help
1422

1523
help: ## Show this help
@@ -19,12 +27,16 @@ run-dev: ## One-command UI dev environment: cluster + models + port-forward + ho
1927
./run-dev.sh
2028

2129
setup: ## Create kind cluster and install dependencies
22-
kind create cluster --name $(CLUSTER_NAME)
30+
# Idempotent: skip creation if the cluster already exists so a setup that
31+
# died midway can be re-run without `make teardown` first.
32+
@kind get clusters | grep -qx $(CLUSTER_NAME) || kind create cluster --name $(CLUSTER_NAME)
2333
# cert-manager
2434
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/$(CERT_MANAGER_VERSION)/cert-manager.yaml
2535
kubectl -n cert-manager rollout status deployment/cert-manager-webhook --timeout=120s
26-
# Gateway API CRDs
27-
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEWAY_API_VERSION)/standard-install.yaml
36+
# Gateway API CRDs. Server-side apply so the field ownership matches the
37+
# eg chart's own SSA on Helm 4, letting it cleanly take co-ownership with
38+
# --force-conflicts (see HELM_FORCE_CONFLICTS above) instead of erroring.
39+
kubectl apply --server-side --force-conflicts -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEWAY_API_VERSION)/standard-install.yaml
2840
# GIE CRDs (includes the graduated inference.networking.k8s.io group)
2941
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/releases/download/$(GIE_VERSION)/manifests.yaml
3042
# Envoy AI Gateway first: envoy-gateway's extensionManager below points at
@@ -36,7 +48,7 @@ setup: ## Create kind cluster and install dependencies
3648
# Envoy Gateway, wired with the AI Gateway ext_proc extension (enableBackend,
3749
# extensionManager, backendResources). Without this the per-model routing
3850
# layer 404s and passthrough upstreams never get a TLS transport socket.
39-
helm upgrade -i eg oci://docker.io/envoyproxy/gateway-helm --version $(ENVOY_GATEWAY_VERSION) -n envoy-gateway-system --create-namespace -f eg-extension-values.yaml
51+
helm upgrade -i eg oci://docker.io/envoyproxy/gateway-helm --version $(ENVOY_GATEWAY_VERSION) -n envoy-gateway-system --create-namespace $(HELM_FORCE_CONFLICTS) -f eg-extension-values.yaml
4052
kubectl -n envoy-gateway-system rollout status deployment/envoy-gateway --timeout=120s
4153
# CRDs (served LLMModels and external-provider PassthroughModels)
4254
kubectl apply -f ../charts/nebari-llm-serving/crds/llmmodel-crd.yaml

dev/manifests/operator.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ spec:
147147
- containerPort: 9443
148148
name: webhook-server
149149
protocol: TCP
150+
# The validating webhook binds 9443 only after controller-runtime
151+
# loads the mounted serving cert. Gate readiness on that port so
152+
# `kubectl rollout status` in `make deploy` does not return before the
153+
# webhook can accept PassthroughModel creates (the manager's /readyz
154+
# is only a ping and goes green before the webhook is serving).
155+
readinessProbe:
156+
tcpSocket:
157+
port: 9443
158+
initialDelaySeconds: 2
159+
periodSeconds: 2
150160
volumeMounts:
151161
- name: cert
152162
mountPath: /tmp/k8s-webhook-server/serving-certs

dev/run-dev.sh

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,20 @@ echo "==> applying OpenRouter credential and dev models..."
4545
kubectl -n "$NS" create secret generic openrouter-api-key \
4646
--from-literal=apiKey="$OPENROUTER_API_KEY" \
4747
--dry-run=client -o yaml | kubectl apply -f - >/dev/null
48-
kubectl apply -f manifests/dev-models.yaml >/dev/null
48+
# The operator's validating webhook gates PassthroughModel creates and isn't
49+
# guaranteed to be serving the instant `make deploy`'s rollout returns (it waits
50+
# on the cert mount). Retry so a momentary "connection refused" doesn't trip
51+
# `set -e` and abort the whole run.
52+
for attempt in $(seq 1 30); do
53+
kubectl apply -f manifests/dev-models.yaml >/dev/null 2>&1 && break
54+
if [[ $attempt -eq 30 ]]; then
55+
echo "ERROR: operator webhook never became ready" >&2
56+
kubectl apply -f manifests/dev-models.yaml >&2 || true
57+
exit 1
58+
fi
59+
echo "==> operator webhook not ready yet, retrying ($attempt)..."
60+
sleep 2
61+
done
4962
for m in claude-sonnet-45 gemini-25-flash llama-33-70b; do
5063
kubectl -n "$NS" wait passthroughmodel/$m --for=jsonpath='{.status.phase}'=Ready --timeout=90s
5164
done
@@ -55,14 +68,18 @@ cleanup() {
5568
echo
5669
echo "==> shutting down..."
5770
[[ -n "${PF_PID:-}" ]] && kill "$PF_PID" 2>/dev/null || true
71+
[[ -n "${PF_LOG:-}" ]] && rm -f "$PF_LOG"
5872
}
5973
trap cleanup EXIT INT TERM
6074

75+
# Fresh temp file per run: a fixed path could carry "Forwarding from" from a
76+
# crashed prior run and make the readiness check below pass instantly.
77+
PF_LOG="$(mktemp)"
6178
echo "==> port-forwarding key-manager to localhost:${KM_PORT}..."
62-
kubectl -n "$NS" port-forward svc/llm-key-manager "${KM_PORT}:8080" >/tmp/km-portforward.log 2>&1 &
79+
kubectl -n "$NS" port-forward svc/llm-key-manager "${KM_PORT}:8080" >"$PF_LOG" 2>&1 &
6380
PF_PID=$!
6481
for _ in $(seq 1 20); do
65-
if grep -q "Forwarding from" /tmp/km-portforward.log 2>/dev/null; then break; fi
82+
if grep -q "Forwarding from" "$PF_LOG" 2>/dev/null; then break; fi
6683
sleep 0.5
6784
done
6885

0 commit comments

Comments
 (0)