Docker 入门:简化 DevOps
已发表: 2022-03-11如果你喜欢鲸鱼,或者只是对快速、轻松地将软件持续交付到生产环境感兴趣,那么我邀请你阅读这篇介绍性的 Docker 教程。 一切似乎都表明软件容器是 IT 的未来,所以让我们快速了解一下容器鲸 Moby Dock 和 Molly。
Docker 以带有友好外观的鲸鱼标志为代表,是一个开源项目,有助于在软件容器内部署应用程序。 它的基本功能是由 Linux 内核的资源隔离特性启用的,但它在其之上提供了一个用户友好的 API。 第一个版本于 2013 年发布,此后非常受欢迎,并被 eBay、Spotify、百度等许多大玩家广泛使用。 在上一轮融资中,Docker 获得了 9500 万美元的巨额资金,它有望成为 DevOps 服务的主要部分。
运输货物类比
Docker 背后的哲学可以用以下简单的类比来说明。 在国际运输行业,货物必须通过叉车、卡车、火车、起重机和轮船等不同方式运输。 这些货物有不同的形状和大小,并且有不同的存储要求:一袋袋糖、牛奶罐、植物等。从历史上看,这是一个痛苦的过程,依赖于每个中转站的人工干预来装卸。
随着多式联运集装箱的采用,这一切都发生了变化。 由于它们采用标准尺寸并且在制造时考虑到了运输,所有相关的机器都可以设计成以最少的人工干预来处理这些。 密封容器的另一个好处是它们可以保护敏感货物的内部环境,如温度和湿度。 因此,运输业可以不再担心货物本身,而是专注于将货物从 A 运送到 B。
这就是 Docker 的用武之地,它为软件行业带来了类似的好处。
它与虚拟机有何不同?
乍一看,虚拟机和 Docker 容器可能看起来很相似。 但是,当您查看下图时,它们的主要区别将变得明显:
除了管理程序之外,在虚拟机中运行的应用程序需要完整的操作系统实例和任何支持库。 另一方面,容器与主机共享操作系统。 Hypervisor 在某种意义上与容器引擎(在镜像上表示为 Docker)相当,它管理容器的生命周期。 重要的区别在于容器内运行的进程就像主机上的本地进程一样,并且不会引入与管理程序执行相关的任何开销。 此外,应用程序可以重用库并在容器之间共享数据。
由于两种技术具有不同的优势,因此通常会找到结合了虚拟机和容器的系统。 一个完美的例子是 Docker 安装部分中描述的名为 Boot2Docker 的工具。
码头工人架构
在架构图的顶部有注册表。 默认情况下,主注册表是托管公共和官方镜像的 Docker Hub。 如果他们愿意,组织也可以托管他们的私有注册表。
在右侧,我们有图像和容器。 可以在启动容器时显式( docker pull imageName
)或隐式从注册表中下载图像。 下载图像后,它会在本地缓存。
容器是图像的实例——它们是有生命的东西。 可能有多个容器基于同一个镜像运行。
在中心,有一个负责创建、运行和监控容器的 Docker 守护进程。 它还负责构建和存储图像。 最后,在左侧有一个 Docker 客户端。 它通过 HTTP 与守护进程对话。 在同一台机器上使用 Unix 套接字,但可以通过基于 HTTP 的 API 进行远程管理。
安装 Docker
有关最新说明,您应该始终参考官方文档。
Docker 在 Linux 上本地运行,因此根据目标发行版,它可能像sudo apt-get install docker.io
一样简单。 有关详细信息,请参阅文档。 通常在 Linux 中,您会在 Docker 命令前加上sudo
,但为了清楚起见,我们将在本文中跳过它。
由于 Docker 守护程序使用 Linux 特定的内核功能,因此无法在 Mac OS 或 Windows 中本地运行 Docker。 相反,您应该安装一个名为 Boot2Docker 的应用程序。 该应用程序由 VirtualBox 虚拟机、Docker 本身和 Boot2Docker 管理实用程序组成。 您可以按照 MacOS 和 Windows 的官方安装说明在这些平台上安装 Docker。
使用 Docker
让我们以一个简单的示例开始本节:
docker run phusion/baseimage echo "Hello Moby Dock. Hello Molly."
我们应该看到这个输出:
Hello Moby Dock. Hello Molly.
然而,幕后发生的事情比你想象的要多得多:
- 图像“phusion/baseimage”是从 Docker Hub 下载的(如果它还没有在本地缓存中)
- 已启动基于此映像的容器
- 命令echo在容器内执行
- 命令退出时容器已停止
首次运行时,您可能会注意到文本在屏幕上打印之前的延迟。 如果图像已在本地缓存,则一切都将花费不到一秒钟的时间。 可以通过运行docker ps -l
来检索有关最后一个容器的详细信息:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES af14bec37930 phusion/baseimage:latest "echo 'Hello Moby Do 2 minutes ago Exited (0) 3 seconds ago stoic_bardeen
进行下一次潜水
如您所知,在 Docker 中运行一个简单的命令就像直接在标准终端上运行一样简单。 为了说明一个更实际的用例,在本文的其余部分,我们将了解如何利用 Docker 部署一个简单的 Web 服务器应用程序。 为简单起见,我们将编写一个 Java 程序来处理对 '/ping' 的 HTTP GET 请求并以字符串 'pong\n' 进行响应。
import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class PingPong { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); server.createContext("/ping", new MyHandler()); server.setExecutor(null); server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "pong\n"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }
Dockerfile
在开始构建自己的 Docker 映像之前,最好先检查 Docker Hub 中是否存在现有映像或您有权访问的任何私有注册表。 例如,我们不会自己安装 Java,而是使用官方镜像: java:8
。

