From f082a70f3c48b6d69b9f5acc4f817265be0934eb Mon Sep 17 00:00:00 2001 From: yanhao98 Date: Mon, 24 Aug 2026 04:10:46 +0000 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20docker-userland-proxy-off.?= =?UTF-8?q?sh=EF=BC=9A=E5=85=B3=E9=97=AD=20Docker=20userland-proxy?= =?UTF-8?q?=EF=BC=8C=E6=B6=88=E9=99=A4=E6=AF=8F=E7=AB=AF=E5=8F=A3=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=20docker-proxy=20=E8=BF=9B=E7=A8=8B=E7=9A=84=E5=86=85?= =?UTF-8?q?=E5=AD=98=E5=BC=80=E9=94=80=EF=BC=88=E5=AE=9E=E6=B5=8B=2014=20?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E7=9C=81=20~70MB=EF=BC=89=EF=BC=9B=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=20Debian/Ubuntu(systemd)=20=E4=B8=8E=20Alpine(OpenRC)?= =?UTF-8?q?=EF=BC=8C=E5=B9=82=E7=AD=89=EF=BC=8C=E5=B8=A6=E5=A4=87=E4=BB=BD?= =?UTF-8?q?=E4=B8=8E=E5=9B=9E=E6=BB=9A=E6=8C=87=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-userland-proxy-off.sh | 138 +++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 docker-userland-proxy-off.sh diff --git a/docker-userland-proxy-off.sh b/docker-userland-proxy-off.sh new file mode 100644 index 0000000..da49d0c --- /dev/null +++ b/docker-userland-proxy-off.sh @@ -0,0 +1,138 @@ +#!/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"