Docker 杂记

Dockerfile

Dockerfile是一个包含容器镜像定义的文本文件,可用于启动容器。这个定义包含命令、基础镜像、配置等。在这篇文章中,我们将介绍每个组件,并讨论在什么场景下使用它们。让我们开始吧。

Docker镜像

在深入了解各个组件之前,我们可能想知道Docker镜像到底是什么。参考:镜像的结构

镜像是一个只读模板,包含创建Docker容器的指令。通常,一个镜像是基于另一个镜像,并进行一些额外的定制。–Docker官方

Docker镜像可以包含很多文件,为了深入了解细节,让我们先拉取一个镜像。

1
2
3
4
5
6
7
8
9
alpine3.18: Pulling from library/python
9fda8d8052c6: Pull complete
e7ae3e644d56: Pull complete
4da8ee49dcce: Pull complete
7c6a85daf644: Pull complete
35f63c7ece91: Pull complete
Digest: sha256:603975e62d85aa07578034d3d10ffa1983b7618a6abb6371cf51941be6b8842c
Status: Downloaded newer image for python:alpine3.18
docker.io/library/python:alpine3.18

从上面的输出中,我们知道我们从dockerhub拉取了python:alpine3.18镜像,这个镜像有5层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
[
{
"Id": "sha256:0e3cec02f9651c77b976ef920ee7414ed059a7be05fb9b0c1e091afbfccd6f32",
"RepoTags": [
"python:alpine3.18"
],
"RepoDigests": [
"python@sha256:603975e62d85aa07578034d3d10ffa1983b7618a6abb6371cf51941be6b8842c"
],
"Parent": "",
"Comment": "buildkit.dockerfile.v0",
"Created": "2023-07-22T09:54:20Z",
"Container": "",
"ContainerConfig": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": null,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"DockerVersion": "",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=C.UTF-8",
"GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
"PYTHON_VERSION=3.11.4",
"PYTHON_PIP_VERSION=23.1.2",
"PYTHON_SETUPTOOLS_VERSION=65.5.1",
"PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/9af82b715db434abb94a0a6f3569f43e72157346/public/get-pip.py",
"PYTHON_GET_PIP_SHA256=45a2bb8bf2bb5eff16fdd00faef6f29731831c7c59bd9fc2bf1f3bed511ff1fe"
],
"Cmd": [
"python3"
],
"ArgsEscaped": true,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "arm64",
"Variant": "v8",
"Os": "linux",
"Size": 57440899,
"VirtualSize": 57440899,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/bc0dd55b4f4eca654fb57ec3ab9997d5511bb31e1fbaa1e37e55d2d0c953ae78/diff:/var/lib/docker/overlay2/667b39e359e38324b6665b23a88143302006a058bccf71cc4465b5abc8f1394c/diff:/var/lib/docker/overlay2/d46312d087510c9820f8a8bf37bf437eda7f8f11a36bfc1497a2ce2a7d9d87de/diff:/var/lib/docker/overlay2/8a1a8952108eed0ca2e6b9b225204b1869cd946e345c7df86434ed56ce1a8a2b/diff",
"MergedDir": "/var/lib/docker/overlay2/48d3c75f4a01ef9eebb0dab62c561a835579ed37f26ae990911e09d15b33175f/merged",
"UpperDir": "/var/lib/docker/overlay2/48d3c75f4a01ef9eebb0dab62c561a835579ed37f26ae990911e09d15b33175f/diff",
"WorkDir": "/var/lib/docker/overlay2/48d3c75f4a01ef9eebb0dab62c561a835579ed37f26ae990911e09d15b33175f/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:b2191e2be29d816fa6fbde954316d54e10df9a882c7ca38e3e087d9cbca49fe6",
"sha256:07c9596e31dd51b9652cd1ca250dbb2632b60b9930a41f8b35f723ad64f7759d",
"sha256:91225753119dc0605d12eeb9754f640054c6c8d936db3c0d7b9a382ddddcf1ec",
"sha256:cc409c6a686ee7a64acc8c0569f57e1a3c93cd53c82e782d8f2a623daa9cda64",
"sha256:edd41b2a090ce8eef948b7d3979bda4726c46c3a5cee13fbe1b87acdbf6315b6"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

基础镜像 / 父镜像

在大多数Dockerfile中,你会看到的第一行是FROM {基础镜像},比如FROM python:3.8

这一行定义了我们要构建的镜像的基础镜像。基础镜像可能包含一个或多个层。

RUN

RUN命令可用于执行一些命令,如安装包、下载文件等。需要提到的一点是,每个命令都会在现有镜像上添加一个新层。将多个命令合并到一个RUN命令中是减小镜像大小和减少层数的常用技巧。

CMD

CMD描述默认的容器参数或命令。用户可以在使用时轻松覆盖默认命令。

ENTRYPOINT

ENTRYPOINT也用于定义始终运行的命令,这意味着它不能被覆盖。ENTRYPOINT可用于确保容器启动时始终运行特定命令,即使用户尝试覆盖它。

COPY

COPY可用于将本地文件复制到容器镜像中,仅此而已。

ADD

ADD命令也允许将文件/文件夹从主机复制到镜像。它支持tarball提取和远程URL支持。通常,ADDCOPY更受欢迎。现在让我们看一个在Dockerfile中使用ADD的例子。

假设我们在一个文件夹/tmp/app中,它有以下文件,

1
server.js node_modules package.json

同时,我们在Dockerfile中写入以下命令。

1
2
ADD server.js /tmp
# ADD <主机路径> <镜像路径>

上述命令将文件/tmp/app/server.js(主机路径)复制到/tmp/server.js(容器镜像路径)。我们注意到第一个参数应该在上下文内,上下文可以解释为我们执行docker build命令的路径。

VOLUME

卷可以被视为Docker容器的持久数据存储。以下命令意味着从此镜像启动的任何容器,主机上的/var/www/html目录将被挂载到容器内的/var/www/html目录。这意味着在容器内对/var/www/html目录所做的任何更改都将反映在主机上,反之亦然。

1
VOLUME /var/www/html

Dive

Dive是一个非常有用的工具,用于分析容器镜像。它使你能够看到每一层的命令和反映在文件系统上的变化。

Dive

参考

  1. OCI镜像清单规范

Docker FileSystem

Docker的filesystem有很多,可以配置,最常用且推荐的目前应该是overlay2,它的性能和稳定性都比较好。如果你对Docker DataRoot list一下,你会发现有很多文件夹,其中有一个是overlay2,这个文件夹就是overlay2的存储位置。

1
2
$ sudo ls /var/lib/docker
buildkit containers engine-id image network overlay2 plugins runtimes swarm tmp volumes

其中有很多各个文件夹,名字都是一串哈希值,这些文件夹就是各个镜像的FileSystem的存储位置,其中是每个都是一个layer,这个layer有可能是一个image 的layer,也可能是正在运行的container的layer,也可能是一个volume的layer。

1
2
3
4
5
6
7
8
$ sudo ls /var/lib/docker/overlay2
074c2e151731c12c6ec2fbb44dc248270142ee04c04d6003795e6a1fc57994f0
89335ec1218350aca441be8a571fd7937473be28bb578ec4908b59b91d6eb8b0
074c2e151731c12c6ec2fbb44dc248270142ee04c04d6003795e6a1fc57994f0-init
205cc1a3d898504822c0a5c74a32a6572af61e2d8219a472f0cba2e0b5612b11
8bb32d29875df42b515543e512571eece693ce9c0b2dc599d5dd9572cd823fe7
sudo ls /var/lib/docker/overlay2/205cc1a3d898504822c0a5c74a32a6572af61e2d8219a472f0cba2e0b5612b11
diff link lower work

这里folder name的hash并不是layer的sha256,而是一个短ID,Overlay2 在磁盘上为该层分配的 diff 目录名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/ead641d5d5794fe289eed7b83bb46408cca181111f434b57a56d44f99504ac78/diff:",
"MergedDir": "/var/lib/docker/overlay2/36849485abab637c098e9f88d4b5db39486a28b61c7d7b6782517c53a6bbf83b/merged",
"UpperDir": "/var/lib/docker/overlay2/36849485abab637c098e9f88d4b5db39486a28b61c7d7b6782517c53a6bbf83b/diff",
"WorkDir": "/var/lib/docker/overlay2/36849485abab637c098e9f88d4b5db39486a28b61c7d7b6782517c53a6bbf83b/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:f2a05f227c1388a37b341c57f367d4fffda24260eb043c30c884d9287c626106",
"sha256:80ddc4ff0604054169d1e2e2ad074d7ee4af7ba8174f94af6f3daeab6b13cca2",
"sha256:f646b5a3df86583662f76ff3202439763dce7cbff0af66288198e2a736e343a4"
]
}

Overlay2 存储结构要点
1. 镜像元数据 & LayerDB
• /var/lib/docker/image/overlay2/imagedb/content/sha256/
这里的
• /var/lib/docker/image/overlay2/layerdb/sha256//cache-id
这个文件里的内容(一个短 ID,比如 36849485abab637c0…)才是 Overlay2 在磁盘上为该层分配的 diff 目录名。
2. Overlay2 工作目录
• /var/lib/docker/overlay2//diff
存放该层的实际文件内容。
• /var/lib/docker/overlay2//merged
容器运行时挂载点,读取时会把 LowerDir + UpperDir overlay 在这里。