jenkins + docker实现java服务的cd

cd(持续部署)是非常好的软件研发实践。

本文介绍如果通过jenkins(和一些插件) + docker实现java项目的持续部署(cd)。

在教程开始前先说几个背景:

  • 方案分为3步: gradle构建、docker镜像编译及上传、docker容器部署
    • gradle构建:jenkins机器
    • docker编译上传:机器B,由于我的jenkins本身也跑在一个docker容器内,再装一层docker就比较蛋疼,所以采用over ssh远程调用另一个安装了docker的机器B完成build并上传到私有docker image 仓库
    • docker容器部署:为了简单,这里我们也使用B作为部署机
  • 代码放在gerrit上,理论上也可以用任意的git服务器,例如github

另外,为了方便重用,本文的许多变量是通过插件注入的,请大家自行理解,不再详细讲解了。

1、配置git仓库

我这里使用的是gerrit,可自行查找资料,或者参考我的开源项目docker-gerrit-test

2、jenkins安装和插件配置

基本安装可以自行查找资料,或者参考我的开源项目docker-jenkins-test

安装以下插件:插件列表:

  • gerrit插件
  • git插件
  • gradle插件
  • publish over ssh插件
  • Environment Injector Plugin插件

3、一个私有docker image仓库

基本安装可以自行查找资料,或者参考我的开源项目docker-camp2-test

4、一个使用gradle进行构建的Java项目

我这里是采用gradle进行的构建,如果你需要使用maven,替换下文对应的插件即可。

https://github.com/liheyuan/eureka-server

我这里把它放到了gerrit中,具体可以自行搜索教程

5、一个远端机器B

这里B机器安装了docker,将来的build和运行都是在这个机器上执行的。

并创建目录 $HOME/jenkins_workspace 这个是将来jenkins工作的根路径

6、配置jenkins上的一个远程登录机器

安装publish over ssh后,可以添加多个远程机器

管理员登录jenkins -> Manage Jenkins -> Publish over SSH

配置下key和host B

7、ci/cd 第1步 gradle构建

(1) 配置gradle版本

在安装了gradle插件后,还需要有一个可执行的gradle版本

Manage Jenkins -> Global Tool Configuration -> Gradle

添加一个新版本,我这里是4.2.1,不推荐远程安装,建议直接拷贝到jenkins机器的某个路径下,然后指定路径。

(2) jenkins新建一个free style项目eureka-server

eureka配置 代码管理选择git,如果没有认证,搞一下add(推荐用直接输入的方式,毕竟ssh-keygen重启容器会消失)

(3) eureka-server 配置一些常量

在Build中新建一个Executer shell 和 Inject environment variables,分别配置如下:

如上图所示,主要是将我们需要的build脚本拷贝到当前工作路径,以及一些环境变量配置,我们通过inject插件注入这些环境变量。

脚本内容如下:docker_build_springboot.sh

#!/bin/bash
set -x

if [ x"$#" != x"4" ];then
    echo "Usage: $0 <project_name> <version> <jar_filename> <expose_port>"
    exit -1
fi

PRIVATE_DOCKER_HUB="docker.coder4.com:5000"
PROJECT_NAME="$1"
PROJECT_VERSION="$2"
JAR_FILE="$3"
EXPOSE_PORT="$4"
DOCKER_IMAGE_VERSION="build_$PROJECT_VERSION"
DOCKER_IMAGE_FULL_VERSION="$PROJECT_NAME:$DOCKER_IMAGE_VERSION"

EXPOSE_CMD=""
if [ x"$EXPOSE_PORT" != x"" ];then
    EXPOSE_CMD="EXPOSE $EXPOSE_PORT"
fi

# Make Docker file
cat > Dockerfile <<EOF
FROM java:8-jdk-alpine

VOLUME /tmp /app
WORKDIR /app
COPY ${JAR_FILE} /app
$EXPOSE_CMD
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/${JAR_FILE}"]
EOF

# docker build
docker build -t $PROJECT_NAME .
docker tag $PROJECT_NAME $PRIVATE_DOCKER_HUB/$DOCKER_IMAGE_FULL_VERSION
docker push $PRIVATE_DOCKER_HUB/$DOCKER_IMAGE_FULL_VERSION

(4)eureka-server 配置 gradle编译及部署

Build中添加一个Invoke Gradle script选择刚才的gradle版本,以及task为build

现在可以Build一把了,没什么问题,会成功的!

8、ci/cd 第2步 docker镜像编译

eureka-server Build中 新增 Send files or execute command OVER SSH,选择node-1 并设置2个transfer set,如下:

上面主要是配置了docker镜像build脚本的分发以及执行,脚本在上面步骤中有了,不再重复。

此时再次执行Build,远程机器应该有类似如下结构:

/home/coder4/jenkins_workspace/eureka_server_1

并且顺利的话,会完成编译、上传到私有docker镜像的步骤

9、ci/cd 第3步 docker运行

我们可以想象下,运行这个过程其实可以重用的(每个不同镜像运行都不会有太多需要定制化的参数),有镜像名字+版本就可以了

所以我们新建一个infra -deploy工程

(1) 选择"This project is parameterized",并添加上述2个参数:

(2) Build增加Send files or execute commands over SSH的步骤

如上图,也是分发脚本,远程执行

脚本内容如下:

#!/bin/bash
set -e

if [ x"$#" != x"2" ];then
    echo "Usage: $0 <project_name> <version>"
    exit -1
fi

PRIVATE_DOCKER_HUB="docker.coder4.com:5000"
NAME="$1"
PROJECT_VERSION="$2"
DOCKER_IMAGE_VERSION="build_$PROJECT_VERSION"
DOCKER_IMAGE_FULL_VERSION="$NAME:$DOCKER_IMAGE_VERSION"

EXPOSE_PARAM=""
if [ x"$EXPOSE_PORT" != x"" ];then
    EXPOSE_PARAM="-p $EXPOSE_PORT:$EXPOSE_PORT"
fi

# remove and run
docker ps -q -a --filter "name=$NAME" | xargs -I {} docker rm -f {}
docker run \
    --network camp \
    --name $NAME \
    --hostname $NAME \
    --detach \
    docker.coder4.com:5000/$DOCKER_IMAGE_FULL_VERSION

保存成功后,我们填入eureka-server和一个成功的构建版本,一切顺利的话,应该可以在机器B上面成功运行这个实例啦!

到这里,基本的配置都已经完成了。

但是,这个ci/cd还是需要2个步骤,能不能合并成一个步骤(即编译最新代码并部署上线)呢?

当然可以!

10、ci/cd 第4步 使用pipeline串联任务

安装pipeline插件,然后新建一个pipeline任务,叫eureka -server -rocket吧

pipeline如下配置

上述分为了2个stage,会分别调用我们上文定义的编译、运行的job

贴一下代码吧:

def build_num

proj_name=env.JOB_NAME.replace("-rocket", "")

node {
    stage('build') {
        def job = build(proj_name)
        build_num = job.number
    }
    stage('deploy') {
        build job: 'infra-deploy', parameters: [
            [$class: 'StringParameterValue', name: 'BUILD_NAME', value: proj_name],
            [$class: 'StringParameterValue', name: 'BUILD_VERSION', value: build_num.toString()],
        ]
    }
}

 

好了,现在在pipeline build一把,享受cd的便捷吧~

 

Leave a Reply

Your email address will not be published. Required fields are marked *