• 怎样编写正确、高效的 Dockerfile


     


    基础镜像

    FROM 基础镜像
    基础镜像的选择非常关键:

    • 如果关注的是镜像的安全和大小,那么一般会选择 Alpine;
    • 如果关注的是应用的运行稳定性,那么可能会选择 Ubuntu、Debian、CentOS。

    构建上下文与.gitignore

    真正的镜像构建工作是由服务器端的“Docker daemon”来完成的,所以“docker”客户端就只能把“构建上下文”目录打包上传(显示信息 Sending build context to Docker daemon ),这样服务器才能够获取本地的这些文件。
    “构建上下文”其实与 Dockerfile 并没有直接的关系,它其实指定了要打包进镜像的一些依赖文件。而 COPY 命令也只能使用基于“构建上下文”的相对路径,因为“Docker daemon”看不到本地环境,只能看到打包上传的那些文件。但这个机制也会导致一些麻烦,如果目录里有的文件(例如 readme/.git/.svn 等)不需要拷贝进镜像,docker 也会一股脑地打包上传,效率很低。为了避免这种问题,你可以在“构建上下文”目录里再建立一个 .dockerignore 文件,语法与 .gitignore 类似,排除那些不需要的文件。

    # docker ignore
    *.swp
    *.sh
    *.git
    

    指令

    每个指令都会生成一个镜像层,所以 Dockerfile 里最好不要滥用指令,尽量精简合并,否则会太多的层会导致镜像臃肿不堪。

    COPY

    在本机上开发测试时会产生一些源码、配置等文件,需要打包进镜像里,可以使用 COPY 命令,它的用法和 Linux 的 cp 差不多,不过拷贝的源文件必须是“构建上下文”路径里的,不能随意指定文件。也就是说,如果要从本机向镜像拷贝文件,就必须把这些文件放到一个专门的目录,然后在 docker build 里指定“构建上下文”到这个目录才行。

    COPY ./a.txt  /tmp/a.txt    # 把构建上下文里的a.txt拷贝到镜像的/tmp目录
    COPY /etc/hosts  /tmp       # 错误!不能使用构建上下文之外的文件
    

    RUN

    Dockerfile 里最重要的一个指令 RUN ,它可以执行任意的 Shell 命令,比如更新系统、安装应用、下载文件、创建目录、编译程序等等,实现任意的镜像构建步骤,非常灵活。
    RUN 通常会是 Dockerfile 里最复杂的指令,会包含很多的 Shell 命令,但 Dockerfile 里一条指令只能是一行,所以有的 RUN 指令会在每行的末尾使用续行符 \,命令之间也会用 && 来连接,这样保证在逻辑上是一行

    RUN apt-get update \
        && apt-get install -y \
            build-essential \
            curl \
            make \
            unzip \
        && cd /tmp \
        && curl -fSL xxx.tar.gz -o xxx.tar.gz\
        && tar xzf xxx.tar.gz \
        && cd xxx \
        && ./config \
        && make
        && make clean
    

    把这些 Shell 命令集中到一个脚本文件里,用 COPY 命令拷贝进去再用 RUN 来执行:

    COPY setup.sh  /tmp/                # 拷贝脚本到/tmp目录
    
    RUN cd /tmp && chmod +x setup.sh \  # 添加执行权限
        && ./setup.sh && rm setup.sh    # 运行脚本然后再删除
    

    RUN 指令实际上就是 Shell 编程,如果你对它有所了解,就应该知道它有变量的概念,可以实现参数化运行,这在 Dockerfile 里也可以做到,需要使用两个指令 ARG 和 ENV。

    ARG IMAGE_BASE="node"
    ARG IMAGE_TAG="alpine"
    
    ENV PATH=$PATH:/tmp
    ENV DEBUG=OFF
    

    EXPOSE

    EXPOSE,它用来声明容器对外服务的端口号,对现在基于 Node.js、Tomcat、Nginx、Go 等开发的微服务系统来说非常有用:

    EXPOSE 443           # 默认是tcp协议
    EXPOSE 53/udp        # 可以指定udp协议
    

    Docker多阶段构建

    什么是多阶段构建

    多阶段构建指在Dockerfile中使用多个FROM语句,每个FROM指令都可以使用不同的基础镜像,并且是一个独立的子构建阶段。使用多阶段构建打包Java/GO应用具有构建安全、构建速度快、镜像文件体积小等优点。

    镜像构建的通用问题

    镜像构建服务使用Dockerfile来帮助用户构建最终镜像,但在具体实践中,存在一些问题:

    • Dockerfile编写有门槛
      开发者(尤其是Java)习惯了语言框架的编译便利性,不知道如何使用Dockerfile构建应用镜像。

    • 镜像容易臃肿
      构建镜像时,开发者会将项目的编译、测试、打包构建流程编写在一个Dockerfile中。每条Dockerfile指令都会为镜像添加一个新的图层,从而导致镜像层次深,镜像文件体积特别大

    • 存在源码泄露风险
      打包镜像时,源代码容易被打包到镜像中,从而产生源代码泄漏的风险。

    多阶段构建优势

    针对Java这类的编译型语言,使用Dockerfile多阶段构建,具有以下优势:

    • 保证构建镜像的安全性
      当您使用Dockerfile多阶段构建镜像时,需要在第一阶段选择合适的编译时基础镜像,进行代码拷贝、项目依赖下载、编译、测试、打包流程。在第二阶段选择合适的运行时基础镜像,拷贝基础阶段生成的运行时依赖文件。最终构建的镜像将不包含任何源代码信息。

    • 优化镜像的层数和体积
      构建的镜像仅包含基础镜像和编译制品,镜像层数少,镜像文件体积小。

    • 提升构建速度
      使用构建工具(Docker、Buildkit等),可以并发执行多个构建流程,缩短构建耗时。

    使用多阶段构建Dockerfile

    以Java Maven项目为例,在Java Maven项目中新建Dockerfile文件,并在Dockerfile文件添加以下内容。

    该Dockerfile文件使用了二阶段构建。

    1. 第一阶段:选择Maven基础镜像(Gradle类型也可以选择相应Gradle基础镜像)完成项目编译,拷贝源代码到基础镜像并运行RUN命令,从而构建Jar包。
    2. 第二阶段:拷贝第一阶段生成的Jar包到OpenJDK镜像中,设置CMD运行命令。
    # First stage: complete build environment
    FROM maven:3.5.0-jdk-8-alpine AS builder
    
    # add pom.xml and source code
    ADD ./pom.xml pom.xml
    ADD ./src src/
    
    # package jar
    RUN mvn clean package
    
    # Second stage: minimal runtime environment
    From openjdk:8-jre-alpine
    
    # copy jar from the first stage
    COPY --from=builder target/my-app-1.0-SNAPSHOT.jar my-app-1.0-SNAPSHOT.jar
    
    EXPOSE 8080
    
    CMD ["java", "-jar", "my-app-1.0-SNAPSHOT.jar"]
    

    go项目两阶段构建示例

    # First stage: complete build environment
    FROM golang:1.17 AS builder
    ENV GOSUMDB=off
    ENV GOPROXY=https://goproxy.cn,direct
    WORKDIR /go/src
    
    # compile
    COPY . .
    RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o httpserver main.go
    
    # Second stage: minimal runtime environment
    # copy binary file
    FROM alpine:3.9.4
    RUN mkdir /app \
         # 日志路径,务必和应用中日志路径保持一致
        && mkdir /var/log/httpserver \
        && addgroup -g 10001 httpserver \
        && adduser -S -u 10001 -G httpserver httpserver
    COPY --from=builder /go/src/httpserver /app/httpserver
    COPY --from=builder /go/src/cert /app/cert/
    
    RUN chown -R 10001:10001 /app \
        && chown -R 10001:10001 /var/log/httpserver
    
    USER httpserver
    WORKDIR /app
    ENTRYPOINT ["./httpserver"]
    
  • 相关阅读:
    HTTPS的工作流程
    页面内Tab切换-工程问题
    相控阵雷达天线不同阵列的特性matlab仿真分析——有限扫描阵,稀疏阵,多波束阵,共形阵
    MYSQL常用语句
    Linux补充知识:
    迁移kubelet、docker和containerd工作目录
    Rust语言和curl库编写程序
    03-jenkins集成环境配置
    【大数据之Hadoop】三十七、Hadoop HA高可用
    Servlet生命周期-9
  • 原文地址:https://www.cnblogs.com/brilliantl/p/16723070.html