超轻量级“虚拟机”—— Docker 初识 & 基本用法归纳

目录


一、前言

  1. 本文所涉及的操作均在良好、无访问限制的网络环境下进行的。
  2. 本文纯属个人学习经验分享,出错再所难免,尤其是概念理解部分。另外我的语言表达能力也非常有限,有些地方也写得比较乱。故本文仅供参考!如果发现错误的地方,可以的话麻烦指点下,特别感谢!


二、Docker 简介

Docker 是一个开源项目

可以把它理解为是一种新兴的超轻量级虚拟化技术。

传统虚拟化技术需要模拟计算机的一整套硬件出来,而且还要有自己的一套操作系统。

而 Docker 却不需要,它只需要与主机共享同一个内核,并充分利用 Linux 上内核的“环境隔离方案”来实现轻量级的虚拟化。

它在一些特定场景下与传统虚拟化技术相比,效率大幅提高,而资源开销却大幅降低。

Docker 的迁移也是十分方便的,基本上只需要把整个 Docker 目录搬过去即可。

Docker 使用 服务器-客户端 架构。

如果想在 Docker 上运行 exe 软件的话,那不用看下去了,左转找 KVM 去吧。


三、理解 Docker 的结构

四个基本结构:容器Container)、镜像Image)、仓库Repository)、注册点Registry)。

看着一脸懵逼对吧!是的,这几个概念确实比较难理解。但是我用类比法还是把它搞明白了。

先想象一个无盘系统是怎么样的,下面我们用一般的无盘系统来类比。

3.1 镜像

无盘服务器硬盘内有各种软件。比如说有 Win 7,还有各类应用软件

而这些软件是相互依赖的。比如微信需要装 Win 7 系统才能运行。

各个无盘计算机(容器)想要运行什么软件可以直接告诉无盘服务器。

无盘服务器会准备好一切所需软件,打成一个包(镜像),然后推送给无盘计算机。

假设整个无盘系统中只有两种包。一种包是 Win 7 & QQ,另一种包是 Win 7 & 微信

但是无论这两种包有多少个,都不会占用额外的硬盘空间(利用 Union mount 实现镜像分层)。只有 Win 7(某个镜像层) 、QQ微信 这三个软件会占用硬盘空间。

一个镜像通常是这样被标识的:<仓库名>:<标签名(版本号)>(或者 <仓库名>:<@sha256:<校验值>>) ,例如 nginx:latest。如果不指定标签,默认为 latest

3.2 容器

相当于无盘计算机。

无盘计算机启动(容器启动)时,要从无盘服务器上拉取所需文件。如果无盘计算机对硬盘有写入操作的话,写入的数据将保存到无盘服务器的缓存区(容器存储层)。

无盘计算机关机(容器停止)时,如果没有额外设置,所有保存到无盘服务器的缓存区的文件(容器存储层)都将丢失。除非另外保存在 U 盘等外接设备(数据卷)中。

各个无盘计算机之间的运行互不干扰。(利用 cgroups 、namespace 实现隔离)

3.3 仓库

相当于同一个软件所有版本的集合。

3.4 注册点

相当于一个应用商店。

无盘服务器会来这里查找并下载软件。

3.5 与传统虚拟机类比

Docker 传统虚拟机
镜像 虚拟硬盘
容器 虚拟计算机
仓库 虚拟硬盘上的软件
注册点 下载软件的地方

四、操作环境

  • 操作系统:CentOS 7.3.1611(已关闭 SELinux)
  • Linux 内核版本:3.10.0-514.26.2.el7.x86_64
  • Docker 版本:17.06.2-ce

五、Docker 的安装

5.1 安装一些组件

yum -y install yum-utils device-mapper-persistent-data lvm2

5.2 添加 Docker 源

yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

5.3 建立 yum 缓存

yum makecache fast

5.4 安装最新版本的 Docker

yum -y install docker-ce

若出现密钥警告,按 y 回车即可。

5.5 启动 Docker 服务

systemctl start docker

如需开机自启动,请执行:

systemctl enable docker

六、运行第一个容器

6.1 运行 hello-world 容器

docker run hello-world

若出现以下执行结果,说明运行成功!

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b04784fba78d: Pull complete 
Digest: sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
#以下内容省略

