第04课:Docker

第04课:Docker 使用入门(上)

本文主要讲解 Docker 的一些基本操作,包括如何从镜像运行一个容器,如何从容器提交一个镜像,以及 Docker仓库的知识。

准备工作

首先,确保你的电脑连接到了互联网;然后按照之前安装教程里介绍的,运行你的 Docker,然后在终端或者 powershell 里面运行:

sudo docker pull ubuntu:16.04

等待下载完成,你就获得了一个 Ubuntu 16.04 的官方镜像。

从镜像到容器

完成本文开头的准备工作之后,你可以使用如下命令查看当前你的电脑里有哪些镜像:

sudo docker images

你会得到类似下面的结果(你的结果可能和我有所不同,如果你是全新安装的 Docker,现在应该只有最后一个镜像:Ubuntu:16.04):

enter image description here

可以看到,每个镜像都有5个属性:属于哪个仓库,标签,ID,创建时间和大小。

好了,现在我们已经有一个 Ubuntu 镜像了,下面我们从这个镜像运行一个容器,Docker run 命令可以从一个镜像运行一个包含一个主进程进程的容器:

enter image description here

现在,在你的终端里输入如下命令:

sudo docker run -ti --name first ubuntu:16.04 bash

命令解释

  • Docker run 是从一个镜像运行一个容器的指令。
  • -ti 参数的含义是:terminal interactive,这个参数可以让我们进入容器的交互式终端。
  • --name 指定容器的名字,后面的 first 就是我们给这个容器起的名字。
  • ubuntu:16.04 是致命从哪个镜像运行容器,ubuntu是仓库名,16.04是标签。
  • bash 指明我们使用 bash 终端。

运行完以上命令,你会得到下面的结果,你会发现我们已经进入了容器:

enter image description here

现在,你可以在容器里运行一些指令,就好像在一个全新的系统里一样,比如我们运行一个创建文件的指令:

touch test.py

然后你会发现你已经有了这个文件:

enter image description here

还记得吗?在“初遇 Docker”一文中,我们特别强调,镜像是一个静态的概念,这里我们就来看看这是什么意思:

现在我们从相同的镜像再运行一个新的容器:

sudo docker run -ti --name second ubuntu:16.04 bash

执行“ls”命令之后你会发现我们创建的文件“test.py”并不存在:

enter image description here

看到这里,你是否学会了怎么从一个镜像运行一个容器呢?你是否对“镜像是一个静态的概念”这句话有了自己的理解呢?

从容器到镜像

现在退出我们在上一步运行的容器:直接输入 exit,然后回车就可以退出。这其实是终止了我们的容器:

enter image description here

注意,并不是容器被删除了,而是容器被终止了,我们依旧可以看到我们的容器,运行以下指令可以查看我们当前的容器:

sudo docker ps -a

你会得到下面的输出:

enter image description here

我们刚刚在名字为 first 的容器里创建了一个文件“test.py”,如果这是很重要的文件,你以后还需要它,你希望每次运行一个新容器,这个文件都要存在于新的容器中,怎么办呢?Docker 早就想到了这一点,你可以把容器提交为一个新的镜像,这样你从新镜像运行容器的话,你在容器里创建的文件就都会存在:

enter image description here

使用如下指令可以把刚才名为 first 的容器提交为一个新的镜像

sudo docker commit first my_image:v1.0 

其中,my_image:v1.0 就是“仓库名:版本号”。

现在,使用“sudo docker images”命令查看,你会发现我们多了一个名为“my_image”的镜像:

enter image description here

现在,从这个新的镜像创建容器,你会发现你之前在容器 first 里面创建的 test.py 文件已经存了:

enter image description here

注意

其实,用这种方式创建镜像是很不好的习惯,具体的原因以及更好的办法会在后面的教程说明。现在为了初学者理解方便,我们暂且使用这种方式帮助大家理解,但是希望读者牢记:真正使用 Docker 开发的时候需要避免这种从容器提交镜像的方法!

更多容器操作

Docker run 做了什么?

前面,我们一直在用“Docker run”命令来创建容器,那么“Docker run”到底做了什么呢?

具体来说,当你运行“Docker run”的时候:

  • 检查本地是否存在指定的镜像,不存在就从公共仓库下载;
  • 利用镜像创建并启动一个容器;
  • 给容器包含一个主进程(Docker 原则之一:一个容器一个进程,只要这个进程还存在,容器就会继续运行);
  • 为容器分配文件系统,IP,从宿主主机配置的网桥接口中桥接一个虚拟接口等(会在之后的教程讲解)。

守护态运行

所谓“守护态运行”其实就是后台运行(background running),有时候,需要让 Docker 在后台运行而不是直接把执行的结果输出到当前的宿主主机下,这个时候需要在运行“docker run”命令的时候加上 “-d”参数(-d means detach)。

