#!/usr/bin/env bash # Registry plumbing: REGISTRY_MODE none | local | mirror | remote. A script, not ctlptl, # because ctlptl cannot express `mirror`. # Usage: registry.sh up | down | status # Notes: docs/notes/registry.md 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 ─────────────────────────────────────────────────────────────── # Copy REGISTRY_CA_FILE into every kind node's trust store (nodes don't inherit host trust). 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