Docker 初学者Hello World教程

目录:

  1. 介绍
  2. 入门
  3. Hello World
  4. 使用 Docker 的 Web 应用程序
  5. 多容器环境
  6. 结论

学习使用 Docker 轻松构建分布式应用程序并将其部署到云端

介绍

什么是 Docker?

维基百科将Docker定义为

一个开源项目,它通过在 Linux 上提供额外的抽象层和操作系统级虚拟化的自动化来自动在容器内部署软件应用程序。

哇!那很拗口。简单来说,Docker 是一种工具,它允许开发人员、系统管理员等轻松地将他们的应用程序部署在沙箱(称为容器)中,以便在主机操作系统(即 Linux)上运行。Docker 的主要好处是它允许用户将应用程序及其所有依赖项打包成一个标准化单元以进行软件开发。与虚拟机不同,容器没有高开销,因此可以更有效地使用底层系统和资源。

什么是容器?

今天的行业标准是使用虚拟机 (VM) 来运行软件应用程序。虚拟机在客户操作系统中运行应用程序,该操作系统在由服务器主机操作系统驱动的虚拟硬件上运行。

VMs 擅长为应用程序提供完整的进程隔离:主机操作系统中的问题很少会影响来宾操作系统中运行的软件,反之亦然。但是这种隔离需要付出巨大的代价——用于虚拟化硬件以供客户操作系统使用的计算开销是巨大的。

容器采用不同的方法:通过利用主机操作系统的低级机制,容器以一小部分计算能力提供大部分虚拟机隔离。

为什么要使用容器?

容器提供了一种逻辑打包机制,在该机制中应用程序可以从它们实际运行的环境中抽象出来。这种解耦允许基于容器的应用程序轻松一致地部署,无论目标环境是私有数据中心、公共云,甚至是开发人员的个人笔记本电脑。这使开发人员能够创建与其余应用程序隔离的可预测环境,并且可以在任何地方运行。

从操作的角度来看,除了可移植性之外,容器还可以对资源进行更精细的控制,从而提高基础设施的效率,从而更好地利用计算资源。

Docker 的 Google 趋势

由于这些好处,容器(和 Docker)已被广泛采用。Google、Facebook、Netflix 和 Salesforce 等公司利用容器来提高大型工程团队的生产力并提高计算资源的利用率。事实上,谷歌认为容器消除了对整个数据中心的需求。

本教程将教我什么?

本教程旨在成为您使用 Docker 的一站式商店。除了揭开 Docker 环境的神秘面纱之外,它还将为您提供在云上构建和部署自己的 web 应用程序的实践经验。我们将使用Amazon Web Services部署一个静态网站,并在EC2上使用Elastic BeanstalkElastic Container Service部署两个动态 Web 应用程序。即使您之前没有部署经验,本教程也应该是您入门所需的全部内容。


入门

本文档包含一系列的几个部分,每个部分都解释了 Docker 的一个特定方面。在每个部分中,我们将输入命令(或编写代码)。本教程中使用的所有代码都可以在Github repo中找到。

注意:本教程使用 Docker 版本18.05.0-ce。如果您发现本教程的任何部分与未来版本不兼容,请提出问题。谢谢!

先决条件

除了熟悉命令行和使用文本编辑器之外,本教程不需要任何特定技能。本教程用于git clone在本地克隆存储库。如果你的系统上没有安装 Git,要么安装它,要么记得从 Github 手动下载 zip 文件。以前开发 Web 应用程序的经验会有所帮助,但不是必需的。随着我们继续学习本教程,我们将使用一些云服务。如果您有兴趣跟随,请在以下每个网站上创建一个帐户:

设置您的计算机

在您的计算机上安装所有工具可能是一项艰巨的任务,但幸运的是,随着 Docker 变得稳定,让 Docker 在您最喜欢的操作系统上启动并运行变得非常容易。

直到几个版本之前,在 OSX 和 Windows 上运行 Docker 还是很麻烦的。然而,最近 Docker 投入了大量资金来改善其用户在这些操作系统上的入职体验,因此现在运行 Docker 是轻而易举的事。Docker入门指南包含在MacLinuxWindows上设置 Docker 的详细说明。

完成 Docker 安装后,通过运行以下命令测试 Docker 安装:

$ docker run hello-world

Hello from Docker.
This message shows that your installation appears to be working correctly.
...

HELLO WORLD

Play with Busybox

现在我们已经完成了所有设置,是时候弄脏我们的手了。在本节中,我们将在我们的系统上运行Busybox容器并体验该docker run命令。

首先,让我们在终端中运行以下命令:

$ docker pull busybox

注意:根据您在系统上安装 docker 的方式,permission denied运行上述命令后可能会看到错误。如果您使用的是 Mac,请确保 Docker 引擎正在运行。如果您使用的是 Linux,请在docker命令前加上sudo. 或者,您可以创建一个 docker 组来解决此问题。

该命令从Docker 注册表pull中获取 busybox映像并将其保存到我们的系统中。您可以使用该命令查看系统上所有图像的列表。docker images

$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
busybox                 latest              c51f86c28340        4 weeks ago         1.109 MB

Docker运行

Great!现在让我们基于这个镜像运行一个 Docker容器。为此,我们将使用全能docker run命令。

$ docker run busybox
$

等等,什么都没发生!那是一个错误吗?嗯,不。在幕后,发生了很多事情。当您调用run时,Docker 客户端会找到映像(在本例中为忙盒),加载容器,然后在该容器中运行命令。当我们运行时docker run busybox,我们没有提供命令,所以容器启动,运行一个空命令然后退出。嗯,是的 – 有点无赖。让我们尝试一些更令人兴奋的事情。

$ docker run busybox echo "hello from busybox"
hello from busybox

很好 – 最后我们看到了一些输出。在这种情况下,Docker 客户端尽职尽责地echo在我们的 busybox 容器中运行该命令,然后退出它。如果你注意到了,所有这一切都发生得很快。想象一下启动一个虚拟机,运行一个命令然后杀死它。现在你知道为什么他们说容器很快了!好的,现在是时候查看docker ps命令了。该docker ps命令向您显示当前正在运行的所有容器。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

由于没有容器在运行,我们看到一个空行。让我们尝试一个更有用的变体:docker ps -a

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
305297d7a235        busybox             "uptime"            11 minutes ago      Exited (0) 11 minutes ago                       distracted_goldstine
ff0a5c3750b9        busybox             "sh"                12 minutes ago      Exited (0) 12 minutes ago                       elated_ramanujan
14e5bd11d164        hello-world         "/hello"            2 minutes ago       Exited (0) 2 minutes ago                        thirsty_euclid

所以我们在上面看到的是我们运行的所有容器的列表。请注意,该STATUS列显示这些容器在几分钟前退出。

您可能想知道是否有一种方法可以在容器中运行多个命令。现在让我们尝试一下:

$ docker run -it busybox sh
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # uptime
 05:45:21 up  5:58,  0 users,  load average: 0.00, 0.01, 0.04

运行run带有-it标志的命令会将我们附加到容器中的交互式 tty。现在我们可以在容器中运行任意数量的命令。花一些时间运行您喜欢的命令。

危险区:如果您特别喜欢冒险,可以rm -rf bin在容器中尝试。确保在容器中而不是在笔记本电脑/台式机中运行此命令。这样做会使任何其他命令,如ls,uptime不起作用。一旦一切停止工作,您可以退出容器(键入exit并按 Enter),然后使用docker run -it busybox sh命令再次启动它。由于 Docker 每次都会创建一个新容器,因此一切都应该重新开始工作。

强大docker run命令的旋风之旅到此结束,这很可能是您最常使用的命令。花一些时间适应它是有道理的。要了解有关 的更多信息run,请使用docker run --help查看它支持的所有标志的列表。随着我们进一步进行,我们将看到更多的docker run.

不过,在我们继续之前,让我们快速谈谈删除容器。我们在上面看到,即使通过运行退出,我们仍然可以看到容器的残余docker ps -a。在本教程中,您将运行docker run多次,并且留下杂散容器会占用磁盘空间。因此,根据经验,我会在完成容器后清理它们。为此,您可以运行该docker rm命令。只需从上面复制容器 ID 并将它们粘贴到命令旁边。

$ docker rm 305297d7a235 ff0a5c3750b9
305297d7a235
ff0a5c3750b9

删除时,您应该会看到 ID 回显给您。如果你有一堆容器要一次性删除,复制粘贴 ID 可能会很乏味。在这种情况下,您可以简单地运行 –

$ docker rm $(docker ps -a -q -f status=exited)

