Amazing Docker

Docker Walkthrough

ZingLix November 14, 2019

如果你尝试过在别的机器上部署过服务,那么我相信你一定会涌现出许多悲伤的回忆。“明明在我的电脑上就可以正常运行,怎么换了台就不行了?”然后发现依赖不全,开始尝试装补齐依赖。“我要的和另一个服务所依赖的版本不一样,换了版本另一个就没法运行了怎么办?”好不容易都搞定了,这时你发现,还有几十台机器都需要在这样部署一遍,emmmm

所以此时 Docker 就闪亮登场了。

Docker 是什么

Docker 很像虚拟机,能够为应用提供独立的环境,但是又很不像虚拟机,因为它们虚拟化的方式完全不同。传统的虚拟机会在宿主机上对硬件做一层抽象,模拟出一套新的硬件,然后再在上面运行一个新的操作系统,而 docker 则直接运行于宿主机内核中,每一个容器都只是一个进程,并没有硬件虚拟,因此更为轻量级,能够在秒级启动,大小也在 MB 级别。

正因为 docker 的轻便,让我们之前许多的幻想都成为了现实

  • 环境:docker 可以提供完全一致的运行时环境,所以只要我们制作 docker 的时候提供了与开发时相同的环境,那么就不必再担心换电脑就无法运行的问题了。而且因为每个容器都是独立的,所以不同服务间依赖冲突的问题也迎刃而解。
  • 部署:制作完的 docker 包含了所有了需要的环境,所以再也不用在每台电脑上再手动安装各种工具,直接启动 docker 即可。更新时候就算依赖变更,有了镜像直接重启就行,这样极大的降低了部署的成本。也因此,加上 docker 对空间的需求小,使得分发 docker 镜像成为了可能。
  • 维护:docker 采用了分层存储的方式,所以我们可以在一个镜像上进行修改,例如在一个基础环境中加上我们要运行的程序就完成了镜像了制作。配合镜像分发使得镜像制作变得极为简单。

这样我们就能理解 docker 的口号 build, share, run 了。

如何运作

docker 运行需要有一个称为 镜像 (image)的东西,然后将其放入 容器(container)就能够运行。

其中镜像就是一个文件,其中包含了所要运行的程序,以及运行所需要的所有依赖、环境等等一切东西,就类似于一个配置好所有环境的虚拟机,然后将其打包得到的东西。而容器是 docker 运行的基本单位,其拿到一个镜像后就可以运行,也可以暂停、删除等等,程序所有的操作都在容器内进行,一旦容器消亡,容器内的变更也都会消失,因此提倡容器应当是无状态呢。值得注意的是,镜像一旦制作完成就不会再变更,那么如果想要留住一些文件,就应当将其映射到宿主机,这样文件操作会直接操作宿主机上的文件,这样即使容器消亡变更依然存在于宿主机上。

如何使用

首先可以根据官方文档安装 docker,之后就可以使用 docker 了。

容器化应用

docker 的制作都根据当前目录的 Dockerfile 文件,其中指明了应用该如何被制作成镜像。

1
2
3
4
5
6
7
8
FROM python:3.6.9

WORKDIR /app
COPY . /app
RUN pip install numpy
EXPOSE 80

CMD [ "python", "test.py" ]

上述为一个简单的 python 应用的 Dockerfile。一般来说第一步是 FROM,指明我们在哪个镜像基础上制作,在 Docker Hub 上许多可使用的镜像。一般镜像名为 name:tag 的格式,name 为镜像名,tag 则一般用于指明版本,比如此处为版本为 3.6.9 的 python 镜像的含义,一般镜像都会有 latesttag 表示最新的。

随后 WORKDIR 表明我们之后在容器内的工作目录路径。之后就是将应用打包进去的步骤,比如 python 运行时需要各种 py 文件,所以这里我将当前目录都复制到了容器内的工作目录中。然后 RUN 可以在制作镜像过程运行一些语句,比如此处我运行了 pip install 在容器内安装了 numpy 依赖。EXPOSE 用于暴露容器的端口,因为容器本来与宿主机应当是隔离的,但是有时需要通信,就可以暴露端口,比如一个 HTTP 的服务器就需要暴露 80 端口才可以正常被访问。最后 CMD 指明了运行镜像时的启动命令。

镜像构建和运行

有了 Dockerfile 我们就可以开始制作镜像了。

1
docker image build -t testpy:latest .

还记得之前我们使用其他镜像作为基础镜像时候吗?没错,我们制作镜像的时候也需要为其提供 name:tag 的名称。构建完成后就该运行了。

1
docker container run --publish 8888:80 --detach --name test testpy:latest

这里 --publish 可以将访问宿主机 8888 端口的请求转发到容器的 80 端口(还记得我们之前暴露 80 端口吗),通过这种方式就可以避免多个容器占用同一问题的问题。之后 --detach 说明我们想它运行于后台,--name 为该容器命名。之后就可以 docker container stop test 停止或者 docker container rm test 移除这一容器了。

又或者如果你想要在其他电脑上部署,通过 docker images 查看所有镜像 id,然后 docker save <image-id> > <filename> 就可以保存镜像,去到另一台电脑 docker load < <filename> 就可以加载,再 docker run 就愉快的完成了部署啦。

持久化数据

之前有说到当容器消亡时,容器内部所有东西都会一同消亡,而往往我们需要持久化一些数据,但默认情况下所有操作都是在容器内进行的,所以我们必须对这部分数据进行一些处理。

docker 中有一个称为 数据卷 的东西,可以在不同容器间共享,而且其生命周期与使用它的容器无关,因此适合用来存放持久化的数据。

docker volume create <name> 就可以创建一个数据卷,之后在运行容器的时候需要添加 --mount source=<name>,target=<path>,这样容器内 <path> 处就会被名为 <name> 的数据卷映射过去,又或者更简单的 -v <vol-name>:<path>,其中 <vol-name> 也可以是宿主机中的路径,这样就会把宿主机中的路径直接映射进容器。

容器互联

很多时候仅凭一个容器是无法完成任务的,例如我一个应用需要访问数据库,而数据库是另一个容器,我还需要用 nginx 转发,此时我们就需要容器间通信,docker 以及为此提供了 network 设施。

docker network create -d bridge my-net 这一语句创建了名为 my-net 的模式为 bridge 的网络,之后运行容器的时候添加 --network my-net 就可以连入该网络。在容器内直接用其他容器的容器名就可以访问其他容器。每个容器都可以同时连入多个不同的网络。

网络分为好多种模式

  • bridge:桥接模式,默认的模式,适合于运行于同一个宿主机上多个容器间进行交互。
  • host:宿主模式,会移除宿主机与容器之间的独立性,容器将直接使用宿主机的网络(IP、端口等等)。
  • overlay:适合于运行于多个不同的宿主机上容器间进行交互。
  • none:禁用所有网络。