fix(web, P0): nginx entrypoint 在 CI bind mount 场景不应 rm default.conf(staging 502 阻塞部署) #1856
@@ -0,0 +1,60 @@
|
||||
name: "Debug: Web container v2 (mount conflict)"
|
||||
on:
|
||||
push:
|
||||
branches: [debug/web-crash-v2]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
web-diag:
|
||||
runs-on: runtime-builder
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Setup SSH and diagnose
|
||||
shell: bash
|
||||
env:
|
||||
STAGING_SSH_KEY: ${{ secrets.PREVIEW_SSH_KEY }}
|
||||
run: |
|
||||
set -x
|
||||
which ssh || (apt-get update -qq && apt-get install -y -qq openssh-client)
|
||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
printf "%s" "$STAGING_SSH_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
H=47.98.113.167; P=22222
|
||||
ssh-keyscan -p $P -H $H >> ~/.ssh/known_hosts 2>/dev/null
|
||||
ssh -p $P -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no root@$H 'bash -s' <<'REMOTE'
|
||||
set -x
|
||||
echo "=== Current staging containers ==="
|
||||
docker ps -a --filter name=xiaoxia-*-staging --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
|
||||
echo ""
|
||||
echo "=== Web container logs (current/current-rolledback) ==="
|
||||
docker logs xiaoxia-web-staging 2>&1 | tail -40
|
||||
echo ""
|
||||
echo "=== Web inspect: env & mounts ==="
|
||||
docker inspect xiaoxia-web-staging --format 'Entrypoint: {{.Config.Entrypoint}} Cmd: {{.Config.Cmd}}'
|
||||
docker inspect xiaoxia-web-staging --format '{{range .Config.Env}}{{.}}{{"\n"}}{{end}}' | grep -E "APP_ENV|VERSION"
|
||||
echo "Mounts:"
|
||||
docker inspect xiaoxia-web-staging --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}} (rw={{.RW}}){{"\n"}}{{end}}'
|
||||
echo ""
|
||||
echo "=== Reproduce: rm on read-only bind mount ==="
|
||||
docker run --rm --name nginx-ro-test \
|
||||
-v /var/lib/xiaoxia-saas-staging/nginx-staging.conf:/etc/nginx/conf.d/default.conf:ro \
|
||||
git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/xiaoxia-saas-web:387514c \
|
||||
sh -c '
|
||||
set -x
|
||||
echo "Before:"
|
||||
ls -la /etc/nginx/conf.d/
|
||||
echo "Try rm (as entrypoint does):"
|
||||
rm -f /etc/nginx/conf.d/default.conf
|
||||
echo "rm exitcode=$?"
|
||||
echo "After rm:"
|
||||
ls -la /etc/nginx/conf.d/
|
||||
echo "Test ln:"
|
||||
ln -s /etc/nginx/nginx-staging.conf /etc/nginx/conf.d/default.conf
|
||||
echo "ln exitcode=$?"
|
||||
ls -la /etc/nginx/conf.d/
|
||||
echo "nginx -t:"
|
||||
nginx -t 2>&1
|
||||
' 2>&1
|
||||
echo ""
|
||||
echo "=== Also test with NEW fixed image (9c0d4b1 if present) ==="
|
||||
docker images | grep xiaoxia-saas-web | head -5
|
||||
REMOTE
|
||||
@@ -1,23 +1,49 @@
|
||||
#!/bin/sh
|
||||
# Select nginx config based on APP_ENV (staging/production).
|
||||
# Both configs are baked into the image at well-known paths.
|
||||
# nginx reads config only at startup, so symlink before exec.
|
||||
# 注意:基础镜像 /etc/nginx/conf.d/default.conf 是普通文件(非 symlink/目录),
|
||||
# alpine busybox ln -sf 在 target 已存在且为普通文件时行为不稳定(会尝试在
|
||||
# target 目录下建子链接),必须先 rm 再 ln 才能正确替换。
|
||||
#
|
||||
# 两种运行模式:
|
||||
# 1. CI/CD 部署(staging/production):部署脚本通过 `-v 宿主机文件:/etc/nginx/conf.d/default.conf:ro`
|
||||
# 把宿主机生成的带 resolver/docker upstream 的配置 bind mount 进来,entrypoint 不应改动。
|
||||
# bind mount 的文件是 readonly 的,rm 会报 EBUSY ("Resource busy"),直接 exec nginx 即可。
|
||||
# 2. 本地 docker-compose / 直接 `docker run`(无外部挂载):镜像烤入了 nginx-staging.conf 与
|
||||
# nginx-production.conf 到 /etc/nginx/,entrypoint 根据 APP_ENV 把 default.conf 换成正确的 symlink。
|
||||
#
|
||||
# 策略:
|
||||
# - 如果 /etc/nginx/conf.d/default.conf 已经是指向目标 conf 的 symlink,什么都不做;
|
||||
# - 否则尝试 rm -f 再 ln -s;rm 失败说明是外部 bind mount(已有正确配置),不阻塞启动;
|
||||
# - 兜底:只要 conf.d 目录里有 .conf 文件(含 bind mount 来的),就直接启动 nginx。
|
||||
set -e
|
||||
|
||||
NGINX_CONF_DIR="/etc/nginx/conf.d"
|
||||
TARGET_CONF=""
|
||||
|
||||
case "${APP_ENV:-production}" in
|
||||
staging)
|
||||
rm -f "$NGINX_CONF_DIR/default.conf"
|
||||
ln -s /etc/nginx/nginx-staging.conf "$NGINX_CONF_DIR/default.conf"
|
||||
TARGET_CONF="/etc/nginx/nginx-staging.conf"
|
||||
;;
|
||||
*)
|
||||
rm -f "$NGINX_CONF_DIR/default.conf"
|
||||
ln -s /etc/nginx/nginx-production.conf "$NGINX_CONF_DIR/default.conf"
|
||||
TARGET_CONF="/etc/nginx/nginx-production.conf"
|
||||
;;
|
||||
esac
|
||||
|
||||
DEFAULT_CONF="$NGINX_CONF_DIR/default.conf"
|
||||
|
||||
# 1. 已经是正确的 symlink:直接启动
|
||||
if [ -L "$DEFAULT_CONF" ] && [ "$(readlink "$DEFAULT_CONF" 2>/dev/null)" = "$TARGET_CONF" ]; then
|
||||
exec nginx -g "daemon off;"
|
||||
fi
|
||||
|
||||
# 2. 尝试替换为目标 symlink(无 bind mount 的场景)
|
||||
# 若 rm 失败(bind mount readonly,EBUSY/EPERM),则认为外部已注入配置,不阻塞。
|
||||
rm -f "$DEFAULT_CONF" 2>/dev/null || true
|
||||
if [ -f "$TARGET_CONF" ] && [ ! -e "$DEFAULT_CONF" ]; then
|
||||
ln -s "$TARGET_CONF" "$DEFAULT_CONF" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 3. 兜底:至少要有一个 .conf 文件,否则 nginx 起不来
|
||||
if ! ls "$NGINX_CONF_DIR"/*.conf >/dev/null 2>&1; then
|
||||
echo "ERROR: no nginx config found in $NGINX_CONF_DIR (tried $TARGET_CONF and external bind mount)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec nginx -g "daemon off;"
|
||||
|
||||
Reference in New Issue
Block a user