微服务CI/CD实践(六)Jenkins & Docker 自动化构建部署Java微服务

微服务CI/CD实践系列:

微服务CI/CD实践(一)环境准备及虚拟机创建
微服务CI/CD实践(二)服务器先决准备
微服务CI/CD实践(三)gitlab部署及nexus3部署
微服务CI/CD实践(四)Jenkins部署及环境配置
微服务CI/CD实践(五)Jenkins + Dokcer 部署微服务前端VUE项目
微服务CI/CD实践(六)Jenkins + Dokcer 部署微服务后端项目
微服务CI/CD实践(七)Minio服务器部署及应用

文章目录

后端微服务项目是基于JDK1.8 + SpringCloudAlibaba框架开发,我们通过Jenkins流水线作业将后端工程打包成Docker镜像的方式进行部署。构建部署流程如下:

  • 拉取代码
  • jenkins服务器maven编译代码
  • 使用dockerfile构建镜像并打包镜像
  • 上传镜像包
  • 执行sh

一、先决条件

1.1 服务器先决条件

Jenkins 和 server服务器先决条件参考微服务CI/CD实践(二)服务器先决准备微服务CI/CD实践(四)Jenkins部署及环境配置

1.2 项目配置

后端项目基于maven Dockerfile 构建镜像。

1.2.1 Dockerfile

bash 复制代码
# docker image deploy
FROM openjdk:8-jdk-alpine
COPY  /uaa-center-server/target/app.jar /app.jar

ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dev","/app.jar"]

上述配置仅是一个最基本Java工程镜像构建脚本,如果需要集成其他插件需要对应修改脚本

以下示例展示了如何通过Dockerfile集成arshs 和 skywalking:

