提示:本节课最终代码为:feature/s28

应用开发完成之后,我们还需要部署应用。通常我们可采用 2 种部署方式:部署在物理机/虚拟机中、部署在 Kubernetes 集群中。前者可看成是应用的传统部署方式,后者可看成是应用的云原生化部署方式。

当前应用的部署方式是朝着云原生化方式去演进的。在我看来,新增的软件,最好的方式是直接采用云原生化的方式去部署,以规避以后的云原生化改造风险。但是,本课程为了让你更全面地了解应用的部署方式,仍然会介绍应用的传统部署方案。

传统应用典型的部署架构

传统部署方案中,根据业务不同、企业部署环境不同,又会有不同的部署方案。因为这些方案很多,没办法一一介绍,所以本节课只介绍传统应用部署中的核心部分。最典型的部署架构如下图所示:

22.应用部署:应用软件传统部署方案介绍 - 图1

上述方案中,我们使用 systemd 来部署并管理应用。为了实现应用的高可用、水平扩容能力,我们通过负载均衡来访问底层注册的应用实例。在传统的部署方案中,使用最多的负载均衡器是 Nginx 和 Haproxy。你可以根据需要选择使用 Nginx 或 Haproxy。

负载均衡可以实现底层应用实例的高可用,但是如果负载均衡是单点的,当负载均衡故障时,也会造成整个应用的不可用,所以为了实现应用的真正高可用,我们还需要实现负载均衡的高可用。最常用的方法是通过 Keepalived 来实现负载均衡的高可用。

关于 systemd 的介绍和使用,网上已经有很多优质的文章进行介绍。为了方便你学习,这里列出 2 篇优质的文章供参考学习:

Systemd 在你今后的开发生涯中,经常会被接触到,这里建议你花点时间,认真学习下 Systemd 知识。

接下来,我会介绍下 Nginx 和 Keepalived,以使你了解二者如何协同工作,来实现应用的高可用和水平扩容能力。

通过 Systemd 部署 miniblog

假设你已经通过Systemd 入门教程:命令篇Systemd 入门教程:实战篇 2 篇文章,详细学习了 Systemd。

如果要通过 Systemd 部署 minibog 服务,首先需要创建 Systemd Unit 文件,内容如下:

  1. [Unit]
  2. Description=APIServer for blog platform.
  3. Documentation=https://github.com/marmotedu/miniblog/blob/master/init/README.md
  4. [Service]
  5. ExecStart=/opt/miniblog/bin/miniblog --config=/etc/miniblog/miniblog.yaml
  6. Restart=always
  7. RestartSec=5
  8. StartLimitInterval=0
  9. [Install]
  10. WantedBy=multi-user.target

将上述文件保存为 /etc/systemd/system/miniblog.service 文件。

通过上述 Systemd Unit 文件我们知道,如果要启动 miniblog 服务,还需要 miniblog 的启动文件 /opt/miniblog/bin/miniblog 和 配置文件 /etc/miniblog/miniblog.yaml。我们可以通过以下命令来创建:

  1. $ sudo mkdir -p /opt/miniblog/bin
  2. $ sudo mkdir -p /etc/miniblog
  3. $ make build
  4. $ make ca
  5. $ sudo cp -a _output/cert/ /etc/miniblog
  6. $ sudo cp _output/miniblog /opt/miniblog/bin
  7. $ sudo cp configs/miniblog.yaml /etc/miniblog

注意,这里需要分别修改 /etc/miniblog/miniblog.yaml 中的 tls.certtls.key 的值为 /etc/miniblog/cert/server.crt/etc/miniblog/cert/server.key

接下来,我们就可以执行 systemctl 启动 miniblog 服务,并测试服务的监控状态:

  1. $ sudo systemctl daemon-reload
  2. $ sudo systemctl start miniblog
  3. $ systemctl status miniblog
  4. $ curl http://127.0.0.1:8080/healthz
  5. {"status":"ok"}

访问 /healthz 接口输出 {"status":"ok"} ,说明我们通过 Systemd 成功部署了 miniblog 服务。

通过 Nginx 实现应用的高可用

Nginx 是一个自由、开源、高性能及轻量级的 HTTP 服务器和反向代理服务器,它有很多功能,主要用来实现正向代理、反向代理、负载均衡、HTTP 服务器(包含动静分离)等。

