#!/usr/bin/env bash # Registry plumbing. THIS is the seam — not a tool. # # Four modes, selected by REGISTRY_MODE in the active profile: # # none Tilt builds straight into the node. No registry at all — and so no # guard against an outward push: an unqualified image name means # docker.io/library/, and only Tilt's kind detection stands # between that and a real push. Throwaway use only; every profile # here now defaults to `local` instead. # local a registry:2 container wired into the cluster. # mirror the same container, but configured as a pull-through CACHE of the # corporate registry. What a locked-down client actually looks like: # images originate from corp, you don't hammer it, and you keep # working when the VPN drops. # remote no local container; pull straight from the corporate registry using # an imagePullSecret. # # Deliberately a script rather than a tool. ctlptl collapses the `local` wiring # into one line, but its Registry spec only accepts name/port/image/listenAddress # — there is no way to set REGISTRY_PROXY_REMOTEURL, so it cannot express # `mirror` at all. Keeping the seam here is what keeps the corporate registry # swappable. # # Usage: registry.sh up | down | status set -euo pipefail cd "$(dirname "$0")" source ./lib/config.sh load_config REG_NAME="${CLUSTER}-registry" REG_PORT="${REGISTRY_PORT:-5005}" K="kubectl --context ${KUBECONTEXT}" # ── CA trust ─────────────────────────────────────────────────────────────── # A corporate registry is almost always fronted by an internal CA, and trust has # to reach three separate places. Nothing does this for you, and the symptom when # it's missing is an opaque: # x509: certificate signed by unknown authority # # 1. the host docker daemon — /etc/docker/certs.d//ca.crt (needs root) # 2. every kind node's containerd — nodes do NOT inherit host trust # 3. anything doing HTTPS from inside the cluster, in its own trust store # # We handle (2) here because it's ours to handle. (1) is reported by check.sh # since it needs root. (3) belongs to the workload. install_ca_into_nodes() { [ -n "${REGISTRY_CA_FILE:-}" ] || return 0 if [ ! -r "$REGISTRY_CA_FILE" ]; then echo "REGISTRY_CA_FILE is set but not readable: $REGISTRY_CA_FILE" >&2 exit 1 fi echo " distributing CA to kind nodes" local node for node in $(kind get nodes --name "$CLUSTER"); do docker cp "$REGISTRY_CA_FILE" "$node:/usr/local/share/ca-certificates/corp-registry.crt" docker exec "$node" update-ca-certificates >/dev/null 2>&1 docker exec "$node" systemctl restart containerd done } # Point containerd at a registry host. The cluster config already set # config_path=/etc/containerd/certs.d, so this is a per-node drop-in and needs no # cluster recreate — which is what lets registry mode change on a live cluster. write_hosts_toml() { local host="$1" upstream="$2" skip_verify="${3:-false}" local node for node in $(kind get nodes --name "$CLUSTER"); do docker exec "$node" mkdir -p "/etc/containerd/certs.d/${host}" docker exec -i "$node" cp /dev/stdin "/etc/containerd/certs.d/${host}/hosts.toml" </dev/null || true)" = "true" ]; then echo " registry container '$REG_NAME' already running" return fi docker rm -f "$REG_NAME" >/dev/null 2>&1 || true local args=(-d --restart=always --name "$REG_NAME" -p "127.0.0.1:${REG_PORT}:5000") if [ "$REGISTRY_MODE" = "mirror" ]; then if [ -z "${REGISTRY_REMOTE_URL:-}" ]; then echo "REGISTRY_MODE=mirror needs REGISTRY_REMOTE_URL in ctrl/.env" >&2 exit 1 fi echo " starting pull-through cache of ${REGISTRY_REMOTE_URL}" args+=(-e "REGISTRY_PROXY_REMOTEURL=${REGISTRY_REMOTE_URL}") [ -n "${REGISTRY_USER:-}" ] && args+=(-e "REGISTRY_PROXY_USERNAME=${REGISTRY_USER}") [ -n "${REGISTRY_PASSWORD:-}" ] && args+=(-e "REGISTRY_PROXY_PASSWORD=${REGISTRY_PASSWORD}") if [ -n "${REGISTRY_CA_FILE:-}" ]; then args+=(-v "$(readlink -f "$REGISTRY_CA_FILE"):/etc/ssl/certs/corp-ca.crt:ro") fi else echo " starting local registry" fi docker run "${args[@]}" "$REGISTRY_IMAGE" >/dev/null } # The registry must share a network with the nodes so they can resolve it by # container name; localhost inside a node is the node, not the host. join_kind_network() { if docker inspect -f '{{json .NetworkSettings.Networks}}' "$REG_NAME" | grep -q '"kind"'; then return fi docker network connect kind "$REG_NAME" >/dev/null 2>&1 || true } # The documented contract that tells tooling (Tilt, skaffold) where the local # registry is, so they don't have to be configured separately. apply_hosting_configmap() { $K apply -f - </dev/null apiVersion: v1 kind: ConfigMap metadata: name: local-registry-hosting namespace: kube-public data: localRegistryHosting.v1: | host: "localhost:${REG_PORT}" help: "https://kind.sigs.k8s.io/docs/user/local-registry/" YAML } # ── modes ────────────────────────────────────────────────────────────────── up() { echo "registry: ${REGISTRY_MODE}" case "$REGISTRY_MODE" in none) echo " no registry — images are built straight into the node" ;; local|mirror) start_registry_container join_kind_network install_ca_into_nodes # Nodes reach the registry by container name on the shared network; # the host reaches it on localhost:PORT. Both names must resolve. write_hosts_toml "localhost:${REG_PORT}" "http://${REG_NAME}:5000" if [ "$REGISTRY_MODE" = "mirror" ]; then # Anything asking for docker.io transparently goes to the cache. write_hosts_toml "docker.io" "http://${REG_NAME}:5000" fi apply_hosting_configmap echo " ready at localhost:${REG_PORT}" ;; remote) if [ -z "${REGISTRY_REMOTE_URL:-}" ]; then echo "REGISTRY_MODE=remote needs REGISTRY_REMOTE_URL in ctrl/.env" >&2 exit 1 fi install_ca_into_nodes local host="${REGISTRY_REMOTE_URL#*://}"; host="${host%%/*}" if [ -n "${REGISTRY_USER:-}" ]; then echo " creating imagePullSecret for ${host}" $K create secret docker-registry regcred \ --docker-server="$host" \ --docker-username="$REGISTRY_USER" \ --docker-password="$REGISTRY_PASSWORD" \ --dry-run=client -o yaml | $K apply -f - >/dev/null # Attach to the default ServiceAccount so plain pods inherit it. $K patch serviceaccount default \ -p '{"imagePullSecrets":[{"name":"regcred"}]}' >/dev/null fi echo " pulling directly from ${host}" ;; *) echo "unknown REGISTRY_MODE '$REGISTRY_MODE' (expected none|local|mirror|remote)" >&2 exit 1 ;; esac } down() { if docker inspect "$REG_NAME" >/dev/null 2>&1; then echo "removing registry container '$REG_NAME'" docker rm -f "$REG_NAME" >/dev/null fi } status() { echo "mode ${REGISTRY_MODE}" if docker inspect "$REG_NAME" >/dev/null 2>&1; then echo "container ${REG_NAME} $(docker inspect -f '{{.State.Status}}' "$REG_NAME")" echo "endpoint localhost:${REG_PORT}" else echo "container none" fi [ -n "${REGISTRY_REMOTE_URL:-}" ] && echo "upstream ${REGISTRY_REMOTE_URL}" [ -n "${REGISTRY_CA_FILE:-}" ] && echo "ca ${REGISTRY_CA_FILE}" return 0 } case "${1:-status}" in up) up ;; down) down ;; status) status ;; *) echo "usage: $0 [up|down|status]" >&2; exit 1 ;; esac