此命令删除所有状态为 的容器exited。如果您想知道,该-q标志仅返回数字 ID 并-f根据提供的条件过滤输出。最后一件有用的事情是--rm可以传递给docker run它的标志,一旦容器退出,它就会自动删除容器。对于一次 docker 运行,--rmflag 非常有用。

在更高版本的 Docker 中,docker container prune可以使用该命令来达到相同的效果。

$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
4a7f7eebae0f63178aff7eb0aa39f0627a203ab2df258c1a00b456cf20063
f98f9c2aa1eaf727e4ec9c0283bcaa4762fbdba7f26191f26c97f64090360

Total reclaimed space: 212 B

最后,您还可以通过运行删除不再需要的图像docker rmi

术语

在上一节中,我们使用了很多 Docker 特定的术语,这可能会让一些人感到困惑。所以在我们进一步讨论之前,让我澄清一些在 Docker 生态系统中经常使用的术语。

  • 图像Images– 构成容器基础的应用程序蓝图。在上面的演示中,我们使用docker pull命令下载了busybox镜像。
  • 容器Containers– 从 Docker 映像创建并运行实际应用程序。我们创建了一个容器,使用docker run我们下载的 busybox 镜像。docker ps使用该命令可以查看正在运行的容器列表。
  • Docker Daemon – 在主机上运行的后台服务,用于管理构建、运行和分发 Docker 容器。守护进程是客户端与之对话的操作系统中运行的进程。
  • Docker Client – 允许用户与守护进程交互的命令行工具。更一般地说,也可以有其他形式的客户端 – 例如为用户提供 GUI 的Kitematic 。
  • Docker Hub – Docker 镜像的注册表。您可以将注册表视为所有可用 Docker 映像的目录。如果需要,可以托管自己的 Docker 注册表,并可以使用它们来拉取镜像。

使用 DOCKER 的 WEB 应用程序

Great!所以我们现在查看docker run了 Docker 容器并掌握了一些术语。有了所有这些知识,我们现在就可以开始接触真正的东西了,即使用 Docker 部署 Web 应用程序!

静态站点

让我们从小步开始。我们要研究的第一件事是如何运行一个非常简单的静态网站。我们将从 Docker Hub 中提取 Docker 映像,运行容器,看看运行 Web 服务器是多么容易。

让我们开始。我们将要使用的图像是一个单页网站,我已经为此演示创建并托管在注册表中- prakhar1989/static-site. 我们可以直接使用docker run. 如上所述,该--rm标志在退出时会自动删除容器,并且该-it标志指定了一个交互式终端,这使得使用 Ctrl+C(在 Windows 上)更容易杀死容器。

$ docker run --rm -it prakhar1989/static-site

由于本地不存在镜像,客户端会先从注册表中获取镜像,然后运行镜像。如果一切顺利,您应该会Nginx is running...在终端中看到一条消息。好的,现在服务器正在运行,如何查看网站?它在哪个端口上运行?更重要的是,我们如何直接从主机访问容器?按 Ctrl+C 停止容器。

好吧,在这种情况下,客户端没有暴露任何端口,所以我们需要重新运行docker run命令来发布端口。当我们这样做时,我们还应该找到一种方法,使我们的终端不附加到正在运行的容器上。这样,您可以愉快地关闭终端并保持容器运行。这称为分离模式。

$ docker run -d -P --name static-site prakhar1989/static-site
e61d12292d69556eabe2a44c16cbd54486b2527e2ce4f95438e504afb7b02810

在上面的命令中,-d将分离我们的终端,-P将所有暴露的端口发布到随机端口,最后--name对应于我们想要给出的名称。现在我们可以通过运行docker port [CONTAINER]命令查看端口

$ docker port static-site
80/tcp -> 0.0.0.0:32769
443/tcp -> 0.0.0.0:32768

您可以在浏览器中打开http://localhost:32769 。

注意:如果您使用的是 docker-toolbox,那么您可能需要使用docker-machine ip default来获取 IP。

您还可以指定客户端将连接转发到容器的自定义端口。

$ docker run -p 8888:80 prakhar1989/static-site
Nginx is running...

要停止分离的容器,docker stop请通过提供容器 ID 运行。在这种情况下,我们可以使用static-site我们用来启动容器的名称。

$ docker stop static-site
static-site

我相信你同意这非常简单。要在真实服务器上部署它,您只需要安装 Docker,然后运行上面的 Docker 命令。现在您已经了解了如何在 Docker 映像中运行 Web 服务器,您一定想知道 – 我如何创建自己的 Docker 映像?这是我们将在下一节探讨的问题。

Docker 图像

我们之前看过镜像,但在本节中,我们将更深入地了解 Docker 镜像是什么并构建我们自己的镜像!最后,我们还将使用该映像在本地运行我们的应用程序,并最终部署到AWS上与我们的朋友分享!兴奋的?伟大的!让我们开始吧。

Docker 镜像是容器的基础。在前面的示例中,我们从注册表中提取Busybox映像,并要求 Docker 客户端运行基于该映像的容器。要查看本地可用的图像列表,请使用docker images命令。

$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
prakhar1989/catnip              latest              c7ffb5626a50        2 hours ago         697.9 MB
prakhar1989/static-site         latest              b270625a1631        21 hours ago        133.9 MB
python                          3-onbuild           cf4002b2c383        5 days ago          688.8 MB
martin/docker-cleanup-volumes   latest              b42990daaca2        7 weeks ago         22.14 MB
ubuntu                          latest              e9ae3c220b23        7 weeks ago         187.9 MB
busybox                         latest              c51f86c28340        9 weeks ago         1.109 MB
hello-world                     latest              0a6ba66e537a        11 weeks ago        960 B

上面列出了我从注册表中提取的图像,以及我自己创建的图像(我们很快就会看到如何)。是TAG指图像的特定快照,并且IMAGE ID是该图像的相应唯一标识符。

为简单起见,您可以将图像视为类似于 git 存储库 – 图像可以提交更改并具有多个版本。如果您不提供特定版本号,则客户端默认为latest. 例如,您可以拉取特定版本的ubuntu图像

$ docker pull ubuntu:18.04

要获取新的 Docker 映像,您可以从注册表(例如 Docker Hub)获取它,也可以创建自己的。Docker Hub上有数以万计的图像可用。您还可以使用 直接从命令行搜索图像docker search

当涉及到图像时,需要注意的一个重要区别是基本图像和子图像之间的区别。

  • Base images基础镜像是没有父镜像的镜像,通常是带有 ubuntu、busybox 或 debian 等操作系统的镜像。
  • Child images子图像是基于基础图像构建并添加附加功能的图像。

然后是官方和用户镜像,可以是基础镜像和子镜像。

  • Official images官方镜像是由 Docker 人员官方维护和支持的镜像。这些通常是一个字长。在上面的图片列表中pythonubuntubusyboxhello-world图片是官方图片。
  • User images用户图像是由像你我这样的用户创建和共享的图像。它们建立在基础镜像之上并添加了额外的功能。通常,这些格式为user/image-name.

我们的第一张图片

现在我们对图像有了更好的理解,是时候创建自己的图像了。我们在本节中的目标是创建一个将简单Flask应用程序沙箱化的图像。出于本次研讨会的目的,我已经创建了一个有趣的小Flask 应用程序.gif,每次加载时都会显示一只随机猫——因为你知道,谁不喜欢猫?如果您还没有,请继续在本地克隆存储库,如下所示 –

$ git clone https://github.com/prakhar1989/docker-curriculum.git
$ cd docker-curriculum/flask-app

这应该在您运行 docker 命令的机器上克隆,而不是在 docker 容器内。

现在的下一步是使用此 Web 应用程序创建图像。如上所述,所有用户图像均基于基础图像。由于我们的应用程序是用 Python 编写的,因此我们将使用的基本映像是Python 3

Dockerfile

Dockerfile是一个简单的文本文件,其中包含 Docker 客户端在创建映像时调用的命令列表。这是一种自动化图像创建过程的简单方法。最好的部分是您在 Dockerfile 中编写的命令几乎与其等效的 Linux 命令相同。这意味着您实际上不必学习新语法来创建自己的 dockerfile。

应用程序目录确实包含一个 Dockerfile,但由于我们是第一次这样做,我们将从头开始创建一个。首先,在我们最喜欢的文本编辑器中创建一个新的空白文件,并将其保存在与Flask应用程序相同的文件夹中,名称为Dockerfile.

我们从指定我们的基础图像开始。使用FROM关键字来做到这一点 –

FROM python:3.8

下一步通常是编写复制文件和安装依赖项的命令。首先,我们设置一个工作目录,然后为我们的应用复制所有文件。

# set a directory for the app
WORKDIR /usr/src/app

