未完待续

1 dockerfile 指令

1.1 from

FROM:选择基础镜像,推荐 alpine

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

1.2 LABEL

maintainer 废弃

按标签组织项目 (不用搞目录什么的.)

LABEL multi.label1="value1" multi.label2="value2" other="value3"

配合 label filter 可过滤镜像查询结果

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

查看镜像的label

# -A 10 看 labels 后10行
docker inspect image_name |grep labels -A 10

1.3 RUN

!!! warning 这两条命令应该永远用&&连接,如果分开执行,RUN apt-get update 构建层被缓存,可能会导致新 package 无法安装

执行命令,最常见的用法是

RUN apt-get update && apt-get install

1.4 CMD

!!! tip 1. 运行的命令会被实际执行是指定的命令覆盖 2. 定义了多个,只有最后一个会执行 3. 当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令

CMD ["executableA", "param1", "param2"...]
CMD ["executableB", "param1", "param2"...]
# 这个echo hello 会覆盖配置里的cmd, 配置里的cmd不会执行
# 在没有 ENTRYPOINT的时候,我们运行容器, 想执行什么命令就执行什么命令
docker run image_name echo hello

1.5 ENTRYPOINT

!!! tips - 定义可以执行的容器镜像入口命令 ,必定会执行,不会被覆盖 - ==在不指定entrypoint的情况下,Docker 会为你提供一个隐含的 ENTRYPOINT,即:/bin/sh -c, 所以你的cmd 是这个的参数哦== - 不过docker run 时指定–entrypoint 可以覆盖

# 我们一般 是不会这么做的...
docker run -it --entrypoint /bin/bash image_name 
# 注意覆盖的是 entrypoint 里的指令,
# 设置的cmd 还是作为 entrypoint 的参数,这里可以指定参数 /usr/share/nginx/html
docker run -it --entrypoint ls nginx  /usr/share/nginx/html

# 这个是进入容器执行, 不是说覆盖了原来的entrypoint..  别混淆了.
# 任何运行的容器,都可以这样操作..
docker exec -it image_name bash
#docker run –-entrypoint 可替换 Dockerfile 中定义的 ENTRYPOINT  
#ENTRYPOINT 的最佳实践是用 ENTRYPOINT 定义镜像主命令,并通过 CMD 定义主要参数,如下所示
ENTRYPOINT ["sed"]
CMD ["--help"]

#可以这样, 这样的话, 就看你运行的时候指定啥了.
CMD []
FROM ubuntu
RUN apt-get update && apt-get install  -y stress
ENTRYPOINT ["/usr/bin/stress"]
CMD []
docker build -t stress .
# 指定的参数-v会覆盖 dockerfile里配置的cmd
docker run --rm stress -v

1.5.1 exec模式 与 shell模式

!!! note “shell模式与exec模式的区别” shell 模式会忽略所有 CMD 命令的参数和 docker run 的命令行参数,ENTRYPOINT 要运行的命令会作为 /bin/sh -c 的子命令运行,而且 /bin/sh 不会传递信号,也就是说 ENTRYPOINT 要运行的命令不是 PID 为 1 的进程,且不会收到 Unix 信号,所以你要执行的命令不会收到 docker stop 发出的 SIGTERM 信号 (这里说的是,不会优雅退出, 就是直接停止了容器, 还没停止里面的程序, 可以写个程序测试)
一般情况下,我们停止一个服务, 是需要优雅退出的. 比如nginx ,会先让其处理已经发送过来的请求.

=== “exec模式 (推荐)” dockerfile # exec 模式 ENTRYPOINT ["executable", "param1", "param2"] dockerfile FROM ubuntu ENTRYPOINT ["top", "-b"] CMD ["-c"] bash docker build -t top . docker run --rm --name top top # 然后在另外一个会话执行 下面的, 可以看到, 执行的命令本身 pid =1 docker exec -it top ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 03:29 ? 00:00:00 top -b -c root 16 0 0 03:30 pts/0 00:00:00 ps -ef 我们可以 ctrl+c 退出 === “shell模式”

```dockerfile
ENTRYPOINT command param1 param2
```

