Only load locally-built images into kind, auto-detect ingress

- Check stack.yml containers: field to determine which images are local builds
- Only load local images via kind load; let k8s pull registry images directly
- Add is_ingress_running() to skip ingress installation if already running
- Fixes deployment failures when public registry images aren't in local Docker

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
A. F. Dudley 2026-02-03 11:32:22 -05:00
parent 3bc7832d8c
commit d82b3fb881
2 changed files with 44 additions and 7 deletions

View File

@ -29,6 +29,7 @@ from stack_orchestrator.deploy.k8s.helpers import (
from stack_orchestrator.deploy.k8s.helpers import ( from stack_orchestrator.deploy.k8s.helpers import (
install_ingress_for_kind, install_ingress_for_kind,
wait_for_ingress_in_kind, wait_for_ingress_in_kind,
is_ingress_running,
) )
from stack_orchestrator.deploy.k8s.helpers import ( from stack_orchestrator.deploy.k8s.helpers import (
pods_in_deployment, pods_in_deployment,
@ -297,17 +298,30 @@ class K8sDeployer(Deployer):
if actual_cluster != self.kind_cluster_name: if actual_cluster != self.kind_cluster_name:
# An existing cluster was found, use it instead # An existing cluster was found, use it instead
self.kind_cluster_name = actual_cluster self.kind_cluster_name = actual_cluster
# Ensure the referenced containers are copied into kind # Only load locally-built images into kind
load_images_into_kind( # Registry images (docker.io, ghcr.io, etc.) will be pulled by k8s
self.kind_cluster_name, self.cluster_info.image_set local_containers = self.deployment_context.stack.obj.get(
"containers", []
) )
if local_containers:
# Filter image_set to only images matching local containers
local_images = {
img
for img in self.cluster_info.image_set
if any(c in img for c in local_containers)
}
if local_images:
load_images_into_kind(self.kind_cluster_name, local_images)
# Note: if no local containers defined, all images come from registries
self.connect_api() self.connect_api()
if self.is_kind() and not self.skip_cluster_management: if self.is_kind() and not self.skip_cluster_management:
# Configure ingress controller (not installed by default in kind) # Configure ingress controller (not installed by default in kind)
install_ingress_for_kind(self.cluster_info.spec.get_acme_email()) # Skip if already running
# Wait for ingress to start if not is_ingress_running():
# (deployment provisioning will fail unless this is done) install_ingress_for_kind(self.cluster_info.spec.get_acme_email())
wait_for_ingress_in_kind() # Wait for ingress to start
# (deployment provisioning will fail unless this is done)
wait_for_ingress_in_kind()
# Create RuntimeClass if unlimited_memlock is enabled # Create RuntimeClass if unlimited_memlock is enabled
if self.cluster_info.spec.get_unlimited_memlock(): if self.cluster_info.spec.get_unlimited_memlock():
_create_runtime_class( _create_runtime_class(

View File

@ -14,6 +14,7 @@
# along with this program. If not, see <http:#www.gnu.org/licenses/>. # along with this program. If not, see <http:#www.gnu.org/licenses/>.
from kubernetes import client, utils, watch from kubernetes import client, utils, watch
from kubernetes.client.exceptions import ApiException
import os import os
from pathlib import Path from pathlib import Path
import subprocess import subprocess
@ -295,6 +296,28 @@ def destroy_cluster(name: str):
_run_command(f"kind delete cluster --name {name}") _run_command(f"kind delete cluster --name {name}")
def is_ingress_running() -> bool:
"""Check if the Caddy ingress controller is already running in the cluster."""
try:
core_v1 = client.CoreV1Api()
pods = core_v1.list_namespaced_pod(
namespace="caddy-system",
label_selector=(
"app.kubernetes.io/name=caddy-ingress-controller,"
"app.kubernetes.io/component=controller"
),
)
for pod in pods.items:
if pod.status and pod.status.container_statuses:
if pod.status.container_statuses[0].ready is True:
if opts.o.debug:
print("Caddy ingress controller already running")
return True
return False
except ApiException:
return False
def wait_for_ingress_in_kind(): def wait_for_ingress_in_kind():
core_v1 = client.CoreV1Api() core_v1 = client.CoreV1Api()
for i in range(20): for i in range(20):