139 lines
5.5 KiB
Bash
139 lines
5.5 KiB
Bash
#!/usr/bin/env bash
|
||
#
|
||
# docker-userland-proxy-off.sh
|
||
#
|
||
# 关闭 Docker userland-proxy,消除"每个发布端口一个 docker-proxy 进程"的内存开销。
|
||
# 实测收益:14 个发布端口时约省 70-90MB 常驻内存。
|
||
# 现代 Docker(>=23,dockerd 统一监听发布端口)关闭后本机 127.0.0.1 访问仍可用,
|
||
# 外部访问走 iptables DNAT 不受影响。
|
||
#
|
||
# 适用系统: Debian/Ubuntu(systemd)/ Alpine(OpenRC)
|
||
# 要求: root 权限
|
||
#
|
||
# 使用方法:
|
||
# URL="https://git.1-h.cc/Scripts/Linux/raw/branch/2026/docker-userland-proxy-off.sh"; curl -fsSL "$URL" | bash
|
||
#
|
||
# 幂等: 已关闭时直接报告"已生效"并退出,不会重复重启 docker。
|
||
|
||
set -euo pipefail
|
||
|
||
# 颜色
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[0;33m'
|
||
NC='\033[0m'
|
||
|
||
# root 检查
|
||
if [ "$(id -u)" -ne 0 ]; then
|
||
echo -e "${RED}错误:需要 root 权限${NC}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# docker 检查
|
||
if ! command -v docker >/dev/null 2>&1; then
|
||
echo -e "${RED}错误:未检测到 docker${NC}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# --- 1. 检测服务管理器,决定重启命令 ---
|
||
RESTART_CMD=""
|
||
if [ -f /etc/alpine-release ] && command -v rc-service >/dev/null 2>&1; then
|
||
RESTART_CMD="rc-service docker restart"
|
||
echo -e "${GREEN}[系统] Alpine / OpenRC${NC}"
|
||
elif command -v systemctl >/dev/null 2>&1; then
|
||
RESTART_CMD="systemctl restart docker"
|
||
echo -e "${GREEN}[系统] systemd${NC}"
|
||
else
|
||
echo -e "${RED}错误:无法识别的服务管理器(仅支持 Alpine/OpenRC 与 systemd)${NC}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# --- 2. 幂等检查:当前是否已生效 ---
|
||
# 注意: docker info 的 EnableUserlandProxy 字段只在 true 时显示,false 时字段消失,
|
||
# 因此以 daemon.json 配置为准,docker info 仅作辅助。
|
||
CFG_VAL="unset"
|
||
if [ -f /etc/docker/daemon.json ]; then
|
||
if command -v jq >/dev/null 2>&1; then
|
||
CFG_VAL=$(jq -r '."userland-proxy" // "unset"' /etc/docker/daemon.json 2>/dev/null || echo unset)
|
||
elif command -v python3 >/dev/null 2>&1; then
|
||
CFG_VAL=$(python3 -c 'import json;v=json.load(open("/etc/docker/daemon.json")).get("userland-proxy", "unset");print("false" if v is False else ("true" if v is True else v))' 2>/dev/null || echo unset)
|
||
elif grep -q '"userland-proxy"[[:space:]]*:[[:space:]]*false' /etc/docker/daemon.json 2>/dev/null; then
|
||
CFG_VAL="false"
|
||
fi
|
||
fi
|
||
if [ "$CFG_VAL" = "false" ]; then
|
||
# 注意: [d]ocker-proxy 正则避免 pgrep 匹配到脚本自身;|| true 规避 pipefail 下 pgrep 无匹配(退出1)触发 set -e
|
||
COUNT=$(pgrep -f '[d]ocker-proxy' 2>/dev/null | wc -l || true)
|
||
echo -e "${GREEN}[已生效] daemon.json 已配置 userland-proxy: false,当前 docker-proxy 进程数: ${COUNT},无需操作${NC}"
|
||
exit 0
|
||
fi
|
||
echo -e "${YELLOW}[状态] 当前 daemon.json 未配置 userland-proxy: false(值: ${CFG_VAL})${NC}"
|
||
|
||
# --- 3. 备份现有 daemon.json ---
|
||
mkdir -p /etc/docker
|
||
if [ -f /etc/docker/daemon.json ]; then
|
||
BAK="/etc/docker/daemon.json.bak-$(date +%Y%m%d%H%M%S)"
|
||
cp /etc/docker/daemon.json "$BAK"
|
||
echo -e "${GREEN}[备份] $BAK${NC}"
|
||
fi
|
||
|
||
# --- 4. 合并写入 userland-proxy: false(保留原有其他配置)---
|
||
if [ -f /etc/docker/daemon.json ]; then
|
||
if command -v jq >/dev/null 2>&1; then
|
||
TMP="/etc/docker/daemon.json.tmp.$$"
|
||
jq '."userland-proxy" = false' /etc/docker/daemon.json > "$TMP"
|
||
mv "$TMP" /etc/docker/daemon.json
|
||
elif command -v python3 >/dev/null 2>&1; then
|
||
python3 - <<'PYEOF'
|
||
import json
|
||
p = "/etc/docker/daemon.json"
|
||
with open(p) as f:
|
||
cfg = json.load(f)
|
||
cfg["userland-proxy"] = False
|
||
with open(p, "w") as f:
|
||
json.dump(cfg, f, indent=2)
|
||
PYEOF
|
||
else
|
||
echo -e "${RED}错误:需要 jq 或 python3 来合并 daemon.json,请先安装其一${NC}" >&2
|
||
exit 1
|
||
fi
|
||
else
|
||
printf '{\n "userland-proxy": false\n}\n' > /etc/docker/daemon.json
|
||
fi
|
||
echo -e "${GREEN}[配置] /etc/docker/daemon.json -> userland-proxy: false${NC}"
|
||
cat /etc/docker/daemon.json
|
||
|
||
# --- 5. 重启 docker(容器会短暂中断几秒)---
|
||
# 注: systemctl restart 在个别环境下会因时序返回非零,实际已生效;
|
||
# 因此不在此处退出,改由下面的验证步骤给出真实结果。
|
||
echo -e "${YELLOW}[重启] $RESTART_CMD${NC}"
|
||
if ! $RESTART_CMD; then
|
||
echo -e "${YELLOW}[提示] 重启命令返回非零,继续验证实际状态...${NC}"
|
||
fi
|
||
sleep 6
|
||
# 等待 docker 就绪(最多 30s)
|
||
for _ in $(seq 1 15); do
|
||
if docker info >/dev/null 2>&1; then break; fi
|
||
sleep 2
|
||
done
|
||
|
||
# --- 6. 验证 ---
|
||
PROXY_COUNT=$(pgrep -f '[d]ocker-proxy' 2>/dev/null | wc -l || true)
|
||
if [ "$PROXY_COUNT" = "0" ]; then
|
||
echo -e "${GREEN}[验证] docker-proxy 进程数: 0${NC}"
|
||
else
|
||
echo -e "${RED}[警告] docker-proxy 进程数: ${PROXY_COUNT}(期望 0),userland-proxy 可能未生效,请检查${NC}"
|
||
fi
|
||
CONF=$(docker info 2>/dev/null | awk -F': ' '/EnableUserlandProxy/{gsub(/ /,"",$2); print $2}' | head -1)
|
||
echo -e "${GREEN}[验证] daemon.json 配置: userland-proxy: false(docker info 字段: ${CONF:-不显示=false 时正常})${NC}"
|
||
echo "--- 内存 ---"
|
||
free -m | head -2
|
||
echo "--- 容器状态 ---"
|
||
docker ps --format '{{.Names}}\t{{.Status}}' 2>/dev/null || true
|
||
echo ""
|
||
echo -e "${YELLOW}验证建议:${NC}"
|
||
echo " 1. 外部访问发布端口(如 curl http://<公网IP>:<端口>)"
|
||
echo " 2. 本机 loopback(如 curl http://127.0.0.1:<端口>)"
|
||
echo " 3. 容器互访(反代容器访问业务容器内网地址)"
|
||
echo -e "${YELLOW}回滚:${NC} rm /etc/docker/daemon.json && $RESTART_CMD"
|