在传统部署方案中,通常会使用到 Nginx 的反向代理负载均衡能力。

Nginx 的更详细介绍可以参考 nginx简易教程)。

安装和启动 Nginx

要使用 Nginx,首先需要部署 Nginx,部署命令如下:

  1. $ sudo yum -y install nginx # 安装 Nginx(CentOS 8.x 安装流程)
  2. $ nginx -v # 确认 Nginx 安装成功
  3. nginx version: nginx/1.14.1
  4. $ sudo systemctl start nginx # 启动 Nginx
  5. $ systemctl enable nginx # 设置开机启动
  6. $ systemctl status nginx # 查看 Nginx 启动状态

Nginx 常用命令如下:

  1. nginx -s stop # 快速关闭 Nginx,可能不保存相关信息,并迅速终止 Web 服务
  2. nginx -s quit # 平稳关闭 Nginx,保存相关信息,有安排的结束 Web 服务
  3. nginx -s reload # 因改变了 Nginx 相关配置,需要重新加载配置而重载
  4. nginx -s reopen # 重新打开日志文件
  5. nginx -c filename # 为 Nginx 指定一个配置文件,来代替默认的
  6. nginx -t # 不运行,而仅仅测试配置文件。Nginx 将检查配置文件的语法的正确性,并尝试打开配置文件中所引用到的文件
  7. nginx -v # 显示 Nginx 的版本
  8. nginx -V # 显示 Nginx 的版本、编译器版本和配置参数

Nginx 默认监听 80 端口,启动 Nginx 前要确保 80 端口没有被占用。当然你也可以通过修改 Nginx 配置文件 /etc/nginx/nginx.conf 改 Nginx 监听端口。

Nginx 反向代理功能

Nginx 最常用的功能之一是作为一个反向代理服务器。反向代理(Reverse Proxy)是指以代理服务器来接收 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器(摘自百度百科)。

为什么需要反向代理呢?在实际的生产环境中,服务部署的网络(内网)跟外部网络(外网)通常是不通的,需要通过一台既能够访问内网又能够访问外网的服务器来做中转,这种服务器就是反向代理服务器。

Nginx 作为反向代理服务器,简单的配置如下:

  1. server {
  2. listen 80;
  3. server_name apiserver.com;
  4. client_max_body_size 1024M;
  5. location / {
  6. proxy_set_header Host $http_host;
  7. proxy_set_header X-Forwarded-Host $http_host;
  8. proxy_set_header X-Real-IP $remote_addr;
  9. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  10. proxy_pass http://127.0.0.1:8080/;
  11. client_max_body_size 100m;
  12. }
  13. }

Nginx 在做反向代理服务器时,能够根据不同的配置规则转发到后端不同的服务器上。

Nginx 负载均衡功能

Nginx 另一个常用的功能是负载均衡,所谓的负载均衡就是指当 Nginx 收到一个 HTTP 请求后,会根据负载策略将请求转发到不同的后端服务器上。比如,miniblog 部署在两台服务器 A 和 B 上,当请求到达 Nginx 后,Nginx 会根据 A 和 B 服务器上的负载情况,将请求转发到负载较小的那台服务器上。这里要求 miniblog 是无状态的服务。

配置 Nginx 作为反向代理

假定要访问的 API 服务器域名为 miniblog.com,在 /etc/nginx/nginx.conf 配置 API 服务器的 server 入口:

22.应用部署:应用软件传统部署方案介绍 - 图2

