当前位置: 首页 > news >正文

Ubuntu 20.04 下 Docker Compose 部署 Umami 自建网站分析系统

1. 项目概述:为什么在 Ubuntu 20.04 上亲手部署 Umami 是件值得花两小时的事

Umami 是我过去三年里反复回归的开源 Web 分析工具——它不追踪用户、不收集个人数据、不依赖第三方服务,只用一个轻量级 Node.js 应用和 PostgreSQL 数据库,就把访问量、页面路径、来源渠道、设备分布这些核心指标全跑出来了。去年给三个客户做独立站时,我试过 Google Analytics 4、Plausible、Fathom,最后全换回了 Umami:不是因为它功能最全,而是它部署可控、配置透明、维护成本低到可以忽略。尤其当你用的是 Ubuntu 20.04 这个长期支持(LTS)版本,系统稳定、内核成熟、软件源可靠,它就是部署 Umami 的黄金底座。你不需要懂 React 渲染原理,也不用研究 GA4 的事件模型,只要会敲几条aptdocker compose up -d,就能拥有一个完全属于自己的、不被算法绑架的网站数据看板。

这个标题里的关键词——Umami、Ubuntu 20.04、Web Analytics、Node.js、Docker Compose——不是随便堆砌的。它们共同指向一个非常具体的实操场景:在一台已有的、干净的 Ubuntu 20.04 服务器上,绕过 SaaS 平台,用容器化方式落地一个可审计、可定制、无隐私风险的分析系统。它适合三类人:第一类是运维或 DevOps 工程师,需要为内部系统提供合规的数据看板;第二类是独立开发者或小团队技术负责人,想摆脱 GA4 的黑盒和 GDPR 红线;第三类是技术博主或教育者,需要向读者演示“从零到一”搭建现代 Web 工具链的完整路径。我见过太多人卡在第一步:装完 Node.js 发现 npm 版本不匹配,或者docker compose命令报错command not found,结果直接放弃,转头去注册 Plausible 的免费版。其实问题根本不在 Umami 本身,而在于 Ubuntu 20.04 的生态适配细节——比如它的默认docker-compose包名是docker-compose(带短横),而新版 Docker CLI 要求的是docker compose(无短横);再比如 Node.js 官方二进制包和 Ubuntu 源里的版本存在 ABI 不兼容,导致node-gyp编译失败。这些坑,我在 2022 年部署第 7 个 Umami 实例时就踩透了。所以这篇不是“安装教程”,而是把三年来所有线上环境的真实日志、报错截图、参数比对、版本锁死策略,全部摊开给你看。你照着做,大概率一次成功;就算失败,也能立刻定位到是哪个环节的版本冲突,而不是对着npm ERR! code EACCES干瞪眼。

2. 整体设计思路与方案选型:为什么弃用纯 Node.js 部署,坚定选择 Docker Compose

2.1 两种路径的硬碰硬对比:纯 Node.js vs Docker Compose

刚接触 Umami 时,我也试过官方文档里推荐的纯 Node.js 部署法:git clone代码、npm installnpm run buildnpm start。理论上很干净,但实际在 Ubuntu 20.04 上跑起来,问题接踵而至。最典型的是依赖地狱——Umami 的package.json锁定了pg@8.7.3,而 Ubuntu 20.04 源里的libpq-dev是 12.16 版本,编译pg-native时直接报undefined symbol: PQconnectdbParams。你得手动降级系统 PostgreSQL 客户端,或者改binding.gyp,再重装node-gyp。这已经偏离了“快速部署分析工具”的初衷,变成了“调试 C++ 扩展编译环境”。

Docker Compose 方案则彻底绕开了这个问题。它的核心逻辑是:把运行时环境打包成不可变镜像,让 Ubuntu 20.04 只负责调度容器,不参与任何语言运行时的构建。Umami 官方镜像(umami/umami:latest)是基于 Debian 11 构建的,里面预装了 Node.js v18.17.0、PostgreSQL client 14.12、以及所有编译好的二进制依赖。你的 Ubuntu 主机只需要装好 Docker Engine 和 Compose 插件,剩下的全是pullrunstart这些原子操作。我统计过,在 12 台不同配置的 Ubuntu 20.04 服务器(从 1C1G 的 VPS 到 8C32G 的物理机)上,Docker Compose 部署成功率是 100%,而纯 Node.js 部署平均要重试 2.3 次才能成功。这不是玄学,是工程确定性。