下面举个例子说明一下:

如果使用“-d”参数:

sudo docker run --name withoutD ubuntu:16.04 bash -c "echo hello world"

容器会把输出结果打印到宿主主机上:

enter image description here

如果使用“-d”参数的话,容器启动后会返回容器 ID,但是不会将输出结果打印到宿主主机:

sudo docker run -d --name withD ubuntu:16.04 bash -c "echo hello world"

enter image description here

注意:

这里说的后台运行容器长久运行不是一回事,后台运行只是说不会在宿主主机的终端打印输出,但是你给定的指令执行完成后,容器就会自动退出,所以,长久运行与否是与你给定的需要容器运行的命令有关,与“-d”参数没有关系。

进入容器和终止容器

进入容器一般有三种方法:

  • ssh 登录
  • attach 和 exec
  • nesenter

本文只介绍 attach 和 exec 方法,因为这是 Docker 自带的命令,使用起来比较方便;而无论是 ssh 还是 nesenter 的使用都需要一些额外的配置,鉴于本课程是针对完全初学者的,所以就不作太多冗余的介绍了。不过,在后面讲解 Docker 网络的时候,可能还是需要使用一下 ssh。(对于我这种“前后矛盾”的做法希望大家可以理解……)

现在让我们来看一下怎么进入一个正在运行的容器吧,不过在此之前先让我们做一些清理工作,顺便借此学习一下如何删除容器

sudo docker rm nameOfContainer

以上命令可以删除容器,把 nameOfContainer 替换成你的容器名,按下回车就可以删除对应的容器了。

然后,我们在后台运行一个新的容器:

sudo docker run -d -ti --name background ubuntu:16.04 bash

现在,使用如下命令进入我们的容器:

sudo docker exec -ti background bash

创建一个新文件:

touch test.java

然后在一个新的终端里再次使用我们刚才提到的指令进入容器,发现刚才建立的文件已经存在了:

enter image description here

现在来看一下如何使用 attach 方法吧:

sudo docker attach background

然后按两次回车就可以进入容器了。

疑点解惑:

你可能想问,这两种方法都能进入容器,为什么 Docker 会有两个完全一样功能的指令呢?其实,这两个指令还是有很多不同的,甚至可以说,他们根本就是完全不同的指令!

我这样说你可能更加疑惑了,不要担心,我们马上就来解释:

还记得我们在3.1小节提到的吗?——Docker run 指令会给容器分配一个进程,并且 Docker 的哲学之一就是:一个容器一个进程!所以 attach 实际就是进入容器的主进程,所以无论你同时 attach 多少,其实都是进入了主进程。比如下图,我使用两次 attach 进入同一个容器,然后我在一个 attach 里面运行的指令也会在另一个 attach 里面同步输出,因为它们两个 attach 进入的根本就是一个进程!

enter image description here

在 attach 进入的容器(前提是你退出了 exec)使用“ps -ef”指令可以看出,我们的容器只有一个 bash 进程和 ps 命令本身:

enter image description here

而 exec 就不一样了,exec 的过程其实是给容器新开了一个进程,比如我们使用 exec 进入容器后,使用 ps -ef 命令查看进程:

enter image description here

你会发现,我们除了 ps 命令本身,还有两个 bash 进程,究其原因,就是因为我们 exec 进入容器的时候实际是在容器里面新开了一个进程。

这就涉及到了另一个问题,如果你在 exec 里面执行 exit 命令,你只是关掉了 exec 命令新开的进程,而主进程依旧在运行,所以容器并不会停止;而在 attach 里面运行 exit 命令,你实际是终止了主进程,所以容器也就随之被停止了。总结一下,attach 的使用不会在容器开辟新的进程;exec 主要用在需要给容器开辟新进程的情况下。

现在来介绍一下如何终止一个运行的容器。我们的容器在后台运行,现在我们觉得这个容器已经完成了任务,可以把它终止了,怎么办呢?一种办法是 attach 进入容器之后运行“exit”结束容器主进程,这样容器也就随之被终止了。另一种比较推荐的方法是运行:

sudo kill nameOfContainer

其它

本小节主要介绍一些有关容器的其它操作,作为前面的补充。

  • Docker 日志

比如你在后台运行一个容器,可是你把“echo”错误输入成了“eceo”:

sudo docker run -d --name logtest ubuntu:16.04 bash -c "eceo hello"

后来,你意识到你的容器没有正常运行,你可以使用“docker logs”指令查看哪里出了问题。

sudo docker logs logtest

enter image description here

你会看到,这条指令会告诉你 eceo 指令未找到。

  • 限制容器资源

资源限制主要包含两个方面的内容——内存限制和 CPU 限制。

内存限制

执行“Docker run”命令时可以使用的和内存限制有关的参数如下:

参数 简介
-m, - -memory 内存限制,格式:数字+单位,单位可以是b, k, m, g,最小4M
-- -memory-swap 内存和交换空间总大小限制,注意:必须比-m参数大

CPU限制

Docker run 命令执行的时候可以使用的限制 CPU 的参数如下:

参数 简介
-- -cpuset-cpus="" 允许使用的CPU集
-c,- -cpu-shares=0 CPU共享权值
-- -cpu-quota=0 限制CPU CFS配额,必须不小于1ms,即>=1000
cpu-period=0 限制CPU CFS调度周期,范围是100ms~1s,即[1000, 1000000]

现在详细介绍一下 CPU 限制的这几个参数。

1、可以设置在哪些 CPU 核上运行,比如下面的指令指定容器进程可以在 CPU1 和 CPU3 上运行:

sudo docker run -ti --cpuset-cpus="1,3" --name cpuset ubuntu:16.04 bash

2、CPU 共享权值——CPU 资源相对限制

默认情况下,所有容器都得到同样比例的 CPU 周期,这个比例叫做 CPU 共享权值,通过“-c”或者“- -cpu-shares”设置。Docker 为每个容器设置的默认权值都是1024,不设置或者设置为0都会使用这个默认的共享权值。

比如你有2个同时运行的容器,第一个容器的 CPU 共享权值为3,第2个容器的 CPU 共享权值为1,那么第一个容器将得到75%的 CPU 时间,而第二个容器只能得到25%的 CPU 时间,如果这时你再添加一个 CPU 共享权值为4的容器,那么第三个容器将得到50%的 CPU 时间,原来的第一个和第二个容器分别得到37.5%和12.5的 CPU 时间。

但是需要注意,这个比例只有在 CPU 密集型任务执行的是有才有用,否则容器根本不会占用这么多 CPU 时间。

3、CPU 资源绝对限制

Linux 通过 CFS 来调度各个进程对 CPU 的使用,CFS 的默认调度周期是 100ms。在使用 Docker 的时候我们可以通过“- -cpu-period”参数设置容器进程的调度周期,以及通过“- -cpu-quota”参数设置每个调度周期内容器能使用的 CPU 时间。一般这两个参数是配合使用的。但是,需要注意的是这里的“绝对”指的是一个上限,并不是说容器一定会使用这么多 CPU 时间,如果容器的任务不是很繁重,可能使用的 CPU 时间不会达到这个上限。

注意:

这里我们仅仅是简单介绍了部分资源限制的参数,更多的参数大家可以随着学习的深入自己慢慢探索。关于资源限制的背后原理我们会在后面的教程里进一步探讨。

  • 查看容器详细信息
sudo docker inspect [nameOfContainer]
  • 查看容器最近一个进程
sudo docker top [nameOfContainer]
  • 停止一个正在运行的容器
sudo docker stop [nameOfContainer]
  • 继续运行一个被停止的容器
sudo docker restart [nameOfContainer]
  • 暂停一个容器进程
sudo docker pause [nameOfContainer]
  • 取消暂停
sudo docker unpause [nameOfContainer]
  • 终止一个容器
sudo docker kill [nameOfContainer]

一些关于容器使用的总结和建议

首先,总结一下容器的生命周期吧:

enter image description here

接下来,根据我们学过的内容,列出一点使用容器的建议,更多的建议会随着阅读的深入进一步提出。

  1. 要在容器里面保存重要文件,因为容器应该只是一个进程,数据需要使用数据卷保存,关于数据卷的内容在下一篇文章介绍;

  2. 尽量坚持“一个容器,一个进程”的使用理念,当然,在调试阶段,可以使用exec命令为容器开启新进程。

更多关于镜像的操作

补充一下之前没有涉及到的镜像操作:

  • 删除镜像
sudo docker rmi [nameOfImage]
  • 查看镜像操作记录
sudo docker history [nameOfImage]
  • 给镜像设置一个新的仓库:版本对
sudo docker tag my_image:v1.0 my:v0.1

  运行了上面的指令我们就得到了一个新的,和原来的镜像一模一样的镜像。

  • 查看镜像详细信息
sudo docker inspect [nameOfImage]

仓库

所谓“仓库”,简单来说就是集中存放镜像的地方

Docker 官方维护着一个公共仓库 Docker store,你可以方便的在 Docker store 寻找自己想要的镜像。Docker store 的链接在这里

enter image description here

当然,你也可以在终端里面登录:

sudo docker login

输入你的用户名和密码就可以登陆了。

然后,可以使用“sudo docker search ubuntu”来搜索 Ubuntu 镜像:

enter image description here

如果你希望建立自己的私有仓库,也是可以的。但是本系列教程不对私有仓库做过多介绍。

接下来做什么?

本文主要介绍了容器和镜像的基本操作,下一篇教程会介绍一些关于网络和数据卷的内容。

上一篇
下一篇
目录