如果你尝试过在别的机器上部署过服务,那么我相信你一定会涌现出许多悲伤的回忆。“明明在我的电脑上就可以正常运行,怎么换了台就不行了?”然后发现依赖不全,开始尝试装补齐依赖。“我要的和另一个服务所依赖的版本不一样,换了版本另一个就没法运行了怎么办?”好不容易都搞定了,这时你发现,还有几十台机器都需要在这样部署一遍,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 镜像的含义,一般镜像都会有 latest
的 tag
表示最新的。
随后 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
:禁用所有网络。