```dockerfile
FROM ubuntu
ENTRYPOINT top -b
CMD -c
```
```bash
# 是 pid=1 的shell 进程的 子进程
docker exec -it stest ps -ef
    UID        PID  PPID  C STIME TTY          TIME CMD
    root         1     0  0 03:36 ?        00:00:00 /bin/sh -c top -b /bin/sh -c -c
    root         7     1  0 03:36 ?        00:00:00 top -b
    root        17     0  0 03:36 pts/0    00:00:00 ps -ef
#ctrl+c 没反应.. 
```

=== “go程序测试” !!! warning 写的代码 测试信号接收..
代码暂时没找到在哪…..

1.6 EXPOSE

暴露端口

  • 是镜像创建者和使用者的约定,告知用户你暴露了什么服务(端口)
  • 在 docker run –P 时,docker 会自动映射 expose 的端口到主机大端口,如0.0.0.0:32768->80/tcp
  • -P 是将容器内所有暴露的端口(写了EXPOSE的) 映射到主机的随机端口上,如果没有写EXPOSE, 就不会映射任何端口
  • -p 指定容器的端口({==无需 dockerfile 写 EXPOSE==})映射到主机
  • 从某种意义上看{==EXPOSE只是一种提醒/告知: 说我这个应用应该访问这个端口,这个端口提供服务==}
--8<-- "devops/docker/code/go-web-simple/main.go"
--8<-- "devops/docker/code/go-web-simple/dockerfile"
EXPOSE <port> [<port>/<protocol>...]

1.7 HEALTHCHECK

健康检查

--interval=<duration>:指定健康检查的时间间隔,默认为 30 秒.
--timeout=<duration>:指定每次健康检查的超时时间,默认为 30 秒.
--retries=<number>:指定在将容器标记为不健康之前尝试健康检查的次数,默认为 3 次.
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

1.8 ENV

设置环境变量, 就是进入容器后,你可以用env 看到这些

ENV <key>=<value> ...
#或者MYSQL_VERSION=5.7
ENV MYSQL_VERSION 5.7
# run 实际上是进入容器执行的. 所以用到的变量可以
RUN agt-get install -y mysql-server="${MYSQL_VERSION}"  

1.9 ADD (不推荐)

!!! note - 从源地址(文件,目录或者 URL)复制文件到目标路径 - 如果 src 是一个本地压缩文件,则在 ADD 的同时完整解压操作 - 如果 dest 不存在,则 ADD 指令会创建目标目录 - 如果 dest 结尾没有/,那么 dest 是目标文件名,如果 dest 结尾有/,那么 dest 是目标目录名 - 如果 src 是一个目录,则所有文件都会被复制至 dest - 如果 src 是一个本地压缩文件,则在 ADD 的同时完整解压操作 - ADD 支持通配符,如 ADD check* /testdir/

ADD [--chown=<user>:<group>] <src>... <dest>  
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] (路径中有空格时使用)

1.10 COPY

!!! note - COPY:从源地址(文件,目录)复制文件到目标路径 - 可以用于多阶段编译场景,可以用前一个临时镜像中拷贝文件 - COPY 不解压文件 - COPY 只支持本地文件的复制

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] # 路径中有空格时使用
COPY --from=build /bin/project /bin/project

```bash title=“src是目录时注意点” FROM ubuntu COPY tmp /root/

. ├── dockerfile └── tmp ├── abc └── t2 └── 123

docker run –rm tmp ls /root abc t2 #没有 tmp 目录,, 所以写copy的时候 要 copy tmp /root/tmp




###  VOLUME
将指定目录定义为外挂存储卷,Dockerfile 中在该指令之后所有对同一目录的修改都无效 VOLUME ["/data"]

等价于 docker run –v /data,可通过 docker inspect 查看主机的 mount point, /var/lib/docker/volumes//_data

### WORKDIR
切换工作目录
WORKDIR,意思是在这一句之后,Dockerfile 后面的操作都以这一句指定的 WORKDIR 目录作为当前目录
还有设置后,你`docker run -it xxx bash` 进入后, `pwd`看到的,就不会是默认的/ 而是这个了.


```dockerfile
WORKDIR /path/to/workdir

