Compare commits
2 Commits
2ef6139957
...
rig-0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 100f389719 | |||
| 3bbfa413a3 |
1
rig/VERSION
Normal file
1
rig/VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.1.0
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
# The pool is derived from the kind Docker network at install time.
|
# The pool is derived from the kind Docker network at install time.
|
||||||
# Notes: docs/notes/addons.md
|
# Notes: docs/notes/addons.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:-$(dirname "$0")/..}"
|
cd "${RIG_CTRL:-$(dirname "$0")/..}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -61,27 +62,7 @@ $K rollout status daemonset/speaker -n metallb-system --timeout=240s
|
|||||||
# on a race that resolves itself in seconds.
|
# on a race that resolves itself in seconds.
|
||||||
echo " configuring the address pool"
|
echo " configuring the address pool"
|
||||||
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
||||||
if $K apply -f - >/dev/null 2>&1 <<YAML
|
if render_manifest "$ADDON_DIR/metallb.yaml" | $K apply -f - >/dev/null 2>&1
|
||||||
apiVersion: metallb.io/v1beta1
|
|
||||||
kind: IPAddressPool
|
|
||||||
metadata:
|
|
||||||
name: default
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
addresses:
|
|
||||||
- ${pool_start}-${pool_end}
|
|
||||||
---
|
|
||||||
# Layer 2 mode: one node answers ARP for each address. No BGP peer needed, which
|
|
||||||
# is what makes this work on a laptop.
|
|
||||||
apiVersion: metallb.io/v1beta1
|
|
||||||
kind: L2Advertisement
|
|
||||||
metadata:
|
|
||||||
name: default
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
ipAddressPools:
|
|
||||||
- default
|
|
||||||
YAML
|
|
||||||
then
|
then
|
||||||
echo " pool ready: ${pool_start}-${pool_end}"
|
echo " pool ready: ${pool_start}-${pool_end}"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
19
rig/ctrl/addons/metallb.yaml
Normal file
19
rig/ctrl/addons/metallb.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: metallb.io/v1beta1
|
||||||
|
kind: IPAddressPool
|
||||||
|
metadata:
|
||||||
|
name: default
|
||||||
|
namespace: metallb-system
|
||||||
|
spec:
|
||||||
|
addresses:
|
||||||
|
- ${pool_start}-${pool_end}
|
||||||
|
---
|
||||||
|
# Layer 2 mode: one node answers ARP for each address. No BGP peer needed, which
|
||||||
|
# is what makes this work on a laptop.
|
||||||
|
apiVersion: metallb.io/v1beta1
|
||||||
|
kind: L2Advertisement
|
||||||
|
metadata:
|
||||||
|
name: default
|
||||||
|
namespace: metallb-system
|
||||||
|
spec:
|
||||||
|
ipAddressPools:
|
||||||
|
- default
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Readiness check: is this machine ready to run rig? Reports and instructs; never fixes.
|
# Readiness check: is this machine ready to run rig? Reports and instructs; never fixes.
|
||||||
# Usage: check.sh [all | mem [status|push|all|backup|restore]] (all = every detail)
|
# Usage: check.sh [all | mem [status|push|all|backup|restore]] (all = every detail)
|
||||||
|
# Prints the memory budget and what to do next first, then the configuration as it is.
|
||||||
# Notes: docs/notes/check.md
|
# Notes: docs/notes/check.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
@@ -12,13 +13,11 @@ if [ "${1:-}" = mem ]; then
|
|||||||
exec bash ./mem.sh "${@:-status}"
|
exec bash ./mem.sh "${@:-status}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Compact by default: facts only with `all`; problems (!) always print.
|
# Facts only with `all`; problems (!) always print.
|
||||||
VERBOSE=""
|
VERBOSE=""
|
||||||
if [ "${1:-}" = all ]; then VERBOSE=1; fi
|
if [ "${1:-}" = all ]; then VERBOSE=1; fi
|
||||||
fact() { if [ -n "$VERBOSE" ]; then echo "$@"; fi; }
|
fact() { if [ -n "$VERBOSE" ]; then echo "$@"; fi; }
|
||||||
|
|
||||||
bash ./deps.sh detect ${VERBOSE:+all}
|
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
load_config
|
load_config
|
||||||
|
|
||||||
@@ -66,49 +65,64 @@ port_busy() {
|
|||||||
grep -qi "^ *[0-9]*: [0-9A-F]*$hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null
|
grep -qi "^ *[0-9]*: [0-9A-F]*$hex " /proc/net/tcp /proc/net/tcp6 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
echo
|
# Who listens on a port, as "pid name", when ss can tell (only for our own processes).
|
||||||
echo "rig"
|
port_owner() {
|
||||||
echo " cluster ${CLUSTER} (${KUBECONTEXT}) profile ${PROFILE_NAME}, ${NODES} node(s), registry ${REGISTRY_MODE}"
|
command -v ss >/dev/null 2>&1 || return 0
|
||||||
if [ -n "${OVERLAY:-}" ]; then
|
ss -ltnpH "sport = :$1" 2>/dev/null \
|
||||||
echo " overlay $(basename "$(_abs_from_ctrl "$OVERLAY_DIR")") ($(_abs_from_ctrl "$OVERLAY_DIR"))"
|
| sed -n 's/.*users:(("\([^"]*\)",pid=\([0-9]*\).*/\2 \1/p' | head -1
|
||||||
elif [ -n "$OVERLAY_DIR" ]; then
|
}
|
||||||
fact " overlay none named — rig's own ${OVERLAY_DIR}"
|
|
||||||
fi
|
# This environment's Tilt: a tilt started from this ctrl/ (the Makefile runs it there).
|
||||||
if [ -n "$VERBOSE" ] && [ -n "$OVERLAY_DIR" ]; then
|
tilt_pid=""
|
||||||
ov=$(_from_ctrl "$OVERLAY_DIR") pieces=""
|
if tilt_owner=$(port_owner "$TILT_PORT") && [ -n "$tilt_owner" ]; then
|
||||||
for piece in rig.env k8s/overlays/dev kind-config.yaml.tpl addons Tiltfile; do
|
pid=${tilt_owner%% *}
|
||||||
[ -e "$ov/$piece" ] && pieces+="$piece "
|
if [ "${tilt_owner#* }" = tilt ] && [ "$(readlink "/proc/$pid/cwd" 2>/dev/null)" = "$(pwd -P)" ]; then
|
||||||
done
|
tilt_pid=$pid
|
||||||
echo " provides: ${pieces:-nothing rig reads}"
|
fi
|
||||||
fi
|
|
||||||
fact " manifests ${MANIFESTS_DIR:-none}"
|
|
||||||
fact " kind config ${KIND_CONFIG}"
|
|
||||||
fact " ingress ${INGRESS_MODE}"
|
|
||||||
if [ ! -f ./.env ]; then
|
|
||||||
fact " .env none — built-in defaults (cp ctrl/.env.example ctrl/.env to set values)"
|
|
||||||
fi
|
|
||||||
if [ -n "${STALE_MANIFESTS_DIR:-}" ]; then
|
|
||||||
echo " ! .env MANIFESTS_DIR=${STALE_MANIFESTS_DIR} is the old default; rig's examples moved"
|
|
||||||
echo " to examples/ — delete that line from ctrl/.env (ignored until then)"
|
|
||||||
fi
|
|
||||||
# registry.sh points containerd at certs.d, which only works if the kind config says so,
|
|
||||||
# and a kind config is fixed at creation: a project's own file that drops it fails silently.
|
|
||||||
if [ "$REGISTRY_MODE" != none ] && ! grep -q 'config_path *= *"/etc/containerd/certs.d"' "$KIND_CONFIG"; then
|
|
||||||
echo " ! kind ${KIND_CONFIG} lacks the containerd config_path patch that registry mode"
|
|
||||||
echo " '${REGISTRY_MODE}' needs — copy it from ctrl/k8s/kind-config.yaml.tpl"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── memory: does this cluster fit right now? Warns; never blocks. ──────────
|
# none | stopped | up | unknown (no docker)
|
||||||
|
cluster_state() {
|
||||||
|
docker info >/dev/null 2>&1 || { echo unknown; return; }
|
||||||
|
local f="label=io.x-k8s.kind.cluster=${CLUSTER}"
|
||||||
|
if [ -n "$(docker ps -q --filter "$f")" ]; then echo up
|
||||||
|
elif [ -n "$(docker ps -aq --filter "$f")" ]; then echo stopped
|
||||||
|
else echo none; fi
|
||||||
|
}
|
||||||
|
state=$(cluster_state)
|
||||||
|
|
||||||
|
# ── memory budget: what this environment needs, against what the machine has ──
|
||||||
|
# The workload's share comes from the overlay: MEMORY_BUDGET="name=MB ..." in its rig.env,
|
||||||
|
# and MEMORY_BUDGET_TIP, optionally, says how to switch its largest part off.
|
||||||
|
# rig only knows what a node costs. Warns; never blocks.
|
||||||
total_mb=$(mb_of MemTotal)
|
total_mb=$(mb_of MemTotal)
|
||||||
avail_mb=$(mb_of MemAvailable)
|
avail_mb=$(mb_of MemAvailable)
|
||||||
swap_used_mb=$(( $(mb_of SwapTotal) - $(mb_of SwapFree) ))
|
swap_used_mb=$(( $(mb_of SwapTotal) - $(mb_of SwapFree) ))
|
||||||
overcommit=$(cat "${OVERCOMMIT_FILE:-/proc/sys/vm/overcommit_memory}" 2>/dev/null || echo '?')
|
overcommit=$(cat "${OVERCOMMIT_FILE:-/proc/sys/vm/overcommit_memory}" 2>/dev/null || echo '?')
|
||||||
need_mb=$(( NODES * NODE_MB ))
|
node_mb=$(( NODES * NODE_MB ))
|
||||||
|
|
||||||
|
budget="" work_mb=0 big_mb=0 big=""
|
||||||
|
for item in ${MEMORY_BUDGET:-}; do
|
||||||
|
name=${item%=*} mb=${item##*=}
|
||||||
|
if [ "$name" = "$item" ] || ! [[ "$mb" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo " ! budget '$item' in MEMORY_BUDGET is not name=MB"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[ "$mb" -gt 0 ] || continue
|
||||||
|
budget+=$(printf ' %-18s %6d MB' "$name" "$mb")$'\n'
|
||||||
|
work_mb=$(( work_mb + mb ))
|
||||||
|
if [ "$mb" -gt "$big_mb" ]; then big_mb=$mb big=$name; fi
|
||||||
|
done
|
||||||
|
need_mb=$(( work_mb + node_mb ))
|
||||||
|
|
||||||
rows=$(container_mb)
|
rows=$(container_mb)
|
||||||
# If our cluster is already up, its memory is already out of MemAvailable: need nothing more.
|
# If our cluster is already up, its memory is already out of MemAvailable.
|
||||||
ours_mb=$(awk -F'\t' -v c="$CLUSTER" '$2 == c { s += $1 } END { print s + 0 }' <<< "$rows")
|
ours_mb=$(awk -F'\t' -v c="$CLUSTER" '$2 == c { s += $1 } END { print s + 0 }' <<< "$rows")
|
||||||
still_mb=$(( ours_mb > 0 ? 0 : need_mb ))
|
if [ "$state" = up ]; then
|
||||||
|
still_mb=$(( need_mb > ours_mb ? need_mb - ours_mb : 0 ))
|
||||||
|
else
|
||||||
|
still_mb=$need_mb
|
||||||
|
fi
|
||||||
headroom=$(( avail_mb - still_mb ))
|
headroom=$(( avail_mb - still_mb ))
|
||||||
|
|
||||||
# The biggest things holding memory, other than this cluster: kind clusters summed, the rest by name.
|
# The biggest things holding memory, other than this cluster: kind clusters summed, the rest by name.
|
||||||
@@ -117,82 +131,164 @@ others=$(awk -F'\t' -v c="$CLUSTER" '
|
|||||||
$2 == "-" { k["container \x27" $3 "\x27"] += $1 }
|
$2 == "-" { k["container \x27" $3 "\x27"] += $1 }
|
||||||
END { for (n in k) printf "%d\t%s\n", k[n], n }' <<< "$rows" | sort -rn)
|
END { for (n in k) printf "%d\t%s\n", k[n], n }' <<< "$rows" | sort -rn)
|
||||||
|
|
||||||
if [ "$still_mb" -eq 0 ] && [ "$headroom" -ge 512 ]; then
|
echo
|
||||||
printf " memory up, holding %d MB — %d MB headroom for what you deploy\n" "$ours_mb" "$headroom"
|
echo "memory budget (${CLUSTER})"
|
||||||
elif [ "$still_mb" -eq 0 ]; then
|
printf '%s' "$budget"
|
||||||
printf " ! memory up, but only %d MB headroom for anything you deploy\n" "$headroom"
|
if [ -z "$budget" ]; then
|
||||||
elif [ "$headroom" -ge 512 ]; then
|
echo " workload ? (the overlay lists none: MEMORY_BUDGET in its rig.env)"
|
||||||
printf " memory fits — ~%d MB for %s node(s), %d MB headroom\n" "$need_mb" "$NODES" "$headroom"
|
fi
|
||||||
|
printf ' %-18s %6d MB (kind control plane, kube-system; %s node(s))\n' "node overhead" "$node_mb" "$NODES"
|
||||||
|
echo " ---------"
|
||||||
|
printf ' %-18s %6d MB\n' "required" "$need_mb"
|
||||||
|
if [ "$state" = up ]; then
|
||||||
|
printf ' %-18s %6d MB (%s is up)\n' "held by it now" "$ours_mb" "$CLUSTER"
|
||||||
|
fi
|
||||||
|
printf ' %-18s %6d MB\n' "this machine has" "$total_mb"
|
||||||
|
printf ' %-18s %6d MB (right now)\n' "available" "$avail_mb"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ "$headroom" -ge 512 ]; then
|
||||||
|
if [ "$state" = up ]; then
|
||||||
|
echo "fits: ${CLUSTER} holds ${ours_mb} MB of its ${need_mb} MB; ${avail_mb} MB still available — ${headroom} MB headroom."
|
||||||
|
else
|
||||||
|
echo "fits: ${need_mb} MB needed, ${avail_mb} MB available — ${headroom} MB headroom."
|
||||||
|
fi
|
||||||
elif [ "$headroom" -ge 0 ]; then
|
elif [ "$headroom" -ge 0 ]; then
|
||||||
printf " ! memory fits, but only %d MB headroom (~%d MB for %s node(s))\n" "$headroom" "$need_mb" "$NODES"
|
echo "! tight: ${still_mb} MB still needed, ${avail_mb} MB available — only ${headroom} MB headroom."
|
||||||
else
|
else
|
||||||
printf " ! memory does not fit: ~%d MB needed, %d MB available\n" "$still_mb" "$avail_mb"
|
echo "! does not fit: ${still_mb} MB still needed, ${avail_mb} MB available."
|
||||||
# Two failures with opposite fixes, and telling them apart is the point.
|
# Two failures with opposite fixes, and telling them apart is the point.
|
||||||
if [ "$still_mb" -le "$total_mb" ]; then
|
if [ "$need_mb" -le "$total_mb" ]; then
|
||||||
echo " something else holds it (below) — stopping that helps, a bigger VM would not."
|
echo " Something else holds the memory (below): stopping it helps, a bigger machine would not."
|
||||||
if grep -q 'kind cluster' <<< "$others"; then
|
if grep -q 'kind cluster' <<< "$others"; then
|
||||||
echo " 'make cluster free' stops the other kind clusters. It stops, never deletes."
|
echo " 'make cluster free' stops the other kind clusters. It stops, never deletes."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " the machine itself is too small: ${total_mb} MB total."
|
echo " The machine itself is too small: ${total_mb} MB in all. Give it more memory"
|
||||||
|
echo " ('make check mem' has the WSL steps), or switch parts of the workload off."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -n "$others" ] && { [ -n "$VERBOSE" ] || [ "$headroom" -lt 512 ]; }; then
|
if [ -n "$others" ] && { [ -n "$VERBOSE" ] || [ "$headroom" -lt 512 ]; }; then
|
||||||
echo " held elsewhere:"
|
echo " held elsewhere:"
|
||||||
head -6 <<< "$others" | awk -F'\t' '{ printf " %6d MB %s\n", $1, $2 }'
|
head -6 <<< "$others" | awk -F'\t' '{ printf " %6d MB %s\n", $1, $2 }'
|
||||||
n_others=$(wc -l <<< "$others")
|
n_others=$(wc -l <<< "$others")
|
||||||
if [ "$n_others" -gt 6 ]; then
|
if [ "$n_others" -gt 6 ]; then
|
||||||
echo " ... and $((n_others - 6)) more"
|
echo " ... and $((n_others - 6)) more"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# One part that outweighs the rest is the first thing to switch off on a smaller machine.
|
||||||
|
if [ "$work_mb" -gt 0 ] && [ $(( big_mb * 2 )) -gt "$work_mb" ] && [ "$big_mb" -ge 1024 ]; then
|
||||||
|
echo
|
||||||
|
echo "${big} is ${big_mb} of the ${work_mb} MB the workload needs — more than the rest"
|
||||||
|
echo "combined. If you are not working on it, ${MEMORY_BUDGET_TIP:-switching it off in the overlay} gets that back."
|
||||||
|
fi
|
||||||
if [ "$swap_used_mb" -gt 0 ]; then
|
if [ "$swap_used_mb" -gt 0 ]; then
|
||||||
fact " ${swap_used_mb} MB already in swap, which 'available' does not count: expect slow before failing"
|
fact " ${swap_used_mb} MB already in swap, which 'available' does not count: expect slow before failing"
|
||||||
fi
|
fi
|
||||||
if [ "$overcommit" = "1" ]; then
|
if [ "$overcommit" = "1" ]; then
|
||||||
fact " overcommit=1: allocations never fail, so read 'fits' as a ceiling (OOM killer settles up)"
|
fact " overcommit=1: allocations never fail, so read 'fits' as a ceiling (OOM killer settles up)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── where things stand, and the next step ──────────────────────────────────
|
||||||
|
echo
|
||||||
|
case "$state" in
|
||||||
|
none)
|
||||||
|
echo "${CLUSTER} does not exist yet. 'make cluster up' creates it${ADDONS:+ (with ${ADDONS})},"
|
||||||
|
echo "then 'make tilt' starts the dev loop." ;;
|
||||||
|
stopped)
|
||||||
|
echo "${CLUSTER} exists but is stopped (nothing is lost). Start it again with:"
|
||||||
|
echo " docker start \$(docker ps -aq -f label=io.x-k8s.kind.cluster=${CLUSTER})" ;;
|
||||||
|
up)
|
||||||
|
if [ -n "$tilt_pid" ]; then
|
||||||
|
echo "${CLUSTER} is up, and its Tilt is running (pid ${tilt_pid}): http://localhost:${TILT_PORT}"
|
||||||
|
else
|
||||||
|
echo "${CLUSTER} is up. 'make tilt' starts the dev loop, at http://localhost:${TILT_PORT}."
|
||||||
|
fi
|
||||||
|
echo "Through the gateway: http://localhost:${HTTP_PORT}" ;;
|
||||||
|
*)
|
||||||
|
echo "docker is not reachable, so the cluster's state is unknown (the machine block below says why)." ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ── the configuration, as it is ────────────────────────────────────────────
|
||||||
|
echo
|
||||||
|
bash ./deps.sh detect ${VERBOSE:+all}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "config"
|
||||||
|
echo " cluster ${CLUSTER} ($([ "$state" = none ] && echo 'not created' || echo "$state"))"
|
||||||
|
echo " context ${KUBECONTEXT}"
|
||||||
|
if [ -n "${OVERLAY:-}" ]; then
|
||||||
|
echo " overlay $(_abs_from_ctrl "$OVERLAY_DIR")"
|
||||||
|
elif [ -n "$OVERLAY_DIR" ]; then
|
||||||
|
echo " overlay none named — rig's own ${OVERLAY_DIR}"
|
||||||
|
fi
|
||||||
|
if [ -n "$VERBOSE" ] && [ -n "$OVERLAY_DIR" ]; then
|
||||||
|
ov=$(_from_ctrl "$OVERLAY_DIR") pieces=""
|
||||||
|
for piece in rig.env k8s/overlays/dev kind-config.yaml.tpl addons Tiltfile; do
|
||||||
|
[ -e "$ov/$piece" ] && pieces+="$piece "
|
||||||
|
done
|
||||||
|
echo " provides: ${pieces:-nothing rig reads}"
|
||||||
|
fi
|
||||||
|
echo " profile ${PROFILE_NAME}"
|
||||||
|
echo " nodes ${NODES}"
|
||||||
|
echo " kind config ${KIND_CONFIG}"
|
||||||
|
echo " manifests ${MANIFESTS_DIR:-none}"
|
||||||
|
echo " ingress ${INGRESS_MODE}"
|
||||||
|
if [ ! -f ./.env ]; then
|
||||||
|
fact " .env none — built-in defaults (cp ctrl/.env.example ctrl/.env to set values)"
|
||||||
|
fi
|
||||||
|
if [ -n "${STALE_MANIFESTS_DIR:-}" ]; then
|
||||||
|
echo " ! .env MANIFESTS_DIR=${STALE_MANIFESTS_DIR} is the old default; rig's examples moved"
|
||||||
|
echo " to examples/ — delete that line from ctrl/.env (ignored until then)"
|
||||||
|
fi
|
||||||
|
# registry.sh points containerd at certs.d, which only works if the kind config says so,
|
||||||
|
# and a kind config is fixed at creation: a project's own file that drops it fails silently.
|
||||||
|
if [ "$REGISTRY_MODE" != none ] && ! grep -q 'config_path *= *"/etc/containerd/certs.d"' "$KIND_CONFIG"; then
|
||||||
|
echo " ! kind ${KIND_CONFIG} lacks the containerd config_path patch that registry mode"
|
||||||
|
echo " '${REGISTRY_MODE}' needs — copy it from ctrl/k8s/kind-config.yaml.tpl"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── ports: checked before creation; docker reports a clash only halfway through. ──
|
# ── ports: checked before creation; docker reports a clash only halfway through. ──
|
||||||
# Ports held by our own cluster are not clashes. Second grep, not `tr -d ':->'` (a tr range).
|
# Ports held by our own cluster are not clashes: its node containers, and its registry,
|
||||||
ours=$(docker ps --filter "label=io.x-k8s.kind.cluster=${CLUSTER}" \
|
# which is a container of its own. Second grep, not `tr -d ':->'` (a tr range).
|
||||||
--format '{{.Ports}}' 2>/dev/null | tr ',' '\n' \
|
ours=$({ docker ps --filter "label=io.x-k8s.kind.cluster=${CLUSTER}" --format '{{.Ports}}'
|
||||||
| grep -oE ':[0-9]+->' | grep -oE '[0-9]+' || true)
|
docker ps --filter "name=^${CLUSTER}-registry\$" --format '{{.Ports}}'; } 2>/dev/null \
|
||||||
|
| tr ',' '\n' | grep -oE ':[0-9]+->' | grep -oE '[0-9]+' || true)
|
||||||
clash=0 list="" mine=0
|
|
||||||
for entry in "HTTP:${HTTP_PORT}" "HTTPS:${HTTPS_PORT}" \
|
clash=0 list=""
|
||||||
"TILT:${TILT_PORT}" "REGISTRY:${REGISTRY_PORT}"; do
|
for entry in "http:${HTTP_PORT}" "https:${HTTPS_PORT}" \
|
||||||
|
"tilt:${TILT_PORT}" "registry:${REGISTRY_PORT}"; do
|
||||||
name="${entry%%:*}"; p="${entry#*:}"
|
name="${entry%%:*}"; p="${entry#*:}"
|
||||||
[ -n "$p" ] || continue
|
[ -n "$p" ] || continue
|
||||||
list+="$p "
|
list+="$name $p "
|
||||||
if ! port_busy "$p"; then
|
if ! port_busy "$p"; then
|
||||||
fact "$(printf " %-9s %-6s free" "$name" "$p")"
|
fact "$(printf " %-9s %-6s free" "$name" "$p")"
|
||||||
elif echo "$ours" | grep -qx "$p"; then
|
elif echo "$ours" | grep -qx "$p"; then
|
||||||
mine=1
|
|
||||||
fact "$(printf " %-9s %-6s in use by this environment's cluster" "$name" "$p")"
|
fact "$(printf " %-9s %-6s in use by this environment's cluster" "$name" "$p")"
|
||||||
|
elif [ "$name" = tilt ] && [ -n "$tilt_pid" ]; then
|
||||||
|
fact "$(printf " %-9s %-6s in use by this environment's Tilt" "$name" "$p")"
|
||||||
else
|
else
|
||||||
printf " ! ports %s %s IN USE by something else\n" "$name" "$p"
|
owner=$(port_owner "$p")
|
||||||
clash=1
|
if [ "$name" = tilt ] && [ "${owner#* }" = tilt ]; then
|
||||||
|
printf " ! ports tilt %s is held by another Tilt (pid %s, in %s)\n" "$p" "${owner%% *}" \
|
||||||
|
"$(readlink "/proc/${owner%% *}/cwd" 2>/dev/null || echo '?')"
|
||||||
|
echo " stop it (kill ${owner%% *}) before 'make tilt'"
|
||||||
|
else
|
||||||
|
printf " ! ports %s %s IN USE by %s\n" "$name" "$p" "${owner:+pid ${owner}}${owner:-something else}"
|
||||||
|
clash=1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
echo " ports ${list% }"
|
||||||
if [ "$clash" -eq 1 ]; then
|
if [ "$clash" -eq 1 ]; then
|
||||||
echo " override it in ctrl/.env (e.g. HTTP_PORT=21080), or rename this directory"
|
echo " override it in ctrl/.env (e.g. HTTP_PORT=21080), or rename this directory"
|
||||||
elif [ "$mine" -eq 1 ]; then
|
|
||||||
echo " ports ${list% } held by this cluster"
|
|
||||||
else
|
|
||||||
echo " ports ${list% } free"
|
|
||||||
fi
|
fi
|
||||||
if [ -n "${OVERLAY:-}" ]; then
|
|
||||||
fact " derived from the overlay's folder name"
|
|
||||||
else
|
|
||||||
fact " derived from the directory name; pin them: bash ctrl/ports.sh persist"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── what `make cluster up` wires in beside the cluster ─────────────────────
|
# ── what `make cluster up` wires in beside the cluster ─────────────────────
|
||||||
REG_NAME="${CLUSTER}-registry"
|
REG_NAME="${CLUSTER}-registry"
|
||||||
if state=$(docker inspect -f '{{.State.Status}}' "$REG_NAME" 2>/dev/null); then
|
if reg_state=$(docker inspect -f '{{.State.Status}}' "$REG_NAME" 2>/dev/null); then
|
||||||
echo " registry localhost:${REGISTRY_PORT} ($state)"
|
echo " registry ${REGISTRY_MODE} localhost:${REGISTRY_PORT} (${reg_state})"
|
||||||
else
|
else
|
||||||
fact " registry no container yet — 'make cluster up' starts it"
|
echo " registry ${REGISTRY_MODE} (no container yet)"
|
||||||
fi
|
fi
|
||||||
echo " addons ${ADDONS:-none}"
|
echo " addons ${ADDONS:-none}"
|
||||||
if [ -n "$VERBOSE" ]; then
|
if [ -n "$VERBOSE" ]; then
|
||||||
|
|||||||
@@ -16,12 +16,30 @@ serve() {
|
|||||||
echo "no docs/index.html" >&2
|
echo "no docs/index.html" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# One worker and no access log: a few static pages need no more, and the
|
||||||
|
# terminal then shows only real errors. The config is a temp file, gone on exit.
|
||||||
|
# Global, not local: the trap runs after serve() has returned.
|
||||||
|
DOCS_CONF=$(mktemp)
|
||||||
|
trap 'rm -f "$DOCS_CONF"' EXIT
|
||||||
|
cat > "$DOCS_CONF" <<'NGINX'
|
||||||
|
worker_processes 1;
|
||||||
|
error_log stderr error;
|
||||||
|
events { worker_connections 64; }
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
access_log off;
|
||||||
|
server { listen 80; root /usr/share/nginx/html; }
|
||||||
|
}
|
||||||
|
NGINX
|
||||||
|
chmod 644 "$DOCS_CONF"
|
||||||
echo "docs for '$CLUSTER' on http://localhost:${DOCS_PORT}"
|
echo "docs for '$CLUSTER' on http://localhost:${DOCS_PORT}"
|
||||||
echo " (ctrl-c to stop; nothing is installed and nothing persists)"
|
echo " (ctrl-c to stop; nothing is installed and nothing persists)"
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--name "${CLUSTER}-docs" \
|
--name "${CLUSTER}-docs" \
|
||||||
|
-e NGINX_ENTRYPOINT_QUIET_LOGS=1 \
|
||||||
-p "${DOCS_PORT}:80" \
|
-p "${DOCS_PORT}:80" \
|
||||||
-v "$REPO/docs:/usr/share/nginx/html:ro" \
|
-v "$REPO/docs:/usr/share/nginx/html:ro" \
|
||||||
|
-v "$DOCS_CONF:/etc/nginx/nginx.conf:ro" \
|
||||||
nginx:alpine
|
nginx:alpine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -208,6 +208,29 @@ render_kind_config() {
|
|||||||
"$KIND_CONFIG"
|
"$KIND_CONFIG"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Render an addon's manifest (a .yaml beside its .sh) to stdout. Every ${NAME}, or
|
||||||
|
# ${NAME:-default}, is filled from the loaded config or the calling script's variables;
|
||||||
|
# a NAME with neither is an error, so a typo never becomes an empty image. $VAR and
|
||||||
|
# $(VAR) are left alone: they belong to the container. Notes: docs/notes/addons.md
|
||||||
|
render_manifest() {
|
||||||
|
local file="$1" text ref key def val
|
||||||
|
text=$(<"$file") || return 1
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
key=${ref#\$\{}; key=${key%\}}; def=""
|
||||||
|
if [[ $key == *:-* ]]; then def=${key#*:-}; key=${key%%:-*}; fi
|
||||||
|
if [ -n "${!key:-}" ]; then
|
||||||
|
val=${!key}
|
||||||
|
elif [[ $ref == *:-* ]]; then
|
||||||
|
val=$def
|
||||||
|
else
|
||||||
|
echo "render_manifest: $file needs \${$key}, which nothing sets" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
text=${text//"$ref"/"$val"}
|
||||||
|
done < <(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}' "$file" | sort -u)
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
}
|
||||||
|
|
||||||
_config_restore() {
|
_config_restore() {
|
||||||
local line
|
local line
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ HOUSE_PAT="$(printf '%s' 'semes' 'ter|local' '\.ar\b')"
|
|||||||
check "✖ S2 no example environment name from the copies era" "0" \
|
check "✖ S2 no example environment name from the copies era" "0" \
|
||||||
"$(cd .. && grep -rIlE "$COPIES_PAT" . --exclude-dir=def --exclude-dir=local --exclude=STALE.md 2>/dev/null | wc -l)"
|
"$(cd .. && grep -rIlE "$COPIES_PAT" . --exclude-dir=def --exclude-dir=local --exclude=STALE.md 2>/dev/null | wc -l)"
|
||||||
check "✖ S3 ctrl/addons makes the cluster work, nothing more" "cert-manager metallb metrics-server" \
|
check "✖ S3 ctrl/addons makes the cluster work, nothing more" "cert-manager metallb metrics-server" \
|
||||||
"$(ls addons/ | sed 's/\.sh$//' | sort | xargs)"
|
"$(ls addons/*.sh | xargs -n1 basename | sed 's/\.sh$//' | sort | xargs)" # the scripts; their manifests sit beside them
|
||||||
check "✖ S3 versions.env pins no workload image" "0" \
|
check "✖ S3 versions.env pins no workload image" "0" \
|
||||||
"$(grep -cE '^(POSTGRES|REDIS|AIRFLOW)_IMAGE=' versions.env)"
|
"$(grep -cE '^(POSTGRES|REDIS|AIRFLOW)_IMAGE=' versions.env)"
|
||||||
check "✖ S4 no namespace named after the cluster" "0" "$(grep -c "CLUSTER + ':namespace'" Tiltfile)"
|
check "✖ S4 no namespace named after the cluster" "0" "$(grep -c "CLUSTER + ':namespace'" Tiltfile)"
|
||||||
@@ -378,6 +378,16 @@ note "the examples are overlays that work as shipped"
|
|||||||
bad=""
|
bad=""
|
||||||
for f in ../examples/*/addons/*.sh; do [ -e "$f" ] && { bash -n "$f" 2>/dev/null || bad+="$f "; }; done
|
for f in ../examples/*/addons/*.sh; do [ -e "$f" ] && { bash -n "$f" 2>/dev/null || bad+="$f "; }; done
|
||||||
check "every example addon parses" "" "$bad"
|
check "every example addon parses" "" "$bad"
|
||||||
|
# An addon's manifest may only name what the config or its own script sets, so a typo
|
||||||
|
# fails here rather than as an empty value on a cluster.
|
||||||
|
bad=""
|
||||||
|
for y in addons/*.yaml ../examples/*/addons/*.yaml; do
|
||||||
|
[ -e "$y" ] || continue
|
||||||
|
for n in $(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\}' "$y" | tr -d '${}' | sort -u); do
|
||||||
|
grep -qE "(^|[[:space:]])$n=" "${y%.yaml}.sh" versions.env ../examples/*/rig.env 2>/dev/null || bad+="$y:$n "
|
||||||
|
done
|
||||||
|
done
|
||||||
|
check "every addon manifest names only what is set" "" "$bad"
|
||||||
if command -v python3 >/dev/null; then
|
if command -v python3 >/dev/null; then
|
||||||
bad=""
|
bad=""
|
||||||
for f in ../examples/*/dags/*.py; do
|
for f in ../examples/*/dags/*.py; do
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
// TODO: PLACEHOLDER — replace with the real estate topology.
|
|
||||||
//
|
|
||||||
// This is where the extracted platform diagrams land. The shape below is
|
|
||||||
// illustrative only: it shows how a mocked dependency, a real service and a
|
|
||||||
// remote system are meant to sit together, not what the system actually is.
|
|
||||||
//
|
|
||||||
// The intended end state is that this file stops being hand-written and is
|
|
||||||
// generated from the running cluster, so the diagram becomes a report of what
|
|
||||||
// exists rather than a drawing of what was once intended.
|
|
||||||
digraph estate {
|
|
||||||
rankdir=LR
|
|
||||||
bgcolor="#0a0e17"
|
|
||||||
fontname="Helvetica"
|
|
||||||
node [fontname="Helvetica" fontsize=11 style=filled color="#1e2a4a" fontcolor="#e8eaf0" shape=box]
|
|
||||||
edge [fontname="Helvetica" fontsize=9 fontcolor="#8892a8" color="#4a5568"]
|
|
||||||
|
|
||||||
label="Estate topology — PLACEHOLDER"
|
|
||||||
labelloc=t
|
|
||||||
fontsize=16
|
|
||||||
fontcolor="#ffc107"
|
|
||||||
|
|
||||||
subgraph cluster_new {
|
|
||||||
label="New"
|
|
||||||
style=dashed
|
|
||||||
color="#1e2a4a"
|
|
||||||
fontcolor="#8892a8"
|
|
||||||
|
|
||||||
api [label="service under work\n(real: built and hot-reloaded)" fillcolor="#1a3a1a" fontcolor="#00c853"]
|
|
||||||
}
|
|
||||||
|
|
||||||
subgraph cluster_core {
|
|
||||||
label="Core (mocked)"
|
|
||||||
style=dashed
|
|
||||||
color="#1e2a4a"
|
|
||||||
fontcolor="#8892a8"
|
|
||||||
|
|
||||||
svc_a [label="upstream service\n(mock: canned responses)" fillcolor="#121829"]
|
|
||||||
db [label="datastore\n(mock)" fillcolor="#121829" shape=cylinder]
|
|
||||||
}
|
|
||||||
|
|
||||||
subgraph cluster_legacy {
|
|
||||||
label="Legacy estate (mocked)"
|
|
||||||
style=dashed
|
|
||||||
color="#1e2a4a"
|
|
||||||
fontcolor="#8892a8"
|
|
||||||
|
|
||||||
batch [label="batch drop\n(mock: writes files on a timer)" fillcolor="#121829"]
|
|
||||||
}
|
|
||||||
|
|
||||||
remote [label="external system\n(remote: ExternalName,\nreachable only from a VDI)" fillcolor="#3a1a1a" fontcolor="#ffc107" shape=octagon]
|
|
||||||
|
|
||||||
api -> svc_a [label="HTTP"]
|
|
||||||
api -> db [label="query"]
|
|
||||||
api -> batch [label="file handoff" style=dashed]
|
|
||||||
api -> remote [label="only when reachable" style=dashed color="#ffc107"]
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
|
||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<!-- Generated by graphviz version 14.1.2 (0)
|
|
||||||
-->
|
|
||||||
<!-- Title: estate Pages: 1 -->
|
|
||||||
<svg width="579pt" height="362pt"
|
|
||||||
viewBox="0.00 0.00 579.00 362.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 357.62)">
|
|
||||||
<title>estate</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-357.62 575.41,-357.62 575.41,4 -4,4"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="285.7" y="-334.42" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#ffc107">Estate topology — PLACEHOLDER</text>
|
|
||||||
<g id="clust1" class="cluster">
|
|
||||||
<title>cluster_new</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="8,-130.12 8,-210.12 199,-210.12 199,-130.12 8,-130.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-190.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">New</text>
|
|
||||||
</g>
|
|
||||||
<g id="clust2" class="cluster">
|
|
||||||
<title>cluster_core</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="357.33,-172.12 357.33,-318.12 534.83,-318.12 534.83,-172.12 357.33,-172.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-298.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Core (mocked)</text>
|
|
||||||
</g>
|
|
||||||
<g id="clust3" class="cluster">
|
|
||||||
<title>cluster_legacy</title>
|
|
||||||
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="341.83,-84.12 341.83,-164.12 551.33,-164.12 551.33,-84.12 341.83,-84.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.58" y="-144.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Legacy estate (mocked)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api -->
|
|
||||||
<g id="node1" class="node">
|
|
||||||
<title>api</title>
|
|
||||||
<polygon fill="#1a3a1a" stroke="#1e2a4a" points="191,-174.12 16,-174.12 16,-138.12 191,-138.12 191,-174.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-159.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">service under work</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-145.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">(real: built and hot-reloaded)</text>
|
|
||||||
</g>
|
|
||||||
<!-- svc_a -->
|
|
||||||
<g id="node2" class="node">
|
|
||||||
<title>svc_a</title>
|
|
||||||
<polygon fill="#121829" stroke="#1e2a4a" points="526.83,-282.12 365.33,-282.12 365.33,-246.12 526.83,-246.12 526.83,-282.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-267.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">upstream service</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-253.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock: canned responses)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->svc_a -->
|
|
||||||
<g id="edge1" class="edge">
|
|
||||||
<title>api->svc_a</title>
|
|
||||||
<path fill="none" stroke="#4a5568" d="M149.07,-174.56C167.54,-182.07 189.23,-190.7 209,-198.12 258.25,-216.6 270.12,-222.85 320.75,-237.12 331.43,-240.13 342.71,-243 353.94,-245.67"/>
|
|
||||||
<polygon fill="#4a5568" stroke="#4a5568" points="353.12,-249.07 363.66,-247.92 354.71,-242.25 353.12,-249.07"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-234.24" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">HTTP</text>
|
|
||||||
</g>
|
|
||||||
<!-- db -->
|
|
||||||
<g id="node3" class="node">
|
|
||||||
<title>db</title>
|
|
||||||
<path fill="#121829" stroke="#1e2a4a" d="M480.7,-223.81C480.7,-226.22 465.18,-228.18 446.08,-228.18 426.97,-228.18 411.45,-226.22 411.45,-223.81 411.45,-223.81 411.45,-184.43 411.45,-184.43 411.45,-182.02 426.97,-180.06 446.08,-180.06 465.18,-180.06 480.7,-182.02 480.7,-184.43 480.7,-184.43 480.7,-223.81 480.7,-223.81"/>
|
|
||||||
<path fill="none" stroke="#1e2a4a" d="M480.7,-223.81C480.7,-221.39 465.18,-219.43 446.08,-219.43 426.97,-219.43 411.45,-221.39 411.45,-223.81"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-207.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">datastore</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-193.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->db -->
|
|
||||||
<g id="edge2" class="edge">
|
|
||||||
<title>api->db</title>
|
|
||||||
<path fill="none" stroke="#4a5568" d="M191.32,-168.36C257.83,-177.73 346.91,-190.28 399.91,-197.75"/>
|
|
||||||
<polygon fill="#4a5568" stroke="#4a5568" points="399.4,-201.22 409.79,-199.15 400.38,-194.29 399.4,-201.22"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-185.69" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">query</text>
|
|
||||||
</g>
|
|
||||||
<!-- batch -->
|
|
||||||
<g id="node4" class="node">
|
|
||||||
<title>batch</title>
|
|
||||||
<polygon fill="#121829" stroke="#1e2a4a" points="537.33,-128.12 354.83,-128.12 354.83,-92.12 537.33,-92.12 537.33,-128.12"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-113.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">batch drop</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-99.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock: writes files on a timer)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->batch -->
|
|
||||||
<g id="edge3" class="edge">
|
|
||||||
<title>api->batch</title>
|
|
||||||
<path fill="none" stroke="#4a5568" stroke-dasharray="5,2" d="M191.32,-144.39C237.67,-138.13 294.98,-130.39 343.4,-123.85"/>
|
|
||||||
<polygon fill="#4a5568" stroke="#4a5568" points="343.61,-127.36 353.05,-122.55 342.67,-120.42 343.61,-127.36"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-143.94" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">file handoff</text>
|
|
||||||
</g>
|
|
||||||
<!-- remote -->
|
|
||||||
<g id="node5" class="node">
|
|
||||||
<title>remote</title>
|
|
||||||
<polygon fill="#3a1a1a" stroke="#1e2a4a" points="571.41,-21.74 571.41,-52.5 497.99,-74.24 394.17,-74.24 320.75,-52.5 320.75,-21.74 394.17,0 497.99,0 571.41,-21.74"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-46.92" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">external system</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-33.42" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">(remote: ExternalName,</text>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-19.92" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">reachable only from a VDI)</text>
|
|
||||||
</g>
|
|
||||||
<!-- api->remote -->
|
|
||||||
<g id="edge4" class="edge">
|
|
||||||
<title>api->remote</title>
|
|
||||||
<path fill="none" stroke="#ffc107" stroke-dasharray="5,2" d="M149.55,-137.68C167.9,-130.34 189.37,-121.97 209,-114.87 254.45,-98.43 305.32,-81.49 348.17,-67.64"/>
|
|
||||||
<polygon fill="#ffc107" stroke="#ffc107" points="349,-71.05 357.44,-64.65 346.85,-64.39 349,-71.05"/>
|
|
||||||
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-117.57" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">only when reachable</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 6.9 KiB |
@@ -2,6 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>rig — local environment installer</title>
|
<title>rig — local environment installer</title>
|
||||||
<style>
|
<style>
|
||||||
@@ -237,13 +238,9 @@
|
|||||||
<div class="nav-backdrop"></div>
|
<div class="nav-backdrop"></div>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="#start">Start here</a>
|
<a href="#start">Start here</a>
|
||||||
<a href="#steps">The steps</a>
|
|
||||||
<a href="#install">Installation</a>
|
<a href="#install">Installation</a>
|
||||||
<a href="#environments">Environments</a>
|
<a href="#environments">Environments</a>
|
||||||
<a href="#overlays">Overlays</a>
|
<a href="#overlays">Overlays</a>
|
||||||
<a href="#profiles">Profiles</a>
|
|
||||||
<a href="#registry">Registry</a>
|
|
||||||
<a href="#architecture">Architecture</a>
|
|
||||||
<a href="#troubleshooting">Troubleshooting</a>
|
<a href="#troubleshooting">Troubleshooting</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -251,114 +248,68 @@
|
|||||||
|
|
||||||
<section class="section" id="start">
|
<section class="section" id="start">
|
||||||
<h2>Start here</h2>
|
<h2>Start here</h2>
|
||||||
<p class="lede">A runnable local model of a large, regulated estate — legacy and new side by side.</p>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>rig builds a disposable Kubernetes environment on your machine so you can
|
<p>rig builds a disposable Kubernetes environment on your machine, so you can
|
||||||
explore how a system fits together without needing access to any of it. Its
|
see how a system fits together without access to any of it. It is for
|
||||||
job is <b>onboarding and exploration</b>, not a production replica.</p>
|
<b>onboarding and exploration</b>, not a production replica.</p>
|
||||||
|
|
||||||
<p>Most services in it are deliberately <b>not real</b>. What has to be faithful
|
<p>Most services in it are <b>not real</b>. What has to be faithful is the
|
||||||
is the topology — the names, the ports, the dependency order, who can reach whom,
|
topology: names, ports, dependency order, who can reach whom, and how it fails.
|
||||||
and how it fails. The workloads themselves are noise. This is what makes the
|
That is what makes a whole estate fit on a laptop. A real 20-service platform
|
||||||
whole estate fit on a laptop: a real 20-service platform will not fit even once
|
does not fit once in 14 GB; stubs of about 30 MB each fit three times.</p>
|
||||||
on 14 GB, but mocks are about 30 MB each, so three faithful copies do.</p>
|
|
||||||
|
|
||||||
<h3>The only prerequisite</h3>
|
<h3>The only prerequisite</h3>
|
||||||
<p><b>Docker.</b> No curl, no jq, no python, no apt repositories to configure.</p>
|
<p><b>Docker.</b> No curl, no jq, no python, no apt repositories to configure.</p>
|
||||||
<pre><code><span class="c"># then, in the environment directory:</span>
|
<pre><code><span class="c"># in the environment directory:</span>
|
||||||
make check <span class="c"># is this machine ready? reports, never fixes</span>
|
make check <span class="c"># is this machine ready? reports, never changes anything</span>
|
||||||
make deps <span class="c"># install the pinned toolchain</span>
|
make deps <span class="c"># install the pinned toolchain; safe to re-run</span>
|
||||||
make cluster up <span class="c"># cluster + registry + addons; ports derive by themselves</span>
|
make cluster up <span class="c"># cluster + registry + addons; ports derive by themselves</span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Read <code>make check</code> before <code>make deps</code>. It never changes
|
<p>Read <code>make check</code> before <code>make deps</code>: it lists what is
|
||||||
anything — it prints what it found and, at the end, the steps it cannot perform
|
missing, including the steps only you can do, such as anything needing
|
||||||
for you.</p>
|
<code>sudo</code> or a Windows-side restart.</p>
|
||||||
</div>
|
|
||||||
</section>
|
<h3>make cluster up</h3>
|
||||||
|
<p>It prints what the kind config locks in before spending the time, because
|
||||||
<section class="section" id="steps">
|
that config is fixed when the cluster is created. Re-running is safe and
|
||||||
<h2>The steps</h2>
|
continues where an interrupted run stopped, rather than reporting that the
|
||||||
<p class="lede">Start to finish, in order, with what each one actually does.</p>
|
cluster already exists and leaving every node <code>NotReady</code>.</p>
|
||||||
<div class="prose">
|
<pre><code>make cluster up <span class="c"># built-in defaults — no profile needed</span>
|
||||||
|
make cluster up PROFILE=mirror <span class="c"># images through an internal registry</span>
|
||||||
<h3>1 · make check</h3>
|
|
||||||
<p>Asks whether this machine is ready. It <b>changes nothing</b> — it
|
|
||||||
reports what it found and, at the end, the things only a human can do
|
|
||||||
(anything needing <code>sudo</code>, or a Windows-side restart). Read it
|
|
||||||
before installing anything; it is faster than discovering the same problems
|
|
||||||
one failure at a time.</p>
|
|
||||||
<pre><code>make check</code></pre>
|
|
||||||
|
|
||||||
<h3>2 · make deps</h3>
|
|
||||||
<p>Installs the pinned toolchain — only what is missing — and tells you
|
|
||||||
if its directory is not on PATH yet. Running it twice is safe.</p>
|
|
||||||
<pre><code>make deps</code></pre>
|
|
||||||
|
|
||||||
<h3>3 · make cluster up</h3>
|
|
||||||
<p>Builds the cluster, starts its registry and installs the profile's
|
|
||||||
addons — there is nothing else to run first. It prints what the profile
|
|
||||||
locks in <i>before</i> spending the time, because the kind config is
|
|
||||||
fixed at creation and cannot be changed afterwards.</p>
|
|
||||||
<p>Re-running is safe and, more importantly, <b>convergent</b>: if a first
|
|
||||||
attempt was interrupted before the CNI was installed, running it again
|
|
||||||
finishes the job rather than reporting "already exists" and leaving every
|
|
||||||
node permanently NotReady.</p>
|
|
||||||
<pre><code>make cluster up <span class="c"># built-in defaults — no profile needed</span>
|
|
||||||
make cluster up PROFILE=mirror <span class="c"># after copying env.d/mirror.env.example: cached registry</span>
|
|
||||||
make cluster up OVERLAY=examples/data <span class="c"># an overlay: what runs, kept outside rig</span>
|
make cluster up OVERLAY=examples/data <span class="c"># an overlay: what runs, kept outside rig</span>
|
||||||
make cluster reset <span class="c"># destroy and rebuild — how an edited kind config takes effect</span>
|
make cluster reset <span class="c"># destroy and rebuild: how an edited kind config takes effect</span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
<h3>4 · make docs</h3>
|
|
||||||
<p>Serves this page from a throwaway container. Works with no cluster and
|
|
||||||
no toolchain, which is deliberate: these pages are the instructions for
|
|
||||||
building everything else, so they cannot depend on it.</p>
|
|
||||||
<pre><code>make docs</code></pre>
|
|
||||||
|
|
||||||
<h3>Checking on things</h3>
|
<h3>Checking on things</h3>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>make cluster list</dt><dd>Every cluster on the machine, its memory cost and its port block. The usual reason a new one will not start is an old one you forgot about; <code>make cluster free</code> frees them without deleting.</dd>
|
<dt>make cluster list</dt><dd>Every cluster on the machine, its memory cost and its port block. A new cluster usually fails to start because of an old one; <code>make cluster free</code> stops them without deleting.</dd>
|
||||||
<dt>make check</dt><dd>Short: host, toolchain, and whether this cluster fits, its ports, registry and addons. Details only appear when something needs attention; <code>make check all</code> prints every one.</dd>
|
<dt>make check</dt><dd>Host, toolchain, and whether this cluster fits, its ports, registry and addons. Only problems print; <code>make check all</code> prints everything.</dd>
|
||||||
<dt>make check mem</dt><dd>Memory in depth: what caps it, how far it really climbs, and on WSL the <code>.wslconfig</code> backup and restore.</dd>
|
<dt>make check mem</dt><dd>Memory in depth: what caps it, how far it climbs, and on WSL the <code>.wslconfig</code> backup and restore.</dd>
|
||||||
|
<dt>make docs</dt><dd>Serves these pages from a throwaway container. It needs no cluster and no toolchain, because these are the instructions for building both.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h3>Running more than one</h3>
|
|
||||||
<p>Name another overlay, or copy the directory and rename it. Cluster name,
|
|
||||||
context, image tags and the port block all follow the folder name — the
|
|
||||||
overlay's, or rig's — so the second environment collides with nothing and
|
|
||||||
neither one's teardown can reach the other.</p>
|
|
||||||
<pre><code>OVERLAY=local/platform-v2 make cluster up
|
|
||||||
cp -r rig ../platform-v3 && cd ../platform-v3 && make cluster up
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section" id="install">
|
<section class="section" id="install">
|
||||||
<h2>Installation</h2>
|
<h2>Installation</h2>
|
||||||
<p class="lede">A container installs onto the host and then gets out of the way.</p>
|
|
||||||
<div class="graph-container">
|
<div class="graph-container">
|
||||||
<a href="viewer.html?src=graphs/01-install.svg"><img src="graphs/01-install.svg" alt="Installation flow"></a>
|
<a href="viewer.html?src=graphs/01-install.svg"><img src="graphs/01-install.svg" alt="How the installer works"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>The installer is a container, not a shell script, for a specific reason: a
|
<p>The installer is a container, not a shell script, because a stock slim Debian
|
||||||
stock slim Debian has no <code>curl</code>, no <code>wget</code>, no
|
has no curl, wget, jq, python3 or CA bundle: a shell installer could not make a
|
||||||
<code>jq</code>, no <code>python3</code> and <b>no CA bundle</b>. A shell
|
verified HTTPS request, let alone check one. The container carries its own
|
||||||
installer could not make a verified HTTPS request, let alone check one. The
|
toolchain, so the host needs only Docker. Nothing runs inside it afterwards —
|
||||||
container carries its own toolchain, so the host needs nothing but Docker.</p>
|
kind, kubectl, tilt and jq run natively.</p>
|
||||||
|
|
||||||
<p>The cluster never runs inside that container. Everything it installs —
|
<p>Every tool is a single binary at a pinned version, checked against a published
|
||||||
kind, kubectl, tilt, jq — runs natively afterwards, so nothing pays a
|
SHA256. Node images are pinned <b>by digest</b>, so upgrading kind cannot move
|
||||||
container tax during daily work.</p>
|
your Kubernetes version.</p>
|
||||||
|
|
||||||
<h3>Pinned and verified</h3>
|
<h3>Two tiers</h3>
|
||||||
<p>Every tool is a single binary fetched at a pinned version and checked
|
<p>A managed or corporate-issued machine is not somewhere to install development
|
||||||
against a published SHA256. Node images are pinned <b>by digest</b>, so
|
tools by default, so installing cluster tooling is a decision rather than a side
|
||||||
upgrading kind cannot silently move your Kubernetes version.</p>
|
effect:</p>
|
||||||
|
|
||||||
<h3>Not every machine should get cluster tooling</h3>
|
|
||||||
<p>A managed or corporate-issued machine — the kind that holds the access
|
|
||||||
you cannot get anywhere else — is not somewhere to install development
|
|
||||||
tools by default. So the toolchain comes in two tiers:</p>
|
|
||||||
<table>
|
<table>
|
||||||
<tr><th>tier</th><th>installs</th><th>for</th></tr>
|
<tr><th>tier</th><th>installs</th><th>for</th></tr>
|
||||||
<tr><td><code>core</code></td><td>kubectl, jq</td><td>talk to a cluster someone else runs</td></tr>
|
<tr><td><code>core</code></td><td>kubectl, jq</td><td>talk to a cluster someone else runs</td></tr>
|
||||||
@@ -367,11 +318,6 @@ cp -r rig ../platform-v3 && cd ../platform-v3 && make cluster up
|
|||||||
<pre><code>make deps core <span class="c"># kubectl and jq only — nothing that creates a cluster</span>
|
<pre><code>make deps core <span class="c"># kubectl and jq only — nothing that creates a cluster</span>
|
||||||
make deps <span class="c"># dev, the default</span>
|
make deps <span class="c"># dev, the default</span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Testing <i>in situ</i> on a managed machine is still possible — install
|
|
||||||
the <code>dev</code> tier deliberately when you need it. The point is that
|
|
||||||
it should be a decision rather than a side effect of installing.</p>
|
|
||||||
<p>The documentation itself needs neither tier: <code>make docs</code>
|
|
||||||
wants only Docker.</p>
|
|
||||||
|
|
||||||
<h3>Air-gapped</h3>
|
<h3>Air-gapped</h3>
|
||||||
<pre><code>make deps image full <span class="c"># bakes every binary into the image</span>
|
<pre><code>make deps image full <span class="c"># bakes every binary into the image</span>
|
||||||
@@ -384,185 +330,129 @@ docker load < rig.tgz && make cluster up PROFILE=offline <span clas
|
|||||||
|
|
||||||
<section class="section" id="environments">
|
<section class="section" id="environments">
|
||||||
<h2>Environments</h2>
|
<h2>Environments</h2>
|
||||||
<p class="lede">One folder is one environment — an overlay's, or rig's own. Copy it, rename it, run it.</p>
|
|
||||||
<div class="graph-container">
|
<div class="graph-container">
|
||||||
<a href="viewer.html?src=graphs/02-environment.svg"><img src="graphs/02-environment.svg" alt="Environment derivation"></a>
|
<a href="viewer.html?src=graphs/02-environment.svg"><img src="graphs/02-environment.svg" alt="Environment derivation"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>Running several versions of a system at once means several clusters on one
|
<p>One folder is one environment. Running several versions of a system at once
|
||||||
machine, not several machines. Everything that could collide is derived from
|
means several clusters on one machine, and everything that could collide is
|
||||||
the folder name (the overlay's when one is named):</p>
|
derived from the folder name (the overlay's, when one is named):</p>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>cluster + context</dt><dd><code>platform-v2/</code> builds <code>platform-v2</code> on <code>kind-platform-v2</code>.</dd>
|
<dt>cluster + context</dt><dd><code>platform-v2/</code> builds <code>platform-v2</code> on <code>kind-platform-v2</code>.</dd>
|
||||||
<dt>port block</dt><dd>Ten ports from a hash of the name, in the 20000+ range — clear of 80, 443, 3000, 5432, 8000 and 8080.</dd>
|
<dt>port block</dt><dd>Ten ports from a hash of the name, above 20000 — clear of 80, 443, 3000, 5432, 8000 and 8080.</dd>
|
||||||
<dt>registry + images</dt><dd>Named after the environment, so two copies never share one.</dd>
|
<dt>registry + images</dt><dd>Named after the environment, so two copies never share one.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<p>Two copies therefore never collide, and neither one's
|
<p>So two copies never collide, and neither one's <code>make cluster down</code>
|
||||||
<code>make cluster down</code> can touch the other. <code>make check</code>
|
can touch the other. <code>make check</code> shows the block;
|
||||||
shows the block; <code>bash ctrl/ports.sh persist</code> freezes it into
|
<code>bash ctrl/ports.sh persist</code> freezes it into <code>ctrl/.env</code>.</p>
|
||||||
<code>ctrl/.env</code> if you want it fixed rather than derived.</p>
|
|
||||||
|
|
||||||
<h3>Configuration layers</h3>
|
<h3>Configuration layers</h3>
|
||||||
<p>Weakest first, later wins: built-in defaults → pinned versions → a
|
<p>Weakest first, later wins: built-in defaults → pinned versions → a profile, if
|
||||||
profile, if you name one → the overlay's <code>rig.env</code> →
|
you name one → the overlay's <code>rig.env</code> → <code>ctrl/.env</code> → the
|
||||||
<code>ctrl/.env</code> → the environment. So
|
caller's environment. So a value on the command line always beats every file.</p>
|
||||||
<code>make cluster up PROFILE=<name></code> always beats every file.</p>
|
|
||||||
|
<h3>Profiles</h3>
|
||||||
|
<p>A profile says how this machine reaches the world. rig needs none.</p>
|
||||||
|
<table>
|
||||||
|
<tr><th>example</th><th>registry</th><th>for</th></tr>
|
||||||
|
<tr><td>none</td><td><code>local</code></td><td>the built-in defaults</td></tr>
|
||||||
|
<tr><td><code>mirror.env.example</code></td><td><code>mirror</code></td><td>images through an internal registry</td></tr>
|
||||||
|
<tr><td><code>offline.env.example</code></td><td><code>local</code></td><td>air-gapped</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Registry modes</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>mode</th><th>what it does</th></tr>
|
||||||
|
<tr><td><code>none</code></td><td>images are built straight into the node</td></tr>
|
||||||
|
<tr><td><code>local</code></td><td>a registry container wired into the cluster</td></tr>
|
||||||
|
<tr><td><code>mirror</code></td><td>that container as a pull-through cache of the corporate registry</td></tr>
|
||||||
|
<tr><td><code>remote</code></td><td>no local container; pull direct with an <code>imagePullSecret</code></td></tr>
|
||||||
|
</table>
|
||||||
|
<p><code>mirror</code> is what a locked-down network looks like: images come from
|
||||||
|
the corporate registry, you do not hammer it, and you keep working when the
|
||||||
|
connection drops. It is also only reachable from where that registry is routable —
|
||||||
|
from a laptop, use <code>local</code> or <code>offline</code>.</p>
|
||||||
|
<p><b>Corporate CA trust is the usual failure.</b> It has to reach three places:
|
||||||
|
the host Docker daemon, every node's containerd (nodes do not inherit host trust),
|
||||||
|
and any in-cluster client. Set <code>REGISTRY_CA_FILE</code>;
|
||||||
|
<code>make check</code> reports which is still missing. Without it the error is
|
||||||
|
<code>x509: certificate signed by unknown authority</code>.</p>
|
||||||
|
|
||||||
|
<h3>LoadBalancer services</h3>
|
||||||
|
<p>Real manifests use <code>type: LoadBalancer</code>. On a bare local cluster
|
||||||
|
those Services sit at <code>EXTERNAL-IP <pending></code> forever, with no
|
||||||
|
error: the deployment looks healthy and is unreachable. The <code>metallb</code>
|
||||||
|
addon fixes that, so upstream manifests work unchanged. Its address pool is
|
||||||
|
derived from the cluster's Docker network at install time, because Docker picks
|
||||||
|
that subnet and it differs between machines.</p>
|
||||||
|
<p>Those addresses are reachable from the Linux side, including inside WSL. A
|
||||||
|
browser on Windows has no route to them: use the ingress host ports instead.</p>
|
||||||
|
|
||||||
|
<h3>Networking and memory</h3>
|
||||||
|
<p>The cluster uses kind's built-in networking, which does enforce standard
|
||||||
|
NetworkPolicy — verified against a no-policy control. A pluggable CNI was tried
|
||||||
|
and removed: it added policy features nothing here needs, in exchange for a
|
||||||
|
slower boot and one more thing to get right at creation time.</p>
|
||||||
|
<p>Every cluster is a running container tree whether you use it or not.
|
||||||
|
<code>make cluster list</code> shows what each costs, and
|
||||||
|
<code>make cluster free</code> stops the others without deleting them.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section" id="overlays">
|
<section class="section" id="overlays">
|
||||||
<h2>Overlays</h2>
|
<h2>Overlays</h2>
|
||||||
<p class="lede">What runs lives outside rig — rig reads it and never writes into it.</p>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<p>An overlay is one folder, kept outside rig's version control, holding a
|
<p>An overlay is one folder, kept outside rig's version control, holding a use
|
||||||
use case. Every piece is optional:</p>
|
case. rig reads it and never writes into it. Every piece is optional:</p>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>in the overlay</th><th>what rig does with it</th></tr>
|
<tr><th>in the overlay</th><th>what rig does with it</th></tr>
|
||||||
<tr><td><code>rig.env</code></td><td>a config layer: addons, namespaces, images — anything a profile could set</td></tr>
|
<tr><td><code>rig.env</code></td><td>a config layer: addons, namespaces, images — anything a profile could set</td></tr>
|
||||||
<tr><td><code>k8s/overlays/dev/</code></td><td>the manifests the dev loop applies</td></tr>
|
<tr><td><code>k8s/overlays/dev/</code></td><td>the manifests the dev loop applies</td></tr>
|
||||||
<tr><td><code>kind-config.yaml.tpl</code></td><td>the cluster's shape, when it needs its own (mounts, ports)</td></tr>
|
<tr><td><code>kind-config.yaml.tpl</code></td><td>the cluster's shape, when it needs its own (mounts, ports)</td></tr>
|
||||||
<tr><td><code>addons/<name>.sh</code></td><td>addons, found before rig's own</td></tr>
|
<tr><td><code>addons/<name>.sh</code></td><td>addons, found before rig's own; each applies its own <code><name>.yaml</code></td></tr>
|
||||||
<tr><td><code>Tiltfile</code></td><td>the workload's half of the dev loop, included by rig's</td></tr>
|
<tr><td><code>Tiltfile</code></td><td>the workload's half of the dev loop, included by rig's</td></tr>
|
||||||
</table>
|
</table>
|
||||||
<pre><code>cp -r examples/starter local/myenv <span class="c"># local/ is gitignored</span>
|
<pre><code>cp -r examples/starter local/myenv <span class="c"># local/ is gitignored</span>
|
||||||
OVERLAY=local/myenv make cluster up
|
OVERLAY=local/myenv make cluster up
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>With none named, rig runs its own <code>examples/starter</code>.
|
<p>With none named, rig runs its own <code>examples/starter</code>.
|
||||||
<code>examples/data</code> carries postgres, redis and airflow as an
|
<code>examples/data</code> carries postgres, redis and airflow as an overlay's own
|
||||||
overlay's own addons. A project can also carry rig at
|
addons. A project can also carry rig at <code><project>/rig/</code> and be
|
||||||
<code><project>/rig/</code> and be the overlay itself, with a
|
the overlay itself, with a three-line forwarding Makefile — see
|
||||||
three-line forwarding Makefile — see <code>docs/notes/overlay.md</code>.</p>
|
<code>docs/notes/overlay.md</code>.</p>
|
||||||
</div>
|
|
||||||
</section>
|
<h3>What runs in it</h3>
|
||||||
|
<p>Each component is one of three things, and switching between them is a
|
||||||
<section class="section" id="profiles">
|
one-line change:</p>
|
||||||
<h2>Profiles</h2>
|
|
||||||
<p class="lede">How this machine reaches the world — optional; rig needs none.</p>
|
|
||||||
<div class="prose">
|
|
||||||
<table>
|
|
||||||
<tr><th>example</th><th>registry</th><th>for</th></tr>
|
|
||||||
<tr><td><i>none</i></td><td>local</td><td>the built-in defaults; no profile needed</td></tr>
|
|
||||||
<tr><td><code>mirror.env.example</code></td><td>mirror</td><td>images through an internal registry</td></tr>
|
|
||||||
<tr><td><code>offline.env.example</code></td><td>local</td><td>air-gapped</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="note"><p><b>The kind config cannot be re-applied.</b> Edit
|
|
||||||
<code>ctrl/k8s/kind-config.yaml.tpl</code> (or the overlay's own); it takes effect when the cluster is created. <code>cluster up</code> prints what it
|
|
||||||
locks in before spending the time, and <code>make cluster reset</code> is
|
|
||||||
the way out.</p></div>
|
|
||||||
|
|
||||||
<h3>LoadBalancer services</h3>
|
|
||||||
<p>Real manifests use <code>type: LoadBalancer</code>, because a real
|
|
||||||
cluster has one. On a bare local cluster those Services sit at
|
|
||||||
<code>EXTERNAL-IP <pending></code> forever, with no error anywhere —
|
|
||||||
the deployment looks healthy and simply is not reachable.</p>
|
|
||||||
<p>The <code>metallb</code> addon fixes that, so the same manifests work
|
|
||||||
here as upstream and nothing has to be rewritten to NodePort. Its address
|
|
||||||
pool is derived from the cluster's Docker network at install time rather
|
|
||||||
than hardcoded, because Docker picks that subnet and it differs between
|
|
||||||
machines.</p>
|
|
||||||
<div class="note"><p><b>Where those addresses are reachable from.</b> The
|
|
||||||
pool lives on the Docker bridge, so LoadBalancer IPs work from the Linux
|
|
||||||
side — including from inside WSL. A browser on Windows has no route to
|
|
||||||
them. Use the ingress host ports for anything you need to open in a
|
|
||||||
browser.</p></div>
|
|
||||||
|
|
||||||
<h3>Networking</h3>
|
|
||||||
<p>The cluster uses kind's built-in networking, which <b>does</b> enforce
|
|
||||||
standard NetworkPolicy — verified against a no-policy control, not assumed.
|
|
||||||
The widely repeated claim that it accepts policies and silently ignores
|
|
||||||
them is out of date.</p>
|
|
||||||
<p>A pluggable CNI was tried and removed: it only added
|
|
||||||
GlobalNetworkPolicy, policy tiers and egress-CIDR rules, none of which are
|
|
||||||
needed yet, in exchange for a slower boot and one more thing that has to be
|
|
||||||
right at creation time. Worth revisiting only when a policy the built-in
|
|
||||||
cannot express actually comes up.</p>
|
|
||||||
|
|
||||||
<h3>Memory</h3>
|
|
||||||
<p>Every cluster is a running container tree whether you are using it or not.
|
|
||||||
<code>make cluster list</code> shows what exists and what it costs;
|
|
||||||
<code>make cluster free</code> stops the others without deleting them.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section" id="registry">
|
|
||||||
<h2>Registry</h2>
|
|
||||||
<p class="lede">Local, cached, or straight to the corporate registry.</p>
|
|
||||||
<div class="prose">
|
|
||||||
<table>
|
|
||||||
<tr><th>mode</th><th>what it does</th></tr>
|
|
||||||
<tr><td><code>none</code></td><td>images are built straight into the node</td></tr>
|
|
||||||
<tr><td><code>local</code></td><td>a registry container wired into the cluster</td></tr>
|
|
||||||
<tr><td><code>mirror</code></td><td>that container as a <b>pull-through cache</b> of the corporate registry</td></tr>
|
|
||||||
<tr><td><code>remote</code></td><td>no local container; pull direct with an imagePullSecret</td></tr>
|
|
||||||
</table>
|
|
||||||
<p><code>mirror</code> is what a locked-down network actually looks like:
|
|
||||||
images originate from the corporate registry, you do not hammer it, and you
|
|
||||||
keep working when the connection drops.</p>
|
|
||||||
|
|
||||||
<div class="note"><p><b>The corporate CA will bite you.</b> A corporate
|
|
||||||
registry is usually behind an internal CA, and trust has to reach
|
|
||||||
<b>three</b> places: the host Docker daemon, every cluster node's containerd
|
|
||||||
(nodes do <i>not</i> inherit host trust), and any in-cluster client. Set
|
|
||||||
<code>REGISTRY_CA_FILE</code> and <code>make check</code> reports which is
|
|
||||||
still missing. The symptom otherwise is an opaque
|
|
||||||
<code>x509: certificate signed by unknown authority</code>.</p></div>
|
|
||||||
|
|
||||||
<p>Reachability also depends on where you are: if the registry is only
|
|
||||||
routable from a managed workspace, <code>mirror</code> and <code>remote</code>
|
|
||||||
will not resolve from a laptop at all. That is what <code>local</code> and
|
|
||||||
<code>offline</code> are for.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section" id="architecture">
|
|
||||||
<h2>Architecture</h2>
|
|
||||||
<p class="lede">The estate being modelled.</p>
|
|
||||||
<div class="note"><p><b>TODO — placeholder.</b> The diagram below is
|
|
||||||
illustrative only: it shows how a mocked dependency, a service under active
|
|
||||||
work, and an unreachable remote system sit together. It is not the real
|
|
||||||
topology. Replace <code>docs/graphs/03-architecture.dot</code> with the
|
|
||||||
extracted platform diagrams, then run <code>make docs graphs</code>.</p></div>
|
|
||||||
<div class="graph-container">
|
|
||||||
<a href="viewer.html?src=graphs/03-architecture.svg"><img src="graphs/03-architecture.svg" alt="Estate topology (placeholder)"></a>
|
|
||||||
</div>
|
|
||||||
<div class="prose">
|
|
||||||
<p>Each component is one of three things, and switching between them should be
|
|
||||||
a one-line change rather than a rewrite:</p>
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt>real</dt><dd>Built from source and hot-reloaded. The thing you are actually working on — usually exactly one.</dd>
|
<dt>real</dt><dd>Built from source and hot-reloaded. What you are working on — usually exactly one.</dd>
|
||||||
<dt>mock</dt><dd>A generic stub with canned responses. Everything you do not care about today.</dd>
|
<dt>mock</dt><dd>A generic stub with canned responses. Everything you do not care about today.</dd>
|
||||||
<dt>remote</dt><dd>No pod at all: a Service of type ExternalName pointing at the real system. In-cluster DNS resolves identically, so callers never change.</dd>
|
<dt>remote</dt><dd>No pod at all: a Service of type <code>ExternalName</code> pointing at the real system. In-cluster DNS resolves identically, so callers never change.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<p>The intended end state is that this diagram is <b>generated from the
|
|
||||||
running cluster</b> rather than drawn by hand — so it becomes a report of
|
|
||||||
what exists instead of a picture of what was once intended.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section" id="troubleshooting">
|
<section class="section" id="troubleshooting">
|
||||||
<h2>Troubleshooting</h2>
|
<h2>Troubleshooting</h2>
|
||||||
<p class="lede">The failures that are hard to diagnose from their symptoms.</p>
|
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h3>Tilt stops noticing file changes</h3>
|
<h3>Tilt stops noticing file changes</h3>
|
||||||
<p>Almost always <code>inotify</code> limits, and it fails <i>silently</i> —
|
<p>Almost always inotify limits, and it fails silently: nothing errors, changes
|
||||||
nothing errors, changes just stop being picked up. Defaults on WSL are far too
|
just stop being picked up. Defaults on WSL are far too low.
|
||||||
low. <code>make check</code> reports it and prints the fix.</p>
|
<code>make check</code> reports it and prints the fix.</p>
|
||||||
|
|
||||||
<h3>Cluster creation dies halfway with a port error</h3>
|
<h3>Cluster creation dies halfway with a port error</h3>
|
||||||
<p>Docker reports <code>failed to bind host port … address already in use</code>
|
<p>Docker reports <code>failed to bind host port … address already in use</code>
|
||||||
partway through creating the cluster. Run <code>make check</code> first — it
|
partway through creating the cluster. <code>make check</code> tests every port in
|
||||||
checks every port in this environment's block before anything is built.</p>
|
this environment's block before anything is built.</p>
|
||||||
|
|
||||||
<h3>Every node stays NotReady</h3>
|
<h3>Every node stays NotReady</h3>
|
||||||
<p>Usually a cluster created with the default CNI disabled but the real CNI
|
<p>Usually a cluster created with the default CNI disabled and the real CNI never
|
||||||
never installed — typically an interrupted first run. Just run
|
installed, after an interrupted first run. Run <code>make cluster up</code> again:
|
||||||
<code>make cluster up</code> again: it converges rather than exiting early, and
|
it finishes the missing steps instead of stopping because the cluster exists.</p>
|
||||||
will finish the missing steps.</p>
|
|
||||||
|
|
||||||
<h3>x509: certificate signed by unknown authority</h3>
|
<h3>x509: certificate signed by unknown authority</h3>
|
||||||
<p>Corporate CA trust has not reached one of the three places it needs to be.
|
<p>Corporate CA trust has not reached one of the three places it needs to be. See
|
||||||
See <a href="#registry">Registry</a>.</p>
|
<a href="#environments">Environments</a>.</p>
|
||||||
|
|
||||||
<h3>kubectl says the context does not exist</h3>
|
<h3>kubectl says the context does not exist</h3>
|
||||||
<p>The cluster can exist while its context does not — a reset or a switched
|
<p>The cluster can exist while its context does not — a reset or a switched
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
Each addon is its own idempotent script — adding one is adding a file, not editing a dispatcher. `ADDONS` names them, in install order; the overlay's `addons/<name>.sh` is found before rig's `ctrl/addons/<name>.sh`, and every one runs from rig's `ctrl/` with `RIG_CTRL` exported, wherever its file lives (see [overlay.md](overlay.md)).
|
Each addon is its own idempotent script — adding one is adding a file, not editing a dispatcher. `ADDONS` names them, in install order; the overlay's `addons/<name>.sh` is found before rig's `ctrl/addons/<name>.sh`, and every one runs from rig's `ctrl/` with `RIG_CTRL` exported, wherever its file lives (see [overlay.md](overlay.md)).
|
||||||
|
|
||||||
|
## The manifest beside the script
|
||||||
|
|
||||||
|
An addon's Kubernetes objects are a plain `<name>.yaml` beside its `<name>.sh`, not a heredoc inside it: the YAML reads, diffs and lints as YAML, and the script keeps only what YAML cannot do (secrets made once, a database created, waiting for a rollout). The script renders it with `render_manifest "$ADDON_DIR/<name>.yaml"` (`lib/config.sh`) and pipes it to `kubectl apply`.
|
||||||
|
|
||||||
|
`render_manifest` fills `${NAME}` and `${NAME:-default}` from the loaded config or the script's own variables, and fails on a name nothing sets, so a typo is an error rather than an empty image. `$VAR` and `$(VAR)` are left alone: they are the container's, as in postgres' `pg_isready -U $POSTGRES_USER`. selftest checks that every name a manifest uses is set somewhere. `ADDON_DIR` is taken before the script's `cd` into rig's `ctrl/`: `ADDON_DIR="$(cd "$(dirname "$0")" && pwd)"`.
|
||||||
|
|
||||||
## What rig ships, and what it does not
|
## What rig ships, and what it does not
|
||||||
|
|
||||||
rig's own addons make the *cluster* work, and are useless outside one: metallb, cert-manager, metrics-server. Things a workload happens to need — a database, a cache, a scheduler — are the workload's, and which workload needs which is not rig's business, so they live with the overlay. `examples/data/addons/` has postgres, redis and airflow as a worked example; an overlay that wants them copies them in.
|
rig's own addons make the *cluster* work, and are useless outside one: metallb, cert-manager, metrics-server. Things a workload happens to need — a database, a cache, a scheduler — are the workload's, and which workload needs which is not rig's business, so they live with the overlay. `examples/data/addons/` has postgres, redis and airflow as a worked example; an overlay that wants them copies them in.
|
||||||
|
|||||||
@@ -34,14 +34,37 @@ A port held by THIS environment's own cluster is not a clash; it is the thing wo
|
|||||||
|
|
||||||
The ports are extracted with a second grep rather than `tr -d ':->'`: in tr, ':->' is the character RANGE ':' to '>', which does not contain '-', so the trailing dash survives and nothing ever matches.
|
The ports are extracted with a second grep rather than `tr -d ':->'`: in tr, ':->' is the character RANGE ':' to '>', which does not contain '-', so the trailing dash survives and nothing ever matches.
|
||||||
|
|
||||||
## Compact by default
|
## Budget first, configuration after (2026-09-23)
|
||||||
|
|
||||||
`make check` prints one line per question — host, toolchain, and for this rig: cluster, memory,
|
`make check` answers the question people run it for first, in the shape the adapter rig had
|
||||||
ports, registry, addons — and adds detail only where something needs attention (`!` lines, the
|
(the user preferred it): a memory budget with one row per part, the node overhead, the total
|
||||||
"held elsewhere" list when memory is tight, the clashing port). `make check all` prints every fact,
|
against what the machine has and has available; then a one-line verdict (`fits`, `! tight`,
|
||||||
as the full report did before 2026-09-17. `deps.sh detect all` is the same switch for the host part,
|
`! does not fit`, with the two opposite fixes told apart); then where things stand and the next
|
||||||
so the standalone `rigdeps.sh detect` is short too. Changed because the long report buried the few
|
command — not created, stopped (with the `docker start` line), up (with the Tilt and gateway
|
||||||
lines that mattered.
|
URLs, and whether this environment's Tilt is already running).
|
||||||
|
|
||||||
|
Below that, the configuration as it is, raw: the host and toolchain (`deps.sh detect`), then
|
||||||
|
one `key value` line each for cluster, context, overlay, profile, nodes, kind config, manifests,
|
||||||
|
ingress, ports (named), registry, addons. `!` lines always print; `make check all` adds every
|
||||||
|
fact (per-port detail, addon list, swap and overcommit notes). `deps.sh detect all` is the same
|
||||||
|
switch for the host part, so the standalone `rigdeps.sh detect` is short too.
|
||||||
|
|
||||||
|
## MEMORY_BUDGET
|
||||||
|
|
||||||
|
rig knows what a node costs (NODE_MB), not what a workload does, so the parts come from the
|
||||||
|
overlay's rig.env: `MEMORY_BUDGET="name=MB ..."`, in the order listed; a 0 is left out, and an
|
||||||
|
entry that is not `name=MB` is a `!` line. rig.env is sourced, so an overlay can size a part by
|
||||||
|
its own knob (poc_rig's airflow follows its mode). When one part outweighs the rest together
|
||||||
|
(and is at least 1 GB), check names it as the first thing to switch off; `MEMORY_BUDGET_TIP`
|
||||||
|
says how, in the overlay's words. No budget: one `?` row says where to add it.
|
||||||
|
|
||||||
|
## Tilt: ours or another
|
||||||
|
|
||||||
|
A Tilt still running after `make cluster down` holds the Tilt port, and the next `make tilt`
|
||||||
|
fails with "address already in use" (2026-09-23, the Workspace). check reads the listener with
|
||||||
|
`ss -p` (our own processes only): a `tilt` whose working directory is this ctrl/ is this
|
||||||
|
environment's Tilt, reported as running with its URL; another `tilt` is a `!` line with its pid,
|
||||||
|
its folder and the `kill` that frees the port. Anything else is the ordinary port clash.
|
||||||
|
|
||||||
## overlay and kind config
|
## overlay and kind config
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ out `python -m http.server` too — a minimal Debian has no python3. What it doe
|
|||||||
have, by definition, is Docker: the single prerequisite rig already demands. So a
|
have, by definition, is Docker: the single prerequisite rig already demands. So a
|
||||||
throwaway nginx container serves a read-only bind mount.
|
throwaway nginx container serves a read-only bind mount.
|
||||||
|
|
||||||
|
It runs with a config of its own: one worker (nginx's `auto` starts one per core,
|
||||||
|
twelve on an ordinary laptop, for a handful of static pages), no access log and a
|
||||||
|
quiet entrypoint, so the terminal shows the URL and nothing else unless something
|
||||||
|
fails. The pages declare an empty icon, so the browser does not ask for a
|
||||||
|
`favicon.ico` that is not there.
|
||||||
|
|
||||||
## Committed SVGs
|
## Committed SVGs
|
||||||
|
|
||||||
Rendered SVGs are committed alongside their `.dot` sources for the same reason:
|
Rendered SVGs are committed alongside their `.dot` sources for the same reason:
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ air-gapped install); an overlay says what runs. `ctrl/.env` is still this
|
|||||||
machine's, and the caller still wins over everything.
|
machine's, and the caller still wins over everything.
|
||||||
|
|
||||||
`rig.env` may not set `PROFILE` or `OVERLAY`: both are chosen before it loads.
|
`rig.env` may not set `PROFILE` or `OVERLAY`: both are chosen before it loads.
|
||||||
|
|
||||||
|
`MEMORY_BUDGET` (and optionally `MEMORY_BUDGET_TIP`) is how an overlay tells `make check` what its
|
||||||
|
workload costs: rig only knows the node. See check.md.
|
||||||
The paths it sets (`MANIFESTS_DIR`, `KIND_CONFIG`) are relative to the overlay.
|
The paths it sets (`MANIFESTS_DIR`, `KIND_CONFIG`) are relative to the overlay.
|
||||||
`MANIFESTS_DIR=none` means rig applies no manifests and the overlay's Tiltfile
|
`MANIFESTS_DIR=none` means rig applies no manifests and the overlay's Tiltfile
|
||||||
does, e.g. when kustomize needs flags.
|
does, e.g. when kustomize needs flags.
|
||||||
@@ -100,7 +103,8 @@ load_config
|
|||||||
```
|
```
|
||||||
|
|
||||||
`OVERLAY_DIR` is set, so an addon can find files beside it
|
`OVERLAY_DIR` is set, so an addon can find files beside it
|
||||||
(`$(_from_ctrl "$OVERLAY_DIR")/...`). rig's own `ctrl/addons/` holds only what
|
(`$(_from_ctrl "$OVERLAY_DIR")/...`). Its manifest is a `<name>.yaml` beside the script,
|
||||||
|
rendered with `render_manifest` (see [addons.md](addons.md)). rig's own `ctrl/addons/` holds only what
|
||||||
makes a cluster work (metallb, cert-manager, metrics-server);
|
makes a cluster work (metallb, cert-manager, metrics-server);
|
||||||
`examples/data/addons/` shows workload ones.
|
`examples/data/addons/` shows workload ones.
|
||||||
|
|
||||||
@@ -151,6 +155,31 @@ $(wordlist 2,$(words $(GOALS)),$(GOALS)):
|
|||||||
The cluster is then named after `<project>`, exactly as a copied rig named
|
The cluster is then named after `<project>`, exactly as a copied rig named
|
||||||
`<project>` was, so moving a copied rig to this layout keeps its cluster and ports.
|
`<project>` was, so moving a copied rig to this layout keeps its cluster and ports.
|
||||||
|
|
||||||
|
rig can also live outside the project, which then holds only the overlay. The
|
||||||
|
forwarder names it with one variable, a machine path that is never committed (the
|
||||||
|
shell, or an ignored `.env.local`), defaulting to the vendored layout:
|
||||||
|
|
||||||
|
```make
|
||||||
|
RIG_DIR ?= $(HERE)/rig
|
||||||
|
override RIG_DIR := $(abspath $(patsubst ~/%,$(HOME)/%,$(RIG_DIR)))
|
||||||
|
```
|
||||||
|
|
||||||
|
No search path: an unset `RIG_DIR` means `./rig`, and nothing else is tried. Moving rig
|
||||||
|
changes nothing in the cluster's name, context or ports; moving or renaming the overlay
|
||||||
|
folder does.
|
||||||
|
|
||||||
|
`VERSION`, at rig's top, names the rig. It travels with the folder, vendored, a
|
||||||
|
worktree or a plain copy, and needs no git. A project records the one it was last run
|
||||||
|
against and warns on another; it does not refuse:
|
||||||
|
|
||||||
|
```make
|
||||||
|
RIG_REF := 0.1.0
|
||||||
|
RIG_AT := $(strip $(shell cat '$(RIG_DIR)/VERSION' 2>/dev/null))
|
||||||
|
ifneq ($(RIG_AT),$(RIG_REF))
|
||||||
|
$(warning rig at $(RIG_DIR) is $(or $(RIG_AT),unversioned); this overlay was last run against rig $(RIG_REF))
|
||||||
|
endif
|
||||||
|
```
|
||||||
|
|
||||||
`rig.env` holds no secrets, by this contract. A repository whose `.gitignore` has a
|
`rig.env` holds no secrets, by this contract. A repository whose `.gitignore` has a
|
||||||
broad `*.env` (a common secrets rule) would still hide it, so an overlay living in
|
broad `*.env` (a common secrets rule) would still hide it, so an overlay living in
|
||||||
such a repo re-includes it in its own `.gitignore`: `!rig.env`.
|
such a repo re-includes it in its own `.gitignore`: `!rig.env`.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
<title>Graph Viewer</title>
|
<title>Graph Viewer</title>
|
||||||
<style>
|
<style>
|
||||||
* { margin: 0; padding: 0; }
|
* { margin: 0; padding: 0; }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
# Requires the postgres addon; refuses to install without it.
|
# Requires the postgres addon; refuses to install without it.
|
||||||
# Notes: ../README.md
|
# Notes: ../README.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -70,77 +71,7 @@ if [ -d "$dags" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo " applying manifests"
|
echo " applying manifests"
|
||||||
$K apply -n "$NS" -f - >/dev/null <<YAML
|
render_manifest "$ADDON_DIR/airflow.yaml" | $K apply -n "$NS" -f - >/dev/null
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: airflow
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: airflow
|
|
||||||
ports:
|
|
||||||
- port: 8080
|
|
||||||
targetPort: 8080
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: airflow
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: airflow
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: airflow
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: airflow
|
|
||||||
image: ${AIRFLOW_IMAGE}
|
|
||||||
args: ["standalone"]
|
|
||||||
env:
|
|
||||||
- name: AIRFLOW__CORE__EXECUTOR
|
|
||||||
value: LocalExecutor
|
|
||||||
- name: AIRFLOW__CORE__LOAD_EXAMPLES
|
|
||||||
value: "false"
|
|
||||||
- name: AIRFLOW__DATABASE__SQL_ALCHEMY_CONN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow-connections, key: SQL_ALCHEMY_CONN}
|
|
||||||
- name: AIRFLOW_CONN_APP_DB
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow-connections, key: AIRFLOW_CONN_APP_DB}
|
|
||||||
- name: AIRFLOW__CORE__FERNET_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow, key: FERNET_KEY}
|
|
||||||
- name: _AIRFLOW_WWW_USER_USERNAME
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow, key: ADMIN_USER}
|
|
||||||
- name: _AIRFLOW_WWW_USER_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef: {name: airflow, key: ADMIN_PASSWORD}
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
volumeMounts:
|
|
||||||
- name: dags
|
|
||||||
mountPath: /opt/airflow/dags
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
# First boot runs the whole migration before it serves anything.
|
|
||||||
initialDelaySeconds: 60
|
|
||||||
periodSeconds: 15
|
|
||||||
failureThreshold: 20
|
|
||||||
volumes:
|
|
||||||
- name: dags
|
|
||||||
configMap:
|
|
||||||
name: airflow-dags
|
|
||||||
optional: true
|
|
||||||
YAML
|
|
||||||
|
|
||||||
echo " waiting for airflow (the first boot migrates the database, so this is slow)..."
|
echo " waiting for airflow (the first boot migrates the database, so this is slow)..."
|
||||||
$K rollout status deployment/airflow -n "$NS" --timeout=600s
|
$K rollout status deployment/airflow -n "$NS" --timeout=600s
|
||||||
|
|||||||
69
rig/examples/data/addons/airflow.yaml
Normal file
69
rig/examples/data/addons/airflow.yaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: airflow
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: airflow
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: airflow
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: airflow
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: airflow
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: airflow
|
||||||
|
image: ${AIRFLOW_IMAGE}
|
||||||
|
args: ["standalone"]
|
||||||
|
env:
|
||||||
|
- name: AIRFLOW__CORE__EXECUTOR
|
||||||
|
value: LocalExecutor
|
||||||
|
- name: AIRFLOW__CORE__LOAD_EXAMPLES
|
||||||
|
value: "false"
|
||||||
|
- name: AIRFLOW__DATABASE__SQL_ALCHEMY_CONN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow-connections, key: SQL_ALCHEMY_CONN}
|
||||||
|
- name: AIRFLOW_CONN_APP_DB
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow-connections, key: AIRFLOW_CONN_APP_DB}
|
||||||
|
- name: AIRFLOW__CORE__FERNET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow, key: FERNET_KEY}
|
||||||
|
- name: _AIRFLOW_WWW_USER_USERNAME
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow, key: ADMIN_USER}
|
||||||
|
- name: _AIRFLOW_WWW_USER_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: {name: airflow, key: ADMIN_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
volumeMounts:
|
||||||
|
- name: dags
|
||||||
|
mountPath: /opt/airflow/dags
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8080
|
||||||
|
# First boot runs the whole migration before it serves anything.
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 20
|
||||||
|
volumes:
|
||||||
|
- name: dags
|
||||||
|
configMap:
|
||||||
|
name: airflow-dags
|
||||||
|
optional: true
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# PostgreSQL for this overlay: plain manifests, one replica on a PVC, password generated once and kept.
|
# PostgreSQL for this overlay: plain manifests, one replica on a PVC, password generated once and kept.
|
||||||
# Notes: ../README.md
|
# Notes: ../README.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -26,77 +27,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo " applying manifests"
|
echo " applying manifests"
|
||||||
$K apply -n "$NS" -f - >/dev/null <<YAML
|
render_manifest "$ADDON_DIR/postgres.yaml" | $K apply -n "$NS" -f - >/dev/null
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: postgres-data
|
|
||||||
spec:
|
|
||||||
accessModes: [ReadWriteOnce]
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: ${POSTGRES_STORAGE:-2Gi}
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: postgres
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: postgres
|
|
||||||
ports:
|
|
||||||
- port: 5432
|
|
||||||
targetPort: 5432
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: postgres
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
# One volume, one writer. Rolling would start a second pod against the same
|
|
||||||
# PVC before the first exits, and Postgres refuses to share a data directory.
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: postgres
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: postgres
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: postgres
|
|
||||||
image: ${POSTGRES_IMAGE}
|
|
||||||
envFrom:
|
|
||||||
- secretRef:
|
|
||||||
name: postgres
|
|
||||||
env:
|
|
||||||
# The image initialises into the volume root otherwise, and a
|
|
||||||
# lost+found from the PVC makes it refuse to initdb.
|
|
||||||
- name: PGDATA
|
|
||||||
value: /var/lib/postgresql/data/pgdata
|
|
||||||
ports:
|
|
||||||
- containerPort: 5432
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /var/lib/postgresql/data
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["sh", "-c", "pg_isready -U \$POSTGRES_USER -d \$POSTGRES_DB"]
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 5
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["sh", "-c", "pg_isready -U \$POSTGRES_USER -d \$POSTGRES_DB"]
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 15
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: postgres-data
|
|
||||||
YAML
|
|
||||||
|
|
||||||
echo " waiting for postgres..."
|
echo " waiting for postgres..."
|
||||||
$K rollout status deployment/postgres -n "$NS" --timeout=240s
|
$K rollout status deployment/postgres -n "$NS" --timeout=240s
|
||||||
|
|||||||
69
rig/examples/data/addons/postgres.yaml
Normal file
69
rig/examples/data/addons/postgres.yaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: postgres-data
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: ${POSTGRES_STORAGE:-2Gi}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
# One volume, one writer. Rolling would start a second pod against the same
|
||||||
|
# PVC before the first exits, and Postgres refuses to share a data directory.
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: ${POSTGRES_IMAGE}
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: postgres
|
||||||
|
env:
|
||||||
|
# The image initialises into the volume root otherwise, and a
|
||||||
|
# lost+found from the PVC makes it refuse to initdb.
|
||||||
|
- name: PGDATA
|
||||||
|
value: /var/lib/postgresql/data/pgdata
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["sh", "-c", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"]
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["sh", "-c", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"]
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 15
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: postgres-data
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# Redis for this overlay: cache and broker (Celery), no persistence.
|
# Redis for this overlay: cache and broker (Celery), no persistence.
|
||||||
# Notes: ../README.md
|
# Notes: ../README.md
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
ADDON_DIR="$(cd "$(dirname "$0")" && pwd)" # its manifest sits beside it
|
||||||
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
cd "${RIG_CTRL:?run it through rig: bash ctrl/addons.sh install}"
|
||||||
|
|
||||||
source ./lib/config.sh
|
source ./lib/config.sh
|
||||||
@@ -13,43 +14,7 @@ NS="${DATA_NAMESPACE:-data}"
|
|||||||
$K get namespace "$NS" >/dev/null 2>&1 || $K create namespace "$NS"
|
$K get namespace "$NS" >/dev/null 2>&1 || $K create namespace "$NS"
|
||||||
|
|
||||||
echo " applying manifests"
|
echo " applying manifests"
|
||||||
$K apply -n "$NS" -f - >/dev/null <<YAML
|
render_manifest "$ADDON_DIR/redis.yaml" | $K apply -n "$NS" -f - >/dev/null
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: redis
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: redis
|
|
||||||
ports:
|
|
||||||
- port: 6379
|
|
||||||
targetPort: 6379
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: redis
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: redis
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: redis
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: redis
|
|
||||||
image: ${REDIS_IMAGE}
|
|
||||||
ports:
|
|
||||||
- containerPort: 6379
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["redis-cli", "ping"]
|
|
||||||
initialDelaySeconds: 3
|
|
||||||
periodSeconds: 5
|
|
||||||
YAML
|
|
||||||
|
|
||||||
echo " waiting for redis..."
|
echo " waiting for redis..."
|
||||||
$K rollout status deployment/redis -n "$NS" --timeout=180s
|
$K rollout status deployment/redis -n "$NS" --timeout=180s
|
||||||
|
|||||||
35
rig/examples/data/addons/redis.yaml
Normal file
35
rig/examples/data/addons/redis.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: redis
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: redis
|
||||||
|
ports:
|
||||||
|
- port: 6379
|
||||||
|
targetPort: 6379
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: redis
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: redis
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: redis
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: ${REDIS_IMAGE}
|
||||||
|
ports:
|
||||||
|
- containerPort: 6379
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["redis-cli", "ping"]
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 5
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
# LocalExecutor and needs no broker: add redis (before airflow) only to switch to Celery.
|
# LocalExecutor and needs no broker: add redis (before airflow) only to switch to Celery.
|
||||||
ADDONS="metallb postgres airflow"
|
ADDONS="metallb postgres airflow"
|
||||||
|
|
||||||
|
# What each part costs in MB, for `make check`'s memory budget (rig adds the kind node).
|
||||||
|
MEMORY_BUDGET="airflow=1800 postgres=300"
|
||||||
|
|
||||||
# Namespace for the dependency containers (k8s/base/kustomization.yaml names it too).
|
# Namespace for the dependency containers (k8s/base/kustomization.yaml names it too).
|
||||||
DATA_NAMESPACE=data
|
DATA_NAMESPACE=data
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,32 @@ render_kind_config ()
|
|||||||
fi;
|
fi;
|
||||||
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
||||||
}
|
}
|
||||||
|
render_manifest ()
|
||||||
|
{
|
||||||
|
local file="$1" text ref key def val;
|
||||||
|
text=$(< "$file") || return 1;
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
key=${ref#\$\{};
|
||||||
|
key=${key%\}};
|
||||||
|
def="";
|
||||||
|
if [[ $key == *:-* ]]; then
|
||||||
|
def=${key#*:-};
|
||||||
|
key=${key%%:-*};
|
||||||
|
fi;
|
||||||
|
if [ -n "${!key:-}" ]; then
|
||||||
|
val=${!key};
|
||||||
|
else
|
||||||
|
if [[ $ref == *:-* ]]; then
|
||||||
|
val=$def;
|
||||||
|
else
|
||||||
|
echo "render_manifest: $file needs \${$key}, which nothing sets" 1>&2;
|
||||||
|
return 1;
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
text=${text//"$ref"/"$val"};
|
||||||
|
done < <(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}' "$file" | sort -u);
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
}
|
||||||
|
|
||||||
# ── configuration, frozen for profile 'default' ──
|
# ── configuration, frozen for profile 'default' ──
|
||||||
load_config() {
|
load_config() {
|
||||||
|
|||||||
@@ -61,6 +61,32 @@ render_kind_config ()
|
|||||||
fi;
|
fi;
|
||||||
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
sed -e "s|\${CLUSTER}|${CLUSTER}|g" -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" -e "s|\${HOST_WORKDIR}|${host_workdir}|g" -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" "$KIND_CONFIG"
|
||||||
}
|
}
|
||||||
|
render_manifest ()
|
||||||
|
{
|
||||||
|
local file="$1" text ref key def val;
|
||||||
|
text=$(< "$file") || return 1;
|
||||||
|
while IFS= read -r ref; do
|
||||||
|
key=${ref#\$\{};
|
||||||
|
key=${key%\}};
|
||||||
|
def="";
|
||||||
|
if [[ $key == *:-* ]]; then
|
||||||
|
def=${key#*:-};
|
||||||
|
key=${key%%:-*};
|
||||||
|
fi;
|
||||||
|
if [ -n "${!key:-}" ]; then
|
||||||
|
val=${!key};
|
||||||
|
else
|
||||||
|
if [[ $ref == *:-* ]]; then
|
||||||
|
val=$def;
|
||||||
|
else
|
||||||
|
echo "render_manifest: $file needs \${$key}, which nothing sets" 1>&2;
|
||||||
|
return 1;
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
text=${text//"$ref"/"$val"};
|
||||||
|
done < <(grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}' "$file" | sort -u);
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
}
|
||||||
|
|
||||||
# ── configuration, frozen for profile 'default' ──
|
# ── configuration, frozen for profile 'default' ──
|
||||||
load_config() {
|
load_config() {
|
||||||
|
|||||||
Reference in New Issue
Block a user