Mystery0の小站

Mystery0の小站

初学Docker(2)——Jenkins+Git+Docker打包应用成Docker镜像

初学Docker(2)——Jenkins+Git+Docker打包应用成Docker镜像

说在前面

虽然说跨度有点大,但是我写这文章是给有些基础的人们看的,而不是入门教程。

Jenkins

什么是Jenkins

Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。

上面的是Jenkins官方对自己的描述。

在开始之前,我们再次描述一下上一篇文章说过的开发流程:省去前面的分析步骤以及开发步骤,软件开发完成之后,我们需要在本地IDE打包,然后将打包之后的jar通过ssh或者其他方式上传到运行环境,然后停止旧版本的jar,运行新版本的jar。而每一次修改、发布,我们都需要做 build-package-upload-stop-run 的重复操作,费时而且每一次的编译都在本地,如果配置不够,那么编译时间也不短(真实经历)。

借助Git,我们能够保证在多台机器上拥有相同版本的源代码,那么我们可以将上面的流程修改为: 本地提交-运行服务器更新源代码-运行服务器编译-运行服务器重启项目 。少去了上传jar的步骤,但是一般来说运行服务器的配置并不算高,如果配置还不错,那么可能会存在性能过剩的问题,况且我们还是需要自己ssh上去执行编译命令,依旧比较繁琐。

好的,说回Jenkins,Jenkins是一个持续构建的可视化web工具,什么是持续构建呢?也就是我们上面提到的 build-package-upload-stop-run 这一套流程,不过是全自动的。

Jenkins的安装

安装步骤照着官网来就行了,对于Linux平台,官方提供了三种方式安装: Dockerwar包管理工具 ,war我没有试过,Docker说是最好不要用这种方式,因为后面我们需要与Docker一起使用,装在里面怪怪的(而且我也没成功过,已经成功了)。最后我选择通过包管理工具进行安装。

安装完成之后通过向导的设置,当询问“安装插件”时,安装建议的插件就行了,虽然还是差一些插件,需要时安装就行了。

提示:安装以及向导的过程最好还是放到互联网的环境中,不然可能会有点慢,等进入之后,可以设置插件的镜像地址,教程:

  1. 进入jenkins系统管理(Manage Jenkins)
  2. 进入插件管理(Manage Plugins)
  3. 点击高级(Advanced),修改升级站点的地址为清华大学镜像地址: https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

Jenkins 中文的方法:前往插件中心安装 Localization: Chinese (Simplified) 插件即可。(我安装了之后部分还是英语,不知道什么原因)

安装需要的插件

  • Git Plugin
  • GitLab Plugin
  • GitHub Plugin

可能还需要安装其他插件,但是记不起来了,暂时先安装这些吧。

Docker

Docker的相关概念以及安装我们已经在上一篇文章中介绍了 (安装的步骤直接用的是官网的,因为真的是被写烂了) ,所以我们在这里,需要在项目中提供一个Dockerfile文件用来构建Docker镜像,弄好Docker的相关环境变量以及配置之后,我们将打包镜像以及上传的操作放到Shell脚本中去,这样子我们的Jenkins只需要执行这一个脚本就行了。

项目的配置

因为我的项目基本上全部都是使用Gradle进行构建的,所以我这里只介绍Gradle项目的构建,除了通过Gradle打包Docker镜像的部分,其他的步骤在maven项目也是一样的,只是命令不同而已。

配置Gradle项目打包Docker镜像

在这里有两个方法,一种是通过Gradle插件进行自动打包甚至可以自动发布,另一种是通过我们编写的Dockerfile进行打包。如果是第一种,那么就用不到Jenkins了,这一种方法我们只打包成Docker镜像并放到本地,后续的上传操作交给脚本进行。我比较推荐的是第二种方法,更加的定制化一些,而且第一种本质上也是通过Gradle配置生成Dockerfile然后打包。