# copy all the files to the container
COPY . .

现在,我们有了文件,我们可以安装依赖项。

# install dependencies
RUN pip install --no-cache-dir -r requirements.txt

我们需要指定的下一件事是需要公开的端口号。由于我们的Flask应用程序在 port 上运行5000,这就是我们要指出的。

EXPOSE 5000

最后一步是编写运行应用程序的命令,就是 – python ./app.py。我们使用CMD命令来做到这一点 –

CMD ["python", "./app.py"]

的主要目的CMD是告诉容器它在启动时应该运行哪个命令。有了这个,我们Dockerfile现在准备好了。这就是它的样子——

FROM python:3.8

# set a directory for the app
WORKDIR /usr/src/app

# copy all the files to the container
COPY . .

# install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# define the port number the container should expose
EXPOSE 5000

# run the command
CMD ["python", "./app.py"]

现在我们有了我们的Dockerfile,我们可以建立我们的形象。该docker build命令完成了从Dockerfile.

下面的部分显示了运行相同的输出。在您自己运行命令之前(不要忘记句点),请确保将我的用户名替换为您的用户名。此用户名应与您在Docker hub上注册时创建的用户名相同。如果您还没有这样做,请继续创建一个帐户。该docker build命令非常简单——它需要一个可选的标签名称-t和一个包含Dockerfile.

$ docker build -t yourusername/catnip .
Sending build context to Docker daemon 8.704 kB
Step 1 : FROM python:3.8
# Executing 3 build triggers...
Step 1 : COPY requirements.txt /usr/src/app/
 ---> Using cache
Step 1 : RUN pip install --no-cache-dir -r requirements.txt
 ---> Using cache
Step 1 : COPY . /usr/src/app
 ---> 1d61f639ef9e
Removing intermediate container 4de6ddf5528c
Step 2 : EXPOSE 5000
 ---> Running in 12cfcf6d67ee
 ---> f423c2f179d1
Removing intermediate container 12cfcf6d67ee
Step 3 : CMD python ./app.py
 ---> Running in f01401a5ace9
 ---> 13e87ed1fbc2
Removing intermediate container f01401a5ace9
Successfully built 13e87ed1fbc2

如果你没有python:3.8镜像,客户端会先拉取镜像,然后再创建你的镜像。因此,您运行命令的输出看起来与我的不同。如果一切顺利,您的图像应该准备好了!运行docker images并查看您的图像是否显示。

本节的最后一步是运行映像并查看它是否真的有效(将我的用户名替换为您的用户名)。

$ docker run -p 8888:5000 yourusername/catnip
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

我们刚刚运行的命令为容器内的服务器使用了端口 5000,并在端口 8888 上将其暴露在外部。转到端口 8888 的 URL,您的应用程序应该在其中运行。

恭喜!你已经成功创建了你的第一个 docker 镜像。

AWS 上的 Docker

不能与朋友分享的应用程序有什么好处,对吧?因此,在本节中,我们将了解如何将我们出色的应用程序部署到云中,以便与朋友分享!我们将使用 AWS Elastic Beanstalk让我们的应用程序只需单击几下即可启动并运行。我们还将看到使用 Beanstalk 使我们的应用程序可扩展和可管理是多么容易!

Docker 推送

在将应用程序部署到 AWS 之前,我们需要做的第一件事是将我们的映像发布到 AWS 可以访问的注册表上。您可以使用许多不同的Docker 注册表(您甚至可以托管自己的Docker 注册表)。现在,让我们使用Docker Hub来发布镜像。

如果这是您第一次推送图像,客户端会要求您登录。提供您用于登录 Docker Hub 的相同凭据。

$ docker login
Login in with your Docker ID to push and pull images from Docker Hub. If you do not have a Docker ID, head over to https://hub.docker.com to create one.
Username: yourusername
Password:
WARNING! Your password will be stored unencrypted in /Users/yourusername/.docker/config.json
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/credential-store

Login Succeeded

要发布,只需键入以下命令,记住将上面的图像标签名称替换为您的名称。拥有的格式很重要,yourusername/image_name以便客户知道在哪里发布。

$ docker push yourusername/catnip

完成后,您可以在 Docker Hub 上查看您的镜像。例如,这是我的图片的网页

注意:在我们继续之前,我想澄清的一件事是,为了部署到 AWS,不必在公共注册表(或任何注册表)上托管您的映像如果您正在为下一个价值百万美元的独角兽初创公司编写代码,您完全可以跳过这一步。我们公开推送镜像的原因是它通过跳过一些中间配置步骤使部署变得超级简单。

现在您的映像已在线,任何安装了 docker 的人都可以通过键入一个命令来玩您的应用程序。

$ docker run -p 8888:5000 yourusername/catnip

如果您过去曾在设置本地开发环境/共享应用程序配置方面费尽心思,那么您就会非常清楚这听起来有多棒。这就是 Docker 如此酷的原因!

Beanstalk

AWS Elastic Beanstalk (EB) 是 AWS 提供的一种 PaaS(平台即服务)。如果您使用过 Heroku、Google App Engine 等,您会感到宾至如归。作为开发人员,您只需告诉 EB 如何运行您的应用程序,它会负责其余的工作 – 包括扩展、监控甚至更新。2014 年 4 月,EB 增加了对运行单容器 Docker 部署的支持,我们将使用它来部署我们的应用程序。尽管 EB 有一个非常直观的C​​LI,但它确实需要一些设置,为了简单起见,我们将使用 Web UI 来启动我们的应用程序。

要继续操作,您需要一个正常运行的AWS账户。如果您还没有,请立即执行此操作 – 您需要输入您的信用卡信息。但别担心,它是免费的,我们在本教程中所做的任何事情也都是免费的!让我们开始吧。

以下是步骤:

  • 单击右上角的“创建新应用程序”
  • 为您的应用取一个令人难忘(但唯一)的名称并提供(可选)描述
  • New Environment屏幕中,创建一个新环境并选择Web Server Environment
  • 通过选择域来填写环境信息。此 URL 是您将与朋友分享的内容,因此请确保它易于记忆。
  • 在基本配置部分下。从预定义的平台中选择Docker
  • 现在我们需要上传我们的应用程序代码。但是由于我们的应用程序被打包在一个 Docker 容器中,我们只需要告诉 EB 我们的容器。打开位于文件夹中的Dockerrun.aws.json 文件flask-app并将图像的名称编辑Name为您的图像名称。不用担心,我会尽快解释文件的内容。完成后,单击“上传代码”的单选按钮,选择此文件,然后单击“上传”。
  • 现在单击“创建环境”。您看到的最终屏幕将有一些微调器,指示您的环境正在设置中。首次设置通常需要大约 5 分钟。

在我们等待的同时,让我们快速查看Dockerrun.aws.json文件包含的内容。该文件基本上是一个 AWS 特定文件,它告诉 EB 关于我们的应用程序和 docker 配置的详细信息。

{
  "AWSEBDockerrunVersion": "1",
  "Image": {
    "Name": "prakhar1989/catnip",
    "Update": "true"
  },
  "Ports": [
    {
      "ContainerPort": 5000,
      "HostPort": 8000
    }
  ],
  "Logging": "/var/log/nginx"
}

该文件应该是不言自明的,但您始终可以参考官方文档以获取更多信息。我们提供了 EB 应该使用的镜像名称以及容器应该打开的端口。

希望到现在为止,我们的实例应该已经准备好了。转到 EB 页面,您应该会看到一个绿色勾号,表示您的应用程序处于活动状态并且正在运行。

继续并在您的浏览器中打开 URL,您应该会看到该应用程序的所有荣耀。随时通过电子邮件/即时消息/ snapchat 将此链接发送给您的朋友和家人,以便他们也可以享受一些猫的 GIF。

清理

一旦您沉浸在应用程序的荣耀中,请记住终止环境,这样您就不会因额外的资源而被收费。

恭喜!您已经部署了您的第一个 Docker 应用程序!这可能看起来有很多步骤,但使用EB 的命令行工具,您几乎可以通过几次击键来模仿 Heroku 的功能!希望您同意 Docker 消除了在云中构建和部署应用程序的许多痛苦。我鼓励您阅读有关单容器 Docker 环境的 AWS文档,以了解存在哪些功能。

在本教程的下一部分(也是最后一部分)中,我们将加大投入,部署一个更贴近真实世界的应用程序;具有持久后端存储层的应用程序。让我们直接开始吧!


多容器环境

在上一节中,我们看到了使用 Docker 运行应用程序是多么简单和有趣。我们从一个简单的静态网站开始,然后尝试了一个 Flask 应用程序。只需几个命令,我们就可以在本地和云端运行这两者。这两个应用程序的一个共同点是它们都在一个容器中运行。

