docker 的出现可以说让应用环境的配置、发布和测试变得异常轻松,尤其是现在大家都在讨论 coine swmp poman,
如果没有 docker 的先决知识,大家听起来可能一头雾水。这期视频呢,我会用实例向大家介绍 docker 的基本概念、安装和使用,以及如何用 docker compose 来同时管理多个容器。
docker 的基本概念
在学习使用 docker 之前,我们需要知道它是用来解决什么样的问题。
比如,你写了一个 web 应用,并且本地调试没有任何问题,这时候你想发给你的朋友试试看,或者部署到远程的云服务器上。那么首先你需要配置相同的软件,比如数据库、web 服务器、必要的插件、库等等。而且你还不能保证软件一定能够正常运行起来,因为别人用的可能是完全不同的操作系统。即便同样是使用 Linux,每种发行版也会有微小的区别。为了模拟完全相同的本地开发环境,我们自然会想到使用虚拟机,但是虚拟机需要模拟硬件,运行整个操作系统,不但体积臃肿,内存这样高,程序的性能也会受到影响。
这时候我们的 docker 就派上了用场。docker 在概念上与虚拟机非常类似,但却轻量很多,它不会去模拟底层的硬件,只会为每一个应用提供完全隔离的运行环境。你可以在环境中配置不同的工具软件,并且不同环境之间相互不影响。这个环境在 docker 中也被称作 container。容器。
讲到这里,我们就不得不提到 docker 中的三个重要概念,Docker file image 和 container image。
Image,镜像
你可以把它理解成一个虚拟机的快照,里面包含了你要部署的应用程序以及它所关联的所有库软件。通过镜像,我们可以创建许多不同的 container 容器。这里的容器就像是一台台运行起来的虚拟机,里面运行了你的应用程序,每个容器是独立运行的,它们相互之间不影响。
最后,Docker file 就像是一个自动化脚本,它主要被用来创建我们之前讲到的镜像。这个过程就好比是我们在虚拟机中安装操作系统和软件一样,只不过是通过 docker file 这个自动化脚本完成了。
快速上手 docker 的最好方法就是亲自安装并去使用它。如果你使用的是 Windows 和 Mac,你可以在官网下载一个叫做 docker desktop 的应用,并且在 WINDOW10 以上。你可以使用 WS2,也就是 Windows 下的 Linux 子系统来运行 docker,这也是我这里使用的配置方式。
如果你使用的不是 Windows 最新的预览版本 WS2 安装可能稍微复杂一点,不过基本也是按照这里的步骤安装。
在 Linux 下面我们可以直接使用包管理工具,我们按照这里的指示一步一步执行即可。如果你使用的是 Vscode,我也非常推荐安装 docker 的扩展,
它会提供 docker file 的语法检测、代码高亮、自动补全等等。
你也可以通过菜单运行各种 docker 命令,
并且在左侧面板中看到你创建的所有镜像容器等等。
部署应用
接下来我们就尝试用 docker 来部署一个应用。这里我用之前写的一个拍 on 程序来举例,这是一个非常简单的用 flask 搭建的记账工具,主要为了方便自己对日常开销的一些统计。
首先,我们在应用的根目录下创建一个 Dockertfile 文件。第一行我们需要用 from 命令指定一个基础镜像,这样可以帮我们节省许多软件安装配置的时间。
可以看到在 docker hub 上提供了许多高质量的操作系统镜像,比如 Ubuntu 、debian 、fedora、 alpine 等等。不同的操作系统提供不同的包管理工具,比如 Ubuntu 上的 apt、fedora 上的 dnf。
但是在 Docker Hub 上还有许多方便某一种语言、某种框架开发的镜像,比如 nginx、redis、node、 Python、tomcat 等等。
由于这里我做的是 Python 应用的开发,自然我会使用 Python 的镜像,这样免去了它安装步骤。
FROM python:3.8-slim-buster
这里的 Python 是官方镜像的名字冒号,后面这一串是版本号,同时也是一个标签。我们可以点击 Python 转到 docker HUB 的镜像页面,里面可以找到所有支持的标签。
比如我们这里用的是 Python3.8 版本,运行在 debian buster 的发行版上
后面的 WORKDIR 指定了之后所有 docker 命令的工作路径,注意是这个命令之后的所有 docker 命令 (working directory),比如我们马上要讲到的 RUN COPY 等等。当然,如果这个路径不存在,Docker 会帮你自动创建,这样可以避免使用绝对路径或者手动 CD 切换路径,增加程序的可读性。
之后呢,我们可以调用 copy 命令将所有的程序拷贝到 docker 镜像中。
第一个参数代表本地文件,"." 代表程序根目录下的所有文件,
第二个参数代表 docker 镜像中的路径,这里的 "." 代表当前的工作路径,也就是之前指定的 APP 目录。随后的 RUN 允许我们在创建镜像时运行任意的 shell 命令。因为我们用的是 Linux 镜像,所以像 echo、pwd、 cp、rm 这些都是合法的。比如这里我用到 pip install 来安装 python 程序的所有关联。
通过以上的所有命令,我们就可以完成一个 docker 镜像的创建。在 docker file 的最后,我们会用到 CMD 来指定当 docker 容器运行起来以后要执行的命令。大家需要注意这里容器和镜像的区别,并且它和我们之前讲到的 RUN 不一样。Run 是创建镜像时候使用的,而 CMD 是运行容器的时候使用的。到这里,我们的自动化脚本 doer file 就完成了。
(terminal 运行)
接下来我们可以使用 docker build 来创建一个镜像,
docker build -t my-finance .
这里的横杠 T 指定了我们镜像的名。字最后面的 "." 告诉 docker,应该在当前目录下寻找这个 docker file,这个不能省略。
第一次调用 docker build 会比较慢,因为 docker 会下载必要的镜像文件,然后一行行运行我们的指令,不过再次调用就会快很多,因为 docker 会缓存之前的每一个操作。这个在 docker 中也被称作分层,这里我们就不展开讨论了。
docker run -p 80:5000 -d my-finance
有了镜像以后,我们可以通过 docker run 来启动一个容器。这里需要注意的是这个 - p 参数,它会将容器上的某一个端口映射到你的本地主机上,这样你才能够从主机上访问容器中的 web 应用。前面的 80 是我们本地主机上的端口,后面是容器上的端口,这个不要搞反了。第二个参数 - d,这样容器在后台运行,这样容器的输出就不会直接显示在控制台。
如果不出意外的话,你已经可以在浏览器中访问这个 web 应用了。我们通过 docker desktop 这个图形界面可以查看应用在后台的所有输出,这个对于调试非常方便,
同时我们也可以看到当前容器的各种信息状态等等。这里的 containers 中显示了我们创建的所有容器。我们可以选择停止、重启或者删除它们还可以通过 shell 远程调试这个容器。这里是它们所对应的命令行指令。
需要注意的是,当我们删除一个容器的时候,之前所做的修改、新添加的数据会全部丢失,这就好比是我们删除一个虚拟机,里面的数据会一同销毁一样。如果我们希望保留容器中的数据,我们可以使用 docker 提供的 volume 数据卷。你可以把当做是一个在本地主机和不同容器中共享的文件夹。比如,你在容器中修改了某一个 volume 的数据,它会同时反映在其他的容器上。
docker run -dp 80:5000 -v my-finance-data:/etc/finace my-finance
我们可以通过 docker volume create 来创建一个数据卷,随后再启动容器的时候,我们可以通过 - v 参数指定将这个数据卷挂载 (mount) 到容器的哪一个路径上。可以看到我们这里将 my finance data 挂载到了 etc/finance 这个路径上,像这个路径写入的任何数据都会被永久的保存在这个数据卷中。
之前我们讲到的例子都只涉及单个容器,但在实际使用中,我们的应用程序可能会用到多个容器共同协作。比如我们可以使用一个容器来运行 web 应用,另一个容器来运行数据库系统 mysql,这样可以做到数据和应用逻辑的有效分离。比如当 web 程序宕机了,数据库依然在有效运转,这个时候我们只需要修复 web 容器即可,
而 docker compose 刚好可以帮我们做到这一点,我们可以创建一个 docker compose.yama 文件,在这个文件下,我们通过 services 来定义多个 container,比如这里我们定义一个 web 容器,它里面运行了我们的。Web 应用。然后在定义一个 DB 容器里面运行了 MYCQL 数据库系统,这里我们可以通过这两个环境变量指定数据库的名字和连接密码。同时在 DB 容器中我们还可以通过 volumes 指定一个数据卷,用来永久存放数据。
定义完毕之后,我们保存文件,使用 docker compose up 来运行所有的容器。这里的 - d 同样代表在后台运行所有容器不直接输出在控制台。与这个命令对应的,我们可以使用 docker compose down . 来停止并删除所有的容器。不过新创建的数据卷需要我们手动删除,除非我们在后面加入 volumes 参数。另外,刚刚讲到的所有操作也都可以在图形界面中完成。