背景
现在公司内使用的是K8S进行服务部署,内网测试环境也提供了K8S环境。团队内多人同时开发多个需求,必然会出现多个需求同时测试的情况。而部署到内网K8S测试环境,一次只能部署一个服务。如果将多个需求分支合并到一个分支上去,势必会相互影响,而且使用公司的TDC服务每次更新都要人工处理,效率偏低。为了解决这个问题,我在公司内网物理机上,针对静态网站和直出网站分别搭建了多分支的测试环境,并配合Gitlab CI自行自动化部署,减少人工部署耗时。
实践路线
目前手上有两个项目,一个是正常的静态网站,一个是服务端直出的网站。它们在部署上是有不同的,静态网站我们开发完打包好静态文件部署到服务器上配置好Nginx
访问指定目录访问就行了。而服务端直出的项目则需要启动一个服务,并配置Nginx
将请求转发到对应的服务上。
因此针对上述情况我们需要事情如下:
- 根据不同的分支输出对应的静态文件或者启动不同的服务;
- 通过配置
Nginx
规则来实现通过访问URL上的信息将请求转发到对应的文件或服务; - 配置
Gitlab CI
并编写自动化脚本进行自动部署。
如何区分不同分支
静态网站
针对静态网站,我们最终是将其部署在服务器上的某个目录中,通过Nginx
转发进行访问。如果我们能够将不同分支打包到不同的目录下,并通过·Nginx
配置进行访问就能实现访问。
一个项目中每个人开发需求都会取一个唯一的分支名,而在Gitlab执行CI时会提供CI_COMMIT_REF_SLUG
这个字段,这个字段的值是分支名称的值全转成小写并将除0-9
和a-z
的其他字符转换成-
后获得的,可以用在URL中。
这样我们只需在执行自动化脚本时,将CI_COMMIT_REF_SLUG
当做参数传入打包脚本,在指定目录下新建对应名称的目录,并将打包文件通过过去变算是完成第一阶段的工作了。
服务端直出网站
服务端渲染的网站需要启动服务去吐出页面,因此我们的Nginx
配置也是指向对应的服务。问题是项目的端口号是固定的。如果我们每个分支都人工修改项目端口号,一则可能上线时会忘记更改回来;二则还要人工确认对应端口是否被占用。显然是不合理的。
刚好公司项目都上了K8S,都是用Docker
进行部署。Docker
的特性里有一条便是将容器内的端口暴露给外部,利用这个特性,我们可以在不改变项目启动端口的情况下,对外暴露不同的访问端口了:
docker run --name $CI_COMMIT_REF_SLUG -P -d <image>:$CI_COMMIT_REF_SLUG
这里我们关注参数-P
,这代表随机指定一个系统端口给当前容器。
但是我们在测试时是需要修改bug的,每次修改完提交重新部署,肯定是希望能够维持已有端口而不是每次提交都变动。那我们可以每次重启服务的时候,先去获取已有服务的端口号:
HOSTPORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "3000/tcp") 0).HostPort}}' $CI_COMMIT_REF_SLUG)
这样一来不同分支启动服务暴露不同端口的问题也就解决了。
Nginx规则配置
静态网站
平时我们配置Nginx
的server_name
的时候,一般都是指定对应域名。但我们是可以在里面使用正则去匹配子域名的:
server {
listen 80;
server_name ~^(.+)?\.static\.test\.com$;
set $www_root $1;
root /data/vhosts/static.test.com/$www_root/;
}
以上是我静态项目的Nginx
配置示例。我的静态网站在测试环境最终访问的链接是:
http://$CI_COMMIT_REF_SLUG.static.test.com
根据上面的正则表达式,Nginx
在接到请求时,拿到了我们的分支别名后,将请求转发到项目目录下的指定分支目录了。
服务端直出网站
服务端直出网站的配置和静态网站的思路是一样的,只是把访问目录改成了访问服务而已:
server {
listen 80;
server_name ~^(.+)?.server.test.com$;
root html;
...
set $port $1;
location ~ / {
proxy_pass http://127.0.0.1:$port;
...
}
}
最终的访问地址是:
http://$port.server.test.com
whistle规则
因为实际生产中两个项目都是在客户端内部打开的,所以其实在测试时给出的两个网址和客户端配置的并不相同。这里可以用whistle
配置规则进行转换,而且还能够支持HTTPS
,非常的方便:
https://static.test.com http://$CI_COMMIT_REF_SLUG.static.test.com
https://server.test.com http://$port.server.test.com
甚至使用了whistle
之后服务端直出网站都可以不用配置Nginx
,直接通过IP访问对应的服务就可以了:
https://server.test.com http://ip:$port
部署脚本
静态网站
静态网站部署脚本相对简单,只需对代码进行打包,然后同步到对应的文件夹即可:
#!/bin/sh
CI_COMMIT_REF_SLUG="$1"
BUILD_COMMAND="yarn install --registry https://registry.npm.taobao.org; yarn build;"
TARGET_DIR="/data/vhosts/static.test.com/$CI_COMMIT_REF_SLUG/"
# 启动docker打包文件
docker run -v $(pwd)/:/app -w /app node:10 /bin/sh -c "$BUILD_COMMAND" || exit 1
# 创建指定目录,-p表示:递归创建目录,如果目录已存在也不会报错
mkdir -p $TARGET_DIR
# 同步打包后文件到对应目录
rsync -av --delete-after ./dist/ $TARGET_DIR
服务端直出网站
服务端直出网站部署脚本相对复杂,主要体现在更新时对容器的处理上:
#!/bin/sh
BUILD_COMMAND="yarn install --registry https://registry.npm.taobao.org; yarn build;"
# 执行脚本时后面传入的第一个参数
CI_COMMIT_REF_SLUG="$1"
# 编译打包项目
docker run -v $(pwd)/:/app -w /app node:10 /bin/sh -c "$BUILD_COMMAND" || exit 1
# 将项目打包到docker镜像,镜像名称带上CI_COMMIT_REF_SLUG
# 运行此命令需要项目根路径有Dockerfile文件,文件比较简单,这里就不列出了
docker build -t game-frontend:$CI_COMMIT_REF_SLUG .
# 判断是否有当前分支对应的容器在运行
if [ ! "$(docker ps -q -f name=$CI_COMMIT_REF_SLUG)" ];
then
# 如果有容器但已退出
if [ "$(docker ps -aq -f status=exited -f name=$CI_COMMIT_REF_SLUG)" ];
then
# 删除容器
docker rm $CI_COMMIT_REF_SLUG
fi
# 利用刚刚构建的镜像启动一个名为CI_COMMIT_REF_SLUG的容器
docker run --name $CI_COMMIT_REF_SLUG -P -d game-frontend:$CI_COMMIT_REF_SLUG
# 获取容器端口号,输出出来,方便查看
HOSTPORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "3000/tcp") 0).HostPort}}' $CI_COMMIT_REF_SLUG)
echo "端口号:$HOSTPORT"
else
# 如果已有容器,则获取端口号
HOSTPORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "3000/tcp") 0).HostPort}}' $CI_COMMIT_REF_SLUG)
echo "端口号:$HOSTPORT"
# 停止容器,并删除
docker stop $CI_COMMIT_REF_SLUG
docker rm $CI_COMMIT_REF_SLUG
# 根据之前端口号启动新的容器
docker run --name $CI_COMMIT_REF_SLUG -p $HOSTPORT:3000 -d game-frontend:$CI_COMMIT_REF_SLUG
fi
配置自动化部署
完成了上面的准备工作之后,就可以配置Gitlab CI了。
注册Gitlab Runner
我们在Gitlab项目页中找到setting下面的CI/CD,按照指引安装Gitlab Runner即可。这里不多说了。
编写.gitlab-ci.yml
cache:
untracked: true
stages:
- dev
before_script:
- git lfs pull
dev:
stage: dev
script:
# 这里就是上面编写的脚本
- chmod +x ./scripts/test_dev.sh
- ls -lsa ./scripts/test_dev.sh
- ./scripts/test_dev.sh $CI_COMMIT_REF_SLUG
tags:
# 指定要使用的Gitlab runner
- test-dev
至此,自动化部署的多分支测试环境就配置完成了。
遇到的问题
- 在执行部署脚本时
Gitlab runner
需要去它的工作目录以外的地方创建目录,这时候出现了权限问题。
因为是测试机,我直接将**/etc/passwd**中的gitlab-runner对于应的第三个参数user id修改为了0(root账号)便可以了:
gitlab-runner:x:0:0:GitLab Runner:/home/gitlab-runner:/bin/bash
- 执行脚本拉取公司docker仓库镜像时报权限问题,但是我机器上明明已经登录过了。这里是因为
gitlab runner
读取的是/home/gitlab-runner/.docker/
下的账号配置,我们需要在ci
中重新登录docker
。