完成 /etc/nginx/nginx.conf 内容如下:

  1. user nginx;
  2. worker_processes 1;
  3. error_log /var/log/nginx/error.log warn;
  4. pid /var/run/nginx.pid;
  5. events {
  6. worker_connections 1024;
  7. }
  8. http {
  9. include /etc/nginx/mime.types;
  10. default_type application/octet-stream;
  11. log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  12. '$status $body_bytes_sent "$http_referer" '
  13. '"$http_user_agent" "$http_x_forwarded_for"';
  14. access_log /var/log/nginx/access.log main;
  15. sendfile on;
  16. #tcp_nopush on;
  17. keepalive_timeout 65;
  18. #gzip on;
  19. include /etc/nginx/conf.d/*.conf;
  20. server {
  21. listen 80;
  22. server_name miniblog.com;
  23. client_max_body_size 1024M;
  24. location / {
  25. proxy_set_header Host $http_host;
  26. proxy_set_header X-Forwarded-Host $http_host;
  27. proxy_set_header X-Real-IP $remote_addr;
  28. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  29. proxy_pass http://127.0.0.1:8080/;
  30. client_max_body_size 5m;
  31. }
  32. }
  33. }

配置说明:

  • 由于 Nginx 默认允许客户端请求的最大单文件字节数为 1MB,实际生产环境中可能太小,所以这里将此限制改为 5MB(client_max_body_size 5m);

  • server_name:说明使用哪个域名来访问;

  • proxy_pass:反向代理的路径(这里是本机的 API 服务,所以 IP 为 127.0.0.1。端口要和 API 服务端口一致:8080)。

提示:如果需要上传图片之类的,可能需要设置成更大的值,比如 50m。因为 Nginx 配置选项比较多,跟实际需求和环境有关,所以这里的配置是基础的、未经优化的配置,在实际生产环境中,需要你再做调节。

测试

测试步骤如下:

  1. 配置完 Nginx 后重启 Nginx。
  1. $ sudo systemctl restart nginx
  1. /etc/hosts 中添加一行: 127.0.0.1 ``miniblog.com

  2. 发送 HTTP 请求。

  1. $ curl http://miniblog.com/healthz
  2. {"status":"ok"}

可以看到成功通过 Nginx 代理访问 miniblog 的 /healthz 接口。

请求流程说明

在用 curl 请求 http://miniblog.com/healthz 后,后端的请求流程实际上是这样的:

  1. 因为在 /etc/hosts 中配置了 127.0.0.1 ``miniblog.com,所以请求 http://miniblog.com/healthz 实际上是请求本机的 Nginx 端口(127.0.0.1:80);

  2. Nginx 在收到请求后,解析到请求域名为 miniblog.com,根据请求域名去匹配 Nginx 的 server 配置,匹配到 server_name ``miniblog.com 配置;

  3. 匹配到 server 后,把请求转发到该 server 的 proxy_pass 路径;

  4. 等待 miniblog 服务返回结果,并返回客户端。

配置 Nginx 作为负载均衡

负载均衡的演示需要多个后端服务,为此我们在同一个服务器上启动多个 miniblog,可以通过配置不同的端口(8080、8082)来实现,并采用 Nginx 默认的轮询转发策略(轮询:每个请求按时间顺序逐一分配到不同的后端服务器)。

/etc/nginx/nginx.conf 中添加 upstream 配置:

22.应用部署:应用软件传统部署方案介绍 - 图3

配置说明:

  • 因为有多个后端,所以需要将之前固定的后端 proxy_pass ``http://127.0.0.1:8080/ 换成具有多个后端的 miniblog.com(通过 upstream);
  • upstream 配置中配置多个后端(ip:port)。
  1. upstream miniblog.com {
  2. server 127.0.0.1:8080;
  3. server 127.0.0.1:8082;
  4. }

测试:

  1. 配置完 Nginx 后重启 Nginx。
  1. $ sudo systemctl restart nginx
  1. 这里需要构建并发请求,编写测试脚本 test.sh,内容为:
  1. #!/bin/bash
  2. for n in $(seq 1 1 10)
  3. do
  4. nohup curl -XGET curl http://miniblog.com/healthz &>/dev/null
  5. done
  1. 在相同服务器上启动两个不同的 HTTP 端口:80808082

注意,miniblog1 可以直接使用上面通过 Systemd 部署的 miniblog 服务。miniblog2 你需要自行部署,为了保证端口、日志文件不冲突,进行以下配置文件修改(miniblog.yaml):

  • 修改 HTTP 端口为 8082、修改 GRPC 端口为 9092、修改 HTTPS 端口为 8445。

  • 修改 /tmp/miniblog.log/tmp/miniblog2.log

之后,你可以使用修改后的 miniblog.yaml 文件启动一个新的 miniblog 服务。

  1. 执行 test.sh 脚本
  1. $ ./test.sh

观察 miniblog 日志,可以看到请求被均衡地转发到后端的两个服务:

miniblog1(8080 端口):

22.应用部署:应用软件传统部署方案介绍 - 图4

miniblog2(8082 端口):

22.应用部署:应用软件传统部署方案介绍 - 图5

可以看到 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(一个或多个),如下图所示:

22.应用部署:应用软件传统部署方案介绍 - 图6

在上图中,将 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 的高可用。