大家好,我是程序员鱼皮。如标题所言,最近这两天,我对我们公司部分项目的部署方式进行了改造升级。
由于部署方式的调整可能会影响到线上用户的正常访问,所以只能挑在用户少的时间(凌晨)进行调整和测试。
结果没想到踩了不少坑,直到昨天半夜我还在跟其他团队的技术同学一起找 Bug:
这篇文章给大家分享下我们项目部署方式升级的形式、过程以及遇到的一些坑点,说不定以后大家也会用到~
为什么要进行升级?
最开始的时候,我们的项目几乎都是部署到服务器上的,而且很多项目是共用一台服务器,像这样:
为啥要这么做呢?
答案很简单,成本低啊!因为我正好有几台配置很高的服务器,这些服务器如果只部署 1 - 2 个项目,CPU、内存、带宽都用不满,妥妥的浪费资源。
而且现在小公司或个人部署项目可以直接使用宝塔 Linux 面板,非常方便。
所以除非必要,我们尽量不会使用额外产生费用的 CDN、按量计费的容器平台等等。
转眼从我创业到现在已经过去了一年多,为什么我们现在要重新调整项目的部署方式呢?
主要的几个原因:
1)随着业务增长,单体项目未必能够满足诉求,我们可能要将同一个项目部署在多个节点上,实现负载均衡和容错,手动部署就太麻烦了。这就需要能够灵活扩缩容机器节点的能力和流水线部署的能力。
2)项目部署在同一服务器,如果服务器宕机,将同时影响多个项目。
3)项目之间存在资源竞争,比如某个项目正在做大力推广、占用大量带宽资源,其他项目的可用带宽就很少了,访问会很慢。
4)权限风险。一旦给开发者开通服务器的访问权限,将能改动所有项目,还有误操作的可能性。
基于这些原因,再加上出现过一些事故,我们决定升级项目的部署方式。
部署方式变更
以前,我们的部署方式如下图:
用户要请求网站时,先通过 DNS 域名解析,找到服务器对应的 IP,经过高防服务器后请求发送到 Nginx Web 服务器。然后 Nginx 根据请求路径判断,如果要访问文件,找到前端网站文件;如果请求的是接口,反向代理到后端服务。
升级后,我们的部署方式如下图:
主要有 3 个改动:
1)接入有安全防护和资源加速能力的 CDN,可以提高前端网站的加载速度。
2)后端使用容器平台进行部署,拥有动态扩缩容、负载均衡的能力。
3)前后端部署分离,不再依赖 Nginx 进行转发,而是区分不同的请求域名,通过 DNS 解析到不同的 CDN 上。
CDN 平台我是同时使用了腾讯云 CDN 和蓝易云 CDN,不同的项目选择了不同的 CDN。蓝易云 CDN(tsycdn.com)虽然不像腾讯云那么有名,但是性价比更高,能够有效防止 DDOS 攻击。在我网站被频繁攻击的那段时间,他们也帮了我不少。
而且最打动我的还是他们的技术支持,能耐心陪我一起改几个小时的 Bug,没谁了:
容器平台的话,我们将一部分服务放在了微信云托管上,可以很方便地配置流水线,实现提交代码到 GitHub 后自动发布和部署:
还可以查看服务日志、资源占用情况:
虽然微信云托管平台感觉很久没更新了,配置容器的灵活性也没那么高,但是能够满足大多数开发者的使用诉求了。
升级过程
1、后端服务迁移
既然后端服务要部署到容器平台,肯定要把项目制作为 Docker 镜像。
方法很简单,在后端项目根目录创建一个 Dockerfile
,编写构建镜像的命令即可。
比如 Spring Boot 项目可以使用类似下面的配置:
bash
# 选择基础镜像
FROM maven:3.8.1-jdk-8-slim as builder
# 解决容器时期与真实时间相差 8 小时的问题
RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone
# 复制代码到容器内
WORKDIR /app
COPY pom.xml .
COPY src ./src
# 打包构建
RUN mvn package -DskipTests
# 容器启动时运行 jar 包
CMD ["java","-jar","/app/target/server.jar","--spring.profiles.active=prod"]
这里有个比较坑的地方,要注意容器环境的时间,有可能会和真实时间相差 8 小时,导致日志时间、以及插入到数据库的时间错误。
2、配置 CDN
配置 CDN 的关键是配置源站的地址。CDN 相当于是缓存,如果用户需要的数据在 CDN 上找不到,CDN 节点就会请求源站来获取数据,所以源站配置一定不能错。
上图中的回源设置,是指 CDN 请求源站的方式,包括协议、域名端口号等。
这里有 2 个注意事项:
1)避免给源站添加任何的重定向逻辑,否则可能重定向时直接暴露了源站地址。
比如 cdn 地址是 "yupi.icu",源站地址是 "base.yupi.icu",一般源站地址是要隐藏起来的,否则用户就可以绕过 CDN 直接攻击你的源站。如果源站配置了重定向逻辑,比如将后缀 "/" 路由到 "/aaa"。那么用户在访问 "yupi.icu/" 时,可能会被自动重定向到 "base.yupi.icu/aaa"!暴露了!
2)如果 CDN 站点开启了 HTTPS,回源协议尽量用 HTTP,否则可能会出现因为相同证书子域名 SSL 配置不一致导致的 421 错误(Misdirected Request),这个错误可以说是非常冷门了,不自己上线个项目,大概率听都没听说过。
3、配置 DNS
打通 CDN 到源站(容器平台)的访问后,最后一步就是配置 DNS,让用户访问的域名(比如 www.code-nav.cn)解析到 CDN。
需要注意的是,DNS 的解析生效时长在全国各地是不等的,所以有可能更改解析后,北京的用户访问不了、上海的用户能访问。所以不要急着把老服务下线掉!
本来以为很顺利,结果呢,CDN 访问源站竟然失败了,源站返回了 444 错误码(连接已关闭)!又是一个冷门的错误!
这个错误可把我折腾坏了,为啥我的服务器会拒绝国内 CDN 节点的连接呢?首先第一个猜测就是服务器封禁了 IP,于是查了高防、查了服务器防火墙、还咨询了云服务商的客服,结果都说没有封禁 IP。。。
于是,我去看了下 Nginx 的日志:
arduino
CDN 节点的 IP - - [29/Apr/2024:00:06:41 +0800] "GET /favicon.ico HTTP/1.1" 444 0 "https://www.code-nav.cn/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
Nginx 既然已经收到了请求,说明大概率是 Nginx 配置拒绝了连接。但是我翻烂了 Nginx 的配置,也没找到在哪配置了 IP 封禁。。。
最后你猜怎么着?我突然想起来几年前,我曾经在这个服务器上购买过 Nginx 防火墙。虽然它早已过期,但貌似还能帮我自动封禁一些 IP。。。估计是因为昨天配 CDN 时我为了测试验证,使得访问源站频率过高导致的。
于是我把 Nginx 防火墙卸载了,就没有这个错误了。
用 4 个字来形容,我想到了 "阴魂不散"。
以上就是本期分享。当然,实际的升级过程,可比我这篇文章描述地还要麻烦,因为还涉及到一些平台(比如微信公众平台)的白名单配置。而且我们升级肯定是需要灰度的,先拿访问量最低的项目去验证流程,再陆续迁移其他项目。