bash 复制代码
FROM openjdk:8-jdk-alpine
COPY  /uaa-center-server/target/app.jar /app.jar
# copy arthas
COPY ‐‐from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
# copy skywalking
COPY /uaa-center-server/agent /usr/local/agent
# 这里可以传递skywalking‐agent 配置
ENTRYPOINT [ "sh", "‐c", "java ‐javaagent:/usr/local/agent/skywalking‐agent.jar ‐
Dskywalking.agent.service_name=yourappname ‐
Dskywalking.collector.backend_service=xx.xx.xx.xx:11800 ‐Dspring.profiles.active=dev ‐
jar /app.jar" ]

可以在ENTRYPOINT 端点指定相关agent配置,也可以在docker run执行脚本中指定。

1.2.2 pom配置

后端工程pom添加maven打包依赖:

xml 复制代码
<build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot-dependencies.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version> <!-- 使用与你的 Maven 版本兼容的版本 -->
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.24</version> <!-- 使用与你的 Maven 版本兼容的版本 -->
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <compilerArgs>
                        <arg>-Aprojectlombok.classpath=${project.build.outputDirectory}</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

后端工程Dockerfile目录结构如下:

Dockerfile 和 具体的server工程在同级目录

1.2.3 部署脚本

后端服务和前端服务部署脚本基本一致,也是通过传入动态参数方式执行sh脚本,sh脚本执行步骤解释参考 微服务CI/CD实践(五)Jenkins + Dokcer 部署微服务前端VUE项目 1.2 项目配置。完整的后端部署脚本如下:

bash 复制代码
#!/usr/bin/env bash

echo "param validate"
if [ $# -lt 5 ]; then
  echo "you must use like this : /usr/docker-sh/publish_project_name.sh <container_name> <image_name> <version> [portal port] [server port] [portal ssl port] [server ssl port]"  
  exit  
fi

container_name="$1"
image_name="$2"
version="$3"
portal_port="$4"
server_port="$5"
if [ "$6" != "" ]; then
   portal_ssl_port="$6"
fi
echo "portal_ssl_port=" $portal_ssl_port
if [ "$7" != "" ]; then
   serve_sslr_port="$7"
fi

echo "执行docker ps"
docker ps
if [[ "$(docker inspect $container_name 2> /dev/null | grep $container_name)" != "" ]];
then
  echo $container_name "容器存在,停止并删除"
  echo "docker stop" $container_name
  docker stop $container_name
  echo "docker rm" $container_name
  docker rm $container_name
else
  echo $container_name "容器不存在"
fi
# 删除镜像
echo "执行docker images"
docker images
if [[ "$(docker images -q $image_name 2> /dev/null)" != "" ]];
then
  echo $image_name '镜像存在,删除镜像'
  docker rmi $(docker images -q $image_name 2> /dev/null) --force
else
  echo $image_name '镜像不存在'
fi

#bak image
echo "bak image" $image_name
BAK_DIR=/opt/bak/docker/$image_name/`date +%Y%m%d`
mkdir -p "$BAK_DIR"
cp "/opt/tmp/$container_name.tar" "$BAK_DIR"/"$image_name"_`date +%H%M%S`.tar

echo "docker load" $image_name
docker load --input /opt/tmp/$container_name.tar

echo "docker run" $image_name
# 这里因为服务本身还运行了netty,所以需要将netty的端口也做映射,如果没有仅需要配置server_port 
docker run -d -p $portal_port:$server_port -p $portal_ssl_port:$portal_ssl_port --name=$container_name --network=my-network -e TZ="Asia/Shanghai" --restart=always -v ./logs:/usr/local/server-log/uaa-center-server $image_name

echo "remove tmp " $image_name
rm -rf /opt/tmp/$container_name.tar

echo "Docker Portal is starting,please try to access $container_name conslone url"

二、Jenkins构建部署

2.1 创建项目

新建一个流水线任务

2.2 配置项目基本信息

创建完成项目,点击项目进入项目页面,点击左侧菜单》配置,进行项目基本配置
step1 项目构建历史存储策略配置

这里存储策略根据自己项目要求进行配置。

step2 配置参数化构建过程

Jenkins List Git Branches插件 构建选择指定git分支,点击添加参数选择List Git branchers选项进行Jenkins List Git Branches插件配置

Jenkins List Git Branches插件配置流程如下:

  • 配置name
  • 配置仓库并选择凭证
  • 选择Parameter Type
  • 配置Branch Filter

2.3 定义 Pipeline script

bash 复制代码
pipeline {
    agent any
    
    environment {   
        REPOSITORY="http://192.168.1.101:8929/hka/hkabackgroud/business/uaa-center.git"
		projectdir="uaa-center-pipeline"
		projectname="uaa-center-server"
		apiname="uaa-center-api"
    }
    
    stages {
        stage('获取代码') {
			steps {
				echo "start fetch code from git:${REPOSITORY} ${branch}"
				deleteDir()
				checkout([
                    $class: 'GitSCM',
                    branches: [[name: '${branch}']],
                    doGenerateSubmoduleConfigurations: false,
                    extensions: [],
                    userRemoteConfigs: [[
                        credentialsId: '2',
                        url: 'http://192.168.1.101:8929/hka/hkabackgroud/business/uaa-center.git'
                    ]]
                ])
			}
		}
		
		stage('替换') {
			steps {
				echo "start replace"
                sh " cd ${WORKSPACE}/${projectname} "
                sh " rm -f target/${projectname}.jar "
                sh "mv ${WORKSPACE}/${projectname}/src/main/resources/bootstrap.yml.example ${WORKSPACE}/${projectname}/src/main/resources/bootstrap.yml "
                sh "mv ${WORKSPACE}/${projectname}/src/main/resources/bootstrap-dev.yml.example ${WORKSPACE}/${projectname}/src/main/resources/bootstrap-dev.yml "
                			
			}
		}
		
		stage('打包') {
			steps {
				echo "start build"
			
				withMaven(maven: 'maven3.8.1') {
                    sh " mvn -f ${WORKSPACE}/${apiname}/pom.xml clean deploy -DskipDockerTag -DskipDockerPush  "
                    sh " mvn -f ${WORKSPACE}/${projectname}/pom.xml clean install -DskipDockerTag -DskipDockerPush  "
                }
			}
		}
        
        stage('Delete Old Docker Container') {
            steps {
                echo "delete docker container"
                sh '''if [[ "$(docker inspect ${projectname} 2> /dev/null | grep ${projectname})" != "" ]]; 
                then 
                  echo ${projectname} "容器存在,停止并删除"
                  echo "docker stop" ${projectname}
                  docker stop ${projectname}
                  echo "docker rm" ${projectname}
                  docker rm ${projectname}
                else 
                  echo ${projectname} "容器不存在"
                fi'''
            }
        }
        
        stage('Delete Old Docker Image') {
            steps {
                echo "delete docker image"
                sh '''if [[ "$(docker images -q ${projectname} 2> /dev/null)" != "" ]]; 
                    then 
                      echo ${projectname} \'镜像存在,删除镜像\'
                      docker rmi $(docker images -q ${projectname} 2> /dev/null) --force
                    else 
                      echo ${projectname} \'镜像不存在,创建镜像\'
                    fi'''
            }
            
        }
        
        stage('Build Docker Image') {
             steps {
                echo "start docker build ${projectname} code"
                sh 'docker build -t ${projectname} .'
                echo "save docker images tar"
                sh 'docker save -o ${projectname}.tar ${projectname}'
             }
            
        }
        
        stage('Delete New Docker Image') {
            steps {
                echo "delete docker image"
                sh '''if [[ "$(docker images -q ${projectname} 2> /dev/null)" != "" ]]; 
                    then 
                      echo ${projectname} \'镜像存在,删除镜像\'
                      docker rmi $(docker images -q ${projectname} 2> /dev/null) --force
                    else 
                      echo ${projectname} \'镜像不存在,创建镜像\'
                    fi'''
            }
        }
        
        stage('Upload img tar') {
            steps {
                sshPublisher(
                    publishers: [
                        sshPublisherDesc(
                            configName: '103',
                            transfers: [
                                sshTransfer(
                                    cleanRemote: false,
                                    excludes: '',
                                    makeEmptyDirs: false,
                                    noDefaultExcludes: false,
                                    patternSeparator: '[, ]+',
                                    remoteDirectory: '',
                                    remoteDirectorySDF: false,
                                    removePrefix: '',
                                    sourceFiles: 'uaa-center-server.tar'
                                )
                            ],
                            usePromotionTimestamp: false,
                            useWorkspaceInPromotion: false,
                            verbose: false
                        )
                    ]
                )
            }
        }
        
        stage('Execute Command sh') {
            steps {
                sshPublisher(
                    publishers: [
                        sshPublisherDesc(
                            configName: '103',
                            transfers: [
                                sshTransfer(
                                    execCommand: '/usr/docker-sh/publish_uaa-center-server.sh uaa-center-server uaa-center-server latest 10005 10005 9000 9000',
                                    execTimeout: 300000
                                )
                            ],
                            usePromotionTimestamp: false,
                            useWorkspaceInPromotion: false,
                            verbose: false
                        )
                    ]
                )
            }
        }
        
        stage('Publish Results') {
            steps {
               echo "End Publish ${projectname}"  
            }
        }

        
    }
}

2.4 构建部署项目

回到项目页面,点击参数化构建,选择用于构建的分支点击Build执行构建任务。

当jenkins 流水线执行完成后可到对应Server服务器验证服务是否部署成功

bash 复制代码
[root@k8s-rancher-node02 ~]# docker ps -a
CONTAINER ID   IMAGE                COMMAND                   CREATED       STATUS        PORTS                                                                                      NAMES
5d65d80b5350   uaa-center-server    "java -jar -Dspring...."   2 days ago    Up 31 hours   0.0.0.0:9000->9000/tcp, :::9000->9000/tcp, 0.0.0.0:10005->10005/tcp, :::10005->10005/tcp   uaa-center-server

三、其他方式构建部署后端服务

上述基于Jenkins流水线方式部署流程稍显复杂,优势是可以灵活配置构建步骤,特别是对于多环境多服务器的支持更加具有优势,比如通过Jenkins实现本地自动化部署并将镜像推送到AWS云原生平台的测试和生产环境,仅需要添加相应流水线流程即可。

bash 复制代码
stage('push image to aws ecr') {
			steps {
				echo "start push"
			script {
			    docker.withRegistry('https://xxxx.dkr.ecr.cn-north-1.amazonaws.com.cn', 'ecr:cn-north-1:aws_ecr') {
                    docker.image('${projectname}').push('${BUILD_NUMBER}')
			    }
			}
		    }
        }
        stage('push image to aws ecr us-west-2') {
			steps {
				echo "start push"
			script {
			    docker.withRegistry('https://xxxxx.dkr.ecr.us-west-2.amazonaws.com', 'ecr:us-west-2:aws_us_west_2_ecr') {
                    docker.image('${projectname}').push('${BUILD_NUMBER}')
			    }
			}
		    }
        }

如果仅在单环境部署,可以考虑使用Maven构建Docker镜像方式构建镜像,该方式流程简单。

使用Maven构建Docker镜像

step1 应用pom文件添加maven-docker-plugin配置

xml 复制代码
<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>1.1.0</version>
    <executions>
        <execution>
            <id>build-image</id>
            <phase>package</phase>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <imageName>${project.artifactId}:${project.version}</imageName>
        <dockerHost>http://registry服务器地址:2375</dockerHost>
        <baseImage>openjdk:8-jdk-alpine</baseImage>
        <entryPoint>["java", "-jar","/${project.build.finalName}.jar"]
        </entryPoint>
        <resources>
            <resource>
                <targetPath>/</targetPath>
                <directory>${project.build.directory}</directory>
                <include>${project.build.finalName}.jar</include>
            </resource>
        </resources>
    </configuration>
</plugin>

配置说明:

executions.execution.phase:此处配置了在maven打包应用时构建docker镜像;

imageName:用于指定镜像名称, p r o j e c t . a r t i f a c t I d 为镜像名称, {project.artifactId}为镜像名称, project.artifactId为镜像名称,{project.version}为仓库版本;

dockerHost:打包后上传到的docker服务器地址;

baseImage:该应用所依赖的基础镜像,此处为java;

entryPoint:docker容器启动时执行的命令;

resources.resource.targetPath:将打包后的资源文件复制到该目录;

resources.resource.directory:需要复制的文件所在目录,maven打包的应用jar包保存在target目录下面;

resources.resource.include:需要复制的文件,打包好的应用jar包。
step2 Jenkins创建项目

Jenkins---》添加Items

根据自己所有选择合适项目类型,这里我们选择Freestyle project,以便我们自定义构建流程。也可以从其他项目copy配置。

step3 配置代码源

说明:

Repository URL:远程代码仓库地址

Credentials:凭据,可选择已添加的全局凭据,或者在此页面自定义全局凭据

指定分支(为空时代表any):指定项目构建分支

构建触发器:可自定义构建触发,如钩子函数、定时任务等

step4 构建流程配置

这一步是因为项目的配置管理策略有关,本地配置均不提交到服务器,如何没有采用该策略可以忽略

点击增加构建步骤>选择执行shell步骤


构建步骤5 使用maven打包构建镜像并推送到server

点击增加构建步骤>选择顶层Maven目标步骤


构建步骤6 远程执行shell,部署镜像

点击增加构建步骤>选择远程执行shell步骤

使用maven-docker-plugin方式构建后端项目流程简单且Jenkins服务器不需要安装额外的Docker,也不需要编写Dokcerfile文件,劣势是无法灵活管理构建步骤。

相关推荐
颜淡慕潇5 小时前
【K8S问题系列 | 9】如何监控集群CPU使用率并设置告警?
后端·云原生·容器·kubernetes·问题解决
运维&陈同学5 小时前
【模块一】kubernetes容器编排进阶实战之k8s基础概念
运维·docker·云原生·容器·kubernetes·云计算
林戈的IT生涯6 小时前
一个基于Zookeeper+Dubbo3+SpringBoot3的完整微服务调用程序示例代码
微服务·rpc·dubbo
研究司马懿10 小时前
【Golang】Go语言环境安装
开发语言·后端·云原生·golang·二开
free_girl_fang11 小时前
SpringClud一站式学习之Eureka服务治理(二)
学习·云原生·eureka
Hello.Reader11 小时前
解析Eureka的架构
云原生·eureka·架构
许苑向上12 小时前
【Elasticsearch】Elasticsearch集成Spring Boot
spring boot·elasticsearch·jenkins
容器魔方12 小时前
KubeEdge 1.19.0版本发布!更完备的节点设备能力,全新的Dashboard体验
云原生·容器·云计算
qq_4337169513 小时前
编写第一个 Appium 测试脚本:从安装到运行!
自动化测试·软件测试·jmeter·ci/cd·职场和发展·appium·jenkins
东方巴黎~Sunsiny17 小时前
Elasticsearch中什么是倒排索引?
大数据·elasticsearch·jenkins