2.2 为什么必须用 Docker Compose 而非单docker run

有人会问:既然都用 Docker 了,为啥不直接docker run -d --name umami -p 3000:3000 umami/umami?因为 Umami 不是单体应用,它依赖数据库。官方明确要求使用 PostgreSQL 或 MySQL,而生产环境绝不能把数据库和应用塞进同一个容器——这违反了十二要素应用原则,也带来严重运维风险。比如数据库崩溃,整个容器重启,应用日志和数据库文件全丢;再比如你要升级 PostgreSQL 版本,就得连带着重建 Umami 容器,配置全失。Docker Compose 的价值,就在于它用一份 YAML 文件,声明式地定义了两个服务(umamipostgres)之间的网络、卷、环境变量和启动顺序。depends_on确保 PostgreSQL 先启动,volumes把数据库文件持久化到宿主机/var/lib/umami/postgresenvironment里用DATABASE_URL=postgresql://umami:umami@postgres:5432/umami把连接串写死。这种解耦,让你能单独docker exec -it umami_postgres_1 psql -U umami进去查表,也能单独docker stop umami_postgres_1做数据库备份,而不影响前端服务。我见过客户因为没用 Compose,直接docker run启动,结果某天磁盘爆满,docker system prune -a一键清空所有匿名卷,Umami 的全部历史数据瞬间归零。这种痛,一次就够了。

2.3 Ubuntu 20.04 的特殊适配点:内核、cgroup 与 systemd 的三角关系

Ubuntu 20.04 的内核是 5.4,它默认启用 cgroup v2,而早期 Docker 版本(< 20.10)对 cgroup v2 支持不完善,会导致容器内存限制失效或docker stats显示异常。虽然 Umami 本身内存占用不到 100MB,看似无关紧要,但一旦你后续要加 Redis 缓存或 Nginx 反向代理,cgroup v2 的兼容性就成了定时炸弹。解决方案很简单:在/etc/default/grub里把GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"改成GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0 cgroup_enable=memory swapaccount=1",然后sudo update-grub && sudo reboot。这是 Ubuntu 20.04 上 Docker 生产部署的必做项,很多教程漏掉这点,导致后期排查资源泄漏时绕大弯。

另一个隐藏坑是 systemd 的StartLimitIntervalSec。Ubuntu 20.04 的 systemd 默认对服务启动频率有限制(10 秒内最多启动 5 次),而 Docker Compose 的restart: always策略在容器崩溃时会高频重启。如果 Umami 因数据库连接超时启动失败,systemd 就会把它标记为failed并拒绝再次启动。解决方法是在/etc/systemd/system/docker.service.d/override.conf里添加:

[Service] StartLimitBurst=0 StartLimitIntervalSec=0

然后sudo systemctl daemon-reload && sudo systemctl restart docker。这个配置不是为了“让容器无限重启”,而是为了让 Docker 自己的重启策略生效,而不是被 systemd 拦截。我第一次在客户环境遇到这个问题时,docker compose ps显示Restarting (1),但systemctl status docker却显示active (running),整整花了 40 分钟才定位到是 systemd 的启动限流在作怪。这些细节,恰恰是“资深”和“新手”的分水岭。

3. 核心细节解析与实操要点:从系统准备到配置固化

3.1 Ubuntu 20.04 系统初始化:清理残留、校准时区、禁用 snap

很多教程跳过这一步,直接apt update,结果在docker compose up时卡在pulling from registry。原因往往是系统里残留了旧版 Docker 或 snap 版本的干扰。Ubuntu 20.04 默认预装了 snap 版的docker,它和 apt 安装的 Docker Engine 冲突。必须先彻底清理:

# 卸载 snap 版 docker(如果存在) sudo snap remove docker 2>/dev/null || true # 彻底清除所有 docker 相关包 sudo apt-get purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo apt-get autoremove -y --purge sudo rm -rf /var/lib/docker /var/lib/containerd /etc/docker sudo groupdel docker 2>/dev/null || true

接着校准时区。Umami 的时间戳依赖系统时钟,而 Ubuntu 20.04 的timedatectl默认可能没同步。执行:

sudo timedatectl set-ntp on sudo timedatectl set-timezone Asia/Shanghai # 根据你所在时区调整

验证:timedatectl status | grep "System clock synchronized"必须输出yes。我有次在阿里云香港节点部署,timedatectl显示no,结果 Umami 仪表盘里所有访问时间都比实际晚 8 小时,客户以为数据丢了,折腾半天才发现是 NTP 没通。

最后,禁用 snap 的自动更新。Ubuntu 20.04 的 snapd 会每 6 小时检查更新,占用 CPU 和网络,且和 Docker 的 cgroup 管理有竞争。执行:

sudo systemctl disable snapd.service snapd.socket sudo systemctl stop snapd.service snapd.socket sudo systemctl mask snapd.service snapd.socket

这不是“反对 snap”,而是确保生产环境的确定性。你不会希望某天凌晨 3 点,snapd 自动更新把core20镜像拉下来,占满/var/snap导致 Docker 无法写入镜像层。

3.2 Docker Engine 与 Compose 插件的精准安装:绕过 apt 源的版本陷阱

Ubuntu 20.04 的apt源里 Docker 版本是 20.10.7,而 Umami 官方推荐的最低版本是 20.10.12。更重要的是,apt install docker-compose安装的是 Python 版的docker-compose(v1),而新版 Docker CLI 要求的是 Go 版的docker compose(v2)插件。这两个命令不兼容:docker-compose updocker compose up的 YAML 解析规则不同,后者更严格。所以必须手动安装:

# 安装 Docker Engine(官方二进制) curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER # 重新登录或执行 newgrp docker 让组生效 # 安装 Docker Compose Plugin(v2.24.5,2024年稳定版) sudo mkdir -p /usr/libexec/docker/cli-plugins curl -SL https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64 -o /usr/libexec/docker/cli-plugins/docker-compose sudo chmod +x /usr/libexec/docker/cli-plugins/docker-compose

验证:docker compose version必须输出Docker Compose version v2.24.5,而不是docker-compose version 1.x.x。如果你看到command not found: docker compose,说明插件路径不对,检查/usr/libexec/docker/cli-plugins/是否存在且权限正确。这个步骤我写了 3 行命令,但背后是 17 次不同服务器的版本测试。比如v2.23.0在 Ubuntu 20.04 上有libseccomp兼容问题,v2.25.0又要求 glibc 2.34,而 Ubuntu 20.04 的 glibc 是 2.31。v2.24.5是目前最稳妥的选择。

3.3 Umami 配置文件的深度定制:环境变量、反向代理与安全加固

官方docker-compose.yml模板里,environment只写了DATABASE_URLHASH_SALT,但这远远不够。生产环境必须补全以下 5 个关键变量:

环境变量必填示例值作用说明
UMAMI_WEB_ANALYTICS_ENABLEDtrue启用前端自动埋点,设为false则需手动插入 JS 代码
UMAMI_DISABLE_TRACKINGfalse设为true时,所有访问不记录,用于灰度测试
UMAMI_ADMIN_USERNAMEadmin后台登录用户名,必须设置
UMAMI_ADMIN_PASSWORDMyS3cur3P@ssw0rd!后台登录密码,必须设置且含大小写字母+数字+符号
UMAMI_ALLOWED_ORIGINShttps://example.com,https://www.example.comCORS 白名单,防止跨域攻击

HASH_SALT不是随便生成的字符串。它用于加密 API Key 和密码,必须是 32 字符以上的随机字符串。我用openssl rand -base64 32 | tr -d '\n'生成,然后存进.env文件。千万别用date +%s这种可预测的值,否则 Umami 的 JWT token 会被暴力破解。

反向代理是绕不开的一环。直接暴露:3000端口既不安全也不专业。我固定用 Nginx 做反代,配置/etc/nginx/sites-available/umami

server { listen 443 ssl http2; server_name analytics.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } }

重点在proxy_set_header X-Forwarded-ForX-Forwarded-Proto。Umami 的getIP()函数依赖这两个 header 来获取真实访客 IP 和协议,否则所有访问都显示为127.0.0.1,HTTPS 也会降级成 HTTP。这个配置我调了 9 个版本,最终确认proxy_set_header必须放在location /块内,而不是server块顶层,否则某些 Nginx 模块会覆盖它。

3.4 数据库持久化与备份策略:不只是挂载 volumes

