通过Jenkins自动化构建
续之前Jenkins相关的实验,这次的服务复杂一些,docker-compose.yml的文件包它包含7个service.
项目概览
项目是前后端分离项目,后端是采用SpringBoot的多模块项目,项目结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| +---backend # 后端
| +---src
| \---target
+---frontend # 前端
| +---dist # 前端生成文件
| \---src
+---others # 后端依赖的模块或拓展功能
| +---mqtt # mqtt的身份验证模块
| +---src
| \---target
+---data # 数据库建表及数据,配置
| +---conf # 配置
| +---init # 用于mysql镜像初始化数据,控制多个sql文件执行顺序
| \---sql # sql语句
+---conf # 配置
| +---emqx # mqtt相关配置
| \---nginx # nginx配置
+---dockerfile # 构建自定义镜像的文件
+---shell # 脚本
\---docker-compose.yml # 所有服务构建配置
|
问题
Jenkins是通过docker构建运行的,此次自动化构建采用Pipeline进行,接下来介绍中间遇到的各种问题,仅供参考;
Pipeline无法构建前端
通过日志发现是找不到npm指令,由于是采用的nvm对node进行管理,参考网上装nodejs插件文章也无法解决,一直报错 /usr/bin/env: node: No such file or directory
;
最后将nvm安装目录及/etc/profile.d/nvm.sh挂载到Jenkins,在前端构建阶段通过source指令使nvm指令生效,然后切换到对应的nodejs版本,如此也没必要安装nodejs插件了,pipeline部分如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| stage('build-frontend') {
steps {
dir("./frontend") {
sh '''
source /etc/profile.d/nvm.sh
nvm use 16.15.0
npm cache clean –force
npm cache verify
if [ -f dist ]; then rm dist -rf; fi;
pnpm config get registry
pnpm config set registry https://registry.npmmirror.com/
pnpm install
pnpm run build:prod
'''
}
}
}
|
注意:类似找不到指令的报错都可以通过挂载安装目录和使配置生效的方法来解决.
通过ssh远程操作
在全局凭据上配置好私钥后,在Pipeline最好通过插件(SSH Pipeline Steps)进行ssh,如果直接采用密码,日志上会回显,参考如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| steps {
withCredentials(bindings: [ \
sshUserPrivateKey(credentialsId: 'private-key',
keyFileVariable: 'primaryKeyVar',
passphraseVariable: 'pwdVar',
usernameVariable: 'userVar')]) {
script {
def remote = [:]
remote.name = '192.168.137.2'
remote.host = '192.168.137.2'
remote.user = userVar
remote.identityFile = primaryKeyVar
remote.allowAnyHosts = true
stage('Remote SSH') {
sshCommand remote: remote, command: "mkdir -p ${REMOTE_WORKDIR}/frontend"
// 前端打包文件
sshPut remote: remote, from: "./frontend/dist", into: "${REMOTE_WORKDIR}/frontend"
// 显示传输所有文件
sshCommand remote: remote, command: "ls -lrt ${REMOTE_WORKDIR}/**"
}
}
}
}
|
控制服务启动顺序
在docker-compose.yml如果需要控制启动顺序,单靠depends_on是不足的,它仅仅控制启动顺序,但不保证服务启动成功才启动下一个;
参考文章说,通过挂载wait-for-it.sh,容器启动通过执行该脚本判断依赖服务是否启动,指导启动成功才运行本服务,也可参考这篇文章;
然而如果镜像已经设定了entrypoint,那么还需要了解该镜像的entrypoint做了什么,只需要将wait-for-it.sh脚本添加到该镜像的entrypoint指令之前;
其中wait-for-it.sh参考https:
//github.com/vishnubob/wait-for-it,我将该文件改名为entrypoint.sh放入shell目录中,挂载到容器,设定CMD或者entrypoint即可,参考如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| service:
backend: # 后端主模块
build:
context: .
dockerfile: dockerfile/backend-Dockerfile
image: backend_app
container_name: prod-backend-app
ports:
- "13245:13245" # 端口映射
environment:
TZ: Asia/Shanghai # 时区
PORT: 13245 # 容器运行端口
PROFILE: prod # 运行环境,默认
volumes:
- ./shell/entrypoint.sh:/entrypoint.sh
depends_on:
- emq
- influx
|
名为backend的service的Dockerfile文件改名为backend-Dockerfile放入dockerfile路径中,由于后端依赖于mysql、redis,所以内容如下:
1
2
3
4
5
| FROM openjdk:8-jdk
ADD backend/target/*.jar /app.jar
CMD bash /entrypoint.sh mysqldb:3306 -t 30 -- echo "mysql started" \
&& bash /entrypoint.sh redisdb:6379 -t 20 -- echo "redisdb started" \
&& java -jar -Dfile.encoding=utf-8 -Dserver.port=${PORT} -Dspring.profiles.active=${PROFILE} /app.jar
|
消息队列emqx配置
我采用的emqx版本为4.4.2,参考了官网通过环境变量的方式配置,其中emq_web_hook插件有效但emq_http_auth插件始终不生效;
参考了Emqx的官方github上的issue也没解决,最后还得靠挂载emqx的配置才解决;
然后,还有个问题待解决,由于emqx用了http认证插件,我单独将认证的auth模块拿出来作为一个service,这时应该是auth启动完才启动emqx,但后端service在启动之前是不会连emq,emq也就不会用到auth模块的认证接口,便没有自定义构建emq镜像,但该问题还得解决;
参考我写的emq服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| service:
emq:
user: root
restart: always
container_name: prod-emqx
image: emqx/emqx:4.4.2
ports:
- "18083:18083"
- "1883:1883"
- "8083:8083"
- "8084:8084"
- "8883:8883"
volumes:
- /etc/localtime:/etc/localtime
- /root/docker/emqx/log:/opt/emqx/log
- ./conf/emqx/emqx_auth_http.conf:/opt/emqx/etc/plugins/emqx_auth_http.conf
- ./conf/emqx/emqx_web_hook.conf:/opt/emqx/etc/plugins/emqx_web_hook.conf
# TODO:存在bug,emq依赖于auth的http认证服务,取消以下两行注释会无法运行,发现emqx自带entrypoint和CMD
# - ./shell/entrypoint.sh:/entrypoint.sh
#entrypoint: bash /entrypoint.sh auth:13244 -t 50 -- echo "auth started" && /opt/emqx/bin/emqx foreground;
environment:
EMQX_ALLOW_ANONYMOUS: false # allow_anonymous
EMQX_LOADED_PLUGINS: "emqx_management,emqx_auth_http,emqx_dashboard,emqx_web_hook"
EMQX_DASHBOARD__DEFAULT_USER__LOGIN: root
EMQX_DASHBOARD__DEFAULT_USER__PASSWORD: 12345
networks:
app_net:
ipv4_address: 172.31.0.10
depends_on:
- auth
links:
- auth
|
前端构建成功但无法访问或请求本地
参考网上,大多采用nginx作为基础镜像构建新镜像,再配置nginx的转发规则;
找到一篇不错的文章,它没有构建新镜像,而是通过挂载前端文件和配置文件实现,节约了构建新镜像所需的时间和空间,文章地址为:https://juejin.cn/post/6844903837774397447;
自己配置的nginx.conf如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
| server {
listen 13246;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#Location配置
location /api/ {
rewrite /api/(.*) /$1 break;
proxy_pass http://backend:13245;
}
}
|
由于对nginx没有深入学过,遇到各种问题,经过反复尝试后,最终达到了预期.
补充些依赖配置
项目基于Vue3和SpringBoot,采用Jenkins的容器进行自动化构建和部署,然而也不能什么软件都装在容器中,最好的办法就是宿主机装常用工具软件,再挂载到容器中;
- 安装docker
1
| curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
|
- 安装jdk8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| rm /tmp/jdk*.tar.gz -f
wget https://repo.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz -P /tmp
ls -lht /tmp/ | grep jdk
mkdir -p /usr/local/java/
tar -zxf /tmp/jdk-8u202-linux-x64.tar.gz -C /usr/local/java/ && ls /usr/local/java/ -l
# 添加环境jdk1.8环境变量
cat > /etc/profile.d/java.sh <<EOF
export JAVA_HOME=/usr/local/java/jdk1.8.0_202
export JRE_HOME=\${JAVA_HOME}/jre
export CLASSPATH=.:\${JAVA_HOME}/lib:\${JRE_HOME}/lib
export PATH=\${JAVA_HOME}/bin:\$PATH
EOF
source /etc/profile.d/java.sh
ln -s ${JAVA_HOME}/bin/java /usr/bin/java
java -version
|
- 安装Maven
maven镜像有许多失效了,建议还是网上找找,下方的maven版本号改成对应的,脚本仅作参考;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| maven_version=3.9.0
rm /tmp/*maven*.tar.gz -rf
#wget https://dlcdn.apache.org/maven/maven-3/3.9.0/binaries/apache-maven-3.9.0-bin.tar.gz -P /tmp
wget https://mirrors.aliyun.com/apache/maven/maven-3/3.9.0/binaries/apache-maven-3.9.0-bin.tar.gz -P /tmp
ls -lht /tmp/ | grep maven
mkdir -p /usr/local/maven/
tar -zxf /tmp/apache-maven-*.tar.gz -C /usr/local/maven/ && ls /usr/local/maven/ -l
# 添加Maven环境变量
cat > /etc/profile.d/maven.sh << EOF
export MAVEN_VERSION="${maven_version}"
export M2_HOME=/usr/local/maven/apache-maven-\${MAVEN_VERSION}
export MAVEN_HOME=/usr/local/maven/apache-maven-3.9.0
export PATH=\${MAVEN_HOME}/bin:\${PATH}
EOF
chmod +x /etc/profile.d/maven.sh
source /etc/profile.d/maven.sh
mvn -version
|
- 安装nvm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| nvm_version=0.39.3
rm /tmp/v${nvm_version}.tar.gz -rf
wget https://github.com/nvm-sh/nvm/archive/refs/tags/v${nvm_version}.tar.gz -P /tmp
ls -lht /tmp/ | grep "v${nvm_version}.tar.gz"
mkdir -p /usr/local/nvm/
tar -zxf /tmp/v${nvm_version}.tar.gz -C /usr/local/nvm/ && ls /usr/local/nvm/ -l
# 添加nvm环境变量
cat << EOF > /etc/profile.d/nvm.sh
export NVM_VERSION="${nvm_version}"
export NVM_DIR="/usr/local/nvm/nvm-\${NVM_VERSION}"
[ -s "\$NVM_DIR/nvm.sh" ] && \. "\$NVM_DIR/nvm.sh" # This loads nvm
[ -s "\$NVM_DIR/bash_completion" ] && \. "\$NVM_DIR/bash_completion"
EOF
chmod +x /etc/profile.d/nvm.sh
source /etc/profile.d/nvm.sh
nvm version
|
- 安装docker-compose
建议docker-compose去github官网下,国内大多可下的版本太旧了;
1
2
3
| curl -L https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose -v
|
总结
通过将新学的Jenkins自动化技术应用于项目中,可以极大地节省时间,运维不必重复地手动部署,测试成了开发的一部分,只需编写测试脚本,开发人员每次代码提交会自动执行测试脚本,再通过ssh部署至远程,由传统的一个一个阶段地开发,不能直接跳过某个阶段,到如今开发能够迅速测试并投入生产得到反馈,效率倍增,或许这就是CI/CD概念及工具流行的原因。
参考
https://github.com/vishnubob/wait-for-it
https://www.cnblogs.com/wang_yb/p/9400291.html
https://juejin.cn/post/6844903837774397447
https://juejin.cn/post/7095729903684829191
2023-02-16 更新
2023-02-26 添加依赖的安装配置
2024-07-06 支持英文