65.9K
CodeProject 正在变化。 阅读更多。
Home

DockerOps:Docker 入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.23/5 (4投票s)

2018 年 10 月 16 日

CPOL

15分钟阅读

viewsIcon

11955

downloadIcon

77

本文将为理解 Docker 引擎奠定概念基础。

引言

如今,几乎每个人至少都听说过 Docker 和容器化这个词。无论他们是构建面向容器服务的专家,还是了解容器如何工作,这都不重要。但他们可能在文档、电子书、杂志、文章或视频中看到过关于容器化应用程序、编排工具或 Docker、Kubernetes 等技术的介绍。关键在于,人们对这项技术有所了解,并且认识到它与虚拟机的演进相似。而这一切现在已经有点过时了。我希望在这门 Docker 101 课程中,能够建立 Docker 设置的核心基础,讲解它的工作原理,如何快速获取 Docker,以及如何进行尝试。

本文将作为系列文章的一部分,我将尽我所能,通过本教程系列的这些文章,涵盖 Docker 和应用程序容器化的各个方面。除了关于 Docker 本身的一些主题,我们还将探讨以下话题:

  • 容器中的数据持久化
  • Docker 中的服务
  • 容器编排
  • Kubernetes 和 Docker

这些是我可以讨论的关于 Docker 的一些话题,既不会让初学者感到迷失,也不会让专家觉得我夸夸其谈。

什么是 Docker?

对于那些仍然不知道 Docker 是什么以及为什么应该关心它的人,这里有一个简短的定义。当人们谈论 Docker 时,他们会想到一个容器化工具,有些人甚至认为它是一个进程编排器。这些说法都是正确的。Docker 是,也可以被看作是:

  1. 包管理器
  2. 编排器
  3. 负载均衡器

我甚至还没有提到互联网上那些听起来很炫的说法,比如 Docker 是同名公司开发的产品,或者 Docker 是一个开源的容器化管理工具,或者像 Docker 是通往云的入口。从各自的角度看,这些说法都是正确的,但它们仍然不能涵盖 Docker 所能做的一切。这就是我写这篇文章的原因,旨在展示 Docker 的功能和特性,而不是仅仅谈论它。让我们从 Docker 的要求和先决条件开始。

平台和要求

Docker 本身可以安装在 Linux、Windows 或 macOS 上,但我将在这里使用 Linux 平台。命令和 Docker 的行为在 Windows 和 macOS 上将是相似的。但我向您保证,如果您使用 Linux 发行版,特别是 Ubuntu 16.04 或更新版本进行设置,那将是一次精彩的旅程。我在此设置中使用了 Ubuntu 18.04。

第二点需要注意的是,我正在使用 Snapcraft 的 Snap。之所以选择这样做,是因为 Snap 的安装、升级和管理非常容易。它们预装了服务、程序和实用工具,可以帮助您轻松管理该实用程序。不用担心,在安装和设置部分,我将根据需要解释并提供其他安装和要求的指导,但主要方法是这个。

安装和配置

如前所述,我们将安装 Docker 的 Snap,对于那些想学习编排的高级主题的人,还会安装 Kubernetes。Snap 在 Linux 发行版上可用,即 2018 年或 2017 年发布的较新版本。Docker 网站上清楚地说明了安装过程,但无论如何,我仍将尝试在几个平台上解释该方法。

Windows

在 Windows 上,在执行 Docker 安装程序之前,您需要确保以下几点:

  • 硬件虚拟化
  • 已安装并启用了 Hyper-V

这也意味着您无法在 Windows 家庭版上尝试这些功能。您需要拥有 Windows 专业版或更高版本。但这不会阻止您在虚拟化环境中尝试 Docker 安装,在那里您可以轻松设置 Linux 发行版并开始下一部分。

但是,如果您已经拥有 Windows 专业版,只需前往 Docker 下载并安装适用于您机器的 Docker。

Linux (Ubuntu 18.04)

虽然任何支持 Snap 的 Linux 发行版都可以,但我将在本文以及后续文章中使用 Ubuntu 发行版。为此,只需确保您的系统中有 Snap。要确保您已安装 Snap 系统,请确保您运行的是 Ubuntu 16.04 或更新版本,这就是为什么我**建议**使用**Ubuntu 18.04**。Snap 守护程序默认安装在这些操作系统上,如果以下命令没有响应,

$ snap version

则表示您的操作系统没有安装 Snap 系统。要安装一个,您可以运行以下命令:

$ sudo apt-get install snapd 

这将安装 snappy 环境的守护程序,之后您可以验证 snappy 是否可用。您也可以遵循 Snap 网站提供的文档,对于 Ubuntu,或选择您选择的操作系统和发行版。

完成之后,直接从 Snap 商店安装 Docker:

$ sudo snap install docker

这需要一些时间,并将为您安装和设置 Docker 引擎。现在您可以继续安装和设置一些其他东西,例如用于试用的 hello world Docker 镜像。但我更愿意在这里提示一些关于 Docker 的内容,然后我们再进入这个 Docker 系列的第二集。现在,要继续进行,前提是您已下载并安装了 Docker 引擎,请继续到下一步,开始探索 Docker。

非 Snap 方法

Docker 也为 Linux 环境发布了相关文件,您可以下载并安装这些 tarball,或使用 apt、yum 等仓库管理器。

以下是一些您可能想要关注的重要链接:

由于 Docker 是开源的,您可以随时自行编译源代码。Packt 的这个 cookbook 片段可以告诉您需要执行哪些步骤来构建 Docker 引擎的源代码。

探索 Docker

在我结束本章之前,让我们从存储、网络和进程信息的角度快速了解一下 Docker 引擎。这将为我们后面将要学习的概念奠定基础。因此,首先,我们将使用我自己的 Node.js 应用程序,原因如下:

  • 这是我自己的开发项目,我可以轻松地解释我做了什么,以及在哪里做的。
  • 它是一个开源项目,需要在 GitHub 上获得社区的贡献和协作。
  • 它轻量级,并展示了一些可以在开发 Dockerized 包时,或者应该 employed 的最佳实践。
  • Docker 包仅仅是 4 行代码,底层项目是一个用 Node.js 编写的完整的 Web 应用程序项目,它暴露了多种设计模式和架构设计,特别是面向 Serverless 的方法。
  • 该项目还附带其他可配置的、可立即执行的脚本,例如 Docker Compose 文件。

您可以在 GitHub 上探索该项目(链接在上面的下载部分提供),亲自查看其结构。但现在,废话不多说,让我们创建我们的第一个 Docker 容器,并可视化这个过程。在我们应用程序中,我们通过以下代码暴露 Web 应用:

// Inside the serverconfig.js
let port = process.env.PORT || process.env.PORT_AZURE || process.env.PORT_AWS || 5000;

// Inside the app.js
app.listen(serverConfigurations.serverPort, () => {
    let serverStatus = `Server listening on localhost:${serverConfigurations.serverPort}.`;
    logger.logEvent("server start", serverStatus);
    console.log(serverStatus);
});

从 Docker 的角度来看,这段代码并不重要,但我想让您看到的是,这将执行并提供一个在容器中运行的 Web 服务器。在一个非常正常的环境中,这将只是执行函数并启动 Node.js 应用程序的事件循环,这意味着您的进程将启动,它将监听该主机名以及我们分配的端口的网络流量。在这种情况下,您的命令输出

$ npm start

将如下所示:

> express-nodejs@1.3.0 start /home/afzaal/Projects/Git/Nodejs-Dockerized/nodejs-dockerized
> node ./src/app.js

Cannot start Application Insights; either pass the value to this app, 
or use the App Insights default environment variable.
[Event] server start: Server listening on localhost:5000..
Server listening on localhost:5000.

从一个天真的角度来看,这一切看起来都很好,但现在我们遇到了一个**问题**。如果您仔细查看(忽略 Application Insights 行),您会发现它写着:“**Server listening on localhost:5000.**”,这让我们清楚地意识到**端口 5000 已经绑定**到该进程。这将导致我们的应用程序的**可伸缩性出现问题**,而该应用程序有权成为一项服务,进而要求我们

  • 要么在不同且可配置的端口上运行应用程序
    • 在我写的应用程序中,这完全可行,您可以传递应用程序要监听的端口,但*这不是推荐的方法*。因为它还需要您管理来自该机器外部的流量转发,这就引出了第二点。
  • 要么在负载均衡器后面运行应用程序,并让负载均衡器将流量转发到每个进程的 5000 端口,您为该服务启动的每个进程。

这种方法的另一个主要,也是最重要的一个问题是,如果我们的进程失败或终止,我们的环境将无法恢复该进程。我们可以通过编写自己的管理程序或利用另一个 Web 服务器程序来克服这个问题,但即使在那里,我们也需要管理和维护如何指定我们的应用程序正在运行。

这些就是 Docker 大显身手的地方,不仅是 Docker,还有其他编排器和进程管理器,如 Kubernetes、DC/OS Marathon 等。但由于我们正在讨论 Docker 的探索,我们将在后面的章节中深入探讨这些功能。在本章中,我们想探索如何在机器上部署应用程序的这种行为是如何实现的。为了理解我们是如何做到的,我们将执行我们之前作为问题提出的两个操作,并将所有这一切都交给 Docker。我们如何做到这一点呢?我们创建一个新的 Docker 镜像,并将我们的应用程序打包在其中。在 Docker 中,打包是通过 Dockerfile 完成的,该文件包含启动**进程**所需的命令序列。Dockerfile 需要以下信息才能启动您的进程:

  1. 依赖项
  2. 源文件
  3. 入口点或命令

遵循这个模式,我们知道我们的应用程序依赖于 Node.js 运行时,并且文件在同一个文件夹中,我们可以使用 `npm start` 命令启动项目。我们的 Dockerfile 将是:

FROM node:10-alpine
COPY . .
RUN [ "npm", "update" ]
CMD [ "npm", "start" ]

就这些!

我们不需要再写一个字。我已经在我的另一篇文章这里详细讨论了这些命令。而且我觉得没有必要再多说。让我们构建镜像并运行它,以验证一切是如何为我们处理的。

# docker build -t codeproject/dockerops:gettingstarted .

此命令将构建我们的第一个容器镜像,并提供具有该名称的容器镜像。

Sending build context to Docker daemon  1.527MB
Step 1/4 : FROM node:10-alpine
 ---> 7ca2f9cb5536
Step 2/4 : COPY . .
 ---> 9ac07aa147b7
Step 3/4 : RUN [ "npm", "update" ]
 ---> Running in ba65eff2e6d9
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN express-nodejs@1.3.1 No repository field.

+ uuid@3.3.2
+ body-parser@1.18.3
+ pug@2.0.3
+ express@4.16.4
+ applicationinsights@1.0.6
added 120 packages from 179 contributors and audited 261 packages in 30.756s
found 0 vulnerabilities

Removing intermediate container ba65eff2e6d9
 ---> 558e5026cb19
Step 4/4 : CMD [ "npm", "start" ]
 ---> Running in 807b13ed3a29
Removing intermediate container 807b13ed3a29
 ---> 46dc26469806
Successfully built 46dc26469806
Successfully tagged codeproject/dockerops:gettingstarted

如您所见,在此过程中,Docker 会注入镜像内部所需的一切。所有必需的组件都会被下载、修补、任何可执行文件都会被运行,最后构建一个镜像。构建过程与其他任何构建过程非常相似,可以将其与 .NET Core 构建过程进行比较,

$ dotnet restore
# Assuming the .NET Core project is in the same directory, we execute
$ dotnet build
# If everything goes fine, we do
$ dotnet run

同样,Docker 上面所做的只是构建了镜像。现在我们的项目已准备好运行。我们将镜像命名为 `codeproject/dockerops:gettingstarted`。这更容易记住,并且我们将在创建容器的过程中使用镜像的标签。这样,我们就可以轻松地管理其他操作,例如检查和删除系统中的容器。让我们继续创建容器,并对其进行检查。

# docker run -d --name gettingstarted codeproject/dockerops:gettingstarted

这只需要几秒钟就能为您创建容器!请注意 `-d` 标志,它将使我们的终端保持空闲,并在**分离模式**下运行容器。我们可以查看日志以了解进程内部发生了什么,这些日志将在文章的后面部分显示,请继续阅读……在上一篇文章中,我们讨论了如何选择不暴露端口,在本篇文章中,我们将看到容器如何始终监听 Docker 引擎映射给它的 IP 地址的网络流量。还记得我们将 5000 指定为进程端口吗?让我们检查容器并找出它的 IP 地址。

# Notice how gettingstarted helps us in passing the container reference.
# docker inspect gettingstarted

这会输出大量信息,而我们只关心(目前仅)容器信息,例如状态、网络以及主机名或 IP 地址信息。如下所示:

[
    {
        "Id": "c3f799ed9a74f1cb23c1046bceeee9144aad1122f70f4a39860722b625c5ef5b",
        "Created": "2018-10-17T00:44:09.340443736Z",
        "Path": "npm",
        "Args": [
            "start"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 16146,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2018-10-17T00:44:10.767427825Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:46dc2646980611d74bd95d24fbee78f6b09a56d090660adf0bd0bc452228f82d",
...
        "Name": "/gettingstarted",
        "RestartCount": 0,
        "Driver": "aufs",
        "Platform": "linux",
...
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
...
        "Config": {
            "Hostname": "c3f799ed9a74",
            "Domainname": "",
...
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "2b6ace2bb890dcd59370798285d3c917f40e3f23330298f8a11dff1fbe27b627",
                    "EndpointID": "12c5e6c571af574e88c796e9804bd571523fc046aaaaa2d39a842580b391e745",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
...

所有标有“...”的行都用于覆盖和截断当前阶段的额外信息。从上面的文本中,您可以提取一些关于容器的信息,而一些信息则留给 Docker 服务的不同部分。请记住,Docker 引擎既可用于独立容器模式,也可用于 Swarm 模式。此值中的大多数设置都是为 Swarm 模式提供的,在该模式下,Docker 将管理机器、服务或堆栈。但是,我们感兴趣的是提供 `IPAddress` 值的区域。这就是托管 Node.js 应用程序的容器的 IP 地址。回想一下我们的端口是 5000,组合并访问 URL,我们得到:

Afzaal Ahmad Zeeshan's Node.js container running via Docker.

图 1:在 Firefox 中运行的容器应用程序的主页。

这就是我们如何从 Docker 访问服务。这和您可能已经看腻了的页面是相同的,但是的,这是从 Docker 引擎暴露的服务。如果我们查看容器的日志,我们会得到以下信息:

# docker logs gettingstarted

> express-nodejs@1.3.1 start /
> node ./src/app.js

Cannot start Application Insights; either pass the value to this app, 
or use the App Insights default environment variable.
[Event] server start: Server listening on localhost:5000..
Server listening on localhost:5000.
[Request] GET: /.
[Request] GET: /about.
[Request] GET: /.
[Request] GET: /contact.
[Request] GET: /.
[Request] GET: /.

我只是在容器中浏览了一会儿网站,如上所示,这是容器的日志。应用程序的执行方式没有任何区别。同样,运行时,或者进程使用的 CPU、RAM 或网络资源的使用方式也没有区别。区别在于当我们从不同的角度看待进程时——不是作为一个单独的玩家,而是作为集群中的一个工人。

有什么不同?您说。

回想一下我们之前提到的问题,1. 容错性,2. 可伸缩性。这两个问题都得到了解决。Docker 在其中发挥着至关重要的作用,它既是服务的健康管理器,也是一个良好且生产级别的负载均衡器。这让我们有机会将 Docker 不仅仅用作应用程序的包管理器,还可以用作应用程序的良好负载均衡器。虽然我必须承认,像 Kubernetes 这样的其他负载均衡器和编排器在处理可伸缩性方面要好得多,但 Docker 对于应用程序和服务的快速部署仍然是一个不错的选择。

**剧透警告**,虽然这是下一集的主要讨论话题,但 Docker Compose 概念在处理容器的可伸缩性和容错性方面非常有用。在同一个GitHub 仓库中可以找到一个非常简单、最小的 Docker Compose 文件示例。

version: '3'
services:
  nodejsapp:
    build: .
    image: afzaalahmadzeeshan/express-nodejs:latest
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: ".1"
          memory: 100M
      restart_policy: 
        condition: on-failure
    ports:
      - "12345:80"
    networks:
      - appnetwork

networks:
  appnetwork:

从这个部署脚本的设置可以看出,我们有以下配置:

restart_policy:
   condition: on-failure

其次是这个:

replicas: 3

这些配置告诉 Docker 引擎(创建并;非按需)将服务扩展到 3 个实例,并确保在容器出现问题时引擎会重新创建容器。我不会在本文中深入探讨这个脚本,而是在下一篇文章中,讨论服务,我们将探索使用 Docker Compose 而不是普通 Docker CLI 的好处,然后我们还将关注 Docker Stack 和 Docker Services。Docker 在可伸缩性方面存在一个很大的问题,但是……下一篇文章将讨论这个问题。:-)

现在,我们将把未来的几集专门用于 Docker 开发和管理的特定领域。

  • DockerOps:服务及其可伸缩性
  • DockerOps:存储和网络
  • DockerOps:编排和可扩展性
  • DockerOps:容器化的最佳实践

我希望您能加入我的旅程,我将探索 Docker 引擎,并展示一些在开发面向 Docker 引擎进行容器化的应用程序时应采用的最佳实践。

继续前进...

在本文中,我们仅仅探索了 Docker 允许我们做什么。我们甚至还没有触及 Docker 及其强大功能和特性的表面。我们将在下一篇文章开始深入挖掘。使用 Docker 的主要好处是我们可以拥有自己独立的应用程序和进程运行环境,并且我们能够控制它们如何增长以及如何进行规模扩展。

现在,您可以删除已创建的资源,以释放机器上的一些硬件资源供进一步使用。或者您可以将它们保留原样,但我们仍将在后续文章中遵循重新创建过程。要删除资源,请运行:

# docker stop gettingstarted && docker rm gettingstarted

这将停止容器然后将其删除,以便您可以再次创建它,如果您希望继续阅读本文。现在,在我们继续下一集之前,这里是您在继续前进之前需要了解的内容:

© . All rights reserved.