提示:本节课最终代码为:feature/s28。
应用开发完成之后,我们还需要部署应用。通常我们可采用 2 种部署方式:部署在物理机/虚拟机中、部署在 Kubernetes 集群中。前者可看成是应用的传统部署方式,后者可看成是应用的云原生化部署方式。
当前应用的部署方式是朝着云原生化方式去演进的。在我看来,新增的软件,最好的方式是直接采用云原生化的方式去部署,以规避以后的云原生化改造风险。但是,本课程为了让你更全面地了解应用的部署方式,仍然会介绍应用的传统部署方案。
传统应用典型的部署架构
传统部署方案中,根据业务不同、企业部署环境不同,又会有不同的部署方案。因为这些方案很多,没办法一一介绍,所以本节课只介绍传统应用部署中的核心部分。最典型的部署架构如下图所示:
上述方案中,我们使用 systemd 来部署并管理应用。为了实现应用的高可用、水平扩容能力,我们通过负载均衡来访问底层注册的应用实例。在传统的部署方案中,使用最多的负载均衡器是 Nginx 和 Haproxy。你可以根据需要选择使用 Nginx 或 Haproxy。
负载均衡可以实现底层应用实例的高可用,但是如果负载均衡是单点的,当负载均衡故障时,也会造成整个应用的不可用,所以为了实现应用的真正高可用,我们还需要实现负载均衡的高可用。最常用的方法是通过 Keepalived 来实现负载均衡的高可用。
关于 systemd 的介绍和使用,网上已经有很多优质的文章进行介绍。为了方便你学习,这里列出 2 篇优质的文章供参考学习:
Systemd 在你今后的开发生涯中,经常会被接触到,这里建议你花点时间,认真学习下 Systemd 知识。
接下来,我会介绍下 Nginx 和 Keepalived,以使你了解二者如何协同工作,来实现应用的高可用和水平扩容能力。
通过 Systemd 部署 miniblog
假设你已经通过Systemd 入门教程:命令篇、Systemd 入门教程:实战篇 2 篇文章,详细学习了 Systemd。
如果要通过 Systemd 部署 minibog 服务,首先需要创建 Systemd Unit 文件,内容如下:
[Unit]
Description=APIServer for blog platform.
Documentation=https://github.com/marmotedu/miniblog/blob/master/init/README.md
[Service]
ExecStart=/opt/miniblog/bin/miniblog --config=/etc/miniblog/miniblog.yaml
Restart=always
RestartSec=5
StartLimitInterval=0
[Install]
WantedBy=multi-user.target
将上述文件保存为 /etc/systemd/system/miniblog.service
文件。
通过上述 Systemd Unit 文件我们知道,如果要启动 miniblog 服务,还需要 miniblog 的启动文件 /opt/miniblog/bin/miniblog
和 配置文件 /etc/miniblog/miniblog.yaml
。我们可以通过以下命令来创建:
$ sudo mkdir -p /opt/miniblog/bin
$ sudo mkdir -p /etc/miniblog
$ make build
$ make ca
$ sudo cp -a _output/cert/ /etc/miniblog
$ sudo cp _output/miniblog /opt/miniblog/bin
$ sudo cp configs/miniblog.yaml /etc/miniblog
注意,这里需要分别修改 /etc/miniblog/miniblog.yaml
中的 tls.cert
、tls.key
的值为 /etc/miniblog/cert/server.crt
和 /etc/miniblog/cert/server.key
。
接下来,我们就可以执行 systemctl
启动 miniblog 服务,并测试服务的监控状态:
$ sudo systemctl daemon-reload
$ sudo systemctl start miniblog
$ systemctl status miniblog
$ curl http://127.0.0.1:8080/healthz
{"status":"ok"}
访问 /healthz
接口输出 {"status":"ok"}
,说明我们通过 Systemd 成功部署了 miniblog 服务。
通过 Nginx 实现应用的高可用
Nginx 是一个自由、开源、高性能及轻量级的 HTTP 服务器和反向代理服务器,它有很多功能,主要用来实现正向代理、反向代理、负载均衡、HTTP 服务器(包含动静分离)等。
在传统部署方案中,通常会使用到 Nginx 的反向代理和负载均衡能力。
Nginx 的更详细介绍可以参考 nginx简易教程)。
安装和启动 Nginx
要使用 Nginx,首先需要部署 Nginx,部署命令如下:
$ sudo yum -y install nginx # 安装 Nginx(CentOS 8.x 安装流程)
$ nginx -v # 确认 Nginx 安装成功
nginx version: nginx/1.14.1
$ sudo systemctl start nginx # 启动 Nginx
$ 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 反向代理功能
Nginx 最常用的功能之一是作为一个反向代理服务器。反向代理(Reverse Proxy)是指以代理服务器来接收 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器(摘自百度百科)。
为什么需要反向代理呢?在实际的生产环境中,服务部署的网络(内网)跟外部网络(外网)通常是不通的,需要通过一台既能够访问内网又能够访问外网的服务器来做中转,这种服务器就是反向代理服务器。
Nginx 作为反向代理服务器,简单的配置如下:
server {
listen 80;
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:8080/;
client_max_body_size 100m;
}
}
Nginx 在做反向代理服务器时,能够根据不同的配置规则转发到后端不同的服务器上。
Nginx 负载均衡功能
Nginx 另一个常用的功能是负载均衡,所谓的负载均衡就是指当 Nginx 收到一个 HTTP 请求后,会根据负载策略将请求转发到不同的后端服务器上。比如,miniblog 部署在两台服务器 A 和 B 上,当请求到达 Nginx 后,Nginx 会根据 A 和 B 服务器上的负载情况,将请求转发到负载较小的那台服务器上。这里要求 miniblog 是无状态的服务。
配置 Nginx 作为反向代理
假定要访问的 API 服务器域名为 miniblog.com
,在 /etc/nginx/nginx.conf
配置 API 服务器的 server
入口:
完成 /etc/nginx/nginx.conf
内容如下:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
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_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name miniblog.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:8080/;
client_max_body_size 5m;
}
}
}
配置说明:
由于 Nginx 默认允许客户端请求的最大单文件字节数为 1MB,实际生产环境中可能太小,所以这里将此限制改为 5MB(
client_max_body_size 5m
);server_name
:说明使用哪个域名来访问;proxy_pass
:反向代理的路径(这里是本机的 API 服务,所以 IP 为 127.0.0.1。端口要和 API 服务端口一致:8080)。
提示:如果需要上传图片之类的,可能需要设置成更大的值,比如 50m。因为 Nginx 配置选项比较多,跟实际需求和环境有关,所以这里的配置是基础的、未经优化的配置,在实际生产环境中,需要你再做调节。
测试
测试步骤如下:
- 配置完 Nginx 后重启 Nginx。
$ sudo systemctl restart nginx
在
/etc/hosts
中添加一行:127.0.0.1 ``miniblog.com
。发送 HTTP 请求。
$ curl http://miniblog.com/healthz
{"status":"ok"}
可以看到成功通过 Nginx 代理访问 miniblog 的 /healthz
接口。
请求流程说明
在用 curl
请求 http://miniblog.com/healthz
后,后端的请求流程实际上是这样的:
因为在
/etc/hosts
中配置了127.0.0.1 ``miniblog.com
,所以请求http://miniblog.com/healthz
实际上是请求本机的 Nginx 端口(127.0.0.1:80
);Nginx 在收到请求后,解析到请求域名为
miniblog.com
,根据请求域名去匹配 Nginx 的 server 配置,匹配到server_name ``miniblog.com
配置;匹配到 server 后,把请求转发到该 server 的
proxy_pass
路径;等待 miniblog 服务返回结果,并返回客户端。
配置 Nginx 作为负载均衡
负载均衡的演示需要多个后端服务,为此我们在同一个服务器上启动多个 miniblog,可以通过配置不同的端口(8080、8082)来实现,并采用 Nginx 默认的轮询转发策略(轮询:每个请求按时间顺序逐一分配到不同的后端服务器)。
在 /etc/nginx/nginx.conf
中添加 upstream
配置:
配置说明:
- 因为有多个后端,所以需要将之前固定的后端
proxy_pass ``http://127.0.0.1:8080/
换成具有多个后端的miniblog.com
(通过upstream
); upstream
配置中配置多个后端(ip:port)。
upstream miniblog.com {
server 127.0.0.1:8080;
server 127.0.0.1:8082;
}
测试:
- 配置完 Nginx 后重启 Nginx。
$ sudo systemctl restart nginx
- 这里需要构建并发请求,编写测试脚本
test.sh
,内容为:
#!/bin/bash
for n in $(seq 1 1 10)
do
nohup curl -XGET curl http://miniblog.com/healthz &>/dev/null
done
- 在相同服务器上启动两个不同的 HTTP 端口:
8080
和8082
。
注意,miniblog1 可以直接使用上面通过 Systemd 部署的 miniblog 服务。miniblog2 你需要自行部署,为了保证端口、日志文件不冲突,进行以下配置文件修改(miniblog.yaml
):
修改 HTTP 端口为 8082、修改 GRPC 端口为 9092、修改 HTTPS 端口为 8445。
修改
/tmp/miniblog.log
为/tmp/miniblog2.log
。
之后,你可以使用修改后的 miniblog.yaml
文件启动一个新的 miniblog 服务。
- 执行 test.sh 脚本
$ ./test.sh
观察 miniblog
日志,可以看到请求被均衡地转发到后端的两个服务:
miniblog1(8080 端口):
miniblog2(8082 端口):
可以看到 miniblog1 和 miniblog2 分别收到了 5 个 /healthz
接口的请求。说明请求被平均负载到了后端注册的 miniblog 实例上。
通过 Keepalived 实现 Nginx 高可用
通过 Nginx 我们可以实现后端服务的高可用,但如果 Nginx 是单点的,整个服务仍然不是高可用的。所以,我们还需要通过 Keepalived 实现 Nginx 的高可用。因为如果要通过 Keepalived 实现 Nginx 的高可用,至少需要 2 台服务器,考虑到你不一定具备部署条件,所以这里只做方案介绍。
Nginx 高可用方案介绍
Nginx 自带负载均衡功能,并且当 Nginx 后端某个服务器挂掉后,Nginx 会自动剔除该服务器,将请求转发到可用的服务器,通过这种方式实现了后端 API 服务的高可用(HA)。但是 Nginx 是单点的,如果 Nginx 挂了,后端的所有服务器就都不能访问,所以在实际生产环境中,也需要对 Nginx 做高可用。
Keepalived 是一个高性能的服务器高可用或热备解决方案,Keepalived 主要来防止服务器单点故障的发生问题,可以通过 Keepalived 对前端 Nginx 实现高可用。Keepalived + Nginx 的高可用方案具有如下特点:服务功能强大、维护简单。
Keepalived 简介
Keepalived 以 VRRP 协议为基础来实现高可用性。VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议)是用于实现路由器冗余的协议,它将两台或多台路由器设备虚拟成一个设备,对外提供虚拟路由器 IP(一个或多个),如下图所示:
在上图中,将 Nginx + Keepalived 部署在两台服务器上,拥有两个真实的 IP(IP1 和 IP2),通过一定的技术(如 LVS)虚拟出一个虚拟 IP(VIP),外界请求通过访问 VIP 来访问服务。
在两台 Nginx + Keepalived 的服务器上,同一时间只有一台会接管 VIP(叫做 Master)提供服务,另一台(叫做 Slave)会检测 Master 的心跳,当发现 Master 停止心跳后,Slave 会自动接管 VIP 以提供服务(此时,Slave 变成 Master)。通过 Keepalived 实现 Nginx 的高可用,而 Nginx 可以对后台 API 服务器做高可用,这样通过 Nginx + Keepalived 的组合方案就实现了整个 API 集群的高可用。
Keepalived 部署
Keepalived + Nginx 的部署方案网上有很多详细的教程。因为小册篇幅限制,这里不再详细说明,大家如需了解,请参考 Keepalived+Nginx实现高可用(HA)。
小结
在生产环境中,应用服务器所在的网络通常不能直接通过外网访问,需要通过可从外网访问的 Nginx 服务器,将请求转发到内网的 应用服务器上。并且随着业务规模越来越大,请求量也会越来越大,这时候需要将应用横向扩容,也需要 Nginx。所以在实际的应用服务部署中 Nginx 经常能派上用场。
Nginx 可以实现应用实例的高可用,但是为了保证整个请求链路的高可用,我们也需要保证 Nginx 的高可用,通常使用 Keepalived 来保证 Nginx 的高可用。