217 lines
8.5 KiB
Bash
Executable File
217 lines
8.5 KiB
Bash
Executable File
#!/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/<name>, 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/<host>/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" <<TOML
|
|
server = "${upstream}"
|
|
|
|
[host."${upstream}"]
|
|
capabilities = ["pull", "resolve"]
|
|
skip_verify = ${skip_verify}
|
|
TOML
|
|
done
|
|
}
|
|
|
|
# ── the local container (local + mirror) ───────────────────────────────────
|
|
|
|
start_registry_container() {
|
|
if [ "$(docker inspect -f '{{.State.Running}}' "$REG_NAME" 2>/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 - <<YAML >/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
|