docker-dockerfile(五)

什么是dockerfile

Dockerfile是一个组合映像命令的文本;可以使用在命令行中调用任何命令;Docker通过dockerfile中的指令自动生成镜像。

如何通过dockerfile构建镜像

通过docker build -t repository:tag ./ 即可构建,要求:./下存在Dockerfile文件

编写规则

  • 文件名必须是 Dockerfile
  • Dockerfile中所用的所有文件一定要和Dockerfile文件在同一级父目录下
  • Dockerfile中相对路径默认都是Dockerfile所在的目录
  • Dockerfile中一能写到一行的指令,一定要写到一行,因为每条指令都被视为一层,层多了执行效率就慢
  • Dockerfile中指令大小写不敏感,但指令都用大写(约定俗成)
  • Dockerfile 非注释行第一行必须是 FROM
  • Dockerfile 工作空间目录下支持隐藏文件(.dockeringore),类似于git的.gitingore

常用指令

FROM

在Dockerfile中的FROM关键字用于指定基础镜像,它定义了你的镜像是基于哪个已有的镜像构建而来的

FROM <image>:<tag> [as other_name]      # tag可选;不写默认是latest版

如:

FROM ubuntu:20.04

LABEL

LABEL 指令在 Dockerfile 中用于向镜像添加元数据。这些元数据以键值对的形式存在,提供了关于镜像的信息,如作者、版本、描述等。这些标签并不影响运行时的容器,而是用于帮助组织和标识镜像。

语法

LABEL key1=value1 key2=value2 ...

如:

LABEL maintainer="Your Name <your.email@example.com>" \
      version="1.0" \
      description="This is a custom Docker image for my application."

MAINTAINER

MAINTAINER 仍然在一些旧的 Dockerfile 中被支持,但官方推荐使用 LABEL 来替代。以下是 MAINTAINERLABEL 的等效用法:

COPY

COPY 指令用于将文件或目录从构建上下文(通常是 Dockerfile 所在的目录)复制到容器文件系统中。这是构建 Docker 镜像时常用的一个指令,用于添加应用程序代码、配置文件等到镜像中。

语法

COPY <源路径> <目标路径>

例如,要将当前 Dockerfile 所在目录下的 app.py 文件复制到容器的 /usr/src/app 目录,可以使用以下指令:

COPY app.py /usr/src/app/

ADD

ADD 指令与 COPY 指令类似,用于将文件、目录或远程 URL 复制到容器文件系统中。与 COPY 不同的是,ADD 具有一些额外的功能,例如支持自动解压缩 tar 文件和支持从 URL 复制文件。

语法

ADD <源路径> <目标路径>

将当前 Dockerfile 所在目录下的 app.tar.gz 文件解压并复制到容器的 /usr/src/app 目录,可以使用以下指令:

ADD app.tar.gz /usr/src/app/

下载并解压

ADD https://example.com/file.tar.gz /usr/src/

WORKDIR

类似于cd命令,为了改变当前的目录域,WORKDIR 是一个非常有用的指令,因为它可以使 Dockerfile 更清晰,避免重复输入相同的路径,并确保在后续的命令中相对路径的正确性。

此后RUN、CMD、ENTRYPOINT、COPY、ADD等命令都在此目录下作为当前工作目录

WORKDIR /path/to/directory

WORKDIR /path/to/directory

ENV

ENV 指令用于在 Dockerfile 中设置环境变量。环境变量可以在容器中的运行时进程中使用,也可以在构建过程中用于传递参数或配置。

语法

ENV key=value

dockerfile定义环境变量并引用

ENV BASE_PATH=/usr
ENV APP_HOME=$BASE_PATH/src/app

使用环境变量的方式

$varname
${varname}
${varname:-default value}           # 设置一个默认值,如果varname未被设置,值为默认值
${varname:+default value}           # 设置默认值;不管值存不存在都使用默认值

 

USER

指令用于指定在容器中运行后续命令的用户名或用户ID。这允许你切换到一个非特权用户,以提高安全性。

USER <用户名或用户ID>

RUN

RUN 指令用于在 Dockerfile 中执行命令。它可以运行任何命令,包括安装软件包、下载文件、编译代码等。每个 RUN 指令都会在容器中创建一个新的层,并在该层中执行指定的命令。

