应用开发完成后,下一步需要部署应用。通常情况下,部署方式可以分为两类:部署在物理机或虚拟机中,或者部署在 Kubernetes 集群中。前者是传统的部署方式,后者则属于云原生环境下的部署方式。
目前,应用的部署方式正逐渐向云原生方向演进。对于新增的软件,直接采用云原生部署是最佳选择,不仅可以享受云原生的优势,还能避免未来进行云原生化改造带来的的成本和风险。相较于传统的物理机部署,云原生部署的流程更为复杂,且云原生部署并非本课程的讨论重点。因此,本节课将重点介绍应用的物理机部署方式。
传统应用典型的部署架构
在传统部署方案中,根据业务需求和企业部署环境的不同,会存在多种部署方式。由于这些方案繁多,无法逐一介绍,因此本节仅对传统应用部署的核心部分进行讲解。最典型的部署架构如图 14-1 所示。
图 14-1 经典部署架构
上述方案使用 Systemd 来部署并管理应用。为了实现应用的高可用性和水平扩容能力,客户端通过负载均衡访问底层注册的应用实例。在传统的部署方案中,使用最广泛的负载均衡器是 Nginx 和 HAProxy。
虽然负载均衡可以实现底层应用实例的高可用性,但如果负载均衡器本身是单点部署,当负载均衡器发生故障时,整个应用仍会不可用。因此,为了实现真正的高可用性,还需要对负载均衡器本身进行高可用配置。最常用的方式是通过 Keepalived 实现负载均衡器的高可用性。
提示:Systemd 是 Linux 系统中的初始化系统和服务管理器,负责系统启动和管理后台服务。在 Linux 系统中,可通过 systemctl 命令方便地管理服务。由于互联网上已有大量关于 Systemd 的介绍,本课程不再赘述。
通过 Systemd 部署 miniblog
要通过 Systemd 部署 miniblog 服务,首先需要创建一个 Systemd Unit 文件。对于 miniblog 项目,其 Systemd Unit 文件为 init/mb-apiserver.service,文件内容如下:
# 描述服务的功能
Description=APIServer for mini blog platform.
# 提供服务文档的链接
Documentation=https://github.com/onexstack/miniblog/blob/master/init/README.md
[Service]
# 启动服务的命令,指定可执行文件及其配置文件
ExecStart=/opt/miniblog/bin/mb-apiserver --config=/opt/miniblog/etc/mb-apiserver.yaml
# 服务出现故障后总是自动重启
Restart=always
# 重启服务的间隔时间为 5 秒
RestartSec=5
# 限制服务启动的失败次数为 5 次
StartLimitInterval=5
[Install]
# 指定服务在启动时的目标,multi-user.target 表示可以在多用户模式下启动
WantedBy=multi-user.target从该 Systemd Unit 文件可以看出,启动 miniblog 服务需要提供启动文件 /opt/miniblog/bin/mb-apiserver 以及配置文件 /opt/miniblog/etc/mb-apiserver.yaml。可以通过以下命令创建所需的目录和文件:
$ sudo mkdir -p /opt/miniblog/bin/
$ sudo mkdir -p /opt/miniblog/etc/
$ make build BINS=mb-apiserver
$ make ca
$ sudo cp -a _output/cert/ /opt/miniblog/etc/
$ sudo cp _output/platforms/linux/amd64/mb-apiserver /opt/miniblog/bin/
$ sudo cp configs/mb-apiserver.yaml /opt/miniblog/etc/注意,请确保 /etc/miniblog/mb-apiserver.yaml 文件中 tls.cert 和 tls.key 的配置路径正确无误。
完成部署文件安装后,可通过以下命令启动 miniblog 服务,并检查其运行状态:
$ sudo cp init/mb-apiserver.service /etc/systemd/system/
$ sudo systemctl daemon-reload
$ sudo systemctl start mb-apiserver
$ systemctl status mb-apiserver|grep running
Active: active (running) since Sat 2025-02-01 00:24:56 CST; 5s ago
$ curl http://127.0.0.1:5555/healthz
{"timestamp":"2025-02-01 00:25:44"}健康检查接口 /healthz 返回了正常状态,表明已通过 Systemd 成功部署 miniblog 服务。
通过 Nginx 实现应用的高可用
Nginx 是一个自由、开源、高性能及轻量级的 HTTP 服务器和反向代理服务器,它有很多功能,主要用来实现正向代理、反向代理、负载均衡、HTTP 服务器(包含动静分离)等。在传统部署方案中,通常会使用到 Nginx 的反向代理和负载均衡能力。
安装和启动 Nginx
要使用 Nginx,首先需要部署 Nginx,部署命令如下:
$ sudo apt update && sudo apt upgrade -y
$ sudo apt install nginx -y
$ nginx -v
$ sudo systemctl start nginx # 启动 Nginx
$ sudo systemctl enable nginx # 设置开机启动
$ systemctl status nginx # 查看 Nginx 启动状态Nginx 常用命令如下:
nginx -s stop # 快速关闭 Nginx,可能不保存相关信息,并迅速终止 Web 服务
nginx -s quit # 平稳关闭 Nginx,保存相关信息,有安排的结束 Web 服务
nginx -s reload # 因改变了 Nginx 相关配置,需要重新加载配置而重载
nginx -s reopen # 重新打开日志文件
nginx -c filename # 为 Nginx 指定一个配置文件,来代替默认的
nginx -t # 仅测试配置文件时,Nginx 会检查语法正确性并尝试打开配置中引用的文件
nginx -v # 显示 Nginx 的版本
nginx -V # 显示 Nginx 的版本、编译器版本和配置参数Nginx 默认监听 80 端口,启动 Nginx 前要确保 80 端口没有被占用。当然你也可以通过修改 Nginx 的配置文件 /etc/nginx/nginx.conf 来修改 Nginx 的监听端口。为了避免端口冲突,本课程修改 Nginx 的监听端口为 7777。
Nginx 反向代理功能
Nginx 的一个常见功能是充当反向代理服务器。反向代理(Reverse Proxy)指通过代理服务器接收客户端的连接请求,将请求转发给内部网络的服务器,并将服务器的响应结果返回给客户端,对外表现为代理服务器提供服务。
在实际生产环境中,服务部署所在的内网服务通常无法直接与外网通信。为实现外网访问,通常需要一台能够同时访问内网和外网的服务器作为中转,这台服务器便是反向代理服务器。
提示:正向代理和反向代理的区别主要在于服务对象和使用场景。正向代理是为客户端服务,代理客户端向目标服务器发送请求,常用于突破网络限制、访问受限资源,隐藏客户端信息。而反向代理是为服务器服务,代理服务器接受客户端请求并转发给内部服务器,常用于负载均衡、流量分发、隐藏服务器信息以及增强安全性。简单来说,正向代理帮助客户端访问服务器,反向代理帮助服务器响应客户端请求。
Nginx 作为反向代理服务器,简单的配置如下:
server {
listen 7777;
server_name apiserver.com;
client_max_body_size 1024M;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:5555/;
client_max_body_size 100m;
}
}Nginx 在做反向代理服务器时,能够根据不同的配置规则转发到后端不同的服务器上。
Nginx 负载均衡功能
Nginx 另一个常用的功能是负载均衡,所谓的负载均衡就是指当 Nginx 收到一个 HTTP 请求后,会根据负载策略将请求转发到不同的后端服务器上。比如,miniblog 部署在两台服务器 A 和 B 上,当请求到达 Nginx 后,Nginx 会根据 A 和 B 服务器上的负载情况,将请求转发到负载较小的那台服务器上。这种负载均衡机制要求 miniblog 为无状态服务,以确保请求能在多台服务器间自由分配而不影响正常运行。
配置 Nginx 作为反向代理
假定要访问的 API 服务器域名为 onexstack.com,在 /etc/nginx/nginx.conf 配置文件中,配置 API 服务器的 server 入口。配置后的 nginx.conf 文件内容如代码清单 14-1 所示。
代码清单 14-1 Nginx 反向代理配置
# 指定工作进程的数量,这里设置为 1 个进程
worker_processes 1;
# 指定错误日志的存放路径和日志级别为 warn(警告)
error_log /var/log/nginx/error.log warn;
# 指定 Nginx 进程 ID 文件的存放路径
pid /var/run/nginx.pid;
events {
# 每个工作进程允许的最大连接数,这里设置为 1024
worker_connections 1024;
}
http {
# 引入 MIME 类型配置文件,以便根据文件类型设置正确的 Content-Type
include /etc/nginx/mime.types;
# 默认的 Content-Type 为 application/octet-stream
default_type application/octet-stream;
# 定义访问日志的格式,包含客户端地址、用户、时间、请求内容、状态码、发送的字节数、来源地址、用户代理和转发的地址
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 指定访问日志的存放路径,并使用上面定义的日志格式
access_log /var/log/nginx/access.log main;
# 启用高效文件传输模式,减少上下文切换,提高性能
sendfile on;
# 该选项用于优化 TCP 传输,通常在需要发送大文件时使用,默认注释掉
#tcp_nopush on;
# 设置长连接的超时时间为 65 秒
keepalive_timeout 65;
# 启用 gzip 压缩,默认注释掉,若启用则会压缩响应数据以减少带宽使用
#gzip on;
# 引入 conf.d 目录下的所有配置文件,便于模块化配置
include /etc/nginx/conf.d/*.conf;
server {
# 监听 7777 端口,处理 HTTP 请求
listen 7777;
# 指定服务器的域名为 onexstack.com
server_name onexstack.com;
# 设置客户端请求体的最大大小为 1024MB
client_max_body_size 1024M;
# 匹配根路径的请求
location / {
# 设置请求头中的 Host 为客户端请求的 Host
proxy_set_header Host $http_host;
# 设置 X-Forwarded-Host 请求头
proxy_set_header X-Forwarded-Host $http_host;
# 设置 X-Real-IP 请求头为客户端的真实 IP 地址
proxy_set_header X-Real-IP $remote_addr;
# 设置 X-Forwarded-For 请求头,记录转发链中的 IP 地址
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 将请求转发到本地 8080 端口的服务
proxy_pass http://127.0.0.1:5555/;
# 对于此 location,设置客户端请求体的最大大小为 5MB
client_max_body_size 5m;
}
}
}在代码清单 14-1 所示的配置中,将单个文件的最大请求限制从默认的 1MB 调整为 5MB(client_max_body_size 5m),通过设置 server_name 来指定访问的域名,使用 proxy_pass 配置反向代理路径,这里代理到本机的 API 服务(IP 为 127.0.0.1,端口需与 API 服务的端口保持一致)。
提示:如果需要上传图片,client_max_body_size 可能需要设置成更大的值,比如 100M。因为 Nginx 配置选项比较多,跟实际需求和环境有关,所以这里的配置是基础的、未经优化的配置,在实际生产环境中,需要你再做调节。
Nginx 配置完成后,执行以下命令来测试:
$ sudo systemctl restart nginx # 配置完 Nginx 后重启 Nginx
$ echo '127.0.0.1 onexstack.com' | sudo tee -a /etc/hosts
$ curl http://onexstack.com:7777/healthz # 请求健康检查接口,检查服务器是否健康
{"timestamp":"2025-02-01 00:49:57"}可以看到成功通过 Nginx 代理访问 miniblog 的 /healthz 接口。
在用 curl 请求 http://onexstack.com:7777/healthz 后,后端的请求流程如下:
- 因为配置了域名映射,所以请求 http://onexstack.com:7777/healthz 实际上是请求本机的 Nginx 端口(127.0.0.1:7777);
- Nginx 在收到请求后,解析到请求域名为 onexstack.com,根据请求域名去匹配 Nginx 的 server 配置,匹配到 server_name onexstack.com 配置;
- 匹配到 server 后,把请求转发到该 server 的 proxy_pass 路径;
- 等待 miniblog 服务返回结果,并返回客户端。
配置 Nginx 作为负载均衡
为了演示负载均衡功能,需要启动多个后端服务。可以在同一台服务器上运行多个 miniblog 实例,并通过配置不同的端口(如 5555 和 5556)实现多实例部署。随后,使用 Nginx 的默认轮询策略(即按时间顺序将每个请求逐一分配到不同的后端服务器)进行请求分发。在 /etc/nginx/nginx.conf 文件中,通过添加 upstream 配置来实现后端服务的负载均衡,新增配置如下所示:
...
http {
...
include /etc/nginx/conf.d/*.conf;
upstream onexstack.com {
server 127.0.0.1:5555
server 127.0.0.1:5556
}
server {
...
}
}由于存在多个后端服务,因此需要将之前的后端地址 proxy_pass http://127.0.0.1:5555/ 替换为通过 upstream 配置的负载均衡地址 onexstack.com。在 upstream 配置中,定义了多个后端实例(以 ip:port 形式)供负载均衡使用。
配置好 Nginx 后,执行以下步骤来测试负载均衡功能。
(1)重启 Nginx
执行以下命令重启 Nginx:
$ sudo systemctl restart nginx # 配置完 Nginx 后重启 Nginx(2)相同服务器上启动两个不同的 mb-apiserver 实例
在相同的服务器上启动两个 mb-apiserver 实例,使用不同的配置,实例 A 的 HTTP 端口为 5555,实例 B 的 HTTP 端口为 5556。
(3)运行测试脚本
执行以下命令,运行测试脚本:
$ ./scripts/test_nginx.sh # 运行测试脚本通过观察 miniblog 的日志,可以发现端口为 5555 和 5556 的实例分别接收到了 5 个 /healthz 接口的请求,这表明请求已被均匀分配到后端注册的 miniblog 实例上,实现了负载均衡。
通过 Keepalived 实现 Nginx 高可用
Nginx 自带负载均衡功能,并且当 Nginx 后端某个服务器挂掉后,Nginx 会自动剔除该服务器,将请求转发到可用的服务器,通过这种方式实现了后端 API 服务的高可用(HA)。但是 Nginx 是单点的,如果 Nginx 挂了,后端的所有服务器就都不能访问,所以在实际生产环境中,也需要对 Nginx 做高可用。在行业中,实现 Nginx 高可用最常用的方案是结合 Keepalived。
提示:因为如果要通过 Keepalived 实现 Nginx 的高可用,至少需要 2 台服务器,考虑到你不一定具备部署条件,所以这里只做方案介绍。
Keepalived 是一个基于 VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议)的高可用解决方案,最初被设计用于防止网络单点故障,后来被广泛应用于服务框架中,实现负载均衡器(如 Nginx 和 HAProxy)的高可用性。
Keepalived 的核心功能是通过 VRRP 创建一个虚拟 IP(VIP),将这个 VIP 绑定到一台主服务器(Master),并以心跳检测的方式检查 Master 的状态。如果 Master 无法正常运行,Keepalived 会自动将 VIP 接管到一台备份服务器(Backup)。这一切对用户是透明的,因此无论 Master 或 Backup 的状态改变,用户访问服务的 IP 和入口始终保持一致。
Keepalived+Nginx 的高可用方案如图 14-2 所示。
图 14-2 Keepalived 高可用原理
在图 14-2 中,Nginx 和 Keepalived 被部署在两台服务器上,分别拥有两个真实 IP(IP1 和 IP2)。通过技术手段(如 LVS),虚拟出一个虚拟 IP(VIP),外部请求通过访问 VIP 来访问服务。
在这两台部署了 Nginx 和 Keepalived 的服务器中,同一时间只会有一台服务器作为 Master 接管 VIP 并提供服务,另一台作为 Slave 监测 Master 的心跳状态。当 Slave 检测到 Master 停止心跳时,会自动接管 VIP 提供服务(此时 Slave 切换为 Master)。通过 Keepalived 实现 Nginx 的高可用性,同时 Nginx 对后端 API 服务器提供高可用支持,从而通过 Nginx+Keepalived 的组合方案实现整个 API 集群的高可用性。
云原生部署
当前软件的部署方式基本都会采用云原生部署。尽管采用云原生架构会引入一定复杂性,这些复杂性主要源于新增的云原生组件及其部署与维护,但对于 Go 开发者而言,通常无需过多关注这些细节。将服务部署在云原生架构之上能够带来诸多优势,例如弹性扩缩容、自愈能力、自动化运维和高可靠性,同时提升开发效率,降低运维复杂性与成本。因此,在企业级应用开发中,完成开发的应用还需选择使用云原生化方法进行部署。
云原生部署涉及诸多概念、知识和操作,不在本课程讨论范围内。为了使你提前了解云原生部署的方法,miniblog 仓库 feature/s37 分支下的 docs/book/cloud-native-deployment.md 文档详细介绍了如何快速容器化部署 miniblog 项目。
小结
本节课围绕传统物理机部署方式,介绍了如何通过 Systemd、Nginx 和 Keepalived 实现应用的部署与高可用性。通过 Systemd,可以高效管理应用服务的启动、停止和重启,确保服务的稳定运行。本节课以 miniblog 服务为例,展示了如何通过 SystemdUnit 文件完成服务的部署和启动。
Nginx 在部署中发挥了重要作用,既可作为反向代理转发外部请求至内网服务,也可通过负载均衡功能将流量分配至多个后端实例,提升服务的扩展性与可靠性。然而,Nginx 本身可能成为单点故障。为此,可以结合 Keepalived,通过主备切换机制实现 Nginx 的高可用性,确保在 Nginx 故障时服务不中断。Nginx 和 Keepalived 的组合方案进一步提升了整个系统的稳定性和高可用性,为实际生产环境提供了可靠的部署参考。