多分支测试环境自动化部署

3,842 阅读5分钟

背景

现在公司内使用的是K8S进行服务部署,内网测试环境也提供了K8S环境。团队内多人同时开发多个需求,必然会出现多个需求同时测试的情况。而部署到内网K8S测试环境,一次只能部署一个服务。如果将多个需求分支合并到一个分支上去,势必会相互影响,而且使用公司的TDC服务每次更新都要人工处理,效率偏低。为了解决这个问题,我在公司内网物理机上,针对静态网站和直出网站分别搭建了多分支的测试环境,并配合Gitlab CI自行自动化部署,减少人工部署耗时。

实践路线

目前手上有两个项目,一个是正常的静态网站,一个是服务端直出的网站。它们在部署上是有不同的,静态网站我们开发完打包好静态文件部署到服务器上配置好Nginx访问指定目录访问就行了。而服务端直出的项目则需要启动一个服务,并配置Nginx将请求转发到对应的服务上。

因此针对上述情况我们需要事情如下:

  1. 根据不同的分支输出对应的静态文件或者启动不同的服务;
  2. 通过配置Nginx规则来实现通过访问URL上的信息将请求转发到对应的文件或服务;
  3. 配置Gitlab CI并编写自动化脚本进行自动部署。

如何区分不同分支

静态网站

针对静态网站,我们最终是将其部署在服务器上的某个目录中,通过Nginx转发进行访问。如果我们能够将不同分支打包到不同的目录下,并通过·Nginx配置进行访问就能实现访问。

一个项目中每个人开发需求都会取一个唯一的分支名,而在Gitlab执行CI时会提供CI_COMMIT_REF_SLUG这个字段,这个字段的值是分支名称的值全转成小写并将除0-9a-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规则配置

静态网站

平时我们配置Nginxserver_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

至此,自动化部署的多分支测试环境就配置完成了。

遇到的问题

  1. 在执行部署脚本时Gitlab runner需要去它的工作目录以外的地方创建目录,这时候出现了权限问题。

因为是测试机,我直接将**/etc/passwd**中的gitlab-runner对于应的第三个参数user id修改为了0(root账号)便可以了:

gitlab-runner:x:0:0:GitLab Runner:/home/gitlab-runner:/bin/bash
  1. 执行脚本拉取公司docker仓库镜像时报权限问题,但是我机器上明明已经登录过了。这里是因为gitlab runner读取的是/home/gitlab-runner/.docker/下的账号配置,我们需要在ci中重新登录docker