1.11 USER

USER:切换运行镜像的用户和用户组, 容器默认会以root的用户去运行容器, 可以用user指定成其他 用户必须存在,没有的话先添加

#RUN USERADD abc
USER <user>[:<group>] 

# 进去容器后,  whoami 能看到

1.12 ARG

from ubuntu
ARG FILE
# 可以有默认值
ARG DIR="/root"

RUN cd $DIR && touch $FILE

# 生成镜像的时候 --build-arg 然后 FILE=hello.txt 传递 参数
docker build --build-arg FILE=hello.txt -t yyy  .

1.13 其他

• ONBUILD  
• STOPSIGNAL  
• HEALTHCHECK 
• SHELL

1.14 dockerfile 最佳实践

  • 每个镜像最好只有一个进程
  • 当无法避免同一镜像运行多进程时,应选择合理的初始化进程(init process)
  • 最小化层级数
    • 只有 RUN,COPY,ADD 会创建新层,其他指令创建临时层,不会增加镜像大小
    • 多条 RUN 命令可通过连接符连接成一条指令集以减少层数
    • 通过多段构建减少镜像层数
  • 把多行参数按字母排序,可以减少可能出现的重复参数,并且提高可读性
  • 编写 dockerfile 的时候,应该把变更频率低的编译指令优先构建以便放在镜像底层以有效利用 build cache
  • 复制文件时,每个文件应独立复制,这确保某个文件变更时,只影响改文件对应的缓存

2 多进程的容器镜像1

  • 选择适当的 init 进程
  • 需要捕获 SIGTERM 信号并完成子进程的优雅终止
  • 负责清理退出的子进程以避免僵尸进程

3 构建 build

3.1 理解构建上下文(Build Context)

!!! note - 当运行 docker build 命令时,当前工作目录被称为构建上下文 - 首先会把构建上下文传输给docker daemon,可能当前目录(构建上下文)里有很多没用的文件,这样就会导致传输时间长,所以当前目录要干净 - 可以通过 .dockerignore 文件从编译上下文排除某些文件 - 可以创建一个专门的目录放置 Dockerfile,并在目录中运行 docker build

# .  就是你的构建目录,会将这个目录下的所有文件包含在构建上下文
# 默认查找当前目录的 Dockerfile ,–f 指定 Dockerfile
docker build . –f ./Dockerfile

3.2 Build Cache

构建容器镜像时,Docker 依次读取 Dockerfile 中的指令,并按顺序依次执行构建指令。
Docker 读取指令后,会先判断缓存中是否有可用的已存镜像,只有已存镜像不存在时才会重新构建。

  • 通常 Docker 简单判断 Dockerfile 中的指令与镜像。
  • 针对 ADD 和 COPY 指令,Docker 判断该镜像层每一个文件的内容并生成一个 checksum,与现存镜像比较时,Docker 比较的是二者的 checksum。
  • 其他指令,比如 RUN apt-get -y update,Docker 简单比较与现存镜像中的指令字串是否一致。
  • 当某一层 cache 失效以后,所有所有层级的 cache 均一并失效,后续指令都重新构建镜像。
    • ==所以构建的时候,不会变的东西放到最上面==

3.3 多段构建(Multi-stage build)

FROM golang:1.10-alpine3.8 AS multistage

RUN apk add --no-cache --update git

WORKDIR /go/src/api
COPY . .

RUN go get -d -v \
  && go install -v \
  && go build

##alpine 空的,啥也没有, 因为我们的go程序本身就是可执行文件.

FROM alpine:3.8
COPY --from=multistage /go/bin/api /go/bin/
EXPOSE 3000
CMD ["/go/bin/api"]

!!! tip 也可以使用--from=0 来表示从第一个阶段来

3.3.1 使用最小的镜像

from scratch # 空的, 里面基础命令都没有, 所以一般我们用 alpine

#很多都有这种 -alpine
from golang:1.17.2-alpine

from openjdk:alpine

4 通过镜像逆向dockerfile

docker run -v /var/run/docker.sock:/var/run/docker.sock alpine/dfimage nginx:alpine

5 .dockerignore

Back to top