mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-29 08:07:33 +09:00
The post-install-prep node-list loop dialed the linstor-controller Service (linstor+ssl://linstor-controller:3371) as soon as the controller Deployment reported Available once. That gate goes stale: the controller carries reloader.stakater.com/auto=true and mounts the cert-manager-issued API-TLS Secret, so when cert-manager (re)writes that Secret during bring-up, reloader rolls the single-replica controller. The roll drops the Service to zero ready endpoints after Available already passed, and a dial into that window fails with "[Errno 113] No route to host" (EHOSTUNREACH). The blind until-loop then churned the CLI on that error instead of waiting for a routable controller. Gate every probe on the linstor-controller Service having a ready endpoint, so the CLI only dials a reachable controller and a mid-loop reload re-waits for the endpoint instead of erroring. The "Online == 3" satellite-convergence assertion and the dedicated 300s budget are preserved; a timeout now dumps controller endpoints and pods for diagnostics before failing. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
167 lines
7.0 KiB
Bash
Executable File
167 lines
7.0 KiB
Bash
Executable File
#!/bin/sh
|
|
# Runs LINSTOR pool + StorageClass + MetalLB pool configuration as soon as
|
|
# their respective prerequisites are reachable. Designed to run in the
|
|
# background during the platform HR reconcile wait, so its wall-clock cost
|
|
# overlaps with the wait instead of compounding it.
|
|
#
|
|
# Each LINSTOR prerequisite below sits at the end of a multi-hop reconcile
|
|
# chain: cozystack-operator -> platform HR -> linstor HR -> piraeus-operator
|
|
# -> cert-manager issues the controller TLS -> linstor-controller Deployment
|
|
# -> controller pod -> DB migration. On a loaded CI runner that chain has been
|
|
# observed to take 7-9 min end to end, and the operator alone needs ~70s just
|
|
# to emit the linstor HR. The earlier per-step "object exists" budgets
|
|
# (timeout 60 / timeout 300) were anchored to this script's start, so they
|
|
# raced that reconcile latency; when one lost (linstor HR appeared at ~+70s
|
|
# against a 60s budget) `set -e` aborted the whole script and the install
|
|
# failed. Instead, drive every wait off one shared deadline -- the same 15m
|
|
# window the installer's `kubectl wait hr --all` uses -- and tolerate
|
|
# not-yet-created objects without aborting, while still failing hard if a
|
|
# resource never becomes ready inside the budget.
|
|
set -eu
|
|
|
|
DEADLINE=$(( $(date +%s) + 900 ))
|
|
|
|
# wait_for <description> <kubectl-wait-args...>
|
|
# Polls `kubectl wait` until it succeeds or the shared deadline elapses.
|
|
# kubectl wait exits non-zero immediately when the object does not exist yet,
|
|
# so the loop tolerates "not created yet" without the set -e cliff that a bare
|
|
# `kubectl wait` would trigger on a NotFound. The per-attempt timeout shrinks
|
|
# to the budget remaining, so the final attempt can consume the rest of it.
|
|
wait_for() {
|
|
desc=$1
|
|
shift
|
|
echo "[post-install-prep] waiting for ${desc}"
|
|
while :; do
|
|
remaining=$(( DEADLINE - $(date +%s) ))
|
|
if [ "$remaining" -le 0 ]; then
|
|
echo "[post-install-prep] timed out waiting for ${desc}" >&2
|
|
return 1
|
|
fi
|
|
if kubectl wait "$@" --timeout="${remaining}s" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
sleep 5
|
|
done
|
|
}
|
|
|
|
# controller_reachable: true only while the linstor-controller Service has at
|
|
# least one *ready* endpoint. The linstor CLI below dials that Service
|
|
# (linstor+ssl://linstor-controller:3371), and a ClusterIP is routable only once
|
|
# its Service has a ready backend -- otherwise the dial fails with
|
|
# "[Errno 113] No route to host" (EHOSTUNREACH). In the core Endpoints object the
|
|
# ready set is .subsets[].addresses (peers not yet ready sit in
|
|
# .subsets[].notReadyAddresses), so a non-empty addresses list is exactly
|
|
# ">=1 ready backend". Missing object / API error -> empty -> not reachable.
|
|
controller_reachable() {
|
|
[ -n "$(kubectl get endpoints linstor-controller -n cozy-linstor \
|
|
-o jsonpath='{.subsets[*].addresses[*].ip}' 2>/dev/null)" ]
|
|
}
|
|
|
|
wait_for "linstor HelmRelease to be Ready" \
|
|
helmrelease/linstor -n cozy-linstor --for=condition=Ready
|
|
wait_for "linstor-controller Deployment to be Available" \
|
|
deployment/linstor-controller -n cozy-linstor --for=condition=available
|
|
|
|
# Wait for 3 satellites to register Online, but gate every probe on a ready
|
|
# controller Service endpoint. The "Deployment Available" check above is a
|
|
# one-shot gate that goes stale: the controller carries
|
|
# reloader.stakater.com/auto=true and mounts the cert-manager-issued API-TLS
|
|
# Secret (see packages/system/linstor/templates/cluster.yaml). When cert-manager
|
|
# (re)writes that Secret during bring-up, reloader rolls the single-replica
|
|
# controller, dropping its Service to zero ready endpoints *after* Available
|
|
# already passed. A blind CLI dial into that window is what surfaced as the
|
|
# tolerated "[Errno 113] No route to host" churn. Probing only while the Service
|
|
# is routable removes that churn at the root -- a mid-loop reload re-waits for
|
|
# the endpoint instead of erroring -- while "Online == 3" stays the real
|
|
# satellite-convergence assertion. The dedicated 300s budget is preserved from
|
|
# the previous `timeout 300`; `set -e` is disabled inside an until-condition, so
|
|
# a not-yet-reachable controller does not abort the script.
|
|
echo "[post-install-prep] waiting for linstor-controller endpoint + 3 LINSTOR nodes Online"
|
|
node_deadline=$(( $(date +%s) + 300 ))
|
|
until controller_reachable \
|
|
&& [ "$(kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor node list 2>/dev/null | grep -c Online)" -eq 3 ]; do
|
|
if [ "$(date +%s)" -ge "$node_deadline" ]; then
|
|
echo "[post-install-prep] timed out waiting for linstor-controller endpoint + 3 LINSTOR nodes Online" >&2
|
|
kubectl get endpoints linstor-controller -n cozy-linstor -o wide >&2 || true
|
|
kubectl get pods -n cozy-linstor -o wide >&2 || true
|
|
exit 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
echo "[post-install-prep] creating LINSTOR storage pools (parallel across nodes)"
|
|
created_pools=$(kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor sp l -s data --pastable | awk '$2 == "data" {printf " " $4} END{printf " "}')
|
|
pids=""
|
|
for node in srv1 srv2 srv3; do
|
|
case $created_pools in
|
|
*" $node "*) echo " pool 'data' already exists on $node"; continue;;
|
|
esac
|
|
kubectl exec -n cozy-linstor deploy/linstor-controller -- linstor ps cdp zfs ${node} /dev/vdc --pool-name data --storage-pool data &
|
|
pids="$pids $!"
|
|
done
|
|
for pid in $pids; do
|
|
wait "$pid"
|
|
done
|
|
|
|
echo "[post-install-prep] applying StorageClasses"
|
|
kubectl apply -f - <<'EOF'
|
|
---
|
|
apiVersion: storage.k8s.io/v1
|
|
kind: StorageClass
|
|
metadata:
|
|
name: local
|
|
annotations:
|
|
storageclass.kubernetes.io/is-default-class: "true"
|
|
provisioner: linstor.csi.linbit.com
|
|
parameters:
|
|
linstor.csi.linbit.com/storagePool: "data"
|
|
linstor.csi.linbit.com/layerList: "storage"
|
|
linstor.csi.linbit.com/allowRemoteVolumeAccess: "false"
|
|
volumeBindingMode: WaitForFirstConsumer
|
|
allowVolumeExpansion: true
|
|
---
|
|
apiVersion: storage.k8s.io/v1
|
|
kind: StorageClass
|
|
metadata:
|
|
name: replicated
|
|
provisioner: linstor.csi.linbit.com
|
|
parameters:
|
|
linstor.csi.linbit.com/storagePool: "data"
|
|
linstor.csi.linbit.com/autoPlace: "3"
|
|
linstor.csi.linbit.com/layerList: "drbd storage"
|
|
linstor.csi.linbit.com/allowRemoteVolumeAccess: "true"
|
|
property.linstor.csi.linbit.com/DrbdOptions/auto-quorum: suspend-io
|
|
property.linstor.csi.linbit.com/DrbdOptions/Resource/on-no-data-accessible: suspend-io
|
|
property.linstor.csi.linbit.com/DrbdOptions/Resource/on-suspended-primary-outdated: force-secondary
|
|
property.linstor.csi.linbit.com/DrbdOptions/Net/rr-conflict: retry-connect
|
|
volumeBindingMode: Immediate
|
|
allowVolumeExpansion: true
|
|
EOF
|
|
|
|
echo "[post-install-prep] waiting for MetalLB CRDs"
|
|
timeout 300 sh -ec 'until kubectl get crd ipaddresspools.metallb.io l2advertisements.metallb.io >/dev/null 2>&1; do sleep 2; done'
|
|
|
|
echo "[post-install-prep] applying MetalLB IPAddressPool"
|
|
kubectl apply -f - <<'EOF'
|
|
---
|
|
apiVersion: metallb.io/v1beta1
|
|
kind: L2Advertisement
|
|
metadata:
|
|
name: cozystack
|
|
namespace: cozy-metallb
|
|
spec:
|
|
ipAddressPools: [cozystack]
|
|
---
|
|
apiVersion: metallb.io/v1beta1
|
|
kind: IPAddressPool
|
|
metadata:
|
|
name: cozystack
|
|
namespace: cozy-metallb
|
|
spec:
|
|
addresses: [192.168.123.200-192.168.123.250]
|
|
autoAssign: true
|
|
avoidBuggyIPs: false
|
|
EOF
|
|
|
|
echo "[post-install-prep] done"
|