docker入门-Dockerfile完全指南

1. 基础镜像的选择

  • 尽量选择官方的镜像。没有官方镜像就尽量选择Dockerfile开源的
  • 尽量固定版本,不是每次都使用latest
  • 尽量选择体积小的镜像

2. 通过RUN执行命令

RUN主要是用于在Image内执行指令,比如安装软件、下载文件等。需要注意的每一行RUN命令都会产生一层image layer,过多的RUN命令会导致镜像的臃肿。

比如当前目录下有Dockerfile_bad和Dockerfile_good两个文件,内容如下:

Dockerfile_bad:

FROM ubuntu:21.04
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

Dockerfile_good:

FROM ubuntu:21.04
RUN apt-get update && \
     apt-get install -y wget && \
     wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
     tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
     mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
     rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

执行docker image build -f Dockerfile_bad/Dockerfile_good -t ipinfo-bad/ipinfo-good . 命令后(-f 后加上需要使用的Dockerfile文件名称,'.'表示执行的是当前目录下的Dockerfile文件),再通过docker image history 容器ID命令查看两个镜像分层情况:

在这里插入图片描述

3. 文件的复制和目录操作

3.1 文件复制

往镜像复制文件有两种方式:COPYADD

  • COPYADD都可以把本地文件复制到镜像中(本地文件的权限同样会复制到镜像中),如果目标目录不存在则会自动创建。比如执行COPY hello.py /app/hello.py,当/app这个目录不存在就会自动创建。
  • 如果复制的是压缩文件,ADD会自动解压文件,COPY不会。

3.2 目录操作

Dockerfile文件内容如下:

FROM python:3.9.5-alpine3.13
WORKDIR /app
COPY hello.py hello.py

WORKDIR /app相当于cd /app,然后执行COPY操作后就会把本地的hello.py复制到/app目录下。

4. 构建参数和环境变量

Dockerfile文件内容如下:

FROM ubuntu:21.04
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz     && \
    tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
    mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

可以看到命令中出现了多次版本号,为了方便修改,需要设置变量。此时会使用到ARGENV。两者使用方式如下:

Dockerfile-env:

FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update && \
       apt-get install -y wget && \
       wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
       tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
       mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
       rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz

Dockerfile-arg:

FROM ubuntu:21.04
ARG VERSION=2.0.1
RUN apt-get update && \
       apt-get install -y wget && \
       wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
       tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
       mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
       rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz

两者在上述代码中几乎没有区别,但是它们的作用范围是有一定区别的:

在这里插入图片描述

使用ARG创建的变量只能存在于构建镜像的时候,构建完镜像后该变量不会保存在镜像中,所以通过该镜像创建容器时也是无法使用该变量的。比如使用docker container run -it ipinfo-arg创建容器并以交互式shell进行操作,此时使用env命令查看环境变量时是没有VERSION这个变量的。

ARG创建的变量同样也有方便的地方:当使用Dockerfile-arg文件构建镜像时,可以使用docker image build -f Dockerfile-arg -t ipinfo-arg --build-arg VERSION=2.0.0 .命令中修改ARG,不需要再打开Dockerfile文件去修改ARG变量

5. 容器启动命令CMD和ENTRYPOINT

5.1 CMD

  1. CMD的作用和注意事项:

    • CMD可以用来设置容器启动时默认执行的命令

    • 如果docker container run启动容器时指定了其他命令,CMD命令会被忽略

    • 如果定义了多个CMD命令,只有最后一个会被执行

  2. CMD的使用方式:

    • 在dockerfile文件中添加。之后在创建容器后会执行'ipinfo',而不是执行ubuntu的基础镜像的'/bin/bash'
    • 在创建容器时添加,比如docker container run -it ipinfo-env ipinfo
FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
    tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
    mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
CMD ['ipinfo']

