虚拟化
物理硬件资源虚拟化为多个虚拟机。操作系统和应用程序,彼此隔离。资源消耗较大。Docker
同一操作系统部署多个应用程序文件系统和进程空间。资源消耗较小。区别
| 特征 | 虚拟化 | Docker |
|---|---|---|
| 虚拟化层 | 硬件层 | 操作系统 |
| 资源隔离 | 虚拟机 | 容器 |
| 操作系统 | 每个虚拟机都有自己的操作系统 | 每个容器共享操作系统的内核 |
| 启动速度 | 较慢 | 较快 |
| 资源消耗 | 较大 | 较小 |

Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个 隔离的环境 里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。
前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层。容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者 绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务, Docker Registry 就是这样的服务。


| 命令 | 作用 |
|---|---|
| attach | 运行容器,如果从这个容器退出,会导致容器的停止 |
| exec | 在运行时的容器内运行命令,如果从这个容器退出,会导致容器的停止 |
| build | 从一个 Dockerfile 文件构建镜像 |
| commit | 把容器的改变提交创建一个新的镜像 |
| cp | 容器和本地文件系统间复制文件/文件夹 |
| create | 创建新容器,但并不启动 |
| diff | 检查容器里文件系统结构的更改 |
| events | 获取服务器的实时事件 |
| export | 导出容器的文件系统为一个 tar 文件 |
| history | 显示镜像的历史 |
| images | 列出所有镜像 |
| import | 导入 tar 的内容创建一个镜像 |
| info | 显示系统信息 |
| inspect | 获取 docker 对象的底层信息 |
| kill | 杀死一个或者多个容器 |
| load | 从 tar 文件加载镜像 |
| login | 登录 Docker registry |
| logout | 退出 Docker registry |
| logs | 获取容器日志 |
| pause | 暂停一个或者多个容器 |
| port | 列出容器的端口映射 |
| ps | 列出所有容器 |
| pull | 从 registry 下载一个 image 或者 repository |
| push | 给 registry 推送一个 image 或者 repository |
| rename | 重命名一个容器 |
| restart | 重启一个或者多个容器 |
| rm | 移除一个或者多个容器 |
| rmi | 移除一个或者多个镜像 |
| run | 创建并启动容器 |
| save | 把一个或者多个镜像保存为 tar 文件 |
| search | 去 docker hub 寻找镜像 |
| start | 启动一个或者多个容器 |
| stats | 显示容器资源的实时使用状态 |
| stop | 停止一个或者多个容器 |
| tag | 给源镜像创建一个新的标签,变成新的镜像 |
| top | 显示正在运行容器的进程 |
| unpause | pause 的反操作 |
| update | 更新一个或者多个 docker 容器配置 |
| version | Show the Docker version information |
| container | 管理容器 |
| image | 管理镜像 |
| network | 管理网络 |
| volume | 管理卷 |
docker help 命令查看每个命令的详细帮助信息。docker --help 命令查看所有命令的列表。docker version 命令查看 Docker 的版本信息。希望这份速查表对您有所帮助!
$ docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
示例:
# 拉取最新的 nginx 镜像
docker pull nginx
# 拉取 nginx:latest 镜像
docker pull nginx:latest
# 拉取特定版本的 nginx 镜像
docker pull nginx:1.19.0
# 从 Docker Hub 上的 myuser 仓库拉取 myapp 镜像
docker pull myuser/myapp
# 从本地仓库拉取 myapp 镜像
docker pull 127.0.0.1:5000/myapp
# 拉取所有标签的 nginx 镜像
docker pull -a nginx
# 拉取所有标签的 nginx 镜像,并静默模式
docker pull -a --quiet nginx
$ docker image ls
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183 MB
nginx latest 05a60462f8ba 5 days ago 181 MB
mongo 3.2 fe9198c04d62 5 days ago 342 MB
<none> <none> 00285df0df87 5 days ago 342 MB
ubuntu 18.04 329ed837d508 3 days ago 63.3MB
ubuntu bionic 329ed837d508 3 days ago 63.3MB
列表包含了 仓库名、标签、镜像 ID、创建时间 以及 所占用的空间。
其中仓库名、标签在之前的基础概念章节已经介绍过了。镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个 标签。因此,在上面的例子中,我们可以看到 ubuntu:18.04 和 ubuntu:bionic 拥有相同的 ID,因为它们对应的是同一个镜像。
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 24 0 1.992GB 1.992GB (100%)
Containers 1 0 62.82MB 62.82MB (100%)
Local Volumes 9 0 652.2MB 652.2MB (100%)
Build Cache 0B 0B
$ docker image rm [选项] <镜像1> [<镜像2> ...]
我们可以用镜像的完整 ID,也称为 长 ID,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。docker image ls 默认列出的就已经是短 ID 了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。
$ docker image rm 501
Untagged: redis:alpine
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa
Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3
Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7
我们也可以用镜像名,也就是 <仓库名>:<标签>,来删除镜像。
$ docker image rm centos
Untagged: centos:latest
Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38
当然,更精确的是使用 镜像摘要 删除镜像。
$ docker image ls --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
node slim sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 6e0c4c8e3913 3 weeks ago 214 MB
$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
成批删除镜像:
删除所有仓库名为 redis 的镜像:
$ docker image rm $(docker image ls -q redis)
$ docker image rm $(docker images -q redis)
删除所有镜像:
$ docker image rm $(docker image ls -aq)
$ docker image rm $(docker images -aq)
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
选项:
-a: 作者信息。-c: 提交信息。-m: 提交信息。-p: 将容器的更改应用到基础镜像。--change: 添加或删除一个文件。--pause: 在提交之前暂停容器。--run: 在提交之前运行一个命令。docker commit 命令用于将一个容器的当前状态保存为一个新的镜像。这可以用于保存容器中所做的更改,以便在以后重新创建相同的容器。
# 可以使用 docker exec 命令进入容器,修改内容
$ docker exec -it webserver bash
root@3729b97e8226:/# echo 'Hello, Docker!
' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit
# 可以通过 docker diff 命令看到具体的改动
$ docker diff webserver
C /root
A /root/.bash_history
...
# 现在我们定制好了变化,我们希望能将其保存下来形成镜像
$ docker commit \
--author "Tao Wang " \
--message "修改了默认网页" \
webserver \
nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
#我们可以在 docker image ls 中看到这个新定制的镜像:
$ docker image ls nginx
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v2 07e334659748 9 seconds ago 181.5 MB
nginx 1.11 05a60462f8ba 12 days ago 181.5 MB
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
#新的镜像定制好后,我们可以来运行这个镜像
docker run --name web2 -d -p 81:80 nginx:v2
docker save 命令用于将一个或多个 Docker 镜像保存到 tar 归档文件中。
docker save [OPTIONS] IMAGE [IMAGE...]
选项:
-o: 指定输出文件路径。--output: 指定输出文件路径(与 -o 相同)。--images-from-file: 从文件中读取要保存的镜像名称。# 将名为 `myimage` 的镜像保存到 `myimage.tar` 文件中
docker save -o myimage.tar myimage
# 将多个镜像保存到 `images.tar` 文件中
docker save -o images.tar image1 image2 image3
# 从文件 `images.txt` 中读取要保存的镜像名称并保存到 `images.tar` 文件中
docker save --images-from-file images.txt -o images.tar
docker load 命令用于从 tar 归档文件中加载 Docker 镜像。
docker load [OPTIONS]
选项:
-i: 指定输入文件路径。--input: 指定输入文件路径(与 -i 相同)。# 从 `myimage.tar` 文件中加载镜像
docker load -i myimage.tar
# 从标准输入加载镜像
cat images.tar | docker load
Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
在一个空白目录中,建立一个文本文件,并命名为 Dockerfile:
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
Dockerfile内容忽略,另开一篇讲解
在 Dockerfile 文件所在目录执行:
docker build [选项] <上下文路径/URL/->
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM nginx
---> e43d811ce2f4
Step 2 : RUN echo 'Hello, Docker!
' > /usr/share/nginx/html/index.html
---> Running in 9cdc27646c7b
---> 44aa4490ce2c
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c
在这里我们指定了最终镜像的名称 -t nginx:v3
docker build 命令最后有一个
.。. 表示当前目录,而Dockerfile就在当前目录,因此不少初学者以为这个路径是在指定 Dockerfile 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定上下文路径。
当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
当构建的时候,用户会指定构建镜像上下文的路径,docker build命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在 Dockerfile 中这么写:
COPY ./package.json /app/
这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制Dockerfile所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json。
因此,COPY 这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
现在就可以理解刚才的命令 docker build -t nginx:v3 . 中的这个 .,实际上是在指定上下文的目录,docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
如果观察 docker build 输出,我们其实已经看到了这个发送上下文的过程:
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...
一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
实际上Dockerfile的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用-f ../Dockerfile.php参数指定某个文件作为 Dockerfile。
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(exited)的容器重新启动。
$ docker run -t -i ubuntu:18.04 /bin/bash
root@af8bae53bdd3:/#
选项:
-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上-i 则让容器的标准输入保持打开-d 此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 docker logs 查看)。docker start [容器ID]
docker stop [容器ID]
docker restart [容器ID]
docker attach [容器ID]
注意: 如果从这个容器退出,会导致容器的停止。
docker exec -it [容器ID] /bin/bash
注意: 如果从这个容器退出,容器不会停止,推荐使用
docker export
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7691a814370e ubuntu:18.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
$ docker export 7691a814370e > ubuntu.tar
docker import
可以使用 docker import 从容器快照文件中再导入为镜像
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB
此外,也可以通过指定 URL 或者某个目录来导入
$ docker import http://example.com/exampleimage.tgz example/imagerepo
注:用户既可以使用 docker load来导入镜像存储文件到本地镜像库,也可以使用
docker import来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
docker rm
如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。
在 https://hub.docker.com 免费注册一个 Docker 账号。
可以通过执行docker login命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。可以通过docker logout退出登录
可以通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。
例如以 centos 为关键词进行搜索:
$ docker search centos
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
centos The official build of CentOS. 6449 [OK]
ansible/centos7-ansible Ansible on Centos7 132 [OK]
consol/centos-xfce-vnc Centos container with "headless" VNC session… 126 [OK]
jdeathe/centos-ssh OpenSSH / Supervisor / EPEL/IUS/SCL Repos - … 117 [OK]
centos/systemd systemd enabled base container. 96 [OK]
$ docker pull centos
Using default tag: latest
latest: Pulling from library/centos
7a0437f04f83: Pull complete
Digest: sha256:5528e8b1b1719d34604c87e11dcd1c0a20bedf46e83b5632cdeac91b8c04efc1
Status: Downloaded newer image for centos:latest
docker.io/library/centos:latest
用户也可以在登录后通过 docker push 命令来将自己的镜像推送到 Docker Hub。
以下命令中的 username 请替换为你的 Docker 账号用户名。
$ docker tag ubuntu:18.04 username/ubuntu:18.04
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 275d79972a86 6 days ago 94.6MB
username/ubuntu 18.04 275d79972a86 6 days ago 94.6MB
$ docker push username/ubuntu:18.04
$ docker search username
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
username/ubuntu
docker-registry 是官方提供的工具,可以用于构建私有的镜像仓库。本文内容基于 docker-registry v2.x 版本。
可以使用官方 registry 镜像来运行
$ docker run -d -p 5000:5000 --restart=always --name registry registry
这将使用官方的 registry 镜像来启动私有仓库。默认情况下,仓库会被创建在容器的 /var/lib/registry 目录下。你可以通过-v参数来将镜像文件存放在本地的指定路径。例如下面的例子将上传的镜像放到本地的/opt/data/registry目录。
$ docker run -d \
-p 5000:5000 \
-v /opt/data/registry:/var/lib/registry \
registry
例如私有仓库地址为 127.0.0.1:5000。
使用 docker tag 将 ubuntu:latest 这个镜像标记为 127.0.0.1:5000/ubuntu:latest。
格式为 docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]。
$ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
127.0.0.1:5000/ubuntu:latest latest ba5877dc9bec 6 weeks ago 192.7 MB
使用 docker push 上传标记的镜像
$ docker push 127.0.0.1:5000/ubuntu:latest
用 curl 查看仓库中的镜像
$ curl 127.0.0.1:5000/v2/_catalog
{"repositories":["ubuntu"]}