生产环境部署
正式上线的推荐做法:ClickHouse、Redis、evstats 各用一台服务器,evstats 前面放 Nginx 终止 HTTPS。这一页按服务器一台一台讲,每一步都给出要执行的命令和验证方法,照着做就能部署出来。
拓扑与规划
HTTPS 443
浏览器、SDK、业务系统 ──────► evstats 服务器(Nginx + evstats,可以多台)
│ │
8123(内网) 6379(内网)
▼ ▼
ClickHouse 服务器 Redis 服务器| 服务器 | 示例内网地址 | 跑什么 | 对谁开放 | 配置建议 |
|---|---|---|---|---|
| ClickHouse | 10.0.0.11 | ClickHouse 24.8 | 8123 只对 evstats 服务器 | 数据量决定;内存和 SSD 优先 |
| Redis | 10.0.0.12 | Redis 7,打开 AOF | 6379 只对 evstats 服务器 | 留出采集积压的内存 |
| evstats | 10.0.0.21 | Nginx + evstats | 443(和跳转用的 80)对公网 | 2 核 4GB 起步,流量大了加台数 |
为什么分开:ClickHouse 查询和合并会吃满 CPU 与内存,和 evstats 抢资源时采集会变慢;Redis 里有还没入库的事件,单独一台便于打开 AOF 和备份;evstats 本身无状态,前面加负载均衡就能横向扩展。
下文的内网地址、域名 stats.example.com 和口令都是示例,换成你自己的。三台服务器要能通过内网互相访问,都装好 Docker(带 Compose v2)。
先生成两个强口令
一个给 ClickHouse,一个给 Redis,后面会用到:
openssl rand -hex 24示例文件里的口令都写的是 change-me,上线前必须全部换掉。
示例文件都在源码仓库的 deploy/production/ 目录,官网也提供下载,下面每一步都给出下载命令。
1. ClickHouse 服务器
在 ClickHouse 服务器(示例 10.0.0.11)上:
mkdir -p /opt/evstats-clickhouse && cd /opt/evstats-clickhouse
curl -fsSLO https://evstats.mosong.cc/deploy/production/clickhouse/compose.yaml
curl -fsSLO https://evstats.mosong.cc/deploy/production/clickhouse/evstats.xml
# 换成你的 ClickHouse 口令和这台机器的内网地址
sed -i 's/change-me/你的ClickHouse口令/g; s/10\.0\.0\.11/这台机器的内网地址/g' compose.yaml
mkdir -p /data/clickhouse /var/log/clickhouse-server
docker compose up -dcompose.yaml:
# 生产环境 · ClickHouse 服务器(示例内网地址 10.0.0.11,换成这台机器自己的内网地址)
#
# 1. 把两处 change-me 换成同一个强口令;evstats 的 EVS_CLICKHOUSE 用的就是它
# 2. mkdir -p /data/clickhouse /var/log/clickhouse-server && docker compose up -d
# 3. 防火墙只允许 evstats 服务器访问 8123;9000 不需要对外
#
# 8123 只绑在内网地址上,不要映射到公网。数据在 /data/clickhouse,建议放在单独的数据盘上。
services:
clickhouse:
image: clickhouse/clickhouse-server:24.8
container_name: evstats-clickhouse
environment:
CLICKHOUSE_USER: evstats
CLICKHOUSE_PASSWORD: change-me
CLICKHOUSE_DB: evstats
ports:
- "10.0.0.11:8123:8123"
volumes:
- /data/clickhouse:/var/lib/clickhouse
- /var/log/clickhouse-server:/var/log/clickhouse-server
- ./evstats.xml:/etc/clickhouse-server/config.d/evstats.xml:ro
ulimits:
nofile:
soft: 262144
hard: 262144
healthcheck:
test: ["CMD", "clickhouse-client", "--user", "evstats", "--password", "change-me", "--query", "SELECT 1"]
interval: 10s
retries: 30
restart: unless-stoppedevstats.xml 挂到 ClickHouse 的 config.d/,限制内存占用比例与日志级别:
<!-- 给 evstats 专用的 ClickHouse 追加的服务器设置,挂到 /etc/clickhouse-server/config.d/ -->
<clickhouse>
<!-- 查询和后台合并最多用本机内存的 80%,其余留给系统与页缓存 -->
<max_server_memory_usage_to_ram_ratio>0.8</max_server_memory_usage_to_ram_ratio>
<!-- 服务器日志只记警告以上,避免把日志盘写满 -->
<logger>
<level>warning</level>
</logger>
</clickhouse>验证:在 evstats 服务器 上能用口令查询,说明网络和账号都通了。
curl -sS -u 'evstats:你的ClickHouse口令' 'http://10.0.0.11:8123/' --data-binary 'SELECT version()'- 数据在
/data/clickhouse,建议放在单独的数据盘上。 - 8123 只绑在内网地址上。只允许 evstats 服务器访问:优先用云厂商的安全组或内网防火墙;在本机限制时注意 Docker 映射的端口不经过 ufw,要写在
DOCKER-USER链里,例如iptables -I DOCKER-USER -p tcp --dport 8123 ! -s 10.0.0.21 -j DROP(有多台 evstats 时逐台放行)。 - 已经有 ClickHouse 集群时,也可以不用这份 Compose:建一个库和一个有建表、插入、查询、加列、建物化视图权限的账号,给
EVS_CLICKHOUSE用即可。
2. Redis 服务器
在 Redis 服务器(示例 10.0.0.12)上:
mkdir -p /opt/evstats-redis && cd /opt/evstats-redis
curl -fsSLO https://evstats.mosong.cc/deploy/production/redis/compose.yaml
curl -fsSLO https://evstats.mosong.cc/deploy/production/redis/redis.conf
# redis.conf 与 compose.yaml 健康检查里的口令要一致
sed -i 's/change-me/你的Redis口令/g' redis.conf compose.yaml
sed -i 's/10\.0\.0\.12/这台机器的内网地址/g' compose.yaml
chmod 600 redis.conf
mkdir -p /data/redis
docker compose up -dcompose.yaml:
# 生产环境 · Redis 服务器(示例内网地址 10.0.0.12,换成这台机器自己的内网地址)
#
# 1. 把 redis.conf 与下面健康检查里的 change-me 换成同一个强口令
# 2. mkdir -p /data/redis && docker compose up -d
# 3. 防火墙只允许 evstats 服务器访问 6379
#
# evstats 的 EVS_REDIS 写成 redis://:口令@10.0.0.12:6379/0。
services:
redis:
image: redis:7
container_name: evstats-redis
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
ports:
- "10.0.0.12:6379:6379"
volumes:
- /data/redis:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
healthcheck:
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "change-me", "ping"]
interval: 10s
retries: 30
restart: unless-stoppedredis.conf:
# 给 evstats 用的 Redis:采集队列、登录会话、限流、验证码、查询结果缓存。
# 容器里听所有地址;对外只映射到内网地址(见 compose.yaml),再由防火墙限制来源
bind 0.0.0.0
protected-mode yes
port 6379
# 换成强口令,和 compose.yaml 健康检查里的一致
requirepass change-me
# 采集队列里是已经回复 SDK 成功、还没写进 ClickHouse 的事件:打开 AOF,每秒落盘
appendonly yes
appendfsync everysec
# 队列里的事件不能被淘汰:内存用满时拒绝写入,而不是悄悄删掉键。
# 需要限制内存时再打开 maxmemory,并留出 ingest.stream_max(默认 100 万条)积压的空间
maxmemory-policy noeviction
# maxmemory 4gb验证:
docker exec evstats-redis redis-cli --no-auth-warning -a '你的Redis口令' ping # PONG- AOF 必须打开:采集队列里是已经回复 SDK 成功、还没写进 ClickHouse 的事件,Redis 丢了它们就丢了。
- 不要让 Redis 淘汰键(
maxmemory-policy noeviction):内存满时宁可拒绝写入,SDK 会在本地排队重试。 - 6379 同样只允许 evstats 服务器访问,做法同上一步。
3. evstats 服务器
在 evstats 服务器(示例 10.0.0.21)上先起 evstats,它默认听 51321,Compose 只映射到本机:
mkdir -p /opt/evstats && cd /opt/evstats
curl -fsSLO https://evstats.mosong.cc/deploy/production/evstats/compose.yaml
# 填 environment 里 EVS_CLICKHOUSE、EVS_REDIS 的口令和内网地址
sed -i 's/change-me/你的口令/g; s/10\.0\.0\.11/ClickHouse服务器的内网地址/g; s/10\.0\.0\.12/Redis服务器的内网地址/g' compose.yaml
docker compose up -d
curl -sS http://127.0.0.1:51321/health看到 "meta":"up"、"redis":"up"、"setup":"need_admin" 就对了:ClickHouse 和 Redis 已经连上,表也建好了,接下来在网页里创建管理员。
compose.yaml:
# 生产环境 · evstats 服务器(示例内网地址 10.0.0.21;多台时每台一份,只改 hostname)
#
# 1. 改掉 environment 里的 change-me 和 10.0.0.x(同名变量见 evstats.env.example)
# 2. docker compose up -d,curl -s http://127.0.0.1:51321/health 看到 "meta":"up"
# 3. 同一台机器上的 Nginx 终止 HTTPS 并转发到 127.0.0.1:51321(deploy/nginx/evstats.conf)
#
# 进程在容器里对所有网卡开放;下面把 51321 只映射到宿主机 127.0.0.1,由本机 Nginx 转发。
# ClickHouse 与 Redis 写在 EVS_CLICKHOUSE、EVS_REDIS 里;管理员、AI、IP 库在第一次打开网页时填。
# 启动参数 -clickhouse、-redis、-port 仍然可用,写了就覆盖同名环境变量。换端口再加 EVS_PORT。
services:
evstats:
image: ghcr.io/mosongcc/evstats:1
container_name: evstats
# 采集状态页按主机名区分节点:多台时每台不同
hostname: evstats-1
working_dir: /var/lib/evstats
environment:
EVS_CLICKHOUSE: http://evstats:change-me@10.0.0.11:8123/evstats
EVS_REDIS: redis://:change-me@10.0.0.12:6379/0
volumes:
- evstats-data:/var/lib/evstats/data
ports:
- "127.0.0.1:51321:51321"
restart: unless-stopped
healthcheck:
test: ["CMD", "/evstats", "healthcheck"]
interval: 10s
volumes:
evstats-data:同样的变量写在 evstats.env.example 里,二进制部署或想把口令单独放一份时可以复制它。Compose 示例把值写在 environment 里,改这一处即可。
# 推荐用环境变量配置 evstats。复制后改掉 change-me 和内网地址。
# 启动参数 -clickhouse、-redis、-port 仍然可用,写了就覆盖同名环境变量。
# 不写时 ClickHouse 默认 http://evstats:evstats@127.0.0.1:8123/evstats,
# Redis 默认 redis://127.0.0.1:6379/0,端口默认 51321。
# 旧名 EVSTATS_CH、EVSTATS_REDIS、EVSTATS_PORT、EVSTATS_HTTP_ADDR 不读取。
EVS_CLICKHOUSE=http://evstats:change-me@10.0.0.11:8123/evstats
EVS_REDIS=redis://:change-me@10.0.0.12:6379/0
# EVS_PORT=51321evstats 连不上 ClickHouse 或 Redis 时会按设计退出并被 Docker 重启,docker compose logs evstats 能看到是哪一个环境变量或启动参数失败,日志里不会出现口令。
配 Nginx 与 HTTPS
在同一台服务器上装 Nginx,把证书放到 /etc/nginx/certs/(用你的证书服务商或 certbot 申请),再放上这份配置:
apt-get install -y nginx # 其它发行版用对应的包管理器
curl -fsSL -o /etc/nginx/conf.d/evstats.conf https://evstats.mosong.cc/deploy/nginx/evstats.conf
sed -i 's/stats\.example\.com/你的域名/g' /etc/nginx/conf.d/evstats.conf
nginx -t && systemctl reload nginx# evstats 的 HTTPS 反向代理示例。把整个站点(所有路径)转给 evstats,不要只转 /api。
# 这里假设 evstats 与 Nginx 在同一台机器,evstats 默认听 :51321:
# - 容器部署:deploy/production/evstats/compose.yaml(映射 127.0.0.1:51321:51321)
# - 二进制部署:不设 EVS_PORT 即对所有网卡听 51321;用防火墙挡住从外面直接访问这个端口
# 配好之后:
# - 系统配置 → 站点:site.public_url 填 https://stats.example.com(找回密码的邮件链接用它)
# - 系统配置 → 采集调优:http.trusted_proxies 填 127.0.0.1/32,否则访客 IP 都是反代自己
server {
listen 80;
server_name stats.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name stats.example.com;
ssl_certificate /etc/nginx/certs/stats.example.com.crt;
ssl_certificate_key /etc/nginx/certs/stats.example.com.key;
# 采集请求体最大 1MB,回放单块最大 2MB,这里留出余量
client_max_body_size 4m;
# 过程指标只给本机或内网监控
location = /metrics {
allow 127.0.0.1;
deny all;
proxy_pass http://127.0.0.1:51321;
}
location / {
proxy_pass http://127.0.0.1:51321;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Agent 的回答是 SSE 流:关掉缓冲,留足读超时
location /dash/agent/ {
proxy_pass http://127.0.0.1:51321;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 180s;
}
}要点:整个站点都转给 evstats;/dash/agent/ 关掉缓冲,Agent 的回答才会逐字出现;/metrics 只对本机开放。Caddy 的写法见 反向代理与 HTTPS。
验证:
curl -sS https://stats.example.com/health
curl -sS -o /dev/null -w '%{http_code}\n' https://stats.example.com/metrics # 4034. 打开网页完成引导
浏览器打开 https://stats.example.com/:
- 设置超级管理员:页面会说明这是首次访问,用来初始化系统。只填账号、密码和确认密码。
- 选择是否创建演示项目:生产环境一般选「不创建,直接进入」。选了创建,服务端会当场写入约 1 万条示例数据,以后不需要时在「系统 → 项目」里停用,停用后不会再建。
显示名、邮箱、AI 和第一个项目登录后在「我的」和「系统」里填。项目创建时会给出写入密钥,之后管理员输入登录密码还能再看。细节见 首次引导。
5. 引导之后必须填的设置
在「系统 → 系统配置」里:
| 配置项 | 填什么 | 不填会怎样 |
|---|---|---|
site.public_url | https://stats.example.com,不带末尾的 / | 不能找回密码 |
http.trusted_proxies | 127.0.0.1/32(Nginx 与 evstats 同机) | 所有访客的 IP 都是 Nginx 自己,地域全是「未知」,Cookie 不带 Secure |
geo.ip2region.url | IP 库的下载地址,页面上有推荐值 | 没有地域分析 |
smtp.* | 发信服务器 | 不能发找回密码的邮件 |
每一项的含义见 配置项。
6. 接入第一个网站
把项目「接入」页签里的片段放进网页,endpoint 是 https://stats.example.com/sa:
<script src="https://unpkg.com/@evstats/web/dist/evstats.min.js"></script>
<script>evstats.init({ endpoint: "https://stats.example.com/sa", key: "evs_wk_xxx" })</script>打开网页后回到「接入」页签,「最近收到的事件」里出现 $pageview 就接通了。其它接入方式见 接入概览。
上线核对清单
- 所有示例口令
change-me都已经换掉。evstats 的compose.yaml(environment里有口令)和redis.conf权限是 600。 - ClickHouse 的 8123 / 9000、Redis 的 6379 只对 evstats 服务器开放,公网访问不到。
https://stats.example.com/health里meta、redis、各分片都是up,setup是ok。/metrics从公网访问返回 403。- 填好了
site.public_url与http.trusted_proxies,「接入检查」里访客 IP 不是 Nginx 的地址。 - Agent 的回答是逐字出现的(反代没有缓冲 SSE)。
- 安排了备份:ClickHouse 的
/data/clickhouse、Redis 的/data/redis,见 备份与恢复。 - 监控:
/health探活,/metrics接进内网的 Prometheus(可选)。
加第二台 evstats
- 在新服务器上重复第 3 步,
compose.yaml里的hostname改成evstats-2(采集状态页靠主机名区分节点),EVS_CLICKHOUSE、EVS_REDIS与第一台完全相同。 - ClickHouse、Redis 的防火墙放行新服务器。
- 在前面加一层负载均衡(云负载均衡或一台 Nginx,参考源码仓库的
deploy/compose/multi-node/nginx.conf),不需要会话粘滞。 http.trusted_proxies改成负载均衡所在的网段。
登录会话、限流、采集队列都在共用的 Redis 里,请求打到哪一台都一样。更多见 多节点与分片。
日常运维
| 要做的事 | 在哪台 | 命令或位置 |
|---|---|---|
| 看 evstats 日志 | evstats | docker compose logs -f evstats |
| 升级 evstats | 每台 evstats,逐台 | docker compose pull && docker compose up -d,见 升级 |
| 看积压和节点 | 浏览器 | 「系统 → 采集状态」,见 运维与排障 |
| 备份 | ClickHouse、Redis | 数据目录与 AOF,见 备份与恢复 |
| 扩容 ClickHouse | 浏览器 | 「系统 → 分片」追加,见 多节点与分片 |
常见问题
evstats 反复重启:多半是 EVS_CLICKHOUSE 或 EVS_REDIS 写错,或者 ClickHouse、Redis 连不上。在 evstats 服务器上先确认能访问这两台机器的端口和口令,再看 docker compose logs evstats。日志只说是哪一个连不上,不回显口令。
上报返回 413:Nginx 的 client_max_body_size 太小,示例里是 4m。
Agent 的回答等很久才一次出来:反代缓冲了 SSE,/dash/agent/ 要 proxy_buffering off。
访客地域都是「未知」:没配 IP 库,或者 http.trusted_proxies 没填,evstats 拿到的都是 Nginx 的地址。