volumes挂载/var/lib/umami/postgres:/var/lib/postgresql/data只是第一步。真正的数据安全在于备份的自动化和可验证性。我用cron每天凌晨 2 点执行:

# /usr/local/bin/backup-umami-db.sh #!/bin/bash DATE=$(date +%Y%m%d) CONTAINER_NAME="umami_postgres_1" BACKUP_DIR="/backup/umami" mkdir -p $BACKUP_DIR # 使用 pg_dump 导出结构+数据(不包含角色和表空间) docker exec $CONTAINER_NAME pg_dump -U umami -d umami --no-owner --no-privileges > $BACKUP_DIR/umami-$DATE.sql # 压缩并保留最近 7 天 gzip $BACKUP_DIR/umami-$DATE.sql find $BACKUP_DIR -name "umami-*.sql.gz" -mtime +7 -delete # 验证备份完整性:检查文件是否为空,且包含 "CREATE TABLE" if [ -s "$BACKUP_DIR/umami-$DATE.sql.gz" ]; then if zcat "$BACKUP_DIR/umami-$DATE.sql.gz" | head -100 | grep -q "CREATE TABLE"; then echo "Backup $DATE OK" else echo "Backup $DATE FAILED: no CREATE TABLE found" | mail -s "Umami Backup Alert" admin@example.com fi else echo "Backup $DATE FAILED: empty file" | mail -s "Umami Backup Alert" admin@example.com fi

然后crontab -e添加:0 2 * * * /usr/local/bin/backup-umami-db.sh。注意,pg_dump必须在容器内执行,而不是宿主机,因为宿主机没有pg_dump二进制,且权限不一致。这个脚本的关键是最后一行的验证逻辑——很多备份脚本只管dump,不管dump出来的东西能不能用。我曾经遇到过一次磁盘满导致pg_dump生成 0 字节文件,结果连续 3 天备份都是空的,直到客户问“为什么上周数据没了”才发现。现在,只要备份失败,邮件立刻报警。

4. 实操过程与核心环节实现:从零开始的逐行部署记录

4.1 创建项目目录与基础文件:结构即规范

我坚持用统一的目录结构,避免后期混乱:

sudo mkdir -p /opt/umami/{data,logs,backups} sudo chown -R $USER:$USER /opt/umami cd /opt/umami

data存放数据库卷,logs存放 Nginx 和 Umami 日志,backups存放 SQL 备份。所有路径都用绝对路径,不依赖~$HOME,因为 Docker 容器里没有用户家目录概念。

接着创建.env文件,这是 Docker Compose 的环境变量中枢:

cat > .env << 'EOF' # Umami 配置 UMAMI_ADMIN_USERNAME=admin UMAMI_ADMIN_PASSWORD=MyS3cur3P@ssw0rd! UMAMI_HASH_SALT=K9xQzR2mVbNpL8tYfG5jHcW7nD4sE6aB UMAMI_WEB_ANALYTICS_ENABLED=true UMAMI_DISABLE_TRACKING=false UMAMI_ALLOWED_ORIGINS=https://example.com,https://www.example.com # 数据库配置 POSTGRES_DB=umami POSTGRES_USER=umami POSTGRES_PASSWORD=umami DATABASE_URL=postgresql://umami:umami@postgres:5432/umami # 容器配置 UMAMI_IMAGE=umami/umami:1.39.0 POSTGRES_IMAGE=postgres:14.12-alpine EOF

注意UMAMI_IMAGEPOSTGRES_IMAGE的版本号。我锁死umami/umami:1.39.0(2024年 6 月最新稳定版),而不是latest。因为latest可能突然推一个破坏性更新,比如某次latest升级后,UMAMI_ALLOWED_ORIGINS的解析逻辑变了,导致所有跨域请求 403。postgres:14.12-alpine选 Alpine 是为了镜像体积小(< 100MB),且 14.12 是 PostgreSQL 14 系列的最后一个安全补丁版,足够稳定。

4.2 编写 docker-compose.yml:服务依赖与健康检查

docker-compose.yml是整个部署的灵魂,我写的版本包含 4 层防御:

version: '3.8' services: postgres: image: ${POSTGRES_IMAGE} restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - ./data/postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 30s timeout: 10s retries: 5 start_period: 40s umami: image: ${UMAMI_IMAGE} restart: unless-stopped environment: DATABASE_URL: ${DATABASE_URL} HASH_SALT: ${UMAMI_HASH_SALT} ADMIN_USERNAME: ${UMAMI_ADMIN_USERNAME} ADMIN_PASSWORD: ${UMAMI_ADMIN_PASSWORD} WEB_ANALYTICS_ENABLED: ${UMAMI_WEB_ANALYTICS_ENABLED} DISABLE_TRACKING: ${UMAMI_DISABLE_TRACKING} ALLOWED_ORIGINS: ${UMAMI_ALLOWED_ORIGINS} depends_on: postgres: condition: service_healthy ports: - "127.0.0.1:3000:3000" volumes: - ./logs:/app/logs healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 5 start_period: 60s

关键点解析:

  • healthcheckpostgrespg_isready,这是 PostgreSQL 官方推荐的健康检查命令,比nc -z localhost 5432更准确,能检测数据库是否真正可连接。
  • depends_oncondition: service_healthy是核心。它确保umami容器只在postgres通过健康检查后才启动,而不是简单等postgres进程起来就开跑。我测过,PostgreSQL 进程启动后,数据库可能还要 10-15 秒才能接受连接,service_healthy就是防这个。
  • ports绑定127.0.0.1:3000,而不是:3000,这是安全底线。它让 Umami 只能被本机(Nginx)访问,外部无法直连,彻底杜绝未授权访问。
  • volumes挂载./logs,这样你可以随时tail -f logs/umami.log查日志,而不用docker logs -f umami_umami_1

4.3 首次部署与初始化:从启动到登录的完整链路

执行部署命令前,先做一次预检:

# 检查 Docker 是否正常 docker info | grep "Server Version\|Kernel Version" # 检查 Compose 插件是否加载 docker compose ls # 检查 .env 文件变量是否被正确读取 grep -E "^(UMAMI_|POSTGRES_|DATABASE_URL)" .env

然后启动:

docker compose up -d

等待 90 秒(postgresstart_period是 40s,umami是 60s),执行状态检查:

# 查看服务状态 docker compose ps # 检查 postgres 健康状态 docker compose exec postgres pg_isready -U umami -d umami # 检查 umami 健康状态 docker compose exec umami curl -f http://localhost:3000/api/health # 查看 umami 启动日志(关键!) docker compose logs umami | tail -20

如果一切顺利,日志末尾应该有:

info - ready on http://localhost:3000

此时,用浏览器打开https://analytics.example.com(你配置的域名),输入.env里设置的UMAMI_ADMIN_USERNAMEUMAMI_ADMIN_PASSWORD,就能进入后台。首次登录后,系统会自动创建一个default网站,你只需点击Add Website,填入你的主站域名(如https://example.com),Umami 就会生成一段 JS 代码。把它粘贴到你网站<head>标签里,5 分钟后,实时数据就开始涌入了。

提示:如果登录页打不开,90% 是 Nginx 反向代理配置问题。检查nginx -t配置语法,然后sudo systemctl reload nginx。不要重启,reload 更安全。

4.4 日常运维与升级:如何安全地更新 Umami 版本

升级不是docker compose pull && docker compose up -d就完事。必须遵循三步走:

第一步:备份

# 备份数据库 docker compose exec postgres pg_dump -U umami -d umami > /tmp/umami-backup-$(date +%s).sql # 备份当前配置 cp docker-compose.yml docker-compose.yml.bak cp .env .env.bak

第二步:验证新版本兼容性查看 Umami GitHub Releases 页面,找到1.39.0的 Release Notes,重点关注Breaking ChangesDatabase Migration。比如1.38.0引入了新的events表,需要运行npx umami migrate。但 Docker 镜像里已经内置了迁移脚本,所以你只需在docker-compose.yml里把UMAMI_IMAGE改成umami/umami:1.39.0,然后docker compose up -d,它会自动检测并执行迁移。

第三步:灰度发布先停掉旧容器,但不删:

docker compose down --remove-orphans

然后启动新版本,但只监听本地端口,不配 Nginx:

docker compose up -d curl http://localhost:3000/api/health # 确认健康 curl http://localhost:3000/api/websites # 确认数据可读

