1. 项目概述:为什么一个Go Web服务需要Docker + Nginx组合部署在Ubuntu 18.04上
你手头刚写完一个用Go语言编写的Web API服务——可能是用户认证模块、订单查询接口,或者一个轻量级的内部管理后台。它用net/http或gin/echo框架跑得飞快,本地go run main.go一切正常,curl http://localhost:8080/health返回200。但当你准备把它扔到生产服务器上时,问题就来了:怎么让这个二进制程序长期稳定运行?怎么保证它崩溃后自动重启?怎么让它监听80端口而不必sudo?怎么同时部署多个不同版本的服务?怎么隔离它和系统其他进程的依赖冲突?怎么快速复制到另一台机器上?这些问题单靠nohup ./app &或systemd服务文件已经不够用了——它们解决的是“启动”,不是“交付”和“运维”。
这就是标题里这个技术组合的真实价值所在:Go提供极致简洁的单二进制交付能力,Docker提供环境一致性与进程隔离边界,Nginx承担流量入口、协议转换与安全兜底职责,而Ubuntu 18.04则是当时企业级服务器最广泛采用的LTS发行版之一,具备成熟稳定的内核、长期安全更新支持和丰富的社区文档支撑。这不是炫技,而是经过大量线上项目验证的最小可行生产部署链路。我经手过的37个Go后端项目中,有29个在第一期上线时就采用了这套组合——不是因为“它很新”,而是因为它把“开发完成”到“用户可用”之间的鸿沟填得足够平滑。它不追求Kubernetes那种复杂度,也不妥协于裸机部署的脆弱性,而是在可控成本下,把可靠性、可复现性和可维护性三个关键指标拉到了一个务实的平衡点。如果你正在为团队搭建第一个Go生产环境,或者正被同事追问“为什么我们的服务总在凌晨三点挂掉”,那么这篇内容就是为你写的——它不讲抽象概念,只讲你在终端里敲下的每一行命令背后,到底发生了什么。
2. 整体架构设计与技术选型逻辑拆解
2.1 为什么是Go?而不是Node.js、Python或Java?
Go语言在这个场景里不是“选择”,而是“自然结果”。它的编译产物是一个静态链接的单二进制文件,不依赖系统glibc版本,不依赖JVM或Node运行时。这意味着你打包好的myapi文件,在Ubuntu 18.04上能跑,在CentOS 7上也能跑,在Alpine Linux这种极简镜像里照样能跑。我曾经把一个Go服务从Ubuntu 18.04交叉编译成Windows可执行文件,发给测试同事双击就能跑通基础功能——这种交付自由度,是其他主流语言难以企及的。更重要的是,Go的并发模型(goroutine + channel)让高并发HTTP服务天然轻量。一个处理JSON API的Go进程,内存常驻通常只有15–30MB,而同等功能的Spring Boot应用动辄300MB+堆内存。在容器资源受限的环境下,这直接决定了单台服务器能承载多少个服务实例。所以,Go在这里的核心价值不是语法多酷,而是交付确定性和资源效率。你不需要教运维同事“先装Python 3.8再装pip再装uvicorn”,你只需要说:“把这个文件放进容器,chmod +x,然后./myapi就行。”
2.2 为什么必须用Docker?裸机部署不行吗?
可以,但代价极高。想象一下:你手动在Ubuntu 18.04上安装Go 1.16(注意,不是系统自带的1.10),配置GOROOT和GOPATH,go mod download拉取所有依赖,go build -o myapi .编译,再写一个systemd服务文件,设置Restart=always,最后还要配置ulimit防止文件描述符耗尽……这一套流程,你做一次没问题;但当你需要部署到5台测试机、3台预发机、6台生产机,并且下周还要升级Go版本、更换日志库、调整超时参数时,就会发现:每一次变更,都是一次手工操作的雪崩风险。而Docker把整个运行时环境(包括Go编译器、依赖包、甚至最终二进制)全部打包进一个不可变的镜像里。docker build命令执行完,你就得到了一个带版本号的、可校验的、可回滚的部署单元。我见过最惨的案例是:某团队用Ansible脚本部署Python服务,因某台机器pip install时网络抖动,漏装了一个requests的子依赖,导致服务上线后部分请求500,排查了整整两天。Docker镜像则彻底规避了这类“环境漂移”问题——镜像ID相同,内容就绝对一致。它不是银弹,但它把“部署”这件事,从一门手艺,变成了一条流水线。
2.3 为什么Nginx不能省?Go自己监听80端口不行吗?
技术上当然可以。http.ListenAndServe(":80", handler)确实能让Go直接绑定80端口。但生产环境里,这是典型的“新手陷阱”。原因有三:第一,权限问题。Linux下绑定1024以下端口需要root权限,而你绝不会让一个业务应用以root身份长期运行——一旦Go代码里有任意一处内存越界或反序列化漏洞,攻击者就能直接拿到root shell。第二,功能缺失。Nginx提供了Go原生HTTP库不具备的关键能力:SSL/TLS终止(HTTPS卸载)、请求限流(防止突发流量打垮后端)、静态文件服务(前端HTML/CSS/JS)、URL重写、跨域头注入、真实客户端IP透传(X-Forwarded-For)、以及最重要的——反向代理健康检查与自动故障转移。第三,安全兜底。Nginx作为成熟的C语言项目,经过数十年互联网流量锤炼,其HTTP解析器对畸形请求、慢速攻击、缓冲区溢出等的防护,远超任何Go HTTP框架的默认配置。我们曾用ab -n 100000 -c 1000压测一个未加Nginx的Go服务,结果服务在第87234次请求时因http.MaxHeaderBytes超限而panic;加上Nginx后,同样的压测,Nginx在应用层就拦截了所有超长header请求,Go服务稳如泰山。所以,Nginx在这里的角色,不是“可有可无的网关”,而是生产级流量的守门人和减压阀。
2.4 为什么锁定Ubuntu 18.04?而不是更新的20.04或22.04?
这是一个务实的选择,而非技术偏好。Ubuntu 18.04 LTS(Bionic Beaver)于2018年4月发布,官方标准支持周期至2023年4月,扩展安全维护(ESM)可延续至2028年。这意味着在2021–2023年这个时间段,它是绝大多数金融、政务、传统企业IT部门的“默认操作系统”——采购审批流程走完,服务器上架,系统镜像早已固化为18.04。它的内核版本(4.15)足够新,支持Docker所需的cgroups v1和overlay2存储驱动;它的systemd版本(237)稳定可靠,能完美管理Docker daemon;它的apt源生态极其成熟,nginx、docker-ce、golang-go等关键包都有官方仓库的长期维护版本。相比之下,Ubuntu 20.04虽然更新,但当时(2021年前后)其docker-ce在某些云厂商的定制内核上偶有兼容性问题;而22.04则因cgroups v2默认启用,曾导致一批老旧监控Agent无法采集指标。选择18.04,本质上是在“技术先进性”和“企业落地确定性”之间做的权衡。它不是一个技术落后的标志,而是一种对现实约束的尊重——就像你不会在银行核心系统里强行上马Rust,不是因为Rust不好,而是因为现有运维体系、审计要求、人员技能栈都锚定在更成熟的工具链上。
3. 核心组件准备与环境初始化实操
3.1 Ubuntu 18.04基础系统加固与依赖清理
在任何安装操作前,先确保系统处于干净、安全、可预测的状态。这不是形式主义,而是避免后续出现“明明教程能跑,我的机器就是不行”的根源排查。登录到你的Ubuntu 18.04服务器(推荐使用SSH密钥登录,禁用密码),执行以下标准化初始化:
# 更新系统并安装基础工具(-y参数自动确认,生产环境建议先看apt list --upgradable) sudo apt update && sudo apt upgrade -y sudo apt install -y curl wget gnupg2 software-properties-common ca-certificates # 清理可能干扰的旧Docker包(Ubuntu官方源里的docker.io版本太老,必须卸载) sudo apt remove -y docker docker-engine docker.io containerd runc # 确保时间同步(NTP是分布式系统基石,时间不同步会导致JWT token校验失败、日志时间错乱) sudo timedatectl set-ntp on sudo systemctl restart systemd-timesyncd提示:
timedatectl status命令能立刻看到当前时间同步状态。如果显示System clock synchronized: no,说明NTP未生效,需检查防火墙是否放行UDP 123端口,或更换NTP服务器(如sudo timedatectl set-ntp false && sudo ntpdate -s time.windows.com)。
接着,禁用不必要的服务以减少攻击面。Ubuntu 18.04默认会启动ufw(防火墙)和apache2(如果误装过),这些都应关闭:
# 检查并停止Apache(很多新手教程会顺手装nginx和apache,造成端口冲突) sudo systemctl is-active apache2 && sudo systemctl stop apache2 && sudo systemctl disable apache2 # 配置UFW,只开放必要端口(假设你的API服务监听80/443,SSH用22,Docker远程API不用开) sudo ufw allow OpenSSH sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable注意:
ufw status verbose会显示当前规则详情。务必确认Status: active且80/tcp状态为ALLOW IN。我踩过的坑是:某次在阿里云ECS上配置UFW后,忘记在云控制台的安全组里也放行80端口,结果从外网curl一直超时,折腾半小时才发现是云厂商的双重防火墙机制。
3.2 Go语言环境安装:从源码编译还是二进制包?
Ubuntu 18.04官方源(apt list golang-go)提供的Go版本是1.10,而当前(2021年)主流Go Web框架(如Gin v1.8+)最低要求Go 1.16。因此,必须手动安装新版Go。这里有两个路径:A)下载官方预编译二进制包(推荐);B)从源码编译(仅当有特殊定制需求时)。我们选A,因为更快、更稳、更符合生产环境“确定性”原则。
# 下载Go 1.16.15(LTS版本,2021年最稳定)的Linux AMD64二进制包 cd /tmp curl -O https://dl.google.com/go/go1.16.15.linux-amd64.tar.gz # 校验SHA256(关键!防止下载包被篡改,官方发布页有对应checksum) echo "b6a9914e5f3e7e5a1a0f1b0e1c2d3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f go1.16.15.linux-amd64.tar.gz" | sha256sum -c # 解压到/usr/local(标准Unix路径,无需修改PATH) sudo tar -C /usr/local -xzf go1.16.15.linux-amd64.tar.gz # 创建软链接方便未来升级(不推荐直接改/usr/local/go指向) sudo ln -sf /usr/local/go /usr/local/golang-current现在配置环境变量。编辑/etc/profile.d/golang.sh(系统级,所有用户生效):
echo 'export GOROOT=/usr/local/golang-current' | sudo tee /etc/profile.d/golang.sh echo 'export GOPATH=$HOME/go' | sudo tee -a /etc/profile.d/golang.sh echo 'export PATH=$GOROOT/bin:$GOPATH/bin:$PATH' | sudo tee -a /etc/profile.d/golang.sh source /etc/profile.d/golang.sh验证安装:
go version # 应输出 go version go1.16.15 linux/amd64 go env GOROOT GOPATH # 确认路径正确实操心得:不要把Go安装到
$HOME目录下。我曾在一个客户环境看到,运维将Go装在/home/admin/go,结果当admin用户被删除后,整个Go环境就消失了。/usr/local是Unix标准的“本地管理员安装软件”路径,受/etc/skel模板保护,最稳妥。
3.3 Docker CE安装:绕过APT源的稳定性陷阱
Ubuntu 18.04的apt源里没有docker-ce(社区版),只有过时的docker.io。我们必须添加Docker官方GPG密钥和仓库。但这里有个大坑:Docker官方仓库的stable分支在2021年已废弃,必须指定edge或test,而生产环境应选stable的替代方案——即使用docker-ce的18.09或20.10长期支持版本。我们选20.10.17(2021年最稳定的LTS版本):
# 添加Docker官方GPG密钥(必须!否则apt会报GPG error) curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # 添加仓库(注意:ubuntu版本代号必须是bionic,不是18.04) echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" | sudo tee /etc/apt/sources.list.d/docker.list # 更新并安装指定版本的docker-ce(避免apt install docker-ce自动装最新不稳定版) sudo apt update sudo apt install -y docker-ce=5:20.10.17~3-0~ubuntu-bionic docker-ce-cli=5:20.10.17~3-0~ubuntu-bionic containerd.io # 启动并设为开机自启 sudo systemctl enable docker sudo systemctl start docker # 将当前用户加入docker组(避免每次docker命令都sudo) sudo usermod -aG docker $USER # 注意:执行完此命令需退出SSH重新登录,或执行 `newgrp docker` 刷新组权限验证Docker:
docker --version # 应输出 Docker version 20.10.17, build 100c701 docker run hello-world # 第一次运行会下载hello-world镜像并打印欢迎信息常见问题:如果
docker run hello-world报错Cannot connect to the Docker daemon at unix:///var/run/docker.sock,说明docker服务没起来,或当前用户没加入docker组。执行sudo systemctl status docker看服务状态,id -nG看用户组列表。别跳过newgrp docker或重新登录这一步,这是Linux组权限刷新的硬性要求。
3.4 Nginx安装与最小化配置
Ubuntu 18.04源里的nginx版本是1.14.0,完全满足反向代理需求,无需升级。安装命令极简:
sudo apt install -y nginx sudo systemctl enable nginx sudo systemctl start nginx此时访问服务器IP,应看到Nginx默认欢迎页。但默认配置是为静态网站设计的,我们需要将其改造为Go应用的反向代理入口。编辑主配置文件/etc/nginx/sites-available/default:
sudo nano /etc/nginx/sites-available/default将原有server块全部替换为以下内容(这是生产环境最小可行配置):
server { listen 80; server_name _; # 匹配任意域名,适合内部服务或IP直连 # 日志格式优化:记录真实客户端IP(X-Forwarded-For)和上游响应时间 log_format upstreamlog '[$time_local] $remote_addr - $remote_user $request ' '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' '$request_time $upstream_response_time $upstream_addr'; access_log /var/log/nginx/access.log upstreamlog; error_log /var/log/nginx/error.log warn; # 安全头:防止点击劫持、MIME类型嗅探、XSS add_header X-Frame-Options "DENY" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; # 反向代理核心配置 location / { proxy_pass http://127.0.0.1:8080; # Go服务监听地址 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_connect_timeout 10s; proxy_send_timeout 30s; proxy_read_timeout 30s; # 缓冲区调优:适应Go JSON API的小包特性 proxy_buffering on; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; } # 健康检查端点(供Nginx自身或外部监控使用) location /healthz { return 200 'OK'; add_header Content-Type text/plain; } }保存后,测试配置语法并重载:
sudo nginx -t # 必须成功,否则reload会失败 sudo systemctl reload nginx关键参数解释:
proxy_set_header X-Real-IP $remote_addr确保Go应用拿到的是真实用户IP,而不是Nginx的127.0.0.1;proxy_read_timeout 30s防止Go服务处理慢请求时Nginx过早断连;add_header系列是基础Web安全加固,哪怕你的Go服务不做任何安全处理,Nginx这层也能挡住大部分初级攻击。我在线上环境强制要求所有Nginx配置必须包含这5个安全头,审计时直接扣分。
4. Go Web应用构建、Docker化与Nginx联动部署全流程
4.1 构建一个最小可运行的Go Web服务
我们不从零写一个复杂项目,而是用net/http构建一个极简但具备生产特征的API。创建项目目录:
mkdir -p ~/go/src/myapi && cd ~/go/src/myapi编写main.go(包含健康检查、请求日志、错误处理):
package main import ( "fmt" "log" "net/http" "time" ) // 自定义日志中间件,记录请求方法、路径、状态码、耗时 func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() log.Printf("Started %s %s", r.Method, r.URL.Path) // 包装ResponseWriter以捕获状态码 lw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK} next.ServeHTTP(lw, r) duration := time.Since(start) log.Printf("Completed %s %s in %v with status %d", r.Method, r.URL.Path, duration, lw.statusCode) }) } // 包装ResponseWriter以获取状态码 type responseWriter struct { http.ResponseWriter statusCode int } func (rw *responseWriter) WriteHeader(code int) { rw.statusCode = code rw.ResponseWriter.WriteHeader(code) } func main() { // 注册路由 http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") fmt.Fprint(w, "OK") }) http.HandleFunc("/api/v1/users", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, `{"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}`) }) // 启用日志中间件 http.Handle("/", loggingMiddleware(http.DefaultServeMux)) // 监听8080端口(与Nginx proxy_pass保持一致) log.Println("Starting server on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }测试本地运行:
go mod init myapi go mod tidy go run main.go # 在另一终端 curl http://localhost:8080/healthz 应返回 OK注意:
go mod init必须在项目根目录执行,模块名myapi将用于后续Docker构建中的go build路径。go mod tidy会自动下载依赖并生成go.sum校验文件,这是Docker多阶段构建可重现性的基础。
4.2 Docker多阶段构建:从源码到精简镜像
Dockerfile是整个部署链路的“蓝图”。我们采用多阶段构建(Multi-stage Build),这是Go项目的黄金标准——第一阶段用完整Go环境编译,第二阶段用极小的Alpine镜像运行,最终镜像大小可压缩到15MB以内(对比Ubuntu基础镜像的100MB+)。
在项目根目录创建Dockerfile:
# 构建阶段:使用golang:1.16-alpine作为编译环境 FROM golang:1.16-alpine AS builder # 设置工作目录 WORKDIR /app # 复制go.mod和go.sum(利用Docker layer cache加速) COPY go.mod go.sum ./ RUN go mod download # 复制源码并编译(-ldflags "-s -w" 去除调试符号,减小体积) COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapi . # 运行阶段:使用alpine:latest作为运行时环境(无Go编译器,仅运行二进制) FROM alpine:latest # 创建非root用户(安全最佳实践) RUN addgroup -g 1001 -f appgroup && adduser -S appuser -u 1001 # 复制上一阶段编译好的二进制 WORKDIR /root/ COPY --from=builder /app/myapi . # 更改所有权给非root用户 RUN chown -R appuser:appgroup /root USER appuser # 暴露端口(仅声明,实际由go listen决定) EXPOSE 8080 # 启动命令 CMD ["./myapi"]构建镜像并打标签:
# 构建(注意最后的 . 表示上下文是当前目录) docker build -t myapi:v1.0.0 . # 查看镜像大小(对比:单阶段构建通常>300MB,多阶段<20MB) docker images myapi原理解析:
CGO_ENABLED=0禁用cgo,使Go编译为纯静态二进制,不依赖系统glibc;GOOS=linux确保编译目标为Linux;-ldflags "-s -w"剥离符号表和调试信息,体积减少30%以上。adduser -S创建的系统用户UID为1001,符合Kubernetes等平台的安全策略(禁止UID 0)。我曾用docker history myapi:v1.0.0分析镜像层,确认最终镜像只有3层:基础Alpine、二进制文件、启动命令,没有任何Go源码或缓存残留。
4.3 启动容器并验证与Nginx的连通性
现在,我们让Docker容器运行Go服务,并确保Nginx能正确代理:
# 启动容器,映射8080端口(--rm表示退出后自动清理容器,便于调试) docker run -d --name myapi-app --rm -p 127.0.0.1:8080:8080 myapi:v1.0.0 # 检查容器是否运行 docker ps -f name=myapi-app # 本地curl测试Go服务直连(应返回OK) curl http://localhost:8080/healthz # 通过Nginx代理测试(此时Nginx配置的proxy_pass指向127.0.0.1:8080) curl http://localhost/healthz如果最后一条命令返回OK,说明Nginx到Go容器的链路已通。但这是本地测试,真正的生产部署需要让容器与Nginx在同一网络,且不暴露8080端口给外网。因此,我们改用Docker用户自定义网络:
# 创建专用网络(bridge模式,名称myapi-net) docker network create myapi-net # 重新运行容器,加入该网络,并取消端口映射(-p参数去掉) docker run -d --name myapi-app --rm --network myapi-net myapi:v1.0.0 # 此时容器只能通过网络名通信,Nginx需配置为proxy_pass http://myapi-app:8080 # 但Nginx在宿主机上,不在Docker网络里,所以不能直接用myapi-app # 解决方案:让Nginx也运行在Docker中,或改用host网络(简单起见,我们用host网络)实操选择:对于Ubuntu 18.04上的单机部署,最简单可靠的方案是让Go容器使用
--network host,这样它直接共享宿主机网络命名空间,127.0.0.1:8080对Nginx完全可见,无需额外网络配置。修改启动命令:docker run -d --name myapi-app --rm --network host myapi:v1.0.0这比折腾Docker网络或Nginx容器化更符合“最小可行”原则。我在线上20+个项目中,90%都采用此模式,稳定运行超2年无网络相关故障。
4.4 Nginx配置增强:支持HTTPS、负载均衡与日志分析
生产环境不可能只有HTTP。我们为Nginx添加HTTPS支持(使用Let's Encrypt免费证书)和简单的负载均衡(为未来水平扩展铺路)。
首先,安装Certbot获取SSL证书:
sudo apt install -y python3-certbot-nginx sudo certbot --nginx -d your-domain.com # 替换为你的域名 # Certbot会自动修改Nginx配置,添加443 server块和证书路径然后,增强/etc/nginx/sites-available/default,添加HTTPS和负载均衡:
# HTTP重定向到HTTPS server { listen 80; server_name your-domain.com; return 301 https://$server_name$request_uri; } # HTTPS server块(Certbot已生成,此处补充负载均衡) upstream go_backend { server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; # 可在此添加更多后端,如 server 127.0.0.1:8081; 实现蓝绿部署 } server { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # 其他配置同上,但proxy_pass指向upstream location / { proxy_pass http://go_backend; # ... 其他proxy_*配置保持不变 } }重载Nginx:
sudo nginx -t && sudo systemctl reload nginx日志分析技巧:Nginx的
upstream_response_time字段是诊断Go服务性能的金矿。在/var/log/nginx/access.log中,一行日志如:[21/Jul/2021:14:23:45 +0000] 192.168.1.100 - - GET /api/v1/users HTTP/1.1 200 128 "-" "curl/7.68.0" 0.023 0.021 127.0.0.1:8080最后的0.023是Nginx处理总耗时,0.021是Go后端响应耗时。如果前者很大(如1.234)而后者很小(0.021),说明瓶颈在Nginx(如SSL握手慢);如果两者接近且都大,说明Go服务本身慢。我用awk '{print $15}' /var/log/nginx/access.log | sort -n | tail -10快速找出最慢的10次请求,再结合Go日志定位具体代码行。
5. 生产就绪检查清单与高频问题实战排障
5.1 上线前10项必检清单
| 检查项 | 检查命令/方法 | 不通过后果 | 我的实操备注 |
|---|---|---|---|
| 1. Go服务监听地址 | ss -tlnp | grep :8080 | Go未启动或端口被占,Nginx 502 | 确保ListenAndServe(":8080"),不是":8080"带空格 |
| 2. Docker容器状态 | docker ps -f name=myapi-app | 容器崩溃退出,Nginx 502 | 查docker logs myapi-app看panic日志 |
| 3. Nginx配置语法 | sudo nginx -t | 配置错误,reload失败,服务中断 | 每次改配置必执行,养成肌肉记忆 |
| 4. UFW端口放行 | sudo ufw status | grep 80 | 外网无法访问,以为服务挂了 | 确认80/tcp和443/tcp均为ALLOW IN |
| 5. SSL证书有效期 | sudo certbot certificates | HTTPS证书过期,浏览器警告 | Certbot自动续期,但需sudo systemctl list-timers | grep certbot确认定时任务启用 |
| 6. Go二进制权限 | ls -l /app/myapi(容器内) | Permission denied,容器启动失败 | 多阶段构建中COPY后需RUN chmod +x myapi,或确保go build输出可执行 |
| 7. 系统文件描述符限制 | cat /proc/$(pidof nginx)/limits | grep "Max open files" | 高并发时Nginx报Too many open files | sudo systemctl edit nginx添加LimitNOFILE=65536 |
| 8. Docker存储驱动 | docker info | grep "Storage Driver" | overlay2未启用,镜像拉取慢或失败 | Ubuntu 18.04默认是overlay2,sudo ls /var/lib/docker/overlay2应存在 |
| 9. Go应用日志路径 | docker logs myapi-app | tail -5 | 无法排查业务逻辑错误 | Go代码中log.SetOutput()应指向os.Stdout,Docker才能捕获 |
| 10. Nginx错误日志级别 | grep "error_log" /etc/nginx/nginx.conf | error.log级别为warn,错过info级错误 | 生产环境建议设为error_log /var/log/nginx/error.log error; |
提示:这份清单不是一次性检查表,而是应该写入CI/CD流水线的自动化脚本。例如,用
bash脚本封装所有检查项,exit 1表示失败,集成到部署后钩子中。我负责的一个支付网关项目,就将此清单转化为Ansible Playbook,每次部署后自动执行,5分钟内给出“Ready for Traffic”或具体失败项报告。
5.2 高频问题与秒级定位法
问题1:Nginx返回502 Bad Gateway
这是最常见问题,90%源于Go服务未就绪。秒级定位法:
curl -v http://localhost:8080/healthz—— 直连Go服务,看是否通。- 如果不通:
docker logs myapi-app看Go启动日志,常见listen tcp :8080: bind: address already in use(端口冲突)或panic: failed to connect to db(数据库未启动)。 - 如果通:
curl -v http://localhost/healthz—— 测试Nginx代理,不通则sudo nginx -t检查配置,或sudo tail -20 /var/log/nginx/error.log看connect() failed错误。
- 如果不通:
问题2:HTTPS页面显示不安全(Mixed Content)
浏览器控制台报`Mixed Content: The page at 'https://...' was loaded over HTTPS