那些在生产环境中运行过服务的人都知道,现在的应用程序通常并不那么简单。几乎总是涉及数据库(或任何其他类型的持久存储)。RedisMemcached等系统已成为大多数 Web 应用程序架构的必备工具。因此,在本节中,我们将花一些时间学习如何将依赖于不同服务运行的应用程序 Docker 化。

特别是,我们将了解如何运行和管理多容器docker 环境。您可能会问为什么要使用多容器?好吧,Docker 的关键点之一是它提供隔离的方式。将进程及其依赖项捆绑在沙箱(称为容器)中的想法使其如此强大。

就像解耦应用程序层是一个很好的策略一样,将每个服务的容器分开是明智的。每一层可能有不同的资源需求,而这些需求可能以不同的速度增长。通过将层分离到不同的容器中,我们可以根据不同的资源需求使用最合适的实例类型来组合每一层。这也与整个微服务运动相得益彰,这是 Docker(或任何其他容器技术)处于现代微服务架构前沿的主要原因之一。

SF Food Trucks

我们要 Dockerize 的应用程序称为 SF Food Trucks。我构建这个应用程序的目标是拥有一些有用的东西(因为它类似于现实世界的应用程序),依赖于至少一项服务,但对于本教程的目的来说不是太复杂。这就是我想出的。

该应用程序的后端是用 Python (Flask) 编写的,它使用Elasticsearch进行搜索。与本教程中的所有其他内容一样,整个源代码都可以在Github上找到。我们将使用它作为我们的候选应用程序来学习如何构建、运行和部署多容器环境。

首先,让我们在本地克隆存储库。

$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ tree -L 2
.
├── Dockerfile
├── README.md
├── aws-compose.yml
├── docker-compose.yml
├── flask-app
│   ├── app.py
│   ├── package-lock.json
│   ├── package.json
│   ├── requirements.txt
│   ├── static
│   ├── templates
│   └── webpack.config.js
├── setup-aws-ecs.sh
├── setup-docker.sh
├── shot.png
└── utils
    ├── generate_geojson.py
    └── trucks.geojson

flask-app文件夹包含 Python 应用程序,而该utils文件夹包含一些用于将数据加载到 Elasticsearch 中的实用程序。该目录还包含一些 YAML 文件和一个 Dockerfile,随着我们在本教程中的进展,我们将更详细地看到所有这些文件。如果您好奇,请随时查看文件。

现在你很兴奋(希望如此),让我们想想我们如何 Dockerize 应用程序。我们可以看到该应用程序由一个 Flask 后端服务器和一个 Elasticsearch 服务组成。拆分此应用程序的一种自然方法是拥有两个容器 – 一个运行 Flask 进程,另一个运行 Elasticsearch (ES) 进程。这样,如果我们的应用程序变得流行,我们可以通过添加更多容器来扩展它,具体取决于瓶颈所在的位置。

太好了,所以我们需要两个容器。那应该不难吧?在上一节中,我们已经构建了自己的 Flask 容器。对于 Elasticsearch,让我们看看能否在集线器上找到一些东西。

$ docker search elasticsearch
NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
elasticsearch                     Elasticsearch is a powerful open source se...   697       [OK]
itzg/elasticsearch                Provides an easily configurable Elasticsea...   17                   [OK]
tutum/elasticsearch               Elasticsearch image - listens in port 9200.     15                   [OK]
barnybug/elasticsearch            Latest Elasticsearch 1.7.2 and previous re...   15                   [OK]
digitalwonderland/elasticsearch   Latest Elasticsearch with Marvel & Kibana       12                   [OK]
monsantoco/elasticsearch          ElasticSearch Docker image                      9                    [OK]

毫不奇怪,有一个官方支持的 Elasticsearch镜像。要让 ES 运行,我们可以简单地使用docker run并立即在本地运行一个单节点 ES 容器。

注意:Elasticsearch 背后的公司 Elastic 维护自己的 Elastic 产品注册表。如果您打算使用 Elasticsearch,建议使用该注册表中的图像。

我们先拉取图片

$ docker pull docker.elastic.co/elasticsearch/elasticsearch:6.3.2

然后在开发模式下通过指定端口和设置环境变量来运行它,将 Elasticsearch 集群配置为单节点运行。

$ docker run -d --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
277451c15ec183dd939e80298ea4bcf55050328a39b04124b387d668e3ed3943

注意:如果您的容器遇到内存问题,您可能需要调整一些 JVM 标志以限制其内存消耗。

如上所示,我们--name es为容器命名,以便在后续命令中使用。容器启动后,我们可以通过docker container logs使用容器名称(或 ID)运行来检查日志来查看日志。如果 Elasticsearch 成功启动,您应该会看到类似于以下内容的日志。

注意:Elasticsearch 需要几秒钟才能启动,因此您可能需要等待才能initialized在日志中看到。

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                                            NAMES
277451c15ec1        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   2 minutes ago       Up 2 minutes        0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   es

