Dockerfile 由浅入深, 理解 docker build, dockerfile 最佳实践思想

发布时间:2023年12月30日

在这里插入图片描述

本文来自我的博客地址

Dockerfile 目标:易管理、少漏洞、镜像小、层级少、利用缓存。

Dockerfile 最佳实践

  • 不要安装无效软件包。

  • 应简化镜像中同时运行的进程数,理想状况下,每个镜像应该只有一个进程。

  • 当无法避免同一镜像运行多进程时,应选择合理的初始化进程(initprocess)。

  • 最小化层级数

    • 最新的docker只有RUN,COPY,ADD创建新层,其他指令创建临时层,不会增加镜像大小。
      • 比如EXPOSE指令就不会生成新层。
    • 多条RUN命令可通过连接符连接成一条指令集以减少层数。
    • 通过多段构建减少镜像层数。
  • 把多行参数按字母排序,可以减少可能出现的重复参数,并且提高可读性。

  • 编写dockerfile的时候,应该把变更频率低的编译指令优先构建以便放在镜像底层以有效利用buildcache。

  • 复制文件时,每个文件应独立复制,这确保某个文件变更时,只影响改文件对应的缓存。

理解 Dockerfile

Dockerfile 可以看成构建镜像的一条条指令, 结合 docker 镜像打包理解, 一条指令大多对应一层, 追求共用层

层的概念:

image-20231217200604743

docker build 构建镜像

$ docker build [选项] <上下文路径/URL/->

通过 docker build 构建镜像会自动扫描 Dockerfile 及其目录下的子目录, 通常将 Dockerfile 置于项目根目录, 如果文件文件过多, 镜像过程也会变慢

Dockerfile 常用指令

image-20231217201317670

image-20231217201335202

From

指定镜像, 默认从 docker hub 上拉去

LABEL

用于为镜像添加元数据

格式: LABEL <key>=<value> <key>=<value> <key>=<value> ...

配合 label filter 进行过滤

$ docker images -f label=multi.label1="value1"

注:
  使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据
  之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。

ENV

设置环境变量

RUN

构建镜像时执行的命令

格式:
    RUN ["executable", "param1", "param2"]
示例:
    RUN ["executable", "param1", "param2"]
    RUN apk update
    RUN ["/etc/execfile", "arg1", "arg1"]

注:RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,
可以在构建时指定–no-cache参数,如:docker build --no-cache

CMD

构建镜像后调用,也就是在容器启动时才进行调用。

格式:
    CMD ["executable","param1","param2"] (执行可执行文件,优先)
    CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
    CMD command param1 param2 (执行shell内部命令)
示例:
    CMD echo "This is a test." | wc -l
    CMD ["/usr/bin/wc","--help"]

注:CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。

ENTRYPOINT

镜像的第一个进程

格式:
    ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
    ENTRYPOINT command param1 param2 (shell内部命令)
示例:
    FROM ubuntu
    ENTRYPOINT ["ls", "/usr/local"]
    CMD ["/usr/local/tomcat"]
  之后,docker run 传递的参数,都会先覆盖cmd,然后由cmd 传递给entrypoint ,做到灵活应用

注:ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,
而docker run命令中指定的任何参数,都会被当做参数再次传递给CMD。
Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,
而只执行最后的ENTRYPOINT指令。
通常情况下, ENTRYPOINT 与CMD一起使用,ENTRYPOINT 写默认命令,当需要参数时候 使用CMD传参

Dockerfile: ENTRYPOINT和CMD的区别 - 知乎 (zhihu.com)

WORKDIR

工作目录,类似于cd命令

ADD

将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget

格式:
    ADD <src>... <dest>
    ADD ["<src>",... "<dest>"] 用于支持包含空格的路径
示例:
    ADD hom* /mydir/          # 添加所有以"hom"开头的文件
    ADD hom?.txt /mydir/      # ? 替代一个单字符,例如:"home.txt"
    ADD test relativeDir/     # 添加 "test" 到 `WORKDIR`/relativeDir/
    ADD test /absoluteDir/    # 添加 "test" 到 /absoluteDir/

COPY

与 ADD 相似, 但不能访问网络资源, 也不能解压, 以 ADD 相比, 行为简单, 因此可控, 推荐使用

VOLUME

挂载

VOLUME ["/path/to/dir"]
示例:
	VOLUME ["/data"]
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"

注:一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:

  1. 卷可以容器间共享和重用
  2. 容器并不一定要和其它容器共享卷
  3. 修改卷后会立即生效
  4. 对卷的修改不会对镜像产生影响
  5. 卷会一直存在,直到没有任何容器在使用它

EXPOSE

暴露端口

EXPOSE <port> [<port>...]

注:EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口
如果没有暴露端口,后期也可以通过-p 8080:80方式映射端口,但是不能通过-P形式映射

USER

指定运行容器的用户/组, 不指定默认 root, 指定的 Dockerfile 中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户, 增强容器的安全性考虑, 做到 POLP(最小特权原则)。

注: 镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。

ARG

用于指定传递给构建运行时的变量(给dockerfile传参),相当于构建镜像时可以在外部为里面传参

From centos:7
ARG parameter
VOLUME /usr/share/nginx
RUN yum -y install $parameter
EXPOSE 80 443
CMD nginx -g "daemon off;"

# 可以这如下这样灵活传参
docker build --build-arg=parameter=net-tools -t nginx:01 . 

Dockerfile 模版

二进制构建 nginx

# Base images 基础镜像
FROM centos

#MAINTAINER 维护者信息
MAINTAINER Ai-feier

#ENV 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH

#ADD  文件放在当前目录下,拷过去会自动解压
ADD nginx-1.8.0.tar.gz /usr/local/  
ADD epel-release-latest-7.noarch.rpm /usr/local/  

#RUN 执行以下命令 
RUN rpm -ivh /usr/local/epel-release-latest-7.noarch.rpm
RUN yum install -y wget lftp gcc gcc-c++ make openssl-devel pcre-devel pcre && yum clean all
RUN useradd -s /sbin/nologin -M www

#WORKDIR 相当于cd
WORKDIR /usr/local/nginx-1.8.0 

RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install

RUN echo "daemon off;" >> /etc/nginx.conf

#EXPOSE 映射端口
EXPOSE 80

#CMD 运行以下命令
CMD ["nginx"]

apt 构建 nginx

# Base images 基础镜像
FROM ubuntu

RUN apt-get update && apt-get install nginx -y && apt-get clean

CMD ["nginx"]

构建springboot应用

FROM openjdk:8-jre # jar包基于jdk ,war包基于tomcat
WORKDIR /app
ADD demo-0.0.1-SNAPSHOT.jar app.jar # 将上下文中 jar包复制到 /app目录下,并且重命名为app.jar
EXPOSE 8081 # 暴露端口
ENTRYPOINT[ "java" , "-jar" ] # 启动应用固定命令
CMD ["app.jar"] # 动态传递jar包名

# CMD ["/bin/sh","-c","java -jar app.jar"]

在 k8s 优雅终止时, 会发送 terminal 信号, 给应用进行优雅终止, 但是, /bin/sh 会无视这个信号, 最后直接被 kill 掉

Dockerfile 多段构造

目的: 将项目编译, 依赖等, 放到 Dockerfile 的早期构建, 最后留下一个干净的镜像, 有效减少镜像层级

示例:

FROM golang:1.16-alpine AS build
RUN apkadd --no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project(只有这个二进制文件是产线需要的,其他都是waste)

FROM scratch
# 直接把镜像编译的包拷贝下来
COPY --from=build /bin/project /bin/project  
ENTRYPOINT ["/bin/project"]
CMD ["--help"]
文章来源:https://blog.csdn.net/m0_51964671/article/details/135256275
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。