关于Gradle插件,有很多,我在这里介绍 bmuschko/gradle-docker-plugin ,我觉得这个插件方便配置一些。

添加plugins

plugins {
    id 'com.bmuschko.docker-spring-boot-application' version '4.10.0'
}

然后在下面添加docker配置项:

docker {
    springBootApplication {
        baseImage = 'openjdk:8-alpine'
        ports = [8080]
        tag = 'awesome-spring-boot:1.115'
        jvmArgs = ['-Dspring.profiles.active=production', '-Xmx2048m']
    }
}

其中 baseImage 是我们的镜像的“父镜像”(还记得上次提到的 FROM 吗), ports 是需要暴露的端口,也就是 EXPOSE 部分。 tag 就不需要说了吧, jvmArgs 是运行时指定的命令。

添加之后刷新Gradle的依赖,然后我们就可以在 Gradle 页面中找到docker的相关task。

docker-study-1

这四个Task分别是:

Task名称依赖于描述
dockerSyncBuildContextclasses将应用程序文件复制到临时目录以进行映像创建。
dockerCreateDockerfiledockerSyncBuildContext为Spring Boot应用程序创建Dockerfile文件。
dockerBuildImagedockerCreateDockerfile为Spring Boot应用程序构建Docker镜像。
dockerPushImagedockerBuildImage将创建的Docker镜像推送到存储库。

从这四个Task来看也知道这个插件的流程,也是通过生成Dockerfile来构建Docker镜像的。

编写Dockerfile

我们也可以通过自己编写Dockerfile构建我们的Docker镜像,Dockerfile的相关命令知识我们在上一次已经介绍过了,所以不再过多的阐述。以下是我自己使用的Dockerfile

FROM openjdk:8-alpine
MAINTAINER mystery0dyl520@gmail.com
LABEL maintainer=mystery0
VOLUME ["/logs","/config"]
COPY build.jar run.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Duser.timezone=GMT+08","-jar","/run.jar"]
EXPOSE 8080

之所以挂载 /logs/config 目录是因为我编译的项目是将jar打包进Docker镜像,而上面的那个插件是将编译之后生成的class文件打包进镜像,所以插件生成的Dockerfile和我自己定义的不相同,而SpringBoot项目启动的时候会在指定的目录寻找配置文件(application.properties 或者 application.yaml),为了在运行时比较灵活,所以将jar所在的根目录下的 /config 暴露出来,挂载我们宿主机的目录,从而在生产环境使用生产环境的配置文件。 /logs 也是一样的原理,logback在项目中配置的日志生成目录在这个目录下,为了方便寻找错误,所以我们将这个目录放在宿主机。

COPY 命令将打包生成的jar拷贝进镜像的根目录,用来运行。 EXPOSE 暴露8080端口。

添加编译脚本build.sh

在Gradle项目中都提供了gradle的脚本,也就是项目根目录的 gradlew 以及 gradlew.bat ,bat是提供给Windows环境使用的,我们是Linux环境,所以在build.sh中使用以下命令即可:

./gradlew clean build dockerBuildImage

clean 以及 build 是清理上一次编译的文件然后重新进行编译,最后的 dockerBuildImage 就是通过插件自动打包Docker镜像到本地仓库中,执行完毕之后使用 docker images 命令就能看到对应的镜像了。

如果你使用的是自己编写Dockerfile的方法,那么最后的 dockerBuildImage Task不用添加,只需要执行 cleanbuild 命令就能得到jar了,但是需要将这个jar拷贝到指定的目录,和Dockerfile放在一起,这样子我们才能使用 docker build 进行打包镜像的操作,所以我们需要得到生成的jar的名字,路径在 build/libs 下面,但是名字根据项目的不同而不同,对应的 baseName 是从 settings.gradle 中获取的, version 是从 build.gradle 中获取的,所以我们需要把这些操作也写到脚本中:

# 获取打包的jar文件名的信息
BASE_NAME=$(cat settings.gradle | grep "rootProject.name = " | awk -F "= \"" '{print substr($NF,0,match($NF,/"/)-1)}')
VERSION_NAME=$(cat build.gradle | grep "version = " | awk -F "= \"" '{print substr($NF,0,match($NF,/"/)-1)}')
# 创建一个临时目录,用来打包Docker镜像
mkdir build/docker
cp "build/libs/$BASE_NAME-$VERSION_NAME.jar" "build/docker/build.jar"
Dockerfile文件我是放在根目录的 script/docker 下面的
cp script/docker/Dockerfile build/docker/Dockerfile
# 打包镜像
docker build -t "$DOCKER_TAG:$VERSION_NAME-$VERSION_CODE" build/docker

因为本人对于脚本不是很熟悉,默认情况下,build.gradle 和 settings.gradle中的相关配置项都是用单引号框起来的,但是我在使用 awk 命令的时候一直没法弄这个单引号,所以就把配置文件中的相关配置改成了双引号。具体如下:

docker-study-2

docker-study-3

至此,我们已经完成了从springboot项目打包Docker镜像到本地的步骤了。

上传到Registry

到了这里我们可以修改Docker镜像的tag,然后执行 docker push 命令将镜像上传到。 在此之前,我们需要一个Registry。

构建自己的Registry

可以参考这篇文章在Docker中搭建自己的Docker私服,然后将Docker镜像上传到私服中就可以了。 因为我只有一台服务器,所以如果要搭建Registry也是和Jenkins放在一起,为了Registry的验证和安全性着想,还有服务器自身的网络复杂(服务器本身没有公网ip,是通过frp将端口映射到公网服务器上,但是转发Registry的时候一直存在问题),所以我折腾了两天之后放弃了,直接使用的是阿里云的容器镜像服务。

使用阿里云的“容器镜像服务”

https://cr.console.aliyun.com/cn-hangzhou/instances/repositories 创建仓库之后里面有对应的登录命令和push、pull命令。

tag然后上传

Docker镜像上传到Registry的时候,是通过tag来区分服务器的,所以我们先用tag命令改成Registry地址,然后再push。

因为我们前面打包时的tag包含了版本号信息,我们只需要修改前面的DOCKER_TAG就行了,也可以在docker build的时候直接生成对应的tag

复制镜像

docker tag "$DOCKER_TAG:$VERSION_NAME-$VERSION_CODE" "$DOCKER_TAG:latest"

上传

docker push "$DOCKER_TAG:latest"

现在,我们所有的流程已经跑通了,一切只差Jenkins的“自动化"了。

创建Jenkins项目

docker-study-4

docker-study-5

然后创建了项目之后继续进行配置。

docker-study-6

在这里填入项目的github或gitlab地址,然后添加对应的账号以及密码,如果github启用了”两步验证“,那么密码需要填 Personal Access Token

docker-study-7

如果没有凭据,那么在这里添加一个。

docker-study-8

添加构建的操作,也就是执行我们写好的 build.sh 脚本。

docker-study-10

docker-study-9

最后,点击保存即可。

开始构建 上面的配置我们是手动的,因为对于后端项目来说不是每一次push都需要构建,所以是手动的。

docker-study-9

下面的就是构建历史,然后在构建的时候可以查看相应的日志。

docker-study-11

日志里面记录了详细的步骤和整个构建的过程,如果构建成功,那么我们就可以在Docker私服中获取构建的Docker镜像了。

至此,整个”自动化“就完成了。接下来我们可以在生产环境上登录私服,然后使用 docker pull 拉取我们的镜像,然后直接运行即可。

参考链接

手把手教你搭建Docker Registry私服

基于Docker+Jenkins+Gitlab搭建持续集成环境

Jenkins 用户手册

跟我一起学docker(11)–jenkins+github+Docker