赞
踩
docker容器化部署是现在后端集群服务部署的主流方式,我们项目也是基于这种方式,通过swarm管理平台进行集群管理,当需要发布项目时从docker镜像仓库拉取需要发布的镜像,然后让镜像在集群的某一台机器生成容器完成发布。这就需要我们开发将自己的项目在开发完成后把项目打包成docker镜像上传到docker镜像仓库中。我们这里分别介绍一下java springboot项目在maven结构和gradle结构下,如何docker插件完成docker镜像的生成和往docker仓库推送镜像的工作。
使用插件是"io.fabric8 docker-maven-plugin",maven插件配置如下:
<plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.40.2</version> <configuration> <authConfig> <!-- <username>test</username>--> <!-- docker.server.username 这些是设置的maven全局变量 --> <username>${docker.server.username}</username> <password>${docker.server.password}</password> </authConfig> <!-- Docker 远程管理地址--> <dockerHost>http://xx.xx.xx.xx:14974</dockerHost> <!-- Docker 推送镜像仓库地址--> <pushRegistry>https://xxxxx.xxxx.com</pushRegistry> <verbose>true</verbose> <images> <image> <!--由于推送到私有镜像仓库,镜像名需要添加仓库地址--> <name>xxxx.xxx.com/${docker.server.image.group}/${project.name}:latest</name> <!--定义镜像构建行为--> <build> <dockerFile>${project.basedir}/src/main/docker/DockerFile</dockerFile> <!--定义哪些文件拷贝到容器中--> <assembly> <!--定义拷贝到容器的目录--> <targetDir>/</targetDir> <!--只拷贝生成的jar包--> <descriptorRef>artifact</descriptorRef> </assembly> </build> </image> <image> <!--由于推送到私有镜像仓库,镜像名需要添加仓库地址--> <name>registry.memeyule.com/${docker.server.image.group}/${project.name}:${project.version}</name> <!--定义镜像构建行为--> <build> <dockerFile>${project.basedir}/src/main/docker/DockerFile</dockerFile> <!--定义哪些文件拷贝到容器中--> <assembly> <!--定义拷贝到容器的目录--> <targetDir>/</targetDir> <!--只拷贝生成的jar包--> <descriptorRef>artifact</descriptorRef> </assembly> </build> </image> </images> </configuration> </plugin>
io.fabric8插件有官方文档,不过是全英文的。里面有各种通过xml的方式来表示dockerFile的内容,具体文档地址:https://dmp.fabric8.io/ 由于查阅起来比较麻烦,所以这使用了该插件直接引用dockerFile的方式,需要注意的是这里配置的docker路径是${project.basedir}/src/main/docker/DockerFile,如果是多模块项目,指的是子模块下的路径,不是根项目路径。dockerFile配置文件如下。
dockerFile配置:
FROM java:8 MAINTAINER xiaosong ENV JAVA_OPTS="-Duser.timezone=GMT+8 -Xms3g -Xmx3g -XX:MetaspaceSize=256m" #ENV TINI_VERSION v0.19.0 EXPOSE 8036 50051 COPY maven / #COPY start.sh /start.sh #COPY tini /tini # 加tini,暂时不需要了 #ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini #RUN chmod +x /tini # 通过脚本启动 # RUN chmod +x /start.sh #ENTRYPOINT ["/tini", "--"] #CMD ["/start.sh"] #ENTRYPOINT exec ./start.sh ENTRYPOINT exec java $JAVA_OPTS -jar /game-core.jar
启动脚本配置 start.sh,该脚本放在dockerFile同目录下,这要可以避免额外的配置(放在其他地方需要额外配置,否则找不到该文件),启动脚本内容如下:
exec java $JAVA_OPTS -jar /game-core.jar
这里说一下entrypoint(docker程序入口命令),有两种执行模式:exec和shell模式,exec模式如下:
ENTRYPOINT ["java","-jar","/game-core.jar"]
shell模式是这样:
ENTRYPOINT java -jar /game-core.jar
区别在于shell模式会在用docker执行该入口命令之前以shell的方式执行一遍该命令,之后再带入docker启动命令执行。而exec模式则不会,直接带入docker启动命令。这两者的区别在于,如果用shell模式,因为先执行了一次shell命令,所以docker容器中进程ID为1的进程是shell进程,而如果使用exec模式的话,进程ID为1的进程则是我们启动命令启动的进程。而当我们docker容器关闭的时候,docker会给容器id为1的进程发送一个关闭信号,我们可以用该关闭信号实现服务的优雅发布,如果我们服务是基于docker这种自动的优雅发布机制就会发现,如果不实用exec模式,这时候就会导致服务进程id不为1,从而导致优雅发布失效。那exec模式有什么缺点呢,就是入口点命令无法接收到环境变量。比如我们上面那样定义了一个JAVA_OPS的环境变量来指定java服务的jvm参数。如果我们使用exec模式,$JAVA_OPS这个参数是无不能解析成对应的环境变量的,会被docker当作一个入口命令的字符串,这往往会直接导致入口命令执行失败。那能不能既让入口命令读到环境变量也能保证我们的服务的进程ID仍然为1呢,就是上面我们这个命令:
ENTRYPOINT exec java $JAVA_OPTS -jar /game-core.jar
以shell模式执行,但是shell中有exec关键字。该关键字的作用是当执行到exec那一行命令时如果启动的是一个子进程,则该子进程会替代当前进程,所以当shell命令执行到java $JAVA_OPTS -jar /game-core.jar后,我们的java进程会替代原本的shell进程成为进程ID为1的进程。
除了这种通过后来的进程替代原本的shell进程实现,既可以接收信号,又可以在启动的时候解析环境变量,还有一些相对复杂的方式,就是使用主控系统,比如/tini,这是一个类似于微型操作系统的进程服务,我们可以使用该进程作为进程ID为1的进程,再用该进程去开启其他服务,当tini接收到信号的时候,会把接受的信号转发该给他的子服务,从而保证子服务的优雅关闭。前面也尝试过,但是子服务一直没有收到信号,导致这条路没走通,最终使用了shell的exec的方式才让环境变量的解析和优雅发布的两全其美。
除了maven结构,gradle也是java的常见项目结构,我们项目中也有部分项目使用的gradle,gradle的docker插件是这个:com.bmuschko,同样他也有他的官方文档:https://bmuschko.github.io/gradle-docker-plugin/#examples_3 这个插件中有一个简易插件叫:com.bmuschko.docker-spring-boot-application,是专门针对springboot项目的,可以很简单的配置就可以实现springboot项目的docker镜像生成:
plugins { id("com.bmuschko.docker-spring-boot-application") version "4.10.0" } apply { plugin("com.bmuschko.docker-spring-boot-application") } docker { url.set("tcp://1.1.1.1:port") registryCredentials { url.set(dockerRegistryUrl) username.set(dockerRegistryUserName) password.set(dockerRegistryPassword) } springBootApplication { baseImage.set("openjdk:8u212-b04-jdk") ports.set(listOf(8016)) maintainer.set("erpang") tag.set("$dockerRegistryHost/$dockerImageGroup/roar:$dockerImageVersion") } tasks { dockerCreateDockerfile { environmentVariable("JAVA_OPTS", "-Duser.timezone=GMT+8 -Xms5g -Xmx5g -XX:MetaspaceSize=256m") entryPoint("sh","-c","exec java \$JAVA_OPTS -cp /app/resources:/app/classes:/app/libs/* com.xxx.xxx.xxx.Application") } } }
这里可以看到我们在配置入口点的时候是[“sh”,“-c”,“exec java $JAVA_OPTS …”]
这种在exec模式下配上 sh -c,他就等价于我们shell模式了,这里这么配置的原因是因为该插件没法以shell模式进行配置,所以这样曲线救国了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。