Appearance
Golang 项目生产环境完整部署方案
适用场景
- 预发布环境 / 灰度验证环境 / 内部测试环境
- 核心需求:需要对外网隐藏,仅允许团队成员或特定 IP 段访问,但不希望在应用代码中实现登录逻辑。
本指南遵循 Linux 文件系统层次规范 (FHS),结合 Systemd、Nginx 与 Cloudflare,提供一套从代码编译到上线的标准化、可复用的部署流程。
1. 项目构建 (本地)
假设您的 Golang 项目名为 myapp,主程序入口输出为 myapp-apiserver。
bash
# 启用 Go 模块代理
go env -w GOPROXY=https://goproxy.cn,direct
# 为 Linux 环境交叉编译
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o myapp-apiserver .CGO_ENABLED=0: 生成静态依赖的可执行文件,避免运行时动态库缺失-ldflags="-s -w": 剥离调试信息,显著减小二进制文件体积- 编译产物:
myapp-apiserver
2. 服务器环境准备 (以 Ubuntu/Debian 为例)
bash
# 更新系统并安装必要工具
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx apache2-utils
# 创建专用服务用户 (安全最佳实践,可选)
# sudo groupadd -r myapp
# sudo useradd -r -g myapp -s /sbin/nologin -M myapp3. 标准化文件部署 (遵循 FHS)
根据 FHS 规范,将文件放置到标准目录:
bash
# 1. 可执行文件 -> /usr/local/bin
sudo cp myapp-apiserver /usr/local/bin/
sudo chmod 755 /usr/local/bin/myapp-apiserver
# 如使用专用用户: sudo chown myapp:myapp /usr/local/bin/myapp-apiserver
# 2. 配置文件 -> /etc/myapp/
sudo mkdir -p /etc/myapp
sudo cp config.yaml /etc/myapp/config.yaml
sudo chown -R root:root /etc/myapp
sudo chmod 755 /etc/myapp
sudo chmod 644 /etc/myapp/config.yaml
# 3. 工作目录与数据持久化 -> /var/lib/myapp/
sudo mkdir -p /var/lib/myapp
sudo chown root:root /var/lib/myapp # 如使用专用用户,则 chown myapp:myapp
# 4. 日志目录 -> /var/log/myapp/
sudo mkdir -p /var/log/myapp
sudo touch /var/log/myapp/apiserver.log
sudo chown root:root /var/log/myapp/apiserver.log4. 创建 Systemd 服务文件
创建 /usr/lib/systemd/system/myapp-apiserver.service:
ini
[Unit]
Description="MyApp API Server"
Requires=network-online.target
After=network-online.target
[Service]
WorkingDirectory=/var/lib/myapp
StandardOutput=append:/var/log/myapp/apiserver.log
StandardError=append:/var/log/myapp/apiserver.log
User=root
Group=root
ExecStart=/usr/local/bin/myapp-apiserver --config-dir /etc/myapp --config-name config.yaml
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGTERM
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Alias=myapp.service操作命令:
bash
# 重载 systemd 并启动服务
sudo systemctl daemon-reload
sudo systemctl enable myapp-apiserver.service # 开机自启
sudo systemctl start myapp-apiserver.service
# 使用别名管理(可选)
sudo systemctl enable myapp.service --now
# 查看状态
sudo systemctl status myapp-apiserver.service
# 优雅重载配置(发送 HUP 信号)
sudo systemctl reload myapp-apiserver.service
# 查看日志(直接查看文件,因为配置了 StandardOutput/Error)
sudo tail -f /var/log/myapp/apiserver.log
# 通过 journal 查看(如果同时需要)
sudo journalctl -u myapp-apiserver -f5. Basic Auth 灰度访问控制
5.1 为什么需要 Basic Auth?(灰度发布场景)
在正式上线前,预发布环境(Staging/Preview)通常需要对外网隐藏,仅限团队成员访问。开启 Nginx auth_basic 有以下优势:
- 零代码侵入:无需在 Go 应用中实现登录逻辑,由 Nginx 层统一拦截
- 快速生效:修改
.htpasswd文件即可增删成员,无需重启服务 - 配合灰度策略:可叠加 IP 白名单,实现灵活的访问控制
5.2 生成密码文件
bash
# 创建密码文件 (首次需 -c)
sudo htpasswd -c /etc/nginx/.htpasswd your_username
# 添加更多用户时去掉 -c
sudo htpasswd /etc/nginx/.htpasswd another_user
# 验证密码文件
sudo cat /etc/nginx/.htpasswd5.3 进阶配置:Basic Auth + IP 白名单(推荐)
如果您希望公司内部 IP 免密码访问,外部访问需要密码,可以使用以下配置:
nginx
location / {
# 满足任一条件即可
satisfy any;
# 允许公司出口 IP 免密码访问
allow 203.0.113.0/24; # 公司办公网段
allow 10.0.0.0/8; # 内网 VPN 网段
deny all; # 拒绝其他所有 IP
# 不在白名单的 IP 需要输入密码
auth_basic "Restricted Access - Internal Preview";
auth_basic_user_file /etc/nginx/.htpasswd;
# ... 其他 proxy_pass 配置
}6. 配置 Nginx 反向代理 + 基础认证
6.0 SSL 证书方案选择
在配置 Nginx HTTPS 之前,您需要选择一种 SSL 证书方案。以下是两种主流免费方案的对比:
| 特性 | Cloudflare Origin CA | Let's Encrypt |
|---|---|---|
| 适用场景 | 域名托管在 Cloudflare | 任意域名(通用) |
| 配置复杂度 | 简单 | 中等 |
| 证书续期 | 15 年有效期,无需续期 | 90 天,自动续期 |
| CDN 加速 | ✅ 需开启 Cloudflare 代理 | ❌ |
| DDoS 防护 | ✅ | ❌ |
| 独立部署 | ❌ 依赖 Cloudflare | ✅ 完全独立 |
选择建议:
- 如果您已在 Cloudflare 托管域名,推荐使用 Cloudflare Origin CA(下文 6.1-6.3 方案)
- 如果您的域名不在 Cloudflare,或希望独立部署,推荐使用 Let's Encrypt(参考 《从零开始部署 Nuxt 应用到 Ubuntu 22.04 服务器》 中的 SSL 配置章节)
以下以 Cloudflare Origin CA 为例进行配置,若使用 Let's Encrypt 可参考上方链接博文。
6.1 准备 Cloudflare Origin CA 证书
从 Cloudflare Dashboard → SSL/TLS → Origin Server 下载证书:
注意
Cloudflare Origin CA 证书是按域区(Zone)签发的,一张证书可以覆盖主域名及所有子域名(如 *.yourdomain.com)。因此证书文件名应使用主域名。
bash
# 放置证书(使用主域名命名)
sudo mkdir -p /etc/nginx/ssl
sudo cp yourdomain.com.pem /etc/nginx/ssl/
sudo cp yourdomain.com.key /etc/nginx/ssl/
sudo chmod 600 /etc/nginx/ssl/yourdomain.com.key6.2 创建 Nginx 站点配置
创建 /etc/nginx/sites-available/api.yourdomain.com.conf:
nginx
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name api.yourdomain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS 配置
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
# 安全响应头
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 全局基础认证 (灰度发布控制)
auth_basic "Restricted Access - Internal Preview";
auth_basic_user_file /etc/nginx/.htpasswd;
# Cloudflare Origin CA 证书(使用主域名证书)
ssl_certificate /etc/nginx/ssl/yourdomain.com.pem;
ssl_certificate_key /etc/nginx/ssl/yourdomain.com.key;
# SSL 优化配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
# 日志
access_log /var/log/nginx/api.yourdomain.com-access.log;
error_log /var/log/nginx/api.yourdomain.com-error.log;
# Cloudflare IP 地址范围(获取真实客户端 IP)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
# 主端点代理
location / {
proxy_pass http://127.0.0.1:2759; # 假设 Go 服务监听此端口
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
proxy_pass http://127.0.0.1:2759;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 健康检查免认证(供负载均衡器探测)
location = /health {
auth_basic off;
proxy_pass http://127.0.0.1:2759;
proxy_set_header Host $host;
}
}6.3 启用站点并重载 Nginx
bash
# 启用站点配置
sudo ln -s /etc/nginx/sites-available/api.yourdomain.com.conf /etc/nginx/sites-enabled/
# 检查语法并重载
sudo nginx -t && sudo systemctl reload nginx7. Cloudflare DNS 与 SSL 模式配置
7.1 添加 DNS A 记录
| 类型 | 名称 | 内容 | 代理状态 |
|---|---|---|---|
| A | api | 您的服务器公网 IP | ✅ 开启(橙色云朵) |
7.2 SSL/TLS 设置
在 Cloudflare SSL/TLS 面板中,将模式设置为 Full (strict)
这要求源站具有有效证书,我们已配置 Origin CA 证书,满足此要求
8. 部署后验证
bash
# 1. 检查 Go 服务状态
sudo systemctl status myapp-apiserver
# 2. 检查 Nginx 代理连通性 (本地)
curl -H "Host: api.yourdomain.com" http://127.0.0.1/health
# 3. 通过 Cloudflare 外网测试 (应要求认证)
curl -I https://api.yourdomain.com
# 4. 带认证访问
curl -I -u your_username:your_password https://api.yourdomain.com
# 5. 验证 HSTS 和安全头
curl -I https://api.yourdomain.com | grep -i "strict-transport-security"
# 6. 验证健康检查免认证
curl -I https://api.yourdomain.com/health9. 常见故障排查
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 访问显示默认 Nginx 页面 | sites-enabled 中有其他 default_server 配置冲突 | 确保删除或移除非必要的 default 配置链接 |
| 502 Bad Gateway | Go 服务未启动或端口不匹配 | sudo systemctl status myapp-apiserver;检查 Nginx proxy_pass 端口 |
| 无认证提示 (直接 200) | auth_basic 未正确继承或被覆盖 | 检查 location 块中是否有 auth_basic off; 或语法错误 |
| 日志中显示客户端 IP 为 Cloudflare 节点 | real_ip_header 配置缺失或顺序错误 | 确认 set_real_ip_from 包含 Cloudflare 所有范围,且 real_ip_header CF-Connecting-IP; 正确 |
证书报错 SSL_ERROR_BAD_CERT_DOMAIN | 证书域名与实际访问域名不匹配 | 确认证书是主域名签发(如 yourdomain.com),可覆盖 *.yourdomain.com |
10. 快速维护命令速查
bash
# 重新编译并替换二进制文件
go build -o myapp-apiserver .
sudo systemctl stop myapp-apiserver
sudo cp myapp-apiserver /usr/local/bin/
sudo systemctl start myapp-apiserver
# 修改配置文件后重载(发送 HUP 信号)
sudo vi /etc/myapp/config.yaml
sudo systemctl reload myapp-apiserver
# 查看实时日志
sudo tail -f /var/log/myapp/apiserver.log
sudo tail -f /var/log/nginx/api.yourdomain.com-access.log
# 更新 Nginx 配置后重载
sudo nginx -t && sudo systemctl reload nginx
# 管理 Basic Auth 用户
sudo htpasswd /etc/nginx/.htpasswd new_user # 添加用户
sudo htpasswd -D /etc/nginx/.htpasswd old_user # 删除用户11. 遵循 FHS 原则的总结
| 文件类型 | 存放路径 | 说明 |
|---|---|---|
| 可执行文件 | /usr/local/bin/myapp-apiserver | 本地编译的程序,不属于发行版仓库 |
| 配置文件 | /etc/myapp/config.yaml | 全局配置目录与文件 |
| 日志文件 | /var/log/myapp/apiserver.log | 应用日志(追加模式) |
| 工作/持久化目录 | /var/lib/myapp/ | 应用状态数据、工作目录 |
| 系统服务 | /usr/lib/systemd/system/myapp-apiserver.service | 系统单元 |
| Web 代理配置 | /etc/nginx/sites-available/ 与 /etc/nginx/sites-enabled/ | Nginx 站点配置 |
快速记忆口诀
二进制找
bin,配置找etc,日志找
var,静态找share,自己编译
local,大厂软件放opt。
这套布局确保了系统的整洁性、可维护性,并与其他 Linux 软件包的行为保持一致。
附录:占位符替换清单
部署时请将以下占位符替换为实际值:
| 占位符 | 示例值 | 说明 |
|---|---|---|
myapp | yourapp | Go 项目名称(目录/服务名前缀) |
myapp-apiserver | yourapp-apiserver | 可执行文件名 |
api.yourdomain.com | api.example.com | API 服务完整域名 |
yourdomain.com | example.com | Cloudflare 证书主域名 |
2759 | 8080 | Go 服务监听端口 |