https://mengz.me/posts/docker...java
在进行应用容器化的实践中,咱们可使用多种方式来建立容器镜像,而使用Dockerfile是咱们最经常使用的方式。
并且在实现CI/CD Pipeline的过程当中,使用Dockerfile来构建应用容器也是必须的。 git
本文不具体介绍Dockerfile的指令和写法,仅仅是在实践中积累的一些写好一个Dockerfile的小提示,体如今一下几个方面:golang
首先来看看下面这个Dockerfilespring
FROM ubuntu:18.04 COPY . /app RUN apt-get update RUN apt-get -y install ssh vim openjdk-8-jdk CMD [“java”,”-jar”,”/app/target/app.jar”]
要减少构建的时间,那咱们能够例如Docker构建的缓存特性,尽可能保留不常常改变的层,而在Dockerfile的指令中, COPY
和RUN
都会产生新的层,并且缓存的有效是与命令的顺序有关系的。
在上面的Dockerfile中,COPY . /app
在RUN apt-get ...
以前,而COPY是常常改变的部分,因此每次构建都会到致使RUN apt-get ...
缓存失效。 docker
Tip-1 : 合理利用缓存,而执行命令的顺序是会影响缓存的可用性的。 ubuntu
要减少构建时间,另外一方面是应该仅仅COPY须要的东西,对于上面这个Dockerfile的目的,应该仅仅须要COPY Java应用的jar文件。 vim
Tip-2 : 构建过程当中仅仅COPY须要的东西。 缓存
上面的Dockerfile对apt-get命令分别使用了两个RUN指令,会生成两个不一样的层。 安全
Tip-3 : 尽可能合并最终镜像的层数。 服务器
还有对于这个示例,咱们最终是想要一个JRE环境来运行Java应用,所以能够选择一个jre的镜像来做为基础镜像,这样不用花时间再去安装jdk。
Tip-4 : 选择合适的基础镜像
这样咱们能够把Dockerfile写成:
FROM ubuntu:18.04 RUN apt-get update \ && apt-get y install ssh vim openjdk-8-jdk COPY target/app.jar /app CMD [“java”,”-jar”,”/app/app.jar”]
进一步,咱们如何尽可能减少最终应用镜像的大小,来加速咱们的CI构建,以及减少镜像在网络上传输的效率。
在上例中,ssh, vim
应该都是没必要要的软件包,它们会咱用镜像的空间。
Tip-5 : 移除没必要要的软件包安装(包括一些debug工具)。
其次,相似apt-get之类的系统包管理工具会产生缓存数据,咱们也应该清除。
Tip-6 : 在使用系统包管理工具安装软件包后清理缓存数据。
另外咱们应该使用Docker提供的多阶段构建特性来减少最终的镜像大小,咱们在后面介绍。
咱们进一步改进Dockerfile:
FROM ubuntu:18.04 RUN apt-get update \ && apt-get y install –no-install-recommends openjdk-8-jdk \ && rm -rf /var/lib/apt/lists/* COPY target/app.jar /app CMD [“java”,”-jar”,”/app/app.jar”]
咱们看看上面的Dockerfile,使用了一个ubuntu
的镜像来安装jdk包,而在安装jdk包的不一样时间点,可能会致使不一样的版本,这样就致使了镜像的不易维护。
Tip-7 : 尽量使用应用(语言)运行时的官方基础镜像,并指定Tag版本
通常来讲,官方会维护一些变种镜像来提供多样性,例如基于 alpine的,还有 -slim 精简版本的,其次对于像Java应用,最终咱们须要的应该只是JRE,所以应该选择jre的镜像,这样既保证了可维护性,同时也能够减少镜像的大小。
FROM openjdk:8-jre-alpine COPY target/app.jar /app CMD [“java”,”-jar”,”/app/app.jar”]
咱们应该保障咱们的镜像构建在任什么时候候,以及任何构建服务器上是一致的,可是咱们看上面的Dockerfile,是将jar文件COPY到容器中,可是这个jar文件是在什么环境构建的呢?
Tip-8 : 在一致的环境中从源代码构建
一样,Docker的多阶段构建提供了最好的解决方案,将源码编译构建放到构建阶段,将最终生成的软件包COPY放到运行是阶段。
Tip-9 : 使用多阶段构建
FROM maven:3.6-jdk-11 AS builder WORKDIR /app COPY pom.xml . RUN mvn -e -B dependency:resolve COPY src ./src RUN mvn -e -B package FROM openjdk:11-jre-slim COPY –from=builder /app/target/app.jar / CMD [“java”,”-jar”,”/app.jar”]
例如上面的Dockerfile,咱们使用了maven
的镜像来构建代码,使用openjdk:jre
的镜像来运行。
最后咱们来看看安全性,如何使咱们的应用容器更加安全。首先,容器里包含的软件包越少,那可能的漏洞就会越少,因此这也是 Tip-5 所强调的。
Tip-10 : 使用非root用户运行容器应用进程
其次,咱们应该使用非root用户来运行咱们的应用,默认状况下容器都是使用root用户来执行,咱们可使用如下两种方法来使用非root用户来运行。
USER
指令,记得在使用USER
指令前建立相应的用户CMD
或者ENTRYPOINT
中使用su-exec
, gosu
等工具来启动应用我推荐使用第二种方法,由于第一种方式,在启动容器后进入容器会默认使用非root用户,这样不便于安装某些调试工具来执行调试(固然也能够经过配置sudo)。
而第二种方式须要安装su-exec
等工具,我建议本身基于官方的基础镜像维护一些本身的运行时基础镜像,这样避免在每次构建应用镜像的时候都进行一次安装。
FROM gradle:6.4-jdk11 as builder WORKDIR /code COPY . . RUN gradle assemble FROM mengzyou/openjdk:11-jre-alpine ENV APP_HOME="/opt/app" \ APP_USER="appuser" \ JAR_OPTS="--spring.profiles.active=prod" RUN addgroup ${APP_USER} && \ adduser -D -h ${APP_HOME} -S -G ${APP_USER} ${APP_USER} COPY --from=builder --chown=${APP_USER}:${APP_USER} /code/build/libs/*.jar ${APP_HOME}/app.jar EXPOSE 8080/tcp WORKDIR ${APP_HOME} CMD su-exec app java ${JAVA_OPTS} -jar ${APP_HOME}/app.jar ${JAR_OPTS} # CMD [“su-exec”,”appuser”,”sh -c”,”java -jar /opt/app/app.jar"]
在来一个golang的示例
FROM golang:1.14-alpine AS builder RUN apk add --no-cache git && \ mkdir -p $GOPATH/src/app \ WORKDIR $GOPATH/src/app COPY . $GOPATH/src/app RUN go mod tidy \ && go build -o /go/bin/app FROM mengzyou/alpine:3.12 ENV APP_HOME=/opt/app \ APP_USER=appuser RUN addgroup ${APP_USER} && \ adduser -D -h ${APP_HOME} -S -G ${APP_USER} ${APP_USER} COPY --from=builder --chown=${APP_USER}:${APP_USER} /go/bin/app ${APP_HOME}/ EXPOSE 8080/tcp WORKDIR ${APP_HOME} CMD su-exec ${APP_USER} ${APP_HOME}/app
这里仅仅是在Dockerfile实践中的一些提示,要写好Dockerfile,还有不少方面须要注意的地方,可参考Docker官方的Best practices for writing Dockerfiles。