$ docker container logs es
[2018-07-29T05:49:09,304][INFO ][o.e.n.Node               ] [] initializing ...
[2018-07-29T05:49:09,385][INFO ][o.e.e.NodeEnvironment    ] [L1VMyzt] using [1] data paths, mounts [[/ (overlay)]], net usable_space [54.1gb], net total_space [62.7gb], types [overlay]
[2018-07-29T05:49:09,385][INFO ][o.e.e.NodeEnvironment    ] [L1VMyzt] heap size [990.7mb], compressed ordinary object pointers [true]
[2018-07-29T05:49:11,979][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-security]
[2018-07-29T05:49:11,980][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-sql]
[2018-07-29T05:49:11,980][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-upgrade]
[2018-07-29T05:49:11,980][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded module [x-pack-watcher]
[2018-07-29T05:49:11,981][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded plugin [ingest-geoip]
[2018-07-29T05:49:11,981][INFO ][o.e.p.PluginsService     ] [L1VMyzt] loaded plugin [ingest-user-agent]
[2018-07-29T05:49:17,659][INFO ][o.e.d.DiscoveryModule    ] [L1VMyzt] using discovery type [single-node]
[2018-07-29T05:49:18,962][INFO ][o.e.n.Node               ] [L1VMyzt] initialized
[2018-07-29T05:49:18,963][INFO ][o.e.n.Node               ] [L1VMyzt] starting ...
[2018-07-29T05:49:19,218][INFO ][o.e.t.TransportService   ] [L1VMyzt] publish_address {172.17.0.2:9300}, bound_addresses {0.0.0.0:9300}
[2018-07-29T05:49:19,302][INFO ][o.e.x.s.t.n.SecurityNetty4HttpServerTransport] [L1VMyzt] publish_address {172.17.0.2:9200}, bound_addresses {0.0.0.0:9200}
[2018-07-29T05:49:19,303][INFO ][o.e.n.Node               ] [L1VMyzt] started
[2018-07-29T05:49:19,439][WARN ][o.e.x.s.a.s.m.NativeRoleMappingStore] [L1VMyzt] Failed to clear cache for realms [[]]
[2018-07-29T05:49:19,542][INFO ][o.e.g.GatewayService     ] [L1VMyzt] recovered [0] indices into cluster_state

现在,让我们尝试看看是否可以向 Elasticsearch 容器发送请求。我们使用9200端口向容器发送cURL请求。

$ curl 0.0.0.0:9200
{
  "name" : "ijJDAOm",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "a_nSV3XmTCqpzYYzb-LhNw",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

甜的!看起来不错!当我们这样做的时候,让我们的 Flask 容器也运行起来。但在我们开始之前,我们需要一个Dockerfile. 在上一节中,我们使用python:3.8图像作为基础图像。然而,这一次,除了通过 安装 Python 依赖项之外pip,我们还希望我们的应用程序还生成用于生产的缩小 Javascript 文件。为此,我们需要 Nodejs。由于我们需要一个自定义构建步骤,我们将从ubuntu基础映像Dockerfile开始从头开始构建我们的。

注意:如果您发现现有图像不能满足您的需求,请随时从另一个基础图像开始并自行调整。对于 Docker Hub 上的大部分镜像,你应该可以Dockerfile在 Github 上找到对应的。阅读现有的 Dockerfile 是学习如何自己动手的最佳方法之一。

我们用于flask应用程序的Dockerfile如下所示 –

# start from base
FROM ubuntu:18.04

MAINTAINER Prakhar Srivastav <prakhar@prakhar.me>

# install system-wide deps for python and node
RUN apt-get -yqq update
RUN apt-get -yqq install python3-pip python3-dev curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash
RUN apt-get install -yq nodejs

# copy our application code
ADD flask-app /opt/flask-app
WORKDIR /opt/flask-app

# fetch app specific deps
RUN npm install
RUN npm run build
RUN pip3 install -r requirements.txt

# expose port
EXPOSE 5000

# start app
CMD [ "python3", "./app.py" ]

这里有很多新东西,所以让我们快速浏览一下这个文件。我们从Ubuntu LTS基础镜像开始,并使用包管理器apt-get来安装依赖项,即 Python 和 Node。该yqq标志用于抑制输出并对所有提示假定为“是”。

然后我们使用ADD命令将我们的应用程序复制到容器中的一个新卷中 – /opt/flask-app. 这是我们的代码将驻留的地方。我们还将其设置为我们的工作目录,以便以下命令将在此位置的上下文中运行。现在我们的系统范围的依赖项已安装,我们开始安装特定于应用程序的依赖项。首先,我们通过从 npm 安装包并运行package.json 文件中定义的构建命令来处理 Node 。我们通过安装 Python 包、公开端口并定义CMD要运行的端口来完成文件,就像我们在上一节中所做的那样。

最后,我们可以继续,构建映像并运行容器(yourusername在下面替换为您的用户名)。

$ docker build -t yourusername/foodtrucks-web .

在第一次运行中,这将需要一些时间,因为 Docker 客户端将下载 ubuntu 映像,运行所有命令并准备您的映像。docker build在您对应用程序代码进行任何后续更改后重新运行几乎是即时的。现在让我们尝试运行我们的应用程序。

$ docker run -P --rm yourusername/foodtrucks-web
Unable to connect to ES. Retying in 5 secs...
Unable to connect to ES. Retying in 5 secs...
Unable to connect to ES. Retying in 5 secs...
Out of retries. Bailing out...

哎呀!我们的flask 应用程序无法运行,因为它无法连接到 Elasticsearch。我们如何告诉一个容器关于另一个容器并让它们相互交谈?答案在下一节。

Docker 网络

在我们讨论 Docker 专门为处理此类场景提供的功能之前,让我们看看是否可以找到解决问题的方法。希望这能让您对我们将要研究的特定功能有所了解。

好的,让我们运行docker container ls(与 相同docker ps),看看我们有什么。

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                                            NAMES
277451c15ec1        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   17 minutes ago      Up 17 minutes       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   es

所以我们有一个 ES 容器在0.0.0.0:9200我们可以直接访问的端口上运行。如果我们可以告诉我们的 Flask 应用程序连接到这个 URL,它应该能够连接并与 ES 对话,对吧?让我们深入研究一下我们的Python 代码,看看连接细节是如何定义的。

es = Elasticsearch(host='es')

为了使它工作,我们需要告诉 Flask 容器 ES 容器正在0.0.0.0主机上运行(默认端口是9200),这应该使它工作,对吗?不幸的是,这是不正确的,因为 IP是从主机(即我的 Mac 0.0.0.0)访问 ES 容器的 IP 。另一个容器将无法在同一 IP 地址上访问它。好吧,如果不是那个 IP,那么应该通过哪个 IP 地址访问 ES 容器?我很高兴你问了这个问题。

现在是开始探索 Docker 网络的好时机。安装 docker 后,它会自动创建三个网络。

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c2c695315b3a        bridge              bridge              local
a875bec5d6fd        host                host                local
ead0e804a67b        none                null                local

桥接网络是容器默认运行的网络。所以这意味着当我运行 ES 容器时,它正在这个桥接网络中运行。为了验证这一点,让我们检查一下网络。

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "c2c695315b3aaf8fc30530bb3c6b8f6692cedd5cc7579663f0550dfdd21c9a26",
        "Created": "2018-07-28T20:32:39.405687265Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "277451c15ec183dd939e80298ea4bcf55050328a39b04124b387d668e3ed3943": {
                "Name": "es",
                "EndpointID": "5c417a2fc6b13d8ec97b76bbd54aaf3ee2d48f328c3f7279ee335174fbb4d6bb",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

您可以看到我们的容器277451c15ec1列在Containers输出部分的下方。我们还看到了这个容器被分配的 IP 地址 – 172.17.0.2。这是我们要查找的 IP 地址吗?让我们通过运行我们的flask 容器并尝试访问这个 IP 来找出答案。

$ docker run -it --rm yourusername/foodtrucks-web bash
root@35180ccc206a:/opt/flask-app# curl 172.17.0.2:9200
{
  "name" : "Jane Foster",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}
root@35180ccc206a:/opt/flask-app# exit

到目前为止,这对您来说应该相当简单。bash我们以与进程交互的方式启动容器。这--rm是一个方便的标志,用于运行一次性命令,因为容器在其工作完成时会被清理。我们尝试 acurl但我们需要先安装它。一旦我们这样做了,我们就会看到我们确实可以在 ES 上与 ES 对话172.17.0.2:9200。惊人的!

尽管我们已经找到了一种让容器相互通信的方法,但这种方法仍然存在两个问题 –

  1. 我们如何告诉 Flask 容器es主机名代表172.17.0.2或其他 IP,因为 IP 可以更改?
  2. 由于默认情况下每个容器都共享桥接网络,因此这种方法是不安全的。我们如何隔离我们的网络?

好消息是 Docker 很好地回答了我们的问题。它允许我们定义自己的网络,同时使用docker network命令将它们隔离。

让我们首先创建我们自己的网络。

$ docker network create foodtrucks-net
0815b2a3bb7a6608e850d05553cc0bda98187c4528d94621438f31d97a6fea3c

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c2c695315b3a        bridge              bridge              local
0815b2a3bb7a        foodtrucks-net      bridge              local
a875bec5d6fd        host                host                local
ead0e804a67b        none                null                local

network create命令创建了一个新的桥接网络,这是我们目前需要的。就 Docker 而言,桥接网络使用软件桥接,它允许连接到同一桥接网络的容器进行通信,同时提供与未连接到该桥接网络的容器的隔离。Docker 网桥驱动程序会自动在宿主机中安装规则,使不同网桥网络上的容器无法直接相互通信。您可以创建其他类型的网络,鼓励您在官方文档中阅读它们。

--net现在我们有了一个网络,我们可以使用这个标志在这个网络中启动我们的容器。让我们这样做 – 但首先,为了启动一个具有相同名称的新容器,我们将停止并删除在桥接(默认)网络中运行的 ES 容器。

$ docker container stop es
es

$ docker container rm es
es

$ docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
13d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673

$ docker network inspect foodtrucks-net
[
    {
        "Name": "foodtrucks-net",
        "Id": "0815b2a3bb7a6608e850d05553cc0bda98187c4528d94621438f31d97a6fea3c",
        "Created": "2018-07-30T00:01:29.1500984Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "13d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673": {
                "Name": "es",
                "EndpointID": "29ba2d33f9713e57eb6b38db41d656e4ee2c53e4a2f7cf636bdca0ec59cd3aa7",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

如您所见,我们的es容器现在正在foodtrucks-net桥接网络中运行。现在让我们检查当我们在foodtrucks-net网络中启动时会发生什么。

$ docker run -it --rm --net foodtrucks-net yourusername/foodtrucks-web bash
root@9d2722cf282c:/opt/flask-app# curl es:9200
{
  "name" : "wWALl9M",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "BA36XuOiRPaghPNBLBHleQ",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}
root@53af252b771a:/opt/flask-app# ls
app.py  node_modules  package.json  requirements.txt  static  templates  webpack.config.js
root@53af252b771a:/opt/flask-app# python3 app.py
Index not found...
Loading data in elasticsearch ...
Total trucks loaded:  733
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
root@53af252b771a:/opt/flask-app# exit

哇!这样可行!在 foodtrucks-net 等用户定义的网络上,容器不仅可以通过 IP 地址进行通信,还可以将容器名称解析为 IP 地址。这种能力称为自动服务发现。Great!现在让我们真正启动我们的 Flask 容器 –

$ docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web yourusername/foodtrucks-web
852fc74de2954bb72471b858dce64d764181dca0cf7693fed201d76da33df794

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED              STATUS              PORTS                                            NAMES
852fc74de295        yourusername/foodtrucks-web                           "python3 ./app.py"       About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp                           foodtrucks-web
13d6415f73c8        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   17 minutes ago       Up 17 minutes       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   es

$ curl -I 0.0.0.0:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3697
Server: Werkzeug/0.11.2 Python/2.7.6
Date: Sun, 10 Jan 2016 23:58:53 GMT

前往http://0.0.0.0:5000现场观看您的精彩应用!尽管这看起来可能需要做很多工作,但实际上我们只需要输入 4 个命令就可以从零开始运行。我在bash 脚本中整理了命令。

#!/bin/bash

# build the flask container
docker build -t yourusername/foodtrucks-web .

# create the network
docker network create foodtrucks-net

# start the ES container
docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2

# start the flask app container
docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web yourusername/foodtrucks-web

现在假设您正在将您的应用程序分发给朋友,或者在安装了 docker 的服务器上运行。只需一个命令即可运行整个应用程序!

$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ ./setup-docker.sh

就是这样!如果你问我,我发现这是一种非常棒的、一种共享和运行应用程序的强大方式!

Docker 撰写

到目前为止,我们一直在探索 Docker 客户端。然而,在 Docker 生态系统中,还有许多其他开源工具与 Docker 配合得非常好。其中一些是 –

  1. Docker Machine – 在您的计算机、云提供商和您自己的数据中心内创建 Docker 主机
  2. Docker Compose – 用于定义和运行多容器 Docker 应用程序的工具。
  3. Docker Swarm – Docker 的原生集群解决方案
  4. Kubernetes – Kubernetes 是一个开源系统,用于自动部署、扩展和管理容器化应用程序。

在本节中,我们将研究其中一个工具 Docker Compose,并了解它如何使处理多容器应用程序变得更容易。

Docker Compose 的背景故事非常有趣。大约在 2014 年 1 月左右,一家名为 OrchardUp 的公司推出了一款名为 Fig 的工具。 Fig 背后的想法是让孤立的开发环境与 Docker 一起工作。该项目在Hacker News上很受欢迎——我奇怪地记得读过它,但并没有完全掌握它。

论坛上的第一条评论实际上很好地解释了 Fig 的含义。

所以在这一点上,这就是 Docker 的意义所在:运行进程。现在 Docker 提供了相当丰富的 API 来运行进程:容器之间的共享卷(目录)(即运行的镜像)、从主机到容器的转发端口、显示日志等等。但就是这样:到目前为止,Docker 仍处于进程级别。

虽然它提供了编排多个容器以创建单个“应用程序”的选项,但它并没有解决将此类容器组作为单个实体进行管理的问题。这就是 Fig 等工具的用武之地:将一组容器作为一个实体进行讨论。想想“运行一个应用程序”(即“运行一个编排的容器集群”)而不是“运行一个容器”。

事实证明,很多使用 docker 的人都同意这种观点。随着 Fig 逐渐流行起来,Docker Inc. 注意到了这一点,收购了该公司并将 Fig 重新命名为 Docker Compose。

那么Compose是用来做什么的呢?Compose 是一个用于以简单的方式定义和运行多容器 Docker 应用程序的工具。它提供了一个名为的配置文件docker-compose.yml,可用于通过一个命令启动应用程序及其所依赖的服务套件。Compose 适用于所有环境:生产、暂存、开发、测试以及 CI 工作流程,尽管 Compose 是开发和测试环境的理想选择。

让我们看看我们是否可以docker-compose.yml为我们的 SF-Foodtrucks 应用程序创建一个文件,并评估 Docker Compose 是否兑现了它的承诺。

然而,第一步是安装 Docker Compose。如果您运行的是 Windows 或 Mac,Docker Compose 已经安装在 Docker 工具箱中。Linux 用户可以按照文档中的说明轻松使用 Docker Compose 。由于 Compose 是用 Python 编写的,因此您也可以简单地执行pip install docker-compose. 使用 – 测试您的安装

$ docker-compose --version
docker-compose version 1.21.2, build a133471

现在我们已经安装了它,我们可以继续下一步,即 Docker Compose 文件docker-compose.yml。YAML 的语法非常简单,并且 repo 已经包含我们将使用的 docker-compose文件。

version: "3"
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
    container_name: es
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
    volumes:
      - esdata1:/usr/share/elasticsearch/data
  web:
    image: yourusername/foodtrucks-web
    command: python3 app.py
    depends_on:
      - es
    ports:
      - 5000:5000
    volumes:
      - ./flask-app:/opt/flask-app
volumes:
  esdata1:
    driver: local

让我分解上面的文件的含义。在父级,我们定义服务的名称 –esweb. 该image参数始终是必需的,对于我们希望 Docker 运行的每个服务,我们可以添加其他参数。对于es,我们只是参考elasticsearchElastic 注册表上可用的图像。对于我们的 Flask 应用程序,我们参考了我们在本节开头构建的图像。

其他参数如commandports提供有关容器的更多信息。该volumes参数指定web代码将驻留的容器中的挂载点。这纯粹是可选的,如果您需要访问日志等,它很有用。我们稍后会看到这在开发过程中如何有用。请参阅在线参考以了解有关此文件支持的参数的更多信息。我们还为容器添加卷,es以便我们加载的数据在重新启动之间保持不变。我们还指定depends_on,它告诉 docker 之前启动es容器web。您可以在docker compose docs上阅读更多相关信息。

注意:您必须在docker-compose.yml文件所在的目录中才能执行大多数 Compose 命令。

伟大的!现在文件准备好了,让我们看看docker-compose实际情况。但在开始之前,我们需要确保端口和名称是免费的。因此,如果您正在运行 Flask 和 ES 容器,让我们将它们关闭。

$ docker stop es foodtrucks-web
es
foodtrucks-web

$ docker rm es foodtrucks-web
es
foodtrucks-web

现在我们可以运行了docker-compose。导航到食品卡车目录并运行docker-compose up

$ docker-compose up
Creating network "foodtrucks_default" with the default driver
Creating foodtrucks_es_1
Creating foodtrucks_web_1
Attaching to foodtrucks_es_1, foodtrucks_web_1
es_1  | [2016-01-11 03:43:50,300][INFO ][node                     ] [Comet] version[2.1.1], pid[1], build[40e2c53/2015-12-15T13:05:55Z]
es_1  | [2016-01-11 03:43:50,307][INFO ][node                     ] [Comet] initializing ...
es_1  | [2016-01-11 03:43:50,366][INFO ][plugins                  ] [Comet] loaded [], sites []
es_1  | [2016-01-11 03:43:50,421][INFO ][env                      ] [Comet] using [1] data paths, mounts [[/usr/share/elasticsearch/data (/dev/sda1)]], net usable_space [16gb], net total_space [18.1gb], spins? [possibly], types [ext4]
es_1  | [2016-01-11 03:43:52,626][INFO ][node                     ] [Comet] initialized
es_1  | [2016-01-11 03:43:52,632][INFO ][node                     ] [Comet] starting ...
es_1  | [2016-01-11 03:43:52,703][WARN ][common.network           ] [Comet] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
es_1  | [2016-01-11 03:43:52,704][INFO ][transport                ] [Comet] publish_address {172.17.0.2:9300}, bound_addresses {[::]:9300}
es_1  | [2016-01-11 03:43:52,721][INFO ][discovery                ] [Comet] elasticsearch/cEk4s7pdQ-evRc9MqS2wqw
es_1  | [2016-01-11 03:43:55,785][INFO ][cluster.service          ] [Comet] new_master {Comet}{cEk4s7pdQ-evRc9MqS2wqw}{172.17.0.2}{172.17.0.2:9300}, reason: zen-disco-join(elected_as_master, [0] joins received)
es_1  | [2016-01-11 03:43:55,818][WARN ][common.network           ] [Comet] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
es_1  | [2016-01-11 03:43:55,819][INFO ][http                     ] [Comet] publish_address {172.17.0.2:9200}, bound_addresses {[::]:9200}
es_1  | [2016-01-11 03:43:55,819][INFO ][node                     ] [Comet] started
es_1  | [2016-01-11 03:43:55,826][INFO ][gateway                  ] [Comet] recovered [0] indices into cluster_state
es_1  | [2016-01-11 03:44:01,825][INFO ][cluster.metadata         ] [Comet] [sfdata] creating index, cause [auto(index api)], templates [], shards [5]/[1], mappings [truck]
es_1  | [2016-01-11 03:44:02,373][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,510][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,593][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,708][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:03,047][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
web_1 |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

前往 IP 现场查看您的应用。那太神奇了不是吗?只需几行配置,我们就有了两个 Docker 容器,可以同时成功运行。让我们停止服务并以分离模式重新运行。

web_1 |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Killing foodtrucks_web_1 ... done
Killing foodtrucks_es_1 ... done

$ docker-compose up -d
Creating es               ... done
Creating foodtrucks_web_1 ... done

$ docker-compose ps
      Name                    Command               State                Ports
--------------------------------------------------------------------------------------------
es                 /usr/local/bin/docker-entr ...   Up      0.0.0.0:9200->9200/tcp, 9300/tcp
foodtrucks_web_1   python3 app.py                   Up      0.0.0.0:5000->5000/tcp

不出所料,我们可以看到两个容器都运行成功。名字从何而来?这些是由 Compose 自动创建的。但是Compose也会自动创建网络吗?好问题!让我们来了解一下。

首先,让我们停止运行服务。我们总是可以通过一个命令将它们恢复。数据量将持续存在,因此可以使用 docker-compose up 使用相同的数据再次启动集群。要销毁集群和数据卷,只需键入docker-compose down -v.

$ docker-compose down -v
Stopping foodtrucks_web_1 ... done
Stopping es               ... done
Removing foodtrucks_web_1 ... done
Removing es               ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1

在此过程中,我们还将删除foodtrucks上次创建的网络。

$ docker network rm foodtrucks-net
$ docker network ls
NETWORK ID          NAME                 DRIVER              SCOPE
c2c695315b3a        bridge               bridge              local
a875bec5d6fd        host                 host                local
ead0e804a67b        none                 null                local

Great!现在我们有了一个全新的状态,让我们重新运行我们的服务,看看Compose是否发挥了它的魔力。

$ docker-compose up -d
Recreating foodtrucks_es_1
Recreating foodtrucks_web_1

$ docker container ls
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
f50bb33a3242        yourusername/foodtrucks-web  "python3 app.py"         14 seconds ago      Up 13 seconds       0.0.0.0:5000->5000/tcp   foodtrucks_web_1
e299ceeb4caa        elasticsearch                "/docker-entrypoint.s"   14 seconds ago      Up 14 seconds       9200/tcp, 9300/tcp       foodtrucks_es_1

到目前为止,一切都很好。是时候看看是否创建了任何网络。

$ docker network ls
NETWORK ID          NAME                 DRIVER
c2c695315b3a        bridge               bridge              local
f3b80f381ed3        foodtrucks_default   bridge              local
a875bec5d6fd        host                 host                local
ead0e804a67b        none                 null                local

您可以看到 compose 继续创建了一个名为的新网络foodtrucks_default,并在该网络中附加了两个新服务,以便每个服务都可以被另一个发现。服务的每个容器都加入默认网络,并且可以被该网络上的其他容器访问,并且可以通过与容器名称相同的主机名被它们发现。

$ docker ps
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED              STATUS              PORTS                              NAMES
8c6bb7e818ec        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   About a minute ago   Up About a minute   0.0.0.0:9200->9200/tcp, 9300/tcp   es
7640cec7feb7        yourusername/foodtrucks-web                           "python3 app.py"         About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp             foodtrucks_web_1

$ docker network inspect foodtrucks_default
[
    {
        "Name": "foodtrucks_default",
        "Id": "f3b80f381ed3e03b3d5e605e42c4a576e32d38ba24399e963d7dad848b3b4fe7",
        "Created": "2018-07-30T03:36:06.0384826Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7640cec7feb7f5615eaac376271a93fb8bab2ce54c7257256bf16716e05c65a5": {
                "Name": "foodtrucks_web_1",
                "EndpointID": "b1aa3e735402abafea3edfbba605eb4617f81d94f1b5f8fcc566a874660a0266",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            },
            "8c6bb7e818ec1f88c37f375c18f00beb030b31f4b10aee5a0952aad753314b57": {
                "Name": "es",
                "EndpointID": "649b3567d38e5e6f03fa6c004a4302508c14a5f2ac086ee6dcf13ddef936de7b",
                "MacAddress": "02:42:ac:13:00:03",
                "IPv4Address": "172.19.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "foodtrucks",
            "com.docker.compose.version": "1.21.2"
        }
    }
]

开发工作流程

在我们跳到下一节之前,我想介绍最后一件事关于 docker-compose。如前所述,docker-compose 非常适合开发和测试。因此,让我们看看如何配置 compose 以使开发过程中的生活更轻松。

在本教程中,我们使用了现成的 docker 镜像。虽然我们从头开始构建图像,但我们还没有接触任何应用程序代码,并且主要限制自己编辑 Dockerfile 和 YAML 配置。您一定想知道的一件事是工作流程在开发过程中的外观如何?是否应该为每次更改继续创建 Docker 映像,然后发布并运行它以查看更改是否按预期工作?我敢肯定这听起来超级乏味。一定有更好的方法。在本节中,这就是我们要探索的内容。

让我们看看如何对刚刚运行的 Foodtrucks 应用程序进行更改。确保您的应用程序正在运行,

$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                              NAMES
5450ebedd03c        yourusername/foodtrucks-web                           "python3 app.py"         9 seconds ago       Up 6 seconds        0.0.0.0:5000->5000/tcp             foodtrucks_web_1
05d408b25dfe        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   10 hours ago        Up 10 hours         0.0.0.0:9200->9200/tcp, 9300/tcp   es

现在让我们看看我们是否可以更改此应用程序以Hello world!在请求/hello路由时显示消息。目前,该应用程序以 404 响应。

$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Mon, 30 Jul 2018 15:34:38 GMT

为什么会这样?由于我们的是 Flask 应用程序,因此我们可以查看app.py链接)以获取答案。在 Flask 中,路由是使用 @app.route 语法定义的。在文件中,您会看到我们只定义了三个路由-/和. 路由渲染主应用,路由用来返回一些调试信息,最后被应用用来查询elasticsearch。/debug/search/debugsearch

$ curl 0.0.0.0:5000/debug
{
  "msg": "yellow open sfdata Ibkx7WYjSt-g8NZXOEtTMg 5 1 618 0 1.3mb 1.3mb\n",
  "status": "success"
}

鉴于这种情况,我们将如何添加新路线hello?你猜对了!让我们flask-app/app.py在我们最喜欢的编辑器中打开并进行以下更改

@app.route('/')
def index():
  return render_template("index.html")

# add a new hello route
@app.route('/hello')
def hello():
  return "hello world!"

现在让我们再次尝试发出请求

$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Mon, 30 Jul 2018 15:34:38 GMT

不好了!那没有用!我们做错了什么?虽然我们确实app.pyyourusername/foodtrucks-web. 为了验证这一点,让我们尝试以下 –

$ docker-compose run web bash
Starting es ... done
root@581e351c82b0:/opt/flask-app# ls
app.py        package-lock.json  requirements.txt  templates
node_modules  package.json       static            webpack.config.js
root@581e351c82b0:/opt/flask-app# grep hello app.py
root@581e351c82b0:/opt/flask-app# exit

我们在这里尝试做的是验证我们的更改不在app.py容器中运行的内容中。我们通过运行命令来做到这一点docker-compose run,它类似于它的表亲,但为服务(在我们的例子docker run中)接受了额外的参数。一旦我们运行,shell就会按照Dockerfile中的指定打开。从 grep 命令我们可以看到我们的更改不在文件中。webbash/opt/flask-app

让我们看看如何修复它。首先,我们需要告诉 docker compose 不要使用图像,而是使用本地文件。我们还将调试模式设置为true以便 Flask 知道在app.py更改时重新加载服务器。像这样替换文件的web一部分docker-compose.yml

version: "3"
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
    container_name: es
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
    volumes:
      - esdata1:/usr/share/elasticsearch/data
  web:
    build: . # replaced image with build
    command: python3 app.py
    environment:
      - DEBUG=True # set an env var for flask
    depends_on:
      - es
    ports:
      - "5000:5000"
    volumes:
      - ./flask-app:/opt/flask-app
volumes:
  esdata1:
    driver: local

通过该更改(diff),让我们停止并启动容器。

$ docker-compose down -v
Stopping foodtrucks_web_1 ... done
Stopping es               ... done
Removing foodtrucks_web_1 ... done
Removing es               ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1

$ docker-compose up -d
Creating network "foodtrucks_default" with the default driver
Creating volume "foodtrucks_esdata1" with local driver
Creating es ... done
Creating foodtrucks_web_1 ... done

作为最后一步,让我们app.py通过添加新路线来进行更改。现在我们尝试卷曲

$ curl 0.0.0.0:5000/hello
hello world

哇!我们得到了有效的回应!尝试在应用程序中进行更多更改。

我们的 Docker Compose 之旅到此结束。使用 Docker Compose,您还可以暂停服务、在容器上运行一次性命令,甚至扩展容器的数量。我还建议您查看 Docker compose 的其他一些用例。希望我能够向您展示使用 Compose 管理多容器环境是多么容易。在最后一部分,我们将把我们的应用程序部署到 AWS!

AWS 弹性容器服务

在上一节中,我们曾经使用docker-compose一个命令在本地运行我们的应用程序:docker-compose up. 现在我们有了一个功能强大的应用程序,我们想与世界分享它,获得一些用户,赚很多钱并在迈阿密买一栋大房子。执行最后三个超出了本教程的范围,因此我们将花时间研究如何使用 AWS 在云上部署我们的多容器应用程序。

如果您已经阅读到这里,您将非常确信 Docker 是一项非常酷的技术。你并不孤单。看到 Docker 的迅速崛起,几乎所有云供应商都开始致力于增加对在其平台上部署 Docker 应用程序的支持。截至今天,您可以在Google Cloud PlatformAWSAzure和许多其他平台上部署容器。我们已经了解了使用 Elastic Beanstalk 部署单个容器应用程序的入门知识,在本节中,我们将了解 AWS 的Elastic Container Service(或 ECS)

AWS ECS 是一种可扩展且超级灵活的容器管理服务,支持 Docker 容器。它允许您通过易于使用的 API 在 EC2 实例之上操作 Docker 集群。Beanstalk 带有合理的默认值,ECS 允许您根据需要完全调整您的环境。在我看来,这使得 ECS 上手起来相当复杂。

幸运的是,ECS 有一个友好的CLI工具,可以理解 Docker Compose 文件并在 ECS 上自动配置集群!由于我们已经有一个功能docker-compose.yml,所以在 AWS 上启动和运行应该不需要太多的努力。所以让我们开始吧!

第一步是安装 CLI。官方文档中非常清楚地解释了在 Mac 和 Linux 上安装 CLI 的说明。继续,安装 CLI,完成后,通过运行验证安装

$ ecs-cli --version
ecs-cli version 1.18.1 (7e9df84)

接下来,我们将配置 CLI,以便我们可以与 ECS 通信。我们将按照AWS ECS 文档的官方指南中详细说明的步骤进行操作。如有任何混淆,请随时参考该指南。

第一步将涉及创建一个配置文件,我们将在本教程的其余部分使用该配置文件。要继续,您需要您的AWS_ACCESS_KEY_IDand AWS_SECRET_ACCESS_KEY。要获取这些信息,请按照本页标题为“访问密钥”和“秘密访问密钥”部分中详细说明的步骤进行操作。

$ ecs-cli configure profile --profile-name ecs-foodtrucks --access-key $AWS_ACCESS_KEY_ID --secret-key $AWS_SECRET_ACCESS_KEY

接下来,我们需要获取一个用于登录实例的密钥对。前往您的EC2 控制台并创建一个新的密钥对。下载密钥对并将其存储在安全位置。在您离开此屏幕之前要注意的另一件事是区域名称。就我而言,我已将密钥命名为 –ecs并将我的区域设置为us-east-1. 这就是我将在本演练的其余部分中假设的内容。

下一步是配置 CLI。

$ ecs-cli configure --region us-east-1 --cluster foodtrucks
INFO[0000] Saved ECS CLI configuration for cluster (foodtrucks)

我们为configure命令提供我们希望集群驻留的区域名称和集群名称。确保您提供的区域名称与您在创建密钥对时使用的名称相同。如果您之前没有在您的计算机上配置过AWS CLI,您可以使用官方指南,它非常详细地解释了如何让一切顺利进行。

下一步使 CLI 能够创建CloudFormation模板。

$ ecs-cli up --keypair ecs --capability-iam --size 1 --instance-type t2.medium
INFO[0000] Using recommended Amazon Linux 2 AMI with ECS Agent 1.39.0 and Docker version 18.09.9-ce
INFO[0000] Created cluster                               cluster=foodtrucks
INFO[0001] Waiting for your cluster resources to be created
INFO[0001] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0062] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0122] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0182] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0242] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-0bbed8536930053a6
Security Group created: sg-0cf767fb4d01a3f99
Subnet created: subnet-05de1db2cb1a50ab8
Subnet created: subnet-01e1e8bc95d49d0fd
Cluster creation succeeded.

在这里,我们提供了我们最初下载的密钥对的名称(ecs在我的例子中)、我们想要使用的实例数量(--size)以及我们希望容器运行的实例类型。该--capability-iam标志告诉 CLI 我们承认此命令可能会创建 IAM 资源。

最后一步也是最后一步是我们将使用我们的docker-compose.yml文件的地方。我们需要做一些小改动,所以我们不要修改原始文件,而是复制它。该文件的内容(进行更改后)如下所示 –

version: '2'
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    cpu_shares: 100
    mem_limit: 3621440000
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    logging:
      driver: awslogs
      options:
        awslogs-group: foodtrucks
        awslogs-region: us-east-1
        awslogs-stream-prefix: es
  web:
    image: yourusername/foodtrucks-web
    cpu_shares: 100
    mem_limit: 262144000
    ports:
      - "80:5000"
    links:
      - es
    logging:
      driver: awslogs
      options:
        awslogs-group: foodtrucks
        awslogs-region: us-east-1
        awslogs-stream-prefix: web

我们对原始容器所做的唯一更改docker-compose.yml是为每个容器提供mem_limit(以字节为单位)和cpu_shares值,并添加一些日志记录配置。这使我们可以在AWS CloudWatch中查看容器生成的日志。前往 CloudWatch创建一个名为foodtrucks. 请注意,由于 ElasticSearch 通常最终会占用更多内存,因此我们给出了大约 3.4 GB 的内存限制。在进入下一步之前,我们需要做的另一件事是在 Docker Hub 上发布我们的镜像。

$ docker push yourusername/foodtrucks-web

伟大的!现在让我们运行将在 ECS 上部署我们的应用程序的最终命令!

$ cd aws-ecs
$ ecs-cli compose up
INFO[0000] Using ECS task definition                     TaskDefinition=ecscompose-foodtrucks:2
INFO[0000] Starting container...                         container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es
INFO[0000] Starting container...                         container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web
INFO[0000] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0000] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0036] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0048] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0048] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0060] Started container...                          container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=RUNNING taskDefinition=ecscompose-foodtrucks:2
INFO[0060] Started container...                          container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=RUNNING taskDefinition=ecscompose-foodtrucks:2

