The noVNC view at chrome.viktorbarzin.me went black: x11vnc (in the novnc sidecar) attaches to the browser container's Xvfb over localhost:6099, and when that container restarted (~8h ago, Chrome exited cleanly) x11vnc lost its X connection and exited. Because the entrypoint ran x11vnc as an unsupervised background child and then exec'd websockify as PID 1, the dead x11vnc was never relaunched — :5900 stayed dead (a defunct zombie), websockify kept returning 'Connection refused', and the view was black until a manual pod restart. Fix: the entrypoint now runs both x11vnc and websockify as supervised background children and exits non-zero via 'wait -n' if either dies, so the kubelet restarts the novnc container, which re-waits for Xvfb and relaunches x11vnc. The bridge now self-heals across browser-container restarts. Mirrors the android-emulator stack's supervision pattern. Architecture doc updated with the new failure mode, diagnosis, immediate-recovery, and SHA-pin deploy note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
2.6 KiB
Bash
60 lines
2.6 KiB
Bash
#!/usr/bin/env bash
|
|
# Connect to the chrome-service container's Xvfb (shared pod network, TCP)
|
|
# and serve the noVNC HTML5 client + websockify bridge on :6080.
|
|
set -e
|
|
|
|
# Containerd grants pods an effectively unbounded RLIMIT_NOFILE (2^31). x11vnc
|
|
# sweeps the WHOLE fd table with fcntl on every client connection, so each VNC
|
|
# connect hangs for ~forever and the noVNC client sits on "Connecting" until it
|
|
# times out. Cap it before launching x11vnc. (Same fix as the android-emulator
|
|
# stack; see docs/architecture/chrome-service.md "noVNC fd-sweep".)
|
|
ulimit -n 65536
|
|
|
|
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
|
|
if echo > /dev/tcp/127.0.0.1/6099 2>/dev/null; then
|
|
echo "Xvfb TCP up after attempt $i"
|
|
break
|
|
fi
|
|
echo "waiting for Xvfb TCP 6099 attempt=$i"
|
|
sleep 2
|
|
done
|
|
|
|
# Both x11vnc and websockify run as supervised children of this entrypoint (PID
|
|
# 1) so their logs land on container stdout and the `wait -n` at the end can catch
|
|
# either one dying. `-noshm` skips MIT-SHM probes that fail across container
|
|
# boundaries (each container has its own /dev/shm); `-noxdamage` skips XDAMAGE
|
|
# which Xvfb doesn't expose; `-quiet` keeps the polling chatter out of pod logs.
|
|
echo "starting x11vnc -> :5900"
|
|
x11vnc -display localhost:99 -nopw -listen 0.0.0.0 -rfbport 5900 \
|
|
-forever -shared -noshm -noxdamage -quiet 2>&1 &
|
|
|
|
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
if echo > /dev/tcp/127.0.0.1/5900 2>/dev/null; then
|
|
echo "x11vnc bound 5900 after attempt $i"
|
|
break
|
|
fi
|
|
echo "waiting for x11vnc :5900 attempt=$i"
|
|
sleep 2
|
|
done
|
|
|
|
if ! echo > /dev/tcp/127.0.0.1/5900 2>/dev/null; then
|
|
echo "ERROR: x11vnc did not bind 5900"
|
|
exit 1
|
|
fi
|
|
|
|
echo "starting websockify -> :6080"
|
|
# Run websockify in the background (it was `exec`ed before) so BOTH it and x11vnc
|
|
# are supervised. x11vnc attaches to the chrome-service container's Xvfb over
|
|
# localhost:6099 (shared pod network); when that container restarts, x11vnc loses
|
|
# its X connection and exits. Previously websockify was PID 1 and x11vnc was an
|
|
# unsupervised child, so a dead x11vnc was never relaunched: :5900 stayed dead and
|
|
# the noVNC view went black until a manual pod restart. Now if EITHER process
|
|
# exits, `wait -n` returns and we exit non-zero so the kubelet restarts this
|
|
# container, which re-waits for Xvfb and relaunches x11vnc — the bridge self-heals
|
|
# across browser-container restarts. (Same supervision pattern as the
|
|
# android-emulator stack's entrypoint.)
|
|
websockify --web=/usr/share/novnc 6080 localhost:5900 &
|
|
|
|
wait -n || true
|
|
echo "novnc: a supervised process (x11vnc or websockify) exited; exiting so the kubelet restarts this container." >&2
|
|
exit 1
|