RUN 在下一次建构期间,会优先查找本地缓存,若不想使用缓存可以通过--no-cache解除

docker build --no-cache

语法

RUN <command>
RUN ["executable","param1","[aram2]"] //RUN ["/bin/bash","-c","echo hello world"]

 

如:在容器中安装一个软件包,可以使用:

在这个例子中,apt-get update 更新包列表,然后 apt-get install -y package-name 安装指定的软件包。

RUN apt-get update && apt-get install -y package-name

可以将多个命令合并成一行,使用 && 连接,可以减少构建层数,从而减小镜像大小。RUN 指令在构建时执行,而不是在容器运行时。如果需要在容器启动时执行命令,可以使用 CMDENTRYPOINT

EXPORT

EXPOSE 指令用于声明容器内部服务监听的网络端口。它并不会实际打开或映射这些端口,只是向Docker守护进程和人类阐明该容器将使用指定的网络端口。

它只是提供了一种标记机制,使得在运行时通过 -p 选项将这些端口映射到宿主机上变得更加方便

如:

使得宿主机的 8080 端口与容器的 80 端口相连

docker run -p 8080:80 my_image

 

这样,运行容器的时候可以更容易地将这些端口映射到宿主机器上。

语法

EXPOSE <port> [<port>/<protocol>...]

例:

EXPOSE 80

多端口

EXPOSE 80 443

指定协议

EXPOSE 80/tcp 443/tcp 8080/udp

VOLUME

实现挂载功能,将宿主机目录挂载到容器中

VOLUME ["/data"]                    # [“/data”]可以是一个JsonArray ,也可以是多个值

VOLUME /var/log 
VOLUME /var/log /opt
  • VOLUME类似于docker run -v /host_data /container_data 。
  • 一般不需要在Dockerfile中写明,且在Kubernetes场景几乎没用

CMD

指令用于指定在容器启动时要运行的默认命令。该命令可以包含可执行文件、脚本或任何在容器启动时执行

  1. 这将在容器启动时执行 echo "Hello, World!"

CMD 指令可以在 Dockerfile 中出现多次,但只有最后一条 CMD 指令会生效,因为每个 CMD 指令都会覆盖前面的设置。

如果在运行容器时指定了命令,则会覆盖 CMD 指令中的默认命令如:

docker run my_image echo "Custom Hello, World!"

语法

CMD ["executable","param1","param2"]
CMD command param1 param2

CMD 指令有两种形式:

Shell 形式:

在这种形式下,command 会被解释为在 /bin/sh -c 中执行。例如:

CMD echo "Hello, World!"

这将在容器启动时执行 echo "Hello, World!"

exec形式

CMD ["echo", "Hello, World!"]

ENTRYPOINT

主要用于指定启动的父进程 pid=1

ENTRYPOINT 指令用于配置容器启动时执行的默认命令。与 CMD 不同,ENTRYPOINT 的参数不会被覆盖,

而是作为默认命令的参数。如果在运行容器时提供了命令,则会覆盖 CMD,但不会覆盖 ENTRYPOINT可以通过增加--entrypoint 参数执行覆盖

语法

ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

如:

FROM ubuntu
ENTRYPOINT ["echo", "Hello, World!"]

ARG

ARG 指令用于在构建时设置变量(构建时参数)。这些变量在构建过程中可用,但在运行时不会保存在最终的镜像中。它允许你在构建 Docker 镜像时传递参数,从而定制镜像的行为。

ARG version=latest
FROM ubuntu:${version}

在构建 Docker 镜像时,可以通过 --build-arg 选项传递参数。例如

docker build --build-arg version=20.04 -t my_image .

ONBUILD

ONBUILD 指令用于在当前 Dockerfile 中定义构建触发器。当这个镜像作为其他镜像的基础镜像时,ONBUILD 中定义的命令将会在子镜像构建过程中执行

例如,假设有一个基础镜像 my-base-image 包含以下 Dockerfile

ONBUILD ADD . /app/src
ONBUILD RUN make /app/src

如果你在创建一个子镜像,并将 my-base-image 作为基础镜像,并且在子镜像的 Dockerfile 中有如下内容:

FROM my-base-image

那么在构建子镜像时,ONBUILD 中定义的命令将会被触发。在这个例子中,子镜像构建过程中会将当前目录下的文件复制到 /app/src 目录,并运行 make 命令。

ONBUILD 主要用于创建可重用的基础镜像,使得其他开发者可以在不必了解底层构建逻辑的情况下构建自己的镜像。然而,由于 ONBUILD 的使用会使得镜像的构建变得复杂,因此在设计镜像时要小心使用。在 Docker 官方的最佳实践中,ONBUILD 不太被推荐使用。

STOPSIGNAL

设置停止时要发送给PID=1进程的信号

主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s。

默认的停止信号为:SIGTERM,也可以通过docker run -s指定

HEALTHCHECK

HEALTHCHECK 指令用于在 Dockerfile 中定义容器的健康检查,以确定容器是否处于正常运行状态。这有助于 Docker 守护进程监控容器的健康状态,并在需要时采取措施(例如重新启动容器)。

语法:

HEALTHCHECK [OPTIONS] CMD command

 

  • OPTIONS 是一些可选的参数,例如设置检查的时间间隔 (--interval)、超时时间 (--timeout) 等。
  • CMD command 是要执行的检查命令。

检查命令通常应返回 0 表示健康,1 表示不健康。如果命令返回非零值,Docker 将认为容器不健康。

 

 SHELL

SHELL 指令用于设置 Dockerfile 中用于执行后续命令的默认 shell。在 Dockerfile 中,默认的 shell 是 /bin/sh -c,但使用 SHELL 指令可以修改为其他 shell。

例如,要将默认 shell 修改为 bash,可以使用以下指令:

SHELL ["/bin/bash", "-c"]

在这个例子中,echo "Hello, World!" 将使用 /bin/bash -c 执行,因为我们在 Dockerfile 开头使用了 SHELL 指令来设置默认 shell。

# 设置默认 shell 为 bash
SHELL ["/bin/bash", "-c"]

# 后续的命令将使用 bash 执行
RUN echo "Hello, World!"

# 其他构建步骤...

Dockerfile 编写的最佳实践规范

选⽤最⼩化基础镜像

编写 Dockerfile 时,通常使⽤⼀个通⽤的容器镜像作为基础镜像,

例如: eclipse-temurin:8-alpine ,选⽤最⼩化基础镜像,即只包含项⽬确实需要的系统⼯具和库的镜像,较⼩的基础镜像可以确保在⼀个新节点上拉起容器

时有更快的启动时间(节省了从镜像仓库拉取镜像的⽹络请求时间),并且能够最⼩化系统的攻击⾯,确保所⽤的操作系统是安全的,⼀般推荐采⽤以 alpine 系统为基础的基础镜像。

# 举一些例子
golang:1.11-alpine3.9
golang:1.15
openjdk:8
adoptopenjdk:8u242-b08-jdk-hotspot-bionic
node:10-alpine
node:16.14.0
node:12-buster-slim
python:3.7-stretch
python:3.7-slim
python:3.7-alpine
nginx:alpine
busybox:alpine
centos:alpine

避免不需要的包

为了降低复杂性、减少依赖、减少安全⻛险、减⼩镜像⽂件⼤⼩、节约构建时间,应该避免构建镜像过程中安装任何不必要的包,例如:不需要在应⽤镜像内包含⽂本编辑器(vim)等。

每个容器只运⾏⼀个进程或应⽤

尽管单个容器确实可以运⾏多个应⽤程序,但出于以下原因,需要尽可能考虑遵循“每个容器⼀个应⽤程序“(以下简称单进程容器)的最佳实践:

单进程容器架构更简单,⽔平伸缩更加容易,⽐如,在⼀个容器内同时包含 tomcat 和 mysql 等,容器的⽔ 平伸缩就会变得⾮常复杂甚⾄⽆法进⾏⽔平伸缩。
单进程容器的排障更简单,进⾏问题排查时,不必对容器内整个系统的各个部分进⾏排查,使应⽤更具有可移 植性和可预测性。
单进程容器使应⽤程序的⽣命周期管理更灵活,应⽤进程即容器主进程,应⽤异常退出容器即会⾃动销毁并重 新启动,使应⽤拥有故障⾃愈的能⼒。
从安全性和隔离性⻆度来看,单进程容器能够提供更安全的服务和应⽤程序间的隔离,以保持更稳定的安全状 态。

构建时使⽤.dockerignore ⽂件

使⽤ Dockerfile 构建镜像时建议将 Dockerfile 放置⼀个新的空⽬录下,将构建所需要的⽂件添加到该⽬录中,为 了提⾼构建镜像的效率,
可以在该⽬录下新建⼀个 .dockerignore ⽂件来指定要忽略的⽂件和目录。 .dockerignore ⽂件的排除模式语法和 git 的 .gitignore ⽂件相似。

最⼩化镜像层数

构建镜像时,Dockerfile 的每⼀条 RUN 指令都会在镜像上构建⼀层,为了减⼩镜像⽂件⼤⼩,建议在 Dockerfile可读性(也包括⻓期的可维护性)和减⼩层数之间做⼀个平衡,
⽐如,将多个 RUN 指令进⾏合并执⾏(通过 &&符串连多条 shell 命令),可有效减少镜像的层数。

合理使⽤构建缓存

镜像的构建过程中,会顺序执⾏ Dockerfile 中的指令,在执⾏每条指令之前,会先从缓存中查找是否已经存在可重 ⽤的镜像,如果有就使⽤现存的镜像,不再重复创建,以加快镜像构建过程。如果不想在构建过程中使⽤缓存,可 在 docker build 命令中使⽤ --no-cache=true 选项,镜像缓存的基本规则包含:

从⼀个基础镜像开始(FROM 指令指定),下⼀条指令将和该基础镜像的所有⼦镜像进⾏匹配,检查这些⼦ 镜像被创建时使⽤的指令是否和被检查的指令完全⼀样。如果不是,则缓存失效。
在⼤多数情况下,只需要简单地对⽐ Dockerfile 中的指令和⼦镜像。然⽽,有些指令需要更多的检查和解 释。
对于 ADD 和 COPY 指令,镜像中对应⽂件的内容也会被检查,每个⽂件都会计算出⼀个校验和。⽂件的最后 修改时间和最后访问时间不会纳⼊校验。在缓存的查找过程中,会将这些校验和和已存在镜像中的⽂件校验和 进⾏对⽐。如果⽂件有任何改变,⽐如内容和元数据,则缓存失效。
除了 ADD 和 COPY 指令,缓存匹配过程不会查看临时容器中的⽂件来决定缓存是否匹配。例如,当执⾏完 RUN apt-get -y update 指令后,容器中⼀些⽂件被更新,但 Docker 不会检查这些⽂件。这种情况下,只有 指令字符串本身被⽤来匹配缓存。
⼀旦缓存失效,所有后续的 Dockerfile 指令都将产⽣新的镜像,缓存不会被使⽤。

让应⽤程序容器内进程ID=1

当⼀个容器被销毁(即停⽌运⾏)时,操作系统会发送 SIGTERM 给容器内的1号进程(pid=1,可通过 ps -ef 命令查看),1号进程进程接收到信号后可进⾏容器终⽌前的资源释放等处理操作。
例如:构建镜像时,可利⽤系统的 exec 命令特性,为应⽤启动编写独⽴的 shell 脚本⽂件,以 exec 形式启动相关 进程

合理利⽤环境变量

应⽤镜像应具有环境⽆关性,即需要确保可以在可预测的任何环境中都⽆需重新构建应⽤镜像即可直接运⾏容器,与环境相关的相关数据应尽可能的通过环境变量⽅式在镜像中引⽤,以确保容器的可移植性和可运维能⼒。
例如:JVM启动参数 JAVA_OPTS( -Xmxn 、 -Xmsn 、 -Dproperty=value 等)、或 Spring Boot 的分区配置定义(spring.profiles.active指定的分区名)等。 出于安全考虑,应禁⽌将应⽤密钥(如:数据库密码、API访问凭据等)保存⾄镜像内.

设置健康检查

可以在K8S层面设置也可以在Dockerfile层面设置

dockerfile实践

案例1

FROM hbdev.lq.com/dev/alpine:3.14 as production #拉取基础镜像

COPY ./dist /opt/lq/filetransfer #程序文件拷贝到镜像

RUN adduser -D -H www \
    && chown -R www /opt/lq/filetransfer/configs \
    && chown -R www /opt/lq/filetransfer/logs \
    && chmod +x /opt/lq/filetransfer/docker/entry-point.sh #创建用户并设置指定目录权限

USER www #使用此用户
WORKDIR /opt/lq/filetransfer #设置工作目录

ENV DB_DRIVER "mysql"
ENV DB_HOST ""
ENV DB_PORT 3306
ENV DB_USERNAME ""
ENV DB_PASSWORD ""
ENV DB_DATABASE ""
ENV DB_DEBUG false

ENV CACHE_DRIVER "redis"
ENV CACHE_ADDR "127.0.0.1:6379"
ENV CACHE_PASSWORD "123456"

ENV AUTH_HOST ""
ENV AUTH_SERV_PORT "80"
ENV AUTH_PORT "8781"
ENV AUTH_CLIENTID ""
ENV AUTH_CLIENT_SECRET ""
ENV AUTH_STORAGE_ADDR ""
ENV AUTH_STORAGE_PWD ""
ENV RSA_KEYID ""
ENV RSA_PRIVATEKEY ""

ENV AUTH_HOST_ENT ""
ENV AUTH_SERV_PORT_ENT "80"
ENV AUTH_PORT_ENT "8781"
ENV AUTH_CLIENTID_ENT ""
ENV AUTH_CLIENT_SECRET_ENT ""
ENV AUTH_STORAGE_ADDR_ENT ""
ENV AUTH_STORAGE_PWD_ENT ""
ENV RSA_KEYID_ENT ""
ENV RSA_PRIVATEKEY_ENT ""

ENV TRACE_COLLECTOR_ENDPOINT "test:8001"
ENV TRACE_COLLECTOR_PASSWORD ""
ENV TRACE_SAMPLER_VALUE "0.01"

ENV LOG_APP_TYPE "stdout"
ENV LOG_APP_PATH "\\/opt\\/lq\\/filetransfer\\/logs"
ENV LOG_APP_LEVEL "info"

ENV LOG_ACCESS_TYPE "stdout"
ENV LOG_ACCESS_PATH "\\/opt\\/lq\\/filetransfer\\/logs"
ENV LOG_ACCESS_LEVEL "info"

ENV LOG_SERVICE_TYPE "stdout"
ENV LOG_SERVICE_PATH "\\/opt\\/lq\\/filetransfer\\/logs"
ENV LOG_SERVICE_LEVEL "info"

ENV USERSVC_GRPC_ENDPOINTS ""

ENV EVENT_MQ_TYPE "nsq"
ENV MQ_SVC_LOOKUPD_ENDPOINT ""
ENV MQ_TOPIC "lq-filetransfer-new"
ENV MQ_TOPIC_CHANNEL "filetransfer001"
ENV MQ_PRODUCE_ENDPOINT ""
ENV MQ_PRODUCE_PWD ""
ENV MQ_CONCURRENCY_NUM 5
ENV MQ_BUFF_SIZE 1

ENV REMOTE_GRPC_ENDPOINTS ""

ENV UPLOAD_ISSSL false
ENV UPLOAD_ISOSS false
ENV UPLOAD_TIMEOUTLIMIT "60"

EXPOSE 8000

ENTRYPOINT ["/opt/lq/filetransfer/docker/entry-point.sh"] #执行程序目录的sh 内部含有启动命令
#! /bin/sh

# 配置不存在先通过模板创建
if [ ! -f "/opt/lq/filetransfer/configs/config.yaml" ]; then #判断文件是否存在
      cp /opt/lq/filetransfer/configs/config.yaml.dist /opt/lq/filetransfer/configs/config.yaml #基于模版创建配置文件
    sed -i "s/%UPLOAD_TIMEOUTLIMIT%/$UPLOAD_TIMEOUTLIMIT/g" /opt/lq/filetransfer/configs/config.yaml #将环境变量进行替换到模版配置文件 如k8s设置读取
...... fi
/opt/lq/filetransfer/bin/filetransfer -c /opt/lq/filetransfer/configs/config.yaml server #启动程序

 

posted @ 2023-12-20 18:23  意犹未尽  阅读(4)  评论(0编辑  收藏  举报