上面的调用看起来与我们在Docker Compose中使用的调用相似,这并非巧合。如果一切顺利,您应该看到desiredStatus=RUNNING lastStatus=RUNNING最后一行。

惊人的!我们的应用程序已上线,但我们如何访问它?

ecs-cli ps
Name                                      State    Ports                     TaskDefinition
845e2368-170d-44a7-bf9f-84c7fcd9ae29/web  RUNNING  54.86.14.14:80->5000/tcp  ecscompose-foodtrucks:2
845e2368-170d-44a7-bf9f-84c7fcd9ae29/es   RUNNING                            ecscompose-foodtrucks:2

继续在浏览器中打开http://54.86.14.14,您应该会看到全黑黄色的美食车!既然我们正在讨论这个主题,那么让我们看看我们的AWS ECS控制台的外观。

我们可以在上面看到我们名为“foodtrucks”的 ECS 集群已创建,现在正在运行 1 个任务和 2 个容器实例。花一些时间浏览此控制台以了解此处的所有选项。

清理

一旦您使用了已部署的应用程序,请记住关闭集群 –

$ ecs-cli down --force
INFO[0001] Waiting for your cluster resources to be deleted...
INFO[0001] Cloudformation stack status                   stackStatus=DELETE_IN_PROGRESS
INFO[0062] Cloudformation stack status                   stackStatus=DELETE_IN_PROGRESS
INFO[0124] Cloudformation stack status                   stackStatus=DELETE_IN_PROGRESS
INFO[0155] Deleted cluster                               cluster=foodtrucks

所以你有它。只需几个命令,我们就可以在 AWS 云上部署我们很棒的应用程序!


结论

这是一个包装!经过漫长、详尽但有趣的教程后,您现在已经准备好席卷容器世界了!如果您一直坚持到最后,那么您绝对应该为自己感到自豪。您学习了如何设置 Docker、运行自己的容器、使用静态和动态网站,最重要的是获得了将应用程序部署到云的经验!

我希望完成本教程能让你对自己处理服务器的能力更有信心。当您有构建下一个应用程序的想法时,您可以确信您将能够以最小的努力将它呈现在人们面前。

下一步

您进入容器世界的旅程才刚刚开始!我编写本教程的目的是激发您的兴趣并向您展示 Docker 的强大功能。在新技术的海洋中,很难独自驾驭水域,而像这样的教程可以提供帮助。这是我刚开始时希望拥有的 Docker 教程。希望它的目的是让您对容器感到兴奋,这样您就不必再从侧面观看动作了。

以下是一些有益的额外资源。对于您的下一个项目,我强烈建议您使用 Docker。请记住——熟能生巧!

相关资源