• springboot项目中使用docker


    docker容器化部署是现在后端集群服务部署的主流方式,我们项目也是基于这种方式,通过swarm管理平台进行集群管理,当需要发布项目时从docker镜像仓库拉取需要发布的镜像,然后让镜像在集群的某一台机器生成容器完成发布。这就需要我们开发将自己的项目在开发完成后把项目打包成docker镜像上传到docker镜像仓库中。我们这里分别介绍一下java springboot项目在maven结构和gradle结构下,如何docker插件完成docker镜像的生成和往docker仓库推送镜像的工作。

    maven场景

    使用插件是"io.fabric8 docker-maven-plugin",maven插件配置如下:

                <plugin>
                    <groupId>io.fabric8groupId>
                    <artifactId>docker-maven-pluginartifactId>
                    <version>0.40.2version>
                    <configuration>
    
                        <authConfig>
    
                            
                            <username>${docker.server.username}username>
                            <password>${docker.server.password}password>
                        authConfig>
    
                        
                        <dockerHost>http://xx.xx.xx.xx:14974dockerHost>
                        
                        <pushRegistry>https://xxxxx.xxxx.compushRegistry>
                        <verbose>trueverbose>
    
                        <images>
                            <image>
                                
                                <name>xxxx.xxx.com/${docker.server.image.group}/${project.name}:latestname>
                                
                                <build>
                                    <dockerFile>${project.basedir}/src/main/docker/DockerFiledockerFile>
                                    
                                    <assembly>
                                        
                                        <targetDir>/targetDir>
                                        
                                        <descriptorRef>artifactdescriptorRef>
                                    assembly>
                                build>
                            image>
                            <image>
                                
                                <name>registry.memeyule.com/${docker.server.image.group}/${project.name}:${project.version}name>
                                
                                <build>
                                    <dockerFile>${project.basedir}/src/main/docker/DockerFiledockerFile>
                                    
                                    <assembly>
                                        
                                        <targetDir>/targetDir>
                                        
                                        <descriptorRef>artifactdescriptorRef>
                                    assembly>
                                build>
                            image>
                        images>
                    configuration>
                plugin>
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    启动脚本配置 start.sh,该脚本放在dockerFile同目录下,这要可以避免额外的配置(放在其他地方需要额外配置,否则找不到该文件),启动脚本内容如下:

    exec java $JAVA_OPTS -jar /game-core.jar
    
    • 1

    这里说一下entrypoint(docker程序入口命令),有两种执行模式:exec和shell模式,exec模式如下:

    ENTRYPOINT ["java","-jar","/game-core.jar"]
    
    • 1

    shell模式是这样:

    ENTRYPOINT java -jar /game-core.jar
    
    • 1

    区别在于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
    
    • 1

    以shell模式执行,但是shell中有exec关键字。该关键字的作用是当执行到exec那一行命令时如果启动的是一个子进程,则该子进程会替代当前进程,所以当shell命令执行到java $JAVA_OPTS -jar /game-core.jar后,我们的java进程会替代原本的shell进程成为进程ID为1的进程。
    除了这种通过后来的进程替代原本的shell进程实现,既可以接收信号,又可以在启动的时候解析环境变量,还有一些相对复杂的方式,就是使用主控系统,比如/tini,这是一个类似于微型操作系统的进程服务,我们可以使用该进程作为进程ID为1的进程,再用该进程去开启其他服务,当tini接收到信号的时候,会把接受的信号转发该给他的子服务,从而保证子服务的优雅关闭。前面也尝试过,但是子服务一直没有收到信号,导致这条路没走通,最终使用了shell的exec的方式才让环境变量的解析和优雅发布的两全其美。

    gradle场景

    除了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")
            }
        }
    
    
    }
    
    • 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

    这里可以看到我们在配置入口点的时候是[“sh”,“-c”,“exec java $JAVA_OPTS …”]
    这种在exec模式下配上 sh -c,他就等价于我们shell模式了,这里这么配置的原因是因为该插件没法以shell模式进行配置,所以这样曲线救国了。

  • 相关阅读:
    什么是数据可视化,为什么数据可视化很重要?
    外部中断1电下降沿平触发
    武装你的WEBAPI-OData聚合查询
    Spring的前置增强,后置增强,异常抛出增强、自定义增强
    【openwrt学习笔记】新patch的制作和旧patch的修改
    人工智能Python语音识别练习
    视频生成的发展史及其原理解析:从Gen2、Emu Video到PixelDance、SVD、Pika 1.0
    独立站shopify卖家如何玩转TikTok?
    线程安全与共享资源
    基于JAVA农村留守儿童帮扶系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
  • 原文地址:https://blog.csdn.net/qq_30095631/article/details/127832743