要构建映像,首先我们需要决定要使用的基础映像。 它由FROM指令表示。 在这里,它是来自 Docker Hub 的 Java 8 官方镜像。 我们将通过发出COPY指令将其复制到我们的 Java 文件中。 接下来,我们将使用RUN编译它。 EXPOSE指令表示图像将在特定端口上提供服务。 ENTRYPOINT是我们希望在启动基于此映像的容器时执行的指令,并且CMD指示我们将传递给它的默认参数。
FROM java:8 COPY PingPong.java / RUN javac PingPong.java EXPOSE 8080 ENTRYPOINT ["java"] CMD ["PingPong"]
将这些指令保存在一个名为“Dockerfile”的文件中后,我们可以通过执行以下命令来构建相应的 Docker 镜像:
docker build -t toptal/pingpong .
Docker 的官方文档有一节专门介绍有关编写 Dockerfile 的最佳实践。
运行容器
镜像构建完成后,我们可以将其作为容器赋予生命。 我们可以通过多种方式运行容器,但让我们从一种简单的方式开始:
docker run -d -p 8080:8080 toptal/pingpong
其中-p [port-on-the-host]:[port-in-the-container]分别表示主机和容器上的端口映射。 此外,我们通过指定-d告诉 Docker 在后台将容器作为守护进程运行。 您可以通过尝试访问“http://localhost:8080/ping”来测试 Web 服务器应用程序是否正在运行。 请注意,在使用 Boot2docker 的平台上,您需要将“localhost”替换为运行 Docker 的虚拟机的 IP 地址。
在 Linux 上:
curl http://localhost:8080/ping
在需要 Boot2Docker 的平台上:
curl $(boot2docker ip):8080/ping
如果一切顺利,您应该会看到响应:
pong
万岁,我们的第一个自定义 Docker 容器是活生生的! 我们还可以以交互模式-i -t启动容器。 在我们的例子中,我们将覆盖入口点命令,因此我们会看到一个 bash 终端。 现在我们可以执行我们想要的任何命令,但是退出容器会停止它:
docker run -i -t --entrypoint="bash" toptal/pingpong
还有更多选项可用于启动容器。 让我们再介绍几个。 例如,如果我们想在容器外持久化数据,我们可以使用-v与容器共享主机文件系统。 默认情况下,访问模式是读写,但可以通过将:ro
附加到容器内卷路径来更改为只读模式。 当我们需要在容器内使用任何安全信息(如凭证和私钥)时,卷尤其重要,这些信息不应存储在映像中。 此外,它还可以防止数据重复,例如通过将本地 Maven 存储库映射到容器来避免您两次下载 Internet。
Docker 还具有将容器链接在一起的能力。 即使没有任何端口暴露,链接的容器也可以相互通信。 它可以通过–link other-container-name来实现。 以下是结合上述参数的示例:
docker run -p 9999:8080 --link otherContainerA --link otherContainerB -v /Users/$USER/.m2/repository:/home/user/.m2/repository toptal/pingpong
其他容器和镜像操作
毫不奇怪,可以应用于容器和图像的操作列表相当长。 为简洁起见,让我们看一下其中的几个:
- stop - 停止正在运行的容器。
- start - 启动一个停止的容器。
- commit - 根据容器的更改创建新图像。
- rm - 移除一个或多个容器。
- rmi - 删除一个或多个图像。
- ps - 列出容器。
- 图像 - 列出图像。
- exec - 在正在运行的容器中运行命令。
最后一个命令对于调试目的可能特别有用,因为它允许您连接到正在运行的容器的终端:
docker exec -i -t <container-id> bash
面向微服务世界的 Docker Compose
如果您拥有的不仅仅是几个相互连接的容器,那么使用像 docker-compose 这样的工具是有意义的。 在配置文件中,您描述了如何启动容器以及它们应该如何相互链接。 无论涉及的容器数量及其依赖关系如何,您都可以使用一个命令启动并运行所有容器: docker-compose up
。
码头工人在野外
让我们看看项目生命周期的三个阶段,看看我们友好的鲸鱼如何提供帮助。
发展
Docker 可帮助您保持本地开发环境的清洁。 无需安装多个版本的不同服务,例如 Java、Kafka、Spark、Cassandra 等,您可以在必要时启动和停止所需的容器。 您可以更进一步,并排运行多个软件堆栈,避免混淆依赖版本。
使用 Docker,您可以节省时间、精力和金钱。 如果您的项目设置起来非常复杂,请“dockerise”它。 经历一次创建 Docker 镜像的痛苦,从这一点开始,每个人都可以快速启动一个容器。
您还可以在本地(或在 CI)上运行“集成环境”,并将存根替换为在 Docker 容器中运行的真实服务。
测试/持续集成
使用 Dockerfile,很容易实现可重现的构建。 Jenkins 或其他 CI 解决方案可以配置为为每个构建创建一个 Docker 映像。 您可以将部分或全部图像存储在私有 Docker 注册表中以供将来参考。
使用 Docker,您只需测试需要测试的内容并将环境排除在外。 在正在运行的容器上执行测试可以帮助保持事情的可预测性。
拥有软件容器的另一个有趣的特性是,很容易使用相同的开发设置来分拆从机。 它对于集群部署的负载测试特别有用。
生产
Docker 可以成为开发人员和运维人员之间的通用接口,消除了摩擦源。 它还鼓励在管道的每个步骤中使用相同的图像/二进制文件。 此外,能够在没有环境差异的情况下部署经过全面测试的容器有助于确保在构建过程中不会引入错误。
您可以将应用程序无缝迁移到生产环境中。 曾经是一个乏味且不稳定的过程现在可以很简单:
docker stop container-id; docker run new-image
如果在部署新版本时出现问题,您可以随时快速回滚或更改到其他容器:
docker stop container-id; docker start other-container-id
…保证不会留下任何混乱或使事情处于不一致的状态。
概括
Docker 所做工作的一个很好的总结包含在它自己的座右铭中:构建、交付、运行。
- 构建 - Docker 允许您从微服务构建应用程序,无需担心开发和生产环境之间的不一致,也无需锁定任何平台或语言。
- Ship - Docker 允许您设计应用程序开发、测试和分发的整个周期,并使用一致的用户界面对其进行管理。
- 运行 - Docker 使您能够在各种平台上安全可靠地部署可扩展服务。
和鲸鱼一起游泳,玩得开心!
这项工作的一部分灵感来自 Adrian Mouat 所著的《Using Docker》一书。