前言
之前在团队中分享了qiankun微服务的单镜像的部署方案, 详细解析了单镜像部署的好处,但由于单镜像部署在构建时比较复杂,如果在上线时人工地去构建镜像,将是一个非常复杂,且容易出错的事情。所以本篇文章会介绍一种使用GitLab CI/CD来构建一个微服务单镜像的流水线,并应用于生产, 我称之为 aio 方案 all in one 。五个应用构建到一个docker镜像中。
方案探索
部署文件目录
之前分享的单镜像部署是在根目录创建一个child,将子应用的编译的静态资源都存放其中。 目录大致是这样的
└── daas-web/ # 根文件夹
|
├── child/ # 存放所有微应用的文件夹
| ├── app1/ # 存放微应用 app1 的文件夹
| ├── app2/ # 存放微应用 app2 的文件夹
| ├── app3/ # 存放微应用 app3 的文件夹
| ├── app4/ # 存放微应用 app4 的文件夹
├── index.html # mainApp的index.html
├── css/ # 主应用mainApp的css文件夹
├── js/ # 主应用mainApp的js文件夹
├── Dockerfile # 镜像构建Dockerfile文件
├── default.conf # nginx配置文件
但为了兼容现有的多镜像部署方案,并将改动降到最小。所以直接将子应用的构建目录放到根目录。于是目录就变成了这样
└── daas-web/ # 根文件夹
|
├── app1/ # 存放微应用 app1 的文件夹
├── app2/ # 存放微应用 app2 的文件夹
├── app3/ # 存放微应用 app3 的文件夹
├── app4/ # 存放微应用 app4 的文件夹
├── index.html # mainApp的index.html
├── css/ # 主应用mainApp的css文件夹
├── js/ # 主应用mainApp的js文件夹
├── Dockerfile # 镜像构建Dockerfile文件
├── default.conf # nginx配置文件
这种文件组织结构不用修改原有项目的 publicPath
有关这两种的文件组织方式可以查看一下qiankun的部署文档
GitLab CI/CD方案
基本思路是这样的
在主应用(基座)创建tag来触发流水线
首先进行流水线的初始化工作,清空或创建存放制品的目录
接着触发自身和其他应用的相同tag的流水线,并行执行多条跨项目流水线
然后将每个应用编译出来的制品dist目录都存放到制品目录
最后在制品目录中构建镜像,最后推送到harbor
拆分来讲:
在构建aio镜像时,首先需要在其他子应用创建一个相同的tag,最后在主应用创建一个相同名称的tag。
这里的顺序不能错,因为创建了主应用就会触发流水线,如果其他子应用没有相同的tag则会报错。先创建子应用tag,再创建主应用tag,并且tag名称必须保持一致,最终的镜像版本会从tag中解析出来。
由于目前已经有几个子应用使用了gitlab ci/cd来发布研发和集成环境,之前的流水线触发条件是
workflow:
rules:
- if: $CI_COMMIT_BRANCH == 'develop'
这种写法已经不满足现有要求,因为在aio方案中主应用的流水线是由tag触发,子应用的流水线是有主应用来触发的。
于是拆分出二个配置模版,模版中会配置job所用的runner,触发条件。
一个(.release_aio_config
)用于aio方案,
一个(.develop_config
)用于研发集成环境的发布。
在job中可以使用关键词extends
来继承模版,使用此方法在job中也可以覆盖模版的配置。
由于在aio的流水线中,存在制品依赖的关系,在构建docker镜像的job中,需要获取到所有应用的制品,gitlab ci/cd提供了need
关键词来是实现制品依赖–跨项目流水线中的制品,在一个job中,可以配置依赖当前流水线中的那些制品,其原理是通过web api来下载制品到当前的工作目录,顺便提下,制品都是上传到gitlab。在尝试了几次后,我发现这是一个付费版本的功能。😄😄。。。。俗话说,上帝给你关了一扇窗,必定会给你打开一扇门。于是我思考片刻,决定从物理层面“暴力突破”付费的限制。该方案就是 基于shell执行器,将各个应用的制品存放在本地。
具体做法是在服务器本地安装gitlab-runner的rpm或deb包,然后注册一个shell执行器的runner。使用该runner编译前端项目,并将各个应用的制品按照规定的目录结构存放到一个固定的“制品汇总目录”。
直接在服务器本地进行构建的,工作目录,文件都存放在本地,所以想把使用容器构建,速度更快。
唯一的缺点是依赖本地的环境(如nodejs,git),迁移麻烦。
最终的流水线概览图是这样的
由于每个子应用都是一个项目,所以这里的流水线必须要使用跨项目流水线。 这种需求在gitlab ci/cd有多种方式实现,使用api,或者使用 trigge
关键词,这里使用的是 trigge
来触发其他项目的。
有关trigger 关键词的使用可以查看该链接
以下是一个job示例
build_other_job:
stage: build
variables:
UPSTREAM_BUILD_TAG: $CI_COMMIT_TAG
trigger:
project: a/b/main-app
branch: $CI_COMMIT_TAG
strategy: depend
该job会触发项目a/b/main-app
的流水线,branch限定了触发的分支,strategy
限定了必须等待跨项目流水线完成后,再执行下一阶段,不加strategy
限定,默认是创建了跨项目流水线就执行下一阶段,这里使用variables向跨项目流水线传递了一个变量,以备后用。
流水线编写
下面开始流水线的编写
首先定义流水线的阶段,变量与缓存。
目录 /home/gitlab-runner/web
用于存放所有应用编译出的制品
stages:
- prebuild
- build
- build_docker
- track
variables:
# 所有制品应该存放的目录
ALL_ARTIFACTS_PATH: '/home/gitlab-runner/web'
default:
cache:
key: $CI_PROJECT_NAME
paths:
- node_modules
定义二个模板,一个是aio模板,一个是 触发其他项目流水线的模板,使用模板可以帮我节省很多代码,将公共部分提取出来,统一管理。这里的runner 的tag使用 shell-dass
只在创建了tag时触发。
定义模板
# 默认配置模板
.release_aio_config:
tags:
- shell-dass
only:
- tags
.build_other_config:
stage: build
variables:
UPSTREAM_BUILD_TAG: $CI_COMMIT_TAG
trigger:
project: a/b/c
branch: $CI_COMMIT_TAG
strategy: depend
only:
- tags
可以在跨项目的流水线中 传递参数 UPSTREAM_BUILD_TAG
。
预编译作业,与主应用的编译作业
ready_job:
extends: .release_aio_config
stage: prebuild
script:
# 清空用于存放制品的目录
- rm -rf $ALL_ARTIFACTS_PATH
- mkdir -p $ALL_ARTIFACTS_PATH
build_base_job:
extends: .release_aio_config
stage: build
script:
- yarn
- yarn build
- cp -rf dist/* $ALL_ARTIFACTS_PATH
预编译清空存放制品的目录。 主应用编译完成后直接将dist下的所有文件拷贝到 ALL_ARTIFACTS_PATH
目录。二个作业都继承 .release_aio_config
模板,都是用 shell-dass 这个 runner,并且都是在创建tag时触发
编译子应用的作业
build_model_job:
extends: .build_other_config
trigger:
project: a/b/c
build_service_job:
extends: .build_other_config
trigger:
project: a/b/d
build_management_job:
extends: .build_other_config
trigger:
project: a/b/e
build_dev_job:
extends: .build_other_config
trigger:
project: a/b/f
编译自定义的作业都继承.build_other_config
模板,只需要覆盖 项目地址即可。
构建docker镜像,推送到harbor,并使用钉钉通知将构建出的镜像推送给钉钉群。
creat_app:
extends: .release_aio_config
stage: build_docker
script:
- echo 'creat_app'
- cp -rf Dockerfile-AIO nginx-config.conf $ALL_ARTIFACTS_PATH
- cd $ALL_ARTIFACTS_PATH
- ls -l
- docker build -f Dockerfile-AIO -t web-aio:$CI_COMMIT_TAG .
- docker tag web-aio:$CI_COMMIT_TAG $PUSH_IMAGE_PATH:$CI_COMMIT_TAG
- docker login -u $HARBOR_USERNAME -p $HARBOR_PWD $HARBOR_SERVER
- docker push $PUSH_IMAGE_PATH:$CI_COMMIT_TAG
- docker rmi $PUSH_IMAGE_PATH:$CI_COMMIT_TAG
- echo '构建aio镜像成功 镜像地址 $PUSH_IMAGE_PATH:$VERSION '
after_script:
- echo '$PUSH_IMAGE_PATH:$VERSION'
- 'curl -H ''Content-type: application/json'' -d ''{"msgtype":"text", "text": {"content":"$PUSH_IMAGE_PATH:$VERSION"}}'' $DING_WEBHOOK'
when: on_success
到了这一步,所有的子应用构建物都已放到了 ALL_ARTIFACTS_PATH
中,只需要进入该目录构建docker镜像即可。
这些需要注意一下,镜像的 Dockerfile 与 nginx 配置文件可能要使用新建的。构建完成后,改成对应的版本号,推送到harbor中。harbor相关变量 以及镜像相关变量是定义在项目的 CI/CD变量中。
最后再编写一个处理流水线失败的job,,当流水线报错时,及时使用钉钉通知告知用户处理。这里注意
when: on_failure
当做之前的job有报错的,才会执行该job。
track_fail:
extends: .release_aio_config
stage: track
variables:
CONTENT: '@$GITLAB_USER_NAME构建aio镜像错误$CI_PIPELINE_URL'
DING_WEBHOOK: 'https://oapi.dingtalk.com/robot/send?access_token=$DING_ACCESS_TOKEN'
script:
- echo $CONTENT
- 'curl -H ''Content-type: application/json'' -d ''{"msgtype":"text", "text": {"content":"部署 $CONTENT"}}'' $DING_WEBHOOK'
when: on_failure
遇到的问题
安装gitlab-runner后,需要将用户gitlab-runner 加入到docker用户组中。
sudo usermod -aG docker gitlab-runner
git报错
fatal: git fetch-pack: expected shallow list
fatal: The remote end hung up unexpectedly
![image.png](https://img-blog.csdnimg.cn/img_convert/09d8917be92db30b9259d2631e19845d.png#clientId=ub2a73473-83bc-4&from=paste&height=430&id=ue419c90e&margin=[object Object]&name=image.png&originHeight=430&originWidth=1188&originalType=binary&ratio=1&size=38458&status=done&style=none&taskId=uc077435e-31d5-46e3-a884-f551b1a6ab6&width=1188)
升级git版本即可解决。升级到 2.13
限定跨项目流水线
使用strategy: depend
来等待其他项目流水线运行完成。
使用branch
来限定触发子项目的那个分支流水线。
nginx 配置
原有的nginx配置无法使用,需要修改。aio方案的nginx配置为。
server {
listen 80;
listen [::]:80;
server_name localhost;
gzip on;
gzip_buffers 32 4K;
gzip_comp_level 6;
gzip_min_length 100;
gzip_types application/javascript text/css text/xml;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 404 /index.html;
location = /index.html {
root /usr/share/nginx/html;
}
}
特别注意 404 转发到index.html 这条规则。
tag转为版本号的处理
将tag中的 tags/tag_
删除掉 如tag为 tags/tag_v2.7.x.20210908 ,镜像版本为 v2.7.x.20210908
${tags/tags\/tag_/}
部署完成后的网络拓扑图
http://topology.le5le.com/preview/?id=6143f2278672cb00018b9ff4&r=1
结语
吾山拔地三千尺,凌空耸翠一万年