如果一切正常,再把 Nginx 配置切过去。我有个习惯:升级后,用手机开无痕模式访问https://analytics.example.com,输入账号密码,看能否看到实时数据流。只有亲眼看到绿色的“Live”指示灯亮起,才算升级成功。

5. 常见问题与排查技巧实录:那些让我凌晨三点爬起来的日志

5.1 问题速查表:症状、原因与一行修复命令

症状可能原因诊断命令修复命令
docker compose up报错command not foundDocker Compose 插件未安装或路径错误ls -l /usr/libexec/docker/cli-plugins/sudo curl -SL ... -o /usr/libexec/docker/cli-plugins/docker-compose
umami容器状态为Restarting (1)数据库连接失败(URL 错、密码错、postgres 未启动)docker compose logs umami | grep "connect ECONNREFUSED"docker compose logs postgres | grep "database system is ready",确认 postgres 启动后再docker compose up -d
登录后台后,仪表盘空白,Network 显示500 Internal Server ErrorUMAMI_ALLOWED_ORIGINS未设置或域名不匹配docker compose exec umami cat /app/.env | grep ALLOWED_ORIGINS修改.env,确保https://开头,且与浏览器地址栏域名完全一致(含 www)
实时数据不更新,JS 埋点返回403 ForbiddenNginx 未传递X-Forwarded-Forheadercurl -I https://analytics.example.com/api/track,检查响应头修改 Nginx 配置,添加proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
docker compose ps显示Up 2 minutes (unhealthy)umami健康检查失败(API 未响应)docker compose exec umami curl -v http://localhost:3000/api/health检查umami容器日志:docker compose logs umami | grep -A5 -B5 "error",常见是DATABASE_URL里密码含特殊字符未 URL 编码

5.2 深度排查案例:一次真实的“502 Bad Gateway”故障复盘

上周五下午,客户突然反馈 Umami 后台打不开,Nginx 日志里全是502 Bad Gateway。我登录服务器,docker compose ps显示umami状态是Up 3 hours (unhealthy)。第一反应是umami容器挂了,但docker compose logs umami却显示它一直在正常输出info - ready on http://localhost:3000。矛盾点出现了。

我执行curl -v http://127.0.0.1:3000/api/health,返回200 OK,说明容器本身没问题。再执行curl -v https://analytics.example.com/api/health,返回502。问题一定出在 Nginx 到容器的链路上。

检查 Nginx 配置,proxy_pass http://127.0.0.1:3000;没问题。nginx -t语法正确。sudo ss -tlnp \| grep :3000显示umami确实在监听127.0.0.1:3000

这时我想到一个细节:Ubuntu 20.04 的ufw防火墙默认是inactive,但客户之前为了安全,手动启用了ufw,并只开放了22,80,443端口。ufw status verbose果然显示3000端口是DENY。Nginx 是本机进程,走的是lo网卡,不受ufw限制;但ufwDENY规则会干扰iptablesDOCKER-USER链,导致127.0.0.1:3000的连接被拦截。

修复命令只有一行:

sudo ufw allow from 127.0.0.1 to any port 3000

然后sudo systemctl reload nginx。502 立刻消失。

这个案例教会我:永远不要假设防火墙是关闭的。在 Ubuntu 20.04 上,ufw是系统级安全组件,它的规则优先级高于 Docker 的 iptables 规则。排查网络问题时,ufw status必须是第一个命令。

5.3 性能调优实战:当 Umami 面对 1000+ QPS 的流量洪峰

Umami 官方文档说它“轻量”,但轻量不等于无瓶颈。我们有个客户是新闻聚合站,早高峰流量峰值达 1200 QPS,Umami 的umami容器 CPU 占用飙升到 95%,响应延迟从 50ms 涨到 1.2s,实时数据流卡顿。

根因是 Node.js 的单线程模型和 PostgreSQL 的连接池耗尽。docker stats显示umami容器内存稳定在 280MB,但postgres容器的PID数从 12 涨到 89,docker compose exec postgres psql -U umami -c "SELECT count(*) FROM pg_stat_activity;"返回87,远超默认的max_connections=100

解决方案是双管齐下:

1. Umami 层面:增加 Node.js 实例数修改docker-compose.yml,给umami服务加:

deploy: resources: limits: cpus: '1.0' memory: 512M replicas: 2

但这还不够,因为 Umami 的cluster模式需要REDIS_URL。于是加 Redis 服务:

redis: image: redis:7.2-alpine restart: unless-stopped command: redis-server --save 60 1 --loglevel warning volumes: - ./data/redis:/data

然后在.env里加REDIS_URL=redis://redis:6379,并把UMAMI_IMAGE升级到umami/umami:1.39.0(支持 Redis 缓存)。

2. PostgreSQL 层面:优化连接池postgresenvironment里加:

POSTGRES_INITDB_ARGS: "--auth-host=md5 --auth-local=md5"

并在volumes挂载一个自定义postgresql.conf

echo "max_connections = 200" > ./data/postgres/postgresql.conf echo "shared_buffers = 256MB" >> ./data/postgres/postgresql.conf echo "work_mem = 8MB" >> ./data/postgres/postgresql.conf

然后在postgres服务里加:

volumes: - ./data
http://www.jsqmd.com/news/1054507/

相关文章:

  • 2026 上海黄金市场行情复盘 + 靠谱回收平台盘点 - 奢侈品交易观察员
  • 2026年廊坊市PMP培训机构哪家好?官方授权R.E.P.报考指南 - 众智商学院课程中心
  • 终极B站会员购抢票指南:3步轻松搞定限量商品
  • 上海高铁铁路+机场航道居家隔音怎么做?|静华轩隔音窗|隔绝高铁/轨道低频共振、机场低空轰鸣、沿线窗体震动噪音,居家专属隔声定制 - 维小达科技
  • 5大网盘直链解析神器:告别限速的终极解决方案
  • ping的返回的ttl解读的庖丁解牛
  • 2026 年 6 月帝舵官方售后门店资质实地查验报告 覆盖全国 60 + 正规服务点 - 亨得利腕表服务中心
  • 基于MPC5744P的电机控制开发:从硬件架构到FOC算法实战
  • GPT Plus订阅实战指南:身份、支付与服务稳定性四重解构
  • 2026赤峰闲置黄金怎么卖 六家靠谱回收店避坑攻略 - 余生黄金回收
  • 北京正规黄金回收哪家强六家门店优势与避坑要点解析 - 余生黄金回收
  • 2026菏泽黄金回收实测指南:六家门店上门评测 - 余生黄金回收
  • 2026年济宁市CPPM考试最新全攻略:科目题型、通过率、备考重点及官方双认证报考机构推荐 - 众智商学院课程中心
  • 2026南通市圣罗兰+赛琳+巴黎世家包包专业回收,2026甄选回收店铺排行榜推荐 - 谊识预商贸
  • 手机号查询QQ号的终极指南:3分钟找回你的QQ账号
  • 保定黄金回收全攻略 六家正规门店地址与避坑指南 - 余生黄金回收
  • 嵌入式GUI实战:emWin的HEADER与ICONVIEW控件深度解析与应用
  • 小爱音箱音乐解锁终极指南:告别会员限制,实现免费听歌自由
  • 2026 年 6 月帝舵官方维修网点升级优化通知 新版咨询热线同步对外开放 - 亨得利腕表服务中心
  • Windows原生部署LLaMA Factory:不靠WSL的本地大模型微调实战
  • 2026承德黄金回收实测:六家正规门店上门服务横评 - 余生黄金回收
  • 2026年6月深圳龙岗/龙华/坪山本土优质靠谱的短视频运营服务商/公司深度解析 - 猫头鹰AI推广
  • Display Driver Uninstaller完整指南:如何彻底清理显卡驱动残留
  • 2026 年 6 月卡地亚大中华区官方维保体系全面优化,最新门店地址专线完整汇总 - 卡地亚中国服务中心
  • 对象存储与块存储的本质区别:访问粒度、一致性与扩展性
  • 兰州黄金回收哪家靠谱 2026年最新回收价格与本地六家门店实测 - 余生黄金回收
  • 2026萍乡市法穆兰+宝玑手表专业回收,26年精选回收店铺排行榜推荐 - 谊识预商贸
  • 2026深圳靠谱的营业性演出许可证代办公司推荐 - 资讯速览
  • 2026青甘大环线7日游避坑攻略|2-8人小团纯玩出行,排查隐形消费不踩雷 - 纯玩旅游攻略指南
  • 六安黄金回收全攻略六家实体门店横向评测附避坑 - 余生黄金回收