签入版本
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
# 11.6 Docker 生产部署
|
||||
|
||||
:::tip 概述
|
||||
项目已内置 Docker 化文件:`docker/Dockerfile`(多阶段静态编译)、`docker/docker-compose.yml`(后端 + MySQL + Redis 编排)、`docker/.env.example`(变量模板)、根目录 `.dockerignore`。开发环境快速体验见 [2.7 Docker 快速体验](../quickstart/docker),本节聚焦生产环境的加固与运维要点。
|
||||
:::
|
||||
|
||||
## 基线:内置文件的生产就绪点
|
||||
|
||||
`docker/Dockerfile` 已包含的生产就绪设计(注释同步自源文件):
|
||||
|
||||
```text
|
||||
□ CGO_ENABLED=0 静态编译(sqlite 驱动为 modernc 系,无 CGO 依赖)
|
||||
□ -trimpath -ldflags "-s -w":去除本地路径与调试符号,二进制更小、不泄露构建路径
|
||||
□ 运行时资产四件独立层:manifest/config、manifest/data(ip2region.xdb)、
|
||||
resource(字体/swagger)、templates(生成器模板)——业务代码变动不破坏资产层缓存
|
||||
□ 非 root 运行(USER app,uid 1000)
|
||||
□ tzdata + Asia/Shanghai(日志与 gcron 定时任务依赖系统时区)
|
||||
□ HEALTHCHECK 打 /api/health(公开白名单路径,不带 /v1)
|
||||
□ 镜像内无源码、无密钥(仅二进制 + 资产)
|
||||
```
|
||||
|
||||
`docker/docker-compose.yml` 已包含:
|
||||
|
||||
```text
|
||||
□ 敏感配置全部 GF_ 前缀环境变量注入,镜像内 config.yaml 保持默认
|
||||
(同一镜像可直接部署 dev/prod,无需为环境单独打镜像)
|
||||
□ mysql/redis healthcheck + depends_on condition: service_healthy(后端等依赖就绪再启动)
|
||||
□ upload/log 宿主卷持久化(../upload、../log,相对 compose 文件目录需 ../ 回项目根)
|
||||
□ 定稿 SQL 挂 docker-entrypoint-initdb.d(仅空数据卷导入一次)
|
||||
□ 日志驱动 json-file + max-size 500m + max-file 10 轮转
|
||||
□ restart: always
|
||||
```
|
||||
|
||||
## 生产环境必改项
|
||||
|
||||
```sh
|
||||
cd docker
|
||||
copy .env.example .env
|
||||
```
|
||||
|
||||
```bash
|
||||
# docker/.env —— 生产清单
|
||||
# JWT_SECRET 生成(≥32 字节密码学随机,各环境独立、勿复用):
|
||||
# Linux: openssl rand -hex 32
|
||||
# Windows: [Convert]::ToBase64String([Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
|
||||
# 轮换此值后所有已签发令牌立即失效,用户需重新登录(安排发版窗口执行)
|
||||
JWT_SECRET=<生成的随机串> # 令牌签名密钥,生产必改
|
||||
MYSQL_PASSWORD=<强密码> # MySQL 密码
|
||||
REDIS_PASSWORD=<强密码> # Redis 密码
|
||||
|
||||
# 连云数据库/Redis(RDS 等)时同时改主机与端口,compose 自带的 mysql/redis
|
||||
# 服务可保留(不依赖即可)或整段删除后去掉后端的 depends_on
|
||||
MYSQL_HOST=rm-xxx.mysql.rds.aliyuncs.com
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USER=<云库账号>
|
||||
REDIS_HOST=r-xxx.redis.rds.aliyuncs.com
|
||||
REDIS_PORT=6379
|
||||
# 容器内 127.0.0.1 指向容器自身,连宿主机请用 host.docker.internal
|
||||
```
|
||||
|
||||
镜像内 `manifest/config/config.yaml` 另有两处默认值偏开发,compose 已用环境变量覆盖,若用 `docker run` 部署需自行带上:
|
||||
|
||||
```sh
|
||||
# 生产覆盖(compose 已内置,docker run 场景勿遗漏)
|
||||
GF_CAPTCHA_BYPASSCODE="" # 清空万能验证码(默认 618618 仅开发用)
|
||||
GF_APP_ERRORMSG="false" # 关闭响应中的详细错误堆栈
|
||||
```
|
||||
|
||||
::: warning 定稿 SQL 与数据卷
|
||||
initdb.d 挂载**仅在 MySQL 数据卷为空时执行一次**。后续表结构变更需手动执行 SQL 或 `docker volume rm` 清卷重建——**生产环境严禁清卷**,变更走 [11.3 数据库迁移](./db-migrate) 的手工变更流程。
|
||||
:::
|
||||
|
||||
## 云服务替代(推荐拓扑)
|
||||
|
||||
生产推荐 MySQL/Redis 用云服务,compose 只编排后端:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
easygoadmin:
|
||||
image: easygoadmin:latest # 或私有仓库地址
|
||||
restart: unless-stopped # 比 always 更适合手工维护场景
|
||||
ports: ["8000:8000"]
|
||||
environment:
|
||||
GF_DATABASE_DEFAULT_LINK: "mysql:user:pass@tcp(rm-xxx.mysql.rds.aliyuncs.com:3306)/easygoadmin?charset=utf8mb4&parseTime=true&loc=Local"
|
||||
GF_REDIS_DEFAULT_ADDRESS: "r-xxx.redis.rds.aliyuncs.com:6379"
|
||||
GF_REDIS_DEFAULT_PASS: "<云 Redis 密码>"
|
||||
GF_FILE_UPLOADDIR: "/data/upload"
|
||||
GF_JWT_SECRET: "${JWT_SECRET}"
|
||||
GF_CAPTCHA_BYPASSCODE: ""
|
||||
GF_APP_ERRORMSG: "false"
|
||||
volumes:
|
||||
- /data/easygoadmin/upload:/data/upload # 云盘/NFS 绝对路径
|
||||
mem_limit: 1g # 防 OOM 拖垮宿主机
|
||||
logging:
|
||||
driver: json-file
|
||||
options: { max-size: "50m", max-file: "3" }
|
||||
```
|
||||
|
||||
## 前端 Nginx 托管与反代
|
||||
|
||||
`docker/` 只覆盖后端;前端 `ui/` 独立构建后由 Nginx 托管:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# 前端路由回退
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# 接口与静态上传反代后端
|
||||
location /api/ { proxy_pass http://backend:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
|
||||
location /upload/ { proxy_pass http://backend:8000; }
|
||||
location /api.json { proxy_pass http://backend:8000; }
|
||||
}
|
||||
```
|
||||
|
||||
::: tip 受信代理与真实 IP(CDN / Nginx / Docker)
|
||||
后端取真实客户端 IP 的规则:**TCP 直连方**在 `api.proxyIps`(支持 CIDR)内才信任 `X-Forwarded-For` 首段 / `X-Real-IP`,否则直接记录直连方 IP。三层要点:
|
||||
|
||||
1. **Docker 端口映射模式下,容器内 TCP 对端恒为网桥网关**(如 172.19.0.1)——CDN/宿主机 Nginx 的回源经 NAT 后都是它。配置默认已含 `172.16.0.0/12`(Docker 网桥默认网段);自定义 docker 网桥子网时按实际网段补充(生产用 `GF_API_PROXYIPS` 覆盖)。
|
||||
2. **CDN 侧**须在回源请求中携带 `X-Forwarded-For: <真实客户端IP>`,且采用**覆盖**而非追加模式——若客户端自带伪造 XFF 被保留在首段,取首段会误判。
|
||||
3. **宿主机 Nginx 反代**场景:Nginx 配 `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for`,并把 Nginx 网段加入 `GF_API_PROXYIPS`。
|
||||
|
||||
症状对照:登录/操作日志记成 172.19.0.1 → 第 1 层网段缺失;记成 CDN 回源 IP → 第 2/3 层 XFF 未传递。**切勿配置 0.0.0.0/0**(等于允许任意来源伪造 XFF)。
|
||||
:::
|
||||
|
||||
::: tip 公开路径勿加 /v1
|
||||
`/api/login`、`/api/register`、`/api/captcha`、`/api/oauth2/token`、`/api/health` 为网关免版本路径,反代与白名单三处须一致,Nginx 无需特殊处理(`location /api/` 前缀已覆盖)。
|
||||
:::
|
||||
|
||||
## 数据持久化
|
||||
|
||||
| 数据 | 容器内位置 | 持久化方式 |
|
||||
|------|-----------|-----------|
|
||||
| 上传文件 | /data/upload | 宿主目录卷 / NFS / 对象存储改造(多实例必须共享) |
|
||||
| 运行日志 | /opt/easygoadmin/log + stdout | stdout 走容器日志驱动轮转;文件卷可选 |
|
||||
| ip2region.xdb | manifest/data | 随镜像(只读),升级镜像即更新 |
|
||||
| 数据库 | MySQL 容器 / 云 RDS | 云优先,定时备份见 [11.9 备份与恢复](./backup) |
|
||||
| Redis | Redis 容器 / 云 Redis | 缓存可丢;JWT 黑名单与在线会话会重置(用户需重新登录) |
|
||||
|
||||
## 零停机发布
|
||||
|
||||
```text
|
||||
单实例:docker compose up -d --build(滚动间隙数秒)
|
||||
多实例:
|
||||
1. 起新容器(新版本)→ HEALTHCHECK 通过
|
||||
2. 摘旧容器流量(Nginx upstream / compose scale 滚动)
|
||||
3. 停旧容器
|
||||
约束:
|
||||
- 定时任务经 utility/cache 的 job:lock:{id} 执行锁(Redis SET NX),多实例不双跑
|
||||
- 上传目录必须共享卷,否则文件访问 404
|
||||
```
|
||||
|
||||
## k8s 要点
|
||||
|
||||
```text
|
||||
Deployment(replicas≥2)+ Service + Ingress(HTTPS)
|
||||
ConfigMap 挂配置 / Secret 挂密钥(GF_ 覆盖,勿改镜像内 config.yaml)
|
||||
PVC 挂 /data/upload(RWX 或对象存储改造)
|
||||
liveness/readinessProbe → /api/health(Dockerfile 已内置 HEALTHCHECK,探针复用同路径)
|
||||
HPA 按 CPU/内存(会话状态在 Redis,无状态可水平扩)
|
||||
```
|
||||
|
||||
## 镜像运维
|
||||
|
||||
```text
|
||||
□ 定期重建(基础镜像 CVE 修复)
|
||||
□ 私有仓库 + 拉取审计;镜像打版本 tag,避免仅用 latest
|
||||
□ 构建在项目根执行:docker build -f docker/Dockerfile -t easygoadmin:<tag> .
|
||||
□ .dockerignore 已排除 ui/bin/log/upload/document/hack/.env,镜像不含源码与密钥
|
||||
□ 容器内不使用代码生成器(产物写容器文件系统,重建即丢)
|
||||
```
|
||||
Reference in New Issue
Block a user