6.2 过程解析

  1. Docker 客户端向 Docker 守护进程发出运行命令。

  2. Docker 守护进程发现没有 hello-world 这个镜像,于是从仓库中寻找并下载它。

  3. 下载完毕之后,Docker 守护进程以 hello-world 这个镜像创建一个新的容器。

  4. 容器向 Docker 守护进程输出内容之后,容器停止。Docker 守护进程把输出的内容传递给 Docker 客户端。


七、Docker 的基本操作

下面以创建一个 Ubuntu 系统的容器为例来了解一下 Docker 的基本操作。

为了方便理解,我把命令完整地写出来。

本节的命令参数只有最基本的参数,需要其他设置(如数据卷)的话会在后面讲到。

docker 命令支持自动补全,这点必须赞!

7.1 获取一个镜像

常用格式

docker image pull [<注册点名>/]<仓库名>[:<标签名(版本号)>]
选项 功能
注册点名 指定注册点名。
如果不指定,将使用默认的 library/
标签名 指定标签名。
如果不指定,将使用默认的 latest

例如

docker image pull ubuntu

执行结果

Using default tag: latest
latest: Pulling from library/ubuntu
d5c6f90da05d: Pull complete 
1300883d87d5: Pull complete 
c220aa3cfc1b: Pull complete 
2e9398f099dc: Pull complete 
dc27a084064f: Pull complete 
Digest: sha256:34471448724419596ca4e890496d375801de21b0e67b81a77fd6155ce001edad
Status: Downloaded newer image for ubuntu:latest

可以明显地看出,镜像被分为了多个块。

7.2 以某个镜像建立一个容器

常用格式

docker container create --interactive --tty [--name=<容器名>] [<--env <变量名>=<变量值>> [--env <变量名>=<变量值>] ... ] [--privileged] <镜像名> [要运行的程序和参数]
选项 功能
--interactive 持续为容器打开 stdin 以便随时接受操作。
--tty 为容器分配一个伪终端。
容器名 指定容器的名字。
如果不指定,系统会自动为之分配一个无重复的容器名。
--env <变量名>=<变量值> 向容器内传递环境变量。
可以传递多个环境变量。
--privileged 开启特权模式。
允许容器进行一些底层的操作(如抓包等)。
镜像名 指定要使用镜像的名字。
如果本地不存在指定的镜像,会自动从注册点中拉取。
要运行的程序和参数 指定容器启动后要运行镜像里的哪一个程序。这个程序运行结束后,容器也会停止。
如果不指定,则使用镜像的默认值。

例如

ubuntu 为镜像,建立一个名为 ubuntu_test 的容器。

docker container create --interactive --tty --name=ubuntu_test ubuntu

执行结果

46cc818c92f0780ccd89811c12906c4527b554d18a61e72b0b2337b663ebab5f

这是自动生成的容器唯一长 ID。

7.3 启动一个容器

常用格式

docker container start <容器名> [容器名] [容器名] ...

可同时启动多个容器。

例如

启动刚才创建的 ubuntu_test 容器。

docker container start ubuntu_test

执行结果

ubuntu_test

返回容器名称,说明启动成功。

7.4 查看容器信息

常用格式

docker container ls [--all] [--no-trunc]

docker container ps [--all] [--no-trunc]
选项 功能
--all 显示所有的容器。如果不加此选项则只显示运行中的容器。
--no-trunc 完整显示容器的长 ID (形如 7.2 中命令的执行结果)。
为了方便查看,一般不需要加此选项。

例如

查看本机所有容器的状态。

docker container ls --all

执行结果

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
#<容器ID>            <镜像名>               <命令参数>          <创建时间>          <状态>                        <打开的端口>         <容器名称>
d4bc3be9148d        hello-world         "/hello"            5 hours ago         Exited (0) 5 hours ago                          practical_rosalind
46cc818c92f0        ubuntu              "/bin/bash"         3 hours ago         Up 2 minutes                                    ubuntu_test

可以看到,除了刚创建的 ubuntu_test 容器之外,还有一个名为 practical_rosalind 容器。practical_rosalind 这个容器正是刚才运行 docker run hello-world 时生成的。

7.5 操作一个容器 & 容器内外进程简析

7.5.1 操作一个容器

常用格式

docker container attach <容器名>

例如

docker container attach ubuntu_test

执行之后按几下回车,如果出现类似 [email protected]:/# 的提示符,那说明您已经在容器内操作了。

我们来查看下系统的版本。

uname -a
Linux 46cc818c92f0 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

好像看不出是 Ubuntu 系统,没关系,我们再执行下以下命令。

cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.3 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.3 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

好,已经确定了是在虚拟的 Ubuntu 系统中操作了!我们再来看一下容器内都有什么进程吧。

7.5.2 容器内进程简析

在容器内执行以下命令:

ps aux

执行结果

USER PID %CPU %MEM VSZ  RSS TTY STAT START  TIME COMMAND
root 1 0.0 0.0 18304 2072 pts/0 Ss  05:19  0:00 /bin/bash
root 229 0.0 0.0 34416 1436 pts/0 R+  07:27  0:00 ps aux

可以看到,容器中目前只存在 bash 和刚开启的 ps 这两个进程,而且 bashPID1

这说明了容器处在与实体机不同的 namespace 中,容器看不到实体机的进程。

容器进程数目与传统虚拟机的进程数目相比大幅减少了,所以说容器的效率非常高,启动基本上是毫秒级的!

7.5.3 容器外进程简析

实体机可以看到容器内的进程吗?答案是可以的。

我们来看下实体机上的进程树。

systemd─┬─ModemManager───2*[{ModemManager}
        ├─dockerd─┬─docker-containe─┬─docker-containe─┬─bash
        │         │                 │                 └─8*[{docker-containe}]
        │         │                 └─12*[{docker-containe}]
        │         └─11*[{dockerd}]
#无关部分已省略

可以看出,bash 属于 dockerd 的子进程。

这说明了容器处在实体机的子 namespace 中,同时需要依赖实体机中的进程才可以运行。

所以,容器并不是完全的“虚拟化”。

7.6 从容器中脱开

前面 7.2 我们已经说过了:容器启动时会运行镜像里指定的应用程序,而这个程序运行结束后,容器也会停止。

现在这个容器启动时运行了镜像默认设定的的 /bin/bash ,所以当 /bin/bash 关闭时,容器就会跟着关闭。

如果直接按 Ctrl + D 退出容器操作的话,bash 就会退出而使整个容器停止运行,我们显然不希望这样。

正确的脱开方法是 先按 Ctrl+P 再按 Ctrl+Q (跟 Screen 的操作方法非常相似)。

执行完该操作之后,如果出现 read escape sequence ,就说明已经从容器中脱开了。

7.7 停止一个容器

常用格式

docker container stop <容器名>

执行完以上命令之后,实体机将向容器内所有的进程发送 SIGTERM 信号,然后给 10 秒的时间,让容器内的进程可以“优雅地”结束。

如果容器内的进程在 10 秒内没有结束,则实体机向未结束的进程发送 SIGKILL 信号来强制结束。

如果想立即强制结束容器的话把 stop 换成 kill 就行了。

例如

docker container stop ubuntu_test

执行结果

ubuntu_test

返回容器名称,说明容器已经停止。

7.8 删除一个容器

常用格式

docker container rm <容器名>

⚠️ 注意:运行中的容器不能被删除。

例如

我们把刚才第一个使用 hello-world 镜像的容器给删掉,容器名从上面 7.4 中得到。

docker container rm practical_rosalind

执行结果

practical_rosalind

返回容器名称,说明容器已经删除。

7.9 查看镜像信息

常用格式

docker images [--all]
选项 功能
--all 显示中间层(依赖)镜像。

例如

docker images

执行结果

这里我们顺便回顾一下,一个镜像是这样标识的: <仓库名>:<标签名(版本号)>

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
#<所在仓库>           <标签>               <镜像 ID>            <创建时间>          <大小>
ubuntu              latest              ccc7a11d65b1        4 weeks ago         120MB
hello-world         latest              1815c82652c0        2 months ago        1.84kB

7.10 删除一个镜像

常用格式

docker image rm <镜像名>

⚠️ 注意:如果有基于要删除镜像的容器,则该镜像不能被删除。

例如

我们把刚才第一个下载的 hello-world:latest 镜像给删掉。

docker image rm hello-world:latest

执行结果

Untagged: hello-world:latest
Untagged: [email protected]:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f
Deleted: sha256:1815c82652c03bfd8644afda26fb184f2ed891d921b20a0703b46768f9755c57
Deleted: sha256:45761469c965421a92a69cc50e92c01e0cfa94fe026cdd1233445ea00e96289a

镜像已经删除。

7.11 使用一次性容器(推荐用于测试或开发环境)

上述步骤目的其实是为了让大家更好地理解 Docker 的结构。

我认为 Docker 有一个缺点,那就是容器一旦创建完成之后,想要修改配置就有点麻烦。而在测试或开发环境中,经常需要修改容器的配置。

好在,容器非常轻,完全可以做到“用时创建、用完即删”!

所以,我还是推荐大家使用以下命令,来做到容器创建启动删除三合一。

常用格式

docker container run --rm [--detach] --interactive --tty [--name=<容器名>] <镜像名> [要运行的程序和参数]
选项 功能
--rm 容器停止之后删除容器。
--detach 容器启动之后不进入容器内操作。

其他选项请参考 7.2

执行完该命令之后,容器会自动创建然后启动。如果没有加入 --detach 选项,容器启动完成后会直接进入到容器中操作(可随时脱开)。

而容器停止之后,容器就会马上被删除,非常方便。

下文均使用一次性容器。

7.12 配置容器自重启(推荐用于生产环境)

常用格式

7.27.11--tty 后面插入以下任一选项:

选项 功能
--restart on-failure:<次数> 容器停止时,若出现错误则自动重启(进程返回值不为 0)。
如果在尝试指定次数后依然出错,则放弃重启。
--restart unless-stopped 容器停止时,若没有出现错误则自动重启(进程返回值为 0)。
在 Docker 服务重启时,不能自动重启。
--restart always 器停止时,若没有出现错误则自动重启(进程返回值为 0)。
在 Docker 服务重启时,通常可以自动重启。

⚠️ 注意:如果执行了 docker container stop 或者 docker container kill 命令,容器自重启将失效。

以上选项不能和 --rm 同时使用,也就是说不适用于一次性容器。

7.13 限制容器占用的资源

常用格式

7.27.11--tty 后面插入以下任一选项(可同时使用):

选项 功能
--cpus <CPU 数量> 限制容器使用的 CPU 数量
--memory <内存容量> 限制容器使用的内存容量

例如

运行一个 hello-world:latest 镜像的一次性容器,限制使用 2 个 CPU 以及 128M 内存。

docker container run --rm --interactive --tty --cpus 2 --memory 128M hello-world:latest

7.14 查看容器的详细配置信息

把容器所有配置参数以 json 格式显示出来,这里只做了解。

常用格式

docker container inspect <容器名>

7.15 查看镜像的详细信息

把镜像所有参数以 json 格式显示出来,这里只做了解。

常用格式

docker image inspect <镜像名>

7.16 手动构建镜像

在很多时候,我们需要从包含 Dockerfile 的 Docker 项目中构建镜像。

例如这个项目

docker-node-hello/
├── Dockerfile
├── Makefile
├── README.md
├── Vagrantfile
├── index.js
├── package.json
└── supervisord
    └── conf.d
        ├── node.conf
        └── supervisord.conf

常用格式

cd <项目所在目录> && docker image build --tag [<注册点名>/]<仓库名>[:<标签名(版本号)>] .

例如

把上面的 Docker 项目构建成镜像,命名为 hello/hello:latest(当前目录已经是项目所在的目录)。

docker image build --tag hello/hello:latest .

执行结果

运行之后,系统会根据 Dockerfile 中的指令自动构建镜像(Dockerfile 指令语法请查阅参考文献)。

Sending build context to Docker daemon  36.35kB
Step 1/13 : FROM node:0.10
0.10: Pulling from library/node
386a066cd84a: Pull complete 
75ea84187083: Pull complete 

# 此处省略

Step 13/13 : CMD ["supervisord", "-n"]
 ---> Running in 5ac9cc1c0c33
Removing intermediate container 5ac9cc1c0c33
 ---> 1742ecbad743
Successfully built 1742ecbad743
Successfully tagged hello/hello:latest
# 构建完成

构建完成之后我们用它来建立一个一次性容器并运行。

docker container run --rm --interactive --tty hello

Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://cloud.docker.com/ For more examples and ideas, visit: https://docs.docker.com/engine/userguide/

运行成功。


八、保存容器中的数据

前面我们已经说过,容器一旦停止,容器内文件的所有改动都将丢失。

所以,我们必须指定一个可以存储数据的方法,才能保存容器内的数据。

8.1 使用数据卷

简单地说,数据卷就是在容器内指定一个目录,存储在这个目录下的数据都可以持久化保存。

常用格式

7.27.11--tty 后面插入如下格式的内容

--volume [<实体机文件或目录>:]<容器内文件或目录> [--volume [<实体机文件或目录>:]<容器内文件或目录>] ...
选项 功能
实体机文件或目录 指定的文件或目录将映射到容器中。
如果不指定,Docker 将会自动分配一个实体机目录(可在 7.1.4 命令的运行结果得到具体位置)。
容器内文件或目录 指定容器内的文件或目录。

为了方便容器的迁移以及维护工作,通常会指定实体机内的某个文件或目录映射到容器内的某个文件或目录中。

可以创建多个数据卷。

⚠️ 注意:有些本身就需要持久化存储的容器(例如 mysql)在创建的时候系统就会自动为其创建一个数据卷。其在实体机上的存储位置可在 7.1.4 命令的运行结果中找到。

例如

ubuntu 为镜像,建立一个名为 ubuntu_test2 的容器并启动,将实体机上的 /root/ubuntu_files1 目录挂载到容器中的 /test/ubuntu_files1 目录中去(实体机上的目录已存在)。

docker container run --rm --interactive --tty --volume /root/ubuntu_files1:/test/ubuntu_files1 --name=ubuntu_test2 ubuntu:latest

执行完该命令后,我们已经是在容器内操作了。此时我们来看看容器内是否出现了挂载的目录。

ls /test/ubuntu_files1

如果没有返回错误信息,说明挂载成功。现在我们来向里面写一点东西,看下能不能保存。

echo "File saved" > /test/ubuntu_files1/1.txt

然后按 Ctrl + D 关闭容器。我们就来到实体机下了,接下来我们来看看实体机有没有这个文件。

cat /root/ubuntu_files1/1.txt

如果返回了 File saved ,说明数据已经可以保存了!

您也可以再次创建容器,然后在容器内看看 /test/ubuntu_files1/1.txt 这个文件在不在。

8.2 打包一个新的镜像(不推荐)

这种方法十分简单粗暴,就是把容器内现有文件全部打包成一个新的镜像,然后新建一个使用该镜像的容器即可实现文件的保存。

之所以不推荐,主要是因为这样做会把容器内运行程序的缓存等无用文件一并打包下来。如果多次执行该操作,容器会变得非常臃肿。其次也可能会造成一定的安全隐患。

常用格式

docker container commit <需要保存的容器名> <打包之后的镜像名>

这里就不举例了。


九、容器的网络连接

9.1 容器联网的基本方式

NAT 模式是容器默认的联网模式。

在启动 Docker 服务之后,Docker 会自动往实体机内添加一个名为 docker0 的网桥,这个网桥默认可以与实体机内所有的网络接口通信。

我们通过执行 brctl show 这条命令来看一下 docker0 网桥的状态。

执行结果

bridge name  bridge id  STP enabled  interfaces
docker0  8000.024229c84e5a  no

当容器启动之后,会生成一个形如 vethXXXXXXX 的容器专用接口。这个接口也会加入到 docker0 的桥接列表中。

docker0 上面有 IP 地址,也可以自动为每个容器分配 IP 地址(非 DHCP 协议)。

我们通过执行 ip addr show dev docker0 还有 brctl show docker0 这条两命令来看一下。

执行结果

6: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
 link/ether 02:42:29:c8:4e:5a brd ff:ff:ff:ff:ff:ff
 inet 172.17.0.1/16 scope global docker0
 valid_lft forever preferred_lft forever
 inet6 fe80::42:29ff:fec8:4e5a/64 scope link
 valid_lft forever preferred_lft forever
bridge name  bridge id  STP enabled  interfaces
docker0  8000.024229c84e5a  no  vethcd2a637

其实 docker0 就相当于一个普通的路由器,通过 NAT 转换实现容器间的相互通信和连接外网。

我们通过执行 iptables -t nat -L POSTROUTING -v -n 来查看相关的 NAT 规则。

执行结果

Chain POSTROUTING (policy ACCEPT 373 packets, 31640 bytes)
 pkts bytes target  prot opt in  out  source  destination         
 12  729 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0

很显然,存在 docker0 网段的 SNAT 规则,说明容器都是通过 NAT 的方式与实体机共享网络的。

如需查看容器的 IP 地址,请执行以下命令:

docker container inspect --format='{{.NetworkSettings.IPAddress}}' <容器名>

9.2 自定义网桥 & 容器 IP 地址

使用默认网桥一般是不能自定义容器 IP 地址的,会提示以下错误:

User specified IP address is supported only when connecting to networks with user configured subnets

这时候,我们就需要自定义一个网桥。

9.2.1 自定义网桥

常用格式

docker network create --driver bridge --subnet <网桥网段> <网桥名>

例如

创建一个名为 docker_br1 的网桥,网段为 192.168.10.0/24

docker network create --driver bridge --subnet 192.168.10.0/24 docker_br1

执行结果

46cc818c92f0780ccd89811c12906c4527b554d18a61e72b0b2337b663ebab5f

这是自动生成的网桥唯一长 ID。

网桥的管理和删除命令格式和上面镜像管理的相似,这里就不再说了。

9.2.2 自定义容器 IP 地址

常用格式

7.27.11--tty 后面插入如下格式的内容

--network=<网桥名> --ip=<IP地址>

例如

创建并运行一个使用刚才创建的 docker_br1 网桥的容器,把 IP 地址设定为 192.168.10.211 ,然后验证结果。

为了方便,这里将直接运行一个包含 ifconfig 命令的镜像的容器,然后执行 ifconfig eth0 命令来验证。

docker container run --rm --interactive --tty --network=docker_br1 --ip=192.168.10.211 --name=see_ip_addr ianneub/network-tools ifconfig eth0

执行结果

#镜像下载过程略
eth0 Link encap:Ethernet HWaddr 02:42:c0:a8:0a:02  
 inet addr:192.168.10.211 Bcast:0.0.0.0 Mask:255.255.255.0
 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
 RX packets:2 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:180 (180.0 B) TX bytes:0 (0.0 B)

显然,这里的 IP 地址已经是我们设定的 192.168.10.211

9.3 端口映射

如果容器需要对外提供服务,在默认情况下需要把容器内的端口映射到实体机上。

常用格式

7.27.11--tty 后面插入如下格式的内容

-p [<实体机接口 IP 地址>:]<实体机端口>:<容器内端口>[/<tcp|udp>] [-p [<实体机接口 IP 地址>:]<实体机端口>:<容器内端口>[/<tcp|udp>]] ...
选项 功能
实体机接口 IP 地址 指定实体机监听的接口 IP。
如果不指定,则监听 0.0.0.0(即所有接口)。
实体机端口 指定实体机使用的端口。
容器内端口 指定容器内要映射的端口。
tcp|udp 指定使用的传输层协议。
如果不指定,默认使用 TCP 协议。

可以创建多个端口映射。

例如

运行一个提供 HTTP 服务的镜像(这里用 nginx)的容器,然后把容器中的 80 端口映射到实体机上的 8888 端口,最后测试能否访问。

docker container run --detach --rm --interactive --tty -p 8888:80 --name=nginx_test nginx && curl 127.0.0.1:8888

执行结果

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......

如果含有以上内容的界面,说明端口映射成功。

9.4 为容器设置域名解析

在很多情况下,我们需要在容器之间进行网络通信。而它们的 IP 地址又是不固定的,这就需要为容器固定一个 DNS 名称。

假设现在有一个容器 A ,而新建的容器 B 需要访问容器 A 上的网络服务,在没设置域名解析的情况下容器 B 只能通过容器 A 的 IP 地址来访问容器 A 。而如果在容器 B 建立的时候设置了域名解析,容器 B 就可以通过容器 A 的名称或别名来访问容器 A 。

常用格式

7.27.11--tty 后面插入如下格式的内容

--link <容器名称>[:<容器别名>]
选项 功能
容器别名 为容器指定一个别名。
如果不指定,将使用容器名称。

9.5 桥接到物理网络

个人不太推荐使用这种方法。

首先,容器都无法靠自身获取 IP 地址,必须借助 pipework 工具来设置。

而且,容器内一般是没有防火墙的,这样会降低整个容器的安全性。

如果需要使用物理网络上的不同 IP 提供不同服务的话,建议在实体机的物理网卡上绑定多个地址,然后把容器特定端口映射到特定的地址上去。

以下简单地说一下设置的方法。

9.5.1 设置网桥

请参考 Linux 网桥的相关教程,新建一个网桥,网桥成员为物理网络接口,并根据实际情况设置网桥的 TCP/IP 参数。

9.5.2 安装 pipework

git clone https://github.com/jpetazzo/pipework.git && cp pipework/pipework /usr/bin && rm -rf pipework

9.5.3 修改 docker 默认使用的网桥

cp /lib/systemd/system/docker.service /etc/systemd/system/docker.service

然后修改 /etc/systemd/system/docker.service

找到 ExecStart= 这一行,把这一行改成 ExecStart=/usr/bin/dockerd -b <网桥名> ,保存文件。

9.5.4 通过 pipework 指定容器 IP 地址

常用格式

pipework <网桥名> `<docker container run 完整命令>` <IP地址/前缀长度@网桥IP>

⚠️ 注意:pipework 只能修改运行中容器的网络配置,并且容器要持续运行。


十、其他实用的命令

10.1 查看 Docker 服务器的基本信息

docker info

10.2 查看容器占用的资源

docker container stats

该命令可以查看所有运行中容器的 CPU、内存使用情况。

状态是实时显示的,查看完毕请按 Ctrl+C 退出。

10.3 查看容器内运行的进程

常用格式

docker container top <容器名>

该命令以宿主机的命名空间来显示容器进程信息。

建议使用这个命令看看容器内运行哪些进程就好了,不要过分纠结 PID 和 UID。

例如

docker container top CentOS_Host

执行结果

UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                4072                4056                0                   02:35               ?                   00:00:00            /bin/bash
root                4138                4072                0                   02:35               ?                   00:00:00            sleep 10000

10.4 查看 Docker 的事件

常用格式

docker events [--since <yyyy-MM-ddThh:mm:ss> [--until <yyyy-MM-ddThh:mm:ss>]]
选项 功能
--since <yyyy-MM-ddThh:mm:Shadowsocks> 指定起始时间。
--until <yyyy-MM-ddThh:mm:Shadowsocks> 指定结束时间。

通过事件流,我们可以知道一个容器发生了什么事。比如开启、停止等。

事件是实时显示的,如果查看完毕请按 Ctrl+C 退出。

例如

显示从 2018 年 4 月 1 日 00:00:00 到 2018 年 4 月 1 日 12:00:00 的所有事件。

docker events --since="2018-04-01T00:00:00" --until="2018-04-01T12:00:00"

执行结果

2018-04-01T10:57:20.011080986+08:00 network connect 99ef690ad66b76dd2a9d6e6ba85da4984a8845ae42d4d4cf5db8f8546ed73626 (container=473b6de7768fd77297b996b7abcce8f93db62ecccdabad9acbfd664a408e68cc, name=bridge, type=bridge)
2018-04-01T10:57:20.437781611+08:00 container start 473b6de7768fd77297b996b7abcce8f93db62ecccdabad9acbfd664a408e68cc (build-date=20180302, image=centos, license=GPLv2, name=CentOS_Host, vendor=CentOS)
2018-04-01T10:58:16.764205384+08:00 container pause 473b6de7768fd77297b996b7abcce8f93db62ecccdabad9acbfd664a408e68cc (build-date=20180302, image=centos, license=GPLv2, name=CentOS_Host, vendor=CentOS)
2018-04-01T10:58:25.290369808+08:00 container unpause 473b6de7768fd77297b996b7abcce8f93db62ecccdabad9acbfd664a408e68cc (build-date=20180302, image=centos, license=GPLv2, name=CentOS_Host, vendor=CentOS)
...

10.5 查看容器的日志

常用格式

docker container logs <容器名>

日志一般记录着容器的 stdoutstderr,可用于容器的调试与排错。

10.6 查看容器存储层的文件变化

常用格式

docker container diff <容器名>

可查看容器存储层相对于原始镜像的变化。

例如

docker container diff 9ffd2df5d849

执行结果

A /11111
C /root
A /root/.ash_history

10.7 快速删除所有容器

常用格式

docker container rm $(docker container ls --all -q)

⚠️ 注意:运行中的容器不能被删除。

10.8 快速删除所有镜像

常用格式

docker image rm $(docker image ls --all -q)

⚠️ 注意:如果有基于要删除镜像的容器,则该镜像不能被删除。


十一、参考文献

  1. Docker 官方文档库
  2. Docker 简明教程
  3. Docker — 从入门到实践
  4. 理解 Docker 容器的进程管理
  5. 10 张图带你深入理解 Docker 容器和镜像
  6. Docker 学习笔记 (3) — 什么是 Docker 镜像、容器和仓库?
  7. CentOS 7 宿主机上建立 Docker 桥接物理网络过程
  8. Docker 实战(二十七)Docker 容器之间的通信
  9. 连接容器 | Docker 中文指南
  10. Dockerfile 语法

发表评论