Featured image of post 通过Jenkins自动化构建

通过Jenkins自动化构建

通过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没有深入学过,遇到各种问题,经过反复尝试后,最终达到了预期.

补充些依赖配置

 项目基于Vue3SpringBoot,采用Jenkins的容器进行自动化构建和部署,然而也不能什么软件都装在容器中,最好的办法就是宿主机装常用工具软件,再挂载到容器中;

  1. 安装docker
1
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
  1. 安装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
  1. 安装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
  1. 安装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
  1. 安装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概念及工具流行的原因。

参考

  1. https://github.com/vishnubob/wait-for-it

  2. https://www.cnblogs.com/wang_yb/p/9400291.html

  3. https://juejin.cn/post/6844903837774397447

  4. https://juejin.cn/post/7095729903684829191


2023-02-16 更新

2023-02-26 添加依赖的安装配置

2024-07-06 支持英文

Licensed under CC BY-NC-SA 4.0
最后更新于 Jul 08, 2024 23:30 +0800