tips:

  • docker system prune -f:快速清理所有退出的容器
  • docker image prune -f :快速清理所有没有使用的镜像
  • 使用第4节的Dockerfile-env文件构建镜像后,并输入docker container run -it ipinfo-env创建容器后,为什么会默认进入shell?因为在ubuntu的基础镜像中,有一层执行了CMD ["/bin/bash"](可以通过docker image history ipinfo-env进行查看层级)
  • 每次执行docker container run -it ipinfo-env ipinfo(创建容器并执行命令)后,就会出现一个退出的容器。可以将命令改为docker container run -rm -it ipinfo-env ipinfo,这样执行完命令后就会删除该退出的容器

5.2 ENTRYPOINT

ENTRYPOINTCMD的区别:

  • CMD 设置的命令,可以在docker container run 时传入其它命令,覆盖掉 使用CMD 的命令,但是 ENTRYPOINT 所设置的命令是一定会被执行的
  • ENTRYPOINTCMD 可以联合使用,ENTRYPOINT 设置执行的命令,CMD传递参数

假设有三个dockerfile文件:

Dockerfile_CMD:

FROM ubuntu:21.04
CMD ["echo", "hello docker"]

Dockerfile_ENTRYPOINT:

FROM ubuntu:21.04
ENTRYPOINT ["echo", "hello docker"]

Dockerfile_BOTH:

FROM ubuntu:21.04
ENTRYPOINT ["echo", "hello docker"]
CMD []

使用docker imgae build -f Dockerfile_CMD/ENTRYPOINT/BOTH -t cmd/entrypoint/both .构建三个镜像:

在这里插入图片描述

执行docker container run --rm -it cmd echo"hello cmd_test"会显示hello cmd_test而不是Dockerfile_CMD文件中的hello docker

执行docker container run --rm -it entrypoint echo "hello enrtypoint_test"会显示hello docker echo hello enrtypoint_test,即Dockerfile_ENTRYPOINT文件中的内容执行了,并且命令行中的echo作为参数传入,所以一同打印出来。这就印证了 ENTRYPOINT 所设置的命令(文件中的echo)是一定会被执行的

执行docker container run --rm -it both "hello both_test"会显示hello both_test

5.3 Shell和Exec格式

CMDENTRYPOINT都存在两种格式:

① Shell格式:

CMD echo "hello docker"
ENTRYPOINT echo "hello docker"

② Exec格式:

CMD ["echo", "hello docker"]
ENTRYPOINT ["echo", "hello docker"]

两者区别:

下面的书写是没有问题的:

FROM ubuntu:21.04
ENV NAME=docker
CMD echo "hello $NAME"

但是改成Exec格式是不行的,下面会打印出hello $NAME不是hello docker

FROM ubuntu:21.04
ENV NAME=docker
CMD ["echo", "hello $NAME"]

要正常执行,需要以Shell脚本方式去执行:

FROM ubuntu:21.04
ENV NAME=docker
CMD ["sh", "-c", "echo hello $NAME"]

6. 构建flask镜像

① 创建app.py文件:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
return 'Hello, World!'

② 创建Dockerfile_flask文件:

FROM python:3.9.5-slim

COPY app.py /src/app.py

RUN pip install flask

WORKDIR /src
ENV FLASK_APP=app.py

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]

文件中的EXPOSE 5000CMD ["flask", "run", "-h", "0.0.0.0"]是为了可以在本机访问flask的web服务,后续会详细说明

③ 创建镜像:输入docker image build -f Dockerfile_flask -t flask_demo .

④ 创建容器:输入docker container run -d -p 5000:5000 flask_demo

完成上述步骤后,本机输入127.0.0.1:5000就可以看到Hello World

7. Dockerfile的使用技巧

7.1 合理使用缓存

假设修改了第6节中app.py文件中的内容,此时要重新构建Dockerfile_flask文件,按理说不需要重新再去执行pip install flak等固定操作。但是实际情况下还是要重新去安装,这就没有使用到缓存。此时将文件改为:

FROM python:3.9.5-slim
RUN pip install flask

WORKDIR /src
ENV FLASK_APP=app.py

COPY app.py /src/app.py

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]

此时再去构建镜像时就可以使用缓存,因为在原始文件中,改变了app.py文件导致COPY app.py /src/app.py之后的命令都无法使用缓存。所以要将固定不变的命令(不是指命令不改变)写在文件前面

7.2 dockerignore

执行docker image build -f Dockerfile_flask -t flask_demo .构建镜像的时候会出现一个build context

在这里插入图片描述

可以看到是具有一定大小的,这个大小取决于当前目录下的文件有多少。因为在构建镜像指定了'.',即本机当前目录,在执行COPY app.py /src/app.py才知道app.py这个文件属于本机的哪个目录下,但此时当前目录下的文件也会发送给docker的服务端,导致构建速度变慢

为了解决上述问题,可以使用.dockerignore文件,使用方式和.git文件一样,将需要忽略的文件名写入该文件中即可

假设当前目录下存在app.py、Dockerfile_BOTH、Dockerfile_CMD、Dockerfile_ENTRYPOINT、Dockerfile_flask,在构建镜像时,除了app.pyDockerfile_flak其他文件并不需要,所以当前目录下创建.dockerignore文件,将其他文件名写入到文件中,重新构建即可:

在这里插入图片描述

可以看出此时的build context已经减小了

7.3 多阶段构建

假如有一个hello.c的程序,要用Docker去做编译,然后执行可执行文件hello

hello.c:

#include <stdio.h>

void main(int argc, char *argv[])
{
 printf("hello %s\n", argv[argc - 1]);
}

Dockerfile_gcc:

FROM gcc:9.4

COPY hello.c /src/hello.c

WORKDIR /src

RUN gcc --static -o hello hello.c

ENTRYPOINT [ "/src/hello" ]

CMD []

使用Dockerfile_gcc构建镜像后会发现该镜像大约有1个多G(主要是基础镜像gcc的大小),但其实gcc镜像的作用只是运行RUN gcc --static -o hello hello.c,即完成编译功能。后续执行/src/hello是不需要gcc的环境的,在Linux系统下均可执行。

这个时候可以使用多阶段构建:

Dockerfile_new:

FROM gcc:9.4 AS builder

COPY hello.c /src/hello.c

WORKDIR /src

RUN gcc --static -o hello hello.c


FROM alpine:3.13.5

COPY --from=builder /src/hello /src/hello

ENTRYPOINT [ "/src/hello" ]

CMD []

Dockerfile_new文件将原始的dockerfile文件分为编译和执行两个阶段。其中,COPY --from=builder /src/hello /src/hello表示将gcc镜像(builder)的指定目录下的hello复制给alpine镜像下的指定目录。在构建新的镜像后,大小约为6M多(主要是基础镜像alpine的大小)

7.4 尽量使用非root用户

假如有一个用户demo,它本身不具有sudo的权限,所以就有很多文件无法进行读写操作,比如/root目录它是无法查看的,但是这个用户有执行docker的权限,也就是它在docker这个group里。因此就可以通过Docker做很多越权的事情了,比如可以把这个无法查看的/root目录映射到docker container里,此时可以自由进行查看了。

哪如何使用非root用户?可以在Dockerfile文件中进行以下修改:

  • 通过groupadduseradd创建一个组和用户
  • 通过USER指定后面的命令要以指定用户的身份运行
FROM python:3.9.5-slim

RUN pip install flask && \
    groupadd -r flask && useradd -r -g flask flask && \
    mkdir /src && \
    chown -R flask:flask /src

USER flask

COPY app.py /src/app.py

WORKDIR /src
ENV FLASK_APP=app.py

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]

8.参考

https://dockertips.readthedocs.io/en/latest/dockerfile-guide.html

https://github.com/docker-library/official-images

https://docs.docker.com/engine/reference/builder/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值