Skip to content

Commit 03b7b27

Browse files
authored
bug: a panel moved to Tailscale kept its LAN InternalIP, so exec, logs and metrics for it failed on the kubelet certificate (#652)
game-node-server-setup.sh restarts the panel's k3s onto its Tailscale IP, which moves the kubelet and its certificate, but the cloud-node controller only takes an address matching alpha.kubernetes.io/provided-node-ip, which the kubelet wrote once at registration. update.sh now moves that annotation to the configured node-ip once k3s reports it, so the InternalIP follows.
1 parent 72ed5bf commit 03b7b27

6 files changed

Lines changed: 113 additions & 1 deletion

File tree

‎.github/workflows/panel-scripts.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
- debug.sh
88
- dev.sh
99
- plugin.sh
10+
- update.sh
1011
- "utils/**"
1112
- "overlays/**/*.env.example"
1213
- base/api/deployment.yaml
@@ -18,6 +19,7 @@ on:
1819
- debug.sh
1920
- dev.sh
2021
- plugin.sh
22+
- update.sh
2123
- "utils/**"
2224
- "overlays/**/*.env.example"
2325
- base/api/deployment.yaml
@@ -30,8 +32,9 @@ jobs:
3032
runs-on: ubuntu-latest
3133
steps:
3234
- uses: actions/checkout@v4
33-
- run: shellcheck -x utils/ensure_auth_cookie_domain.sh tests/scripts/*.sh tests/scripts/stub/kubectl
35+
- run: shellcheck -x utils/ensure_auth_cookie_domain.sh utils/ensure_node_ip.sh tests/scripts/*.sh tests/scripts/stub/kubectl
3436
- run: bash tests/scripts/setup_env_test.sh
3537
- run: bash tests/scripts/debug_test.sh
3638
- run: bash tests/scripts/startup_probe_test.sh
3739
- run: bash tests/scripts/plugin_test.sh
40+
- run: bash tests/scripts/node_ip_test.sh

‎tests/scripts/node_ip_test.sh‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
3+
# shellcheck source=tests/scripts/lib.sh
4+
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
5+
6+
STUB_BIN="$(cd "$(dirname "${BASH_SOURCE[0]}")/stub" && pwd)"
7+
8+
nodes() {
9+
printf '%s\n' "$@" >"$WORK_DIR/nodes"
10+
}
11+
12+
run_ensure_node_ip() {
13+
if [ -n "$1" ]; then
14+
printf 'node-ip: %s\n' "$1" >"$WORK_DIR/config.yaml"
15+
fi
16+
(
17+
export PATH="$STUB_BIN:$PATH" K3S_CONFIG="$WORK_DIR/config.yaml" KUBECONFIG=/dev/null NODE_IP_ATTEMPTS=1
18+
# shellcheck source=utils/colors.sh
19+
source "$TEST_ROOT/utils/colors.sh"
20+
# shellcheck source=utils/ensure_node_ip.sh
21+
source "$TEST_ROOT/utils/ensure_node_ip.sh"
22+
ensure_node_ip
23+
) >"$WORK_DIR/output" 2>&1 || fail "ensure_node_ip exited $?: $(cat "$WORK_DIR/output")"
24+
}
25+
26+
annotations() {
27+
grep ' annotate ' "$WORK_DIR/calls" 2>/dev/null || true
28+
}
29+
30+
# The panel registered on its LAN IP, then game-node-server-setup.sh moved k3s
31+
# to Tailscale: the kubelet followed, the Node's InternalIP never did.
32+
test_a_panel_moved_to_tailscale_has_its_provided_ip_moved_too() {
33+
nodes "$(printf 'dietpi\t100.117.134.62,fd7a:115c:a1e0::c02b:863f\t192.168.2.199')" \
34+
"$(printf 'game-node\t100.64.0.9\t100.64.0.9')"
35+
run_ensure_node_ip 100.117.134.62
36+
assert_eq "$(annotations)" "--kubeconfig=/dev/null annotate node dietpi alpha.kubernetes.io/provided-node-ip=100.117.134.62 --overwrite"
37+
}
38+
39+
test_a_node_already_on_its_ip_is_left_alone() {
40+
nodes "$(printf 'dietpi\t100.117.134.62\t100.117.134.62')"
41+
run_ensure_node_ip 100.117.134.62
42+
assert_eq "$(annotations)" ""
43+
}
44+
45+
test_a_panel_without_node_ip_never_asks_the_cluster() {
46+
nodes "$(printf 'dietpi\t192.168.2.199\t192.168.2.199')"
47+
run_ensure_node_ip ""
48+
assert_eq "$(cat "$WORK_DIR/calls" 2>/dev/null)" ""
49+
}
50+
51+
# Annotating before k3s has restarted onto the new IP would name an address
52+
# the node does not have yet.
53+
test_a_node_k3s_has_not_moved_yet_is_left_alone() {
54+
nodes "$(printf 'dietpi\t192.168.2.199\t192.168.2.199')"
55+
run_ensure_node_ip 100.117.134.62
56+
assert_eq "$(annotations)" ""
57+
assert_contains "$(cat "$WORK_DIR/output")" "re-run ./update.sh"
58+
}
59+
60+
run_tests

‎tests/scripts/stub/kubectl‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ while [ $# -gt 0 ]; do
1919
done
2020

2121
case "$args" in
22+
*" get nodes "*) if [ -f "$WORK_DIR/nodes" ]; then cat "$WORK_DIR/nodes"; fi ;;
2223
*" get pods "*) cat "$WORK_DIR/pods" ;;
2324
*" get pod "*) cat "$WORK_DIR/containers-$pod" ;;
2425
*" logs "*)

‎update.sh‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ if ! plan_timescaledb_major_upgrade; then
99
exit 1
1010
fi
1111

12+
# Also how a panel that game-node-server-setup.sh already moved to Tailscale gets
13+
# repaired: see utils/ensure_node_ip.sh.
14+
ensure_node_ip
15+
1216
# Nothing terminates TLS in the cluster in this mode, so any Certificate left
1317
# over from a previous https install keeps retrying HTTP-01 against a server
1418
# that no longer answers the challenge -- silently accumulating failed orders

‎utils/ensure_node_ip.sh‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/bash
2+
3+
# k3s's node-ip moves the kubelet (its serving certificate and the
4+
# k3s.io/internal-ip annotation) but not the Node's InternalIP. The cloud-node
5+
# controller only takes an address that matches alpha.kubernetes.io/provided-node-ip,
6+
# and the kubelet writes that once, when the node first registers. A panel
7+
# installed on its LAN IP and then moved to Tailscale by
8+
# game-node-server-setup.sh kept its LAN InternalIP, which its kubelet's
9+
# certificate no longer covers, so every exec, log stream and metrics scrape of
10+
# that node failed with x509 errors.
11+
ensure_node_ip() {
12+
local config="${K3S_CONFIG:-/etc/rancher/k3s/config.yaml}"
13+
local attempts="${NODE_IP_ATTEMPTS:-30}"
14+
local want attempt name internal provided
15+
16+
want=$(sed -n 's/^node-ip:[[:space:]]*//p' "$config" 2>/dev/null | tail -n 1 | tr -d '\r')
17+
if [ -z "$want" ]; then
18+
return 0
19+
fi
20+
21+
# Right after a restart k3s has not rewritten k3s.io/internal-ip yet, and
22+
# annotating before it has would name an address the node does not have.
23+
for ((attempt = 1; attempt <= attempts; attempt++)); do
24+
while IFS=$'\t' read -r name internal provided; do
25+
if [ "${internal%%,*}" != "${want%%,*}" ]; then
26+
continue
27+
fi
28+
if [ "$provided" = "$want" ]; then
29+
return 0
30+
fi
31+
if ! kubectl --kubeconfig="$KUBECONFIG" annotate node "$name" "alpha.kubernetes.io/provided-node-ip=$want" --overwrite >/dev/null; then
32+
warn "Could not move node $name to $want; kubectl exec, logs and metrics for it stay broken."
33+
return 0
34+
fi
35+
echo "Node $name moved to $want (was ${provided:-unset}); its InternalIP follows within 5 minutes."
36+
return 0
37+
done < <(kubectl --kubeconfig="$KUBECONFIG" get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.k3s\.io/internal-ip}{"\t"}{.metadata.annotations.alpha\.kubernetes\.io/provided-node-ip}{"\n"}{end}' 2>/dev/null)
38+
if [ "$attempt" -lt "$attempts" ]; then
39+
sleep 2
40+
fi
41+
done
42+
warn "No node reports the node-ip $want from $config yet; re-run ./update.sh once k3s is up."
43+
}

‎utils/utils.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ source "$SCRIPT_DIR/replace_rand32_in_env_files.sh"
1818
source "$SCRIPT_DIR/ensure_turn_secret.sh"
1919
source "$SCRIPT_DIR/ensure_s3_config_keys.sh"
2020
source "$SCRIPT_DIR/ensure_auth_cookie_domain.sh"
21+
source "$SCRIPT_DIR/ensure_node_ip.sh"
2122
source "$SCRIPT_DIR/resync_vault_secrets.sh"
2223
source "$SCRIPT_DIR/update_timescaledb_extension.sh"
2324
source "$SCRIPT_DIR/upgrade_timescaledb_major.sh"

0 commit comments

Comments
 (0)