如何通过 AWS Elastic Container Service 运行 Docker 容器

2025-06-09

如何通过 AWS Elastic Container Service 运行 Docker 容器

Amazon Web Services 内部提供多种不同的计算服务。AWS Lambda 提供无服务器路由,您可以配置工作负载并仅在需要时运行。弹性计算云 (EC2) 允许您在按小时付费的虚拟机中运行任何工作负载。

但是,如今许多人都在使用 Docker 创建容器化工作负载。那么,在 AWS 中运行容器时,您有哪些选择呢?在本文中,我们将创建一个示例 Docker 镜像。然后,我们将创建 AWS 基础设施来托管该镜像,并通过 AWS Elastic Container Service (ECS) 运行它。在此基础上,我们将探索如何直接从终端部署镜像的新版本。

让我们首先创建一个可以部署的示例 Docker 容器。

示例 Docker 镜像

为了达到我们的目的,我们先创建一个包含单个端点的示例 Node Express 应用。首先,我们需要设置项目。yarn init除了入口点之外,其余内容均保留默认设置,为此我们输入server.js

$ mkdir sample-express-app
$ cd sample-express-app
$ yarn init
question name (sample-express-app): 
question version (1.0.0): 
question description: 
question entry point (index.js): server.js
question repository url: 
question author: kylegalbraith
question license (MIT): 
question private: 
Enter fullscreen mode Exit fullscreen mode

一旦通过设置了项目yarn,我们就可以继续安装express到项目中。

$ yarn add express
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
✨  Done in 0.79s.
Enter fullscreen mode Exit fullscreen mode

太棒了!现在我们可以继续配置我们的端点,在server.js文件中添加以下内容。

const express = require('express');

const PORT = 8080;
const HOST = '0.0.0.0';

const api = express();
api.get('/', (req, res) => {
  res.send('Sample Endpoint\n');
});

api.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
Enter fullscreen mode Exit fullscreen mode

现在我们有了示例expressAPI,接下来就开始设置我们的。最终效果如下DockerfileDockerfile

FROM node:10

WORKDIR /src/api

COPY package.json ./
COPY yarn.lock ./

RUN yarn install

COPY . .

EXPOSE 8080
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

为了进行健全性检查,让我们构建这个图像并使用它启动一个容器。

$ docker build -t sample-express-app .
$ docker run -p 8080:8080 -d sample-express-app
5f3eaa088b35d895411c8d60f684aeba5d68d85f3bc07172c672542fe6b95537
$ curl localhost:8080
Sample Endpoint
Enter fullscreen mode Exit fullscreen mode

太棒了!我们看到,当我们在端口上运行容器时,8080我们可以通过 调用我们的端点curl并返回响应Sample Endpoint

现在我们已经有一个要构建和部署的 Docker 镜像,让我们在 AWS 上设置一个容器注册表,以便将镜像推送到该注册表。

将 Docker 镜像发布到 Elastic Container Repository (ECR)

为了继续我们的主题“通过使用 AWS 来学习 AWS”,我们将在 AWS 中配置一个 ECR 存储库。我们将使用此存储库来托管我们的 Docker 镜像。不过,在此之前,您需要安装并配置 AWS CLI。我们还将使用AWS CDK将我们的基础设施表示为代码,因此也请完成相关设置。

安装并配置好 AWS CLI 和 CDK 了吗?太棒了,让我们初始化 CDK 项目,开始用 Typescript 来表示我们的基础设施。在infrastructure代码库的根目录下创建一个名为“//www.cdk.org/”。然后在终端中通过 CDK 初始化一个新项目。

$ cdk init --language typescript
Applying project template app for typescript
Executing npm install...
# Useful commands

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template
Enter fullscreen mode Exit fullscreen mode

现在,我们的文件夹中已经有一个 CDK 项目infrastructure。我们将要添加基础设施的关键文件是lib/infrastructure-stack.ts。目前,我们这里的内容还不多,所以让我们来修改一下。

首先,让我们添加 ECR 存储库资源。首先,我们需要将 ECR 模块添加到我们的 CDK 项目中。

$ npm install @aws-cdk/aws-ecr
Enter fullscreen mode Exit fullscreen mode

现在我们可以通过更新来配置我们的 ECR 存储库infrastructure-stack.ts,如下所示。

import cdk = require('@aws-cdk/core');
import ecr = require('@aws-cdk/aws-ecr');

export class InfrastructureStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ECR repository
    const repository = new ecr.Repository(this, 'sample-express-app', {
      repositoryName: 'sample-express-app'
    });  
  }
}
Enter fullscreen mode Exit fullscreen mode

要部署我们的 CDK 基础设施,我们需要从命令行运行一个deploy命令。

$ cdk deploy
InfrastructureStack: deploying...
InfrastructureStack: creating CloudFormation changeset...
 0/3 | 15:51:50 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | InfrastructureStack User Initiated
 0/3 | 15:51:53 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata   | CDKMetadata
 0/3 | 15:51:53 | CREATE_IN_PROGRESS   | AWS::ECR::Repository | sample-express-app (sampleexpressapp99ADE4E3)
 0/3 | 15:51:54 | CREATE_IN_PROGRESS   | AWS::ECR::Repository | sample-express-app (sampleexpressapp99ADE4E3) Resource creation Initiated
 1/3 | 15:51:54 | CREATE_COMPLETE      | AWS::ECR::Repository | sample-express-app (sampleexpressapp99ADE4E3)
 1/3 | 15:51:55 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata   | CDKMetadata Resource creation Initiated
 2/3 | 15:51:55 | CREATE_COMPLETE      | AWS::CDK::Metadata   | CDKMetadata
 3/3 | 15:51:57 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | InfrastructureStack
Enter fullscreen mode Exit fullscreen mode

💥 现在,我们的 AWS 账户中有一个 ECR 存储库,用于托管新的 Docker 镜像。创建存储库后,我们需要登录才能推送新镜像。为此,我们以反引号形式运行以下命令,以便在返回docker login后立即调用该命令get-login

$ `aws ecr get-login --no-include-email`
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
$ aws ecr describe-repositories
{
    "repositories": [
        {
            "registryId": "<aws-id>",
            "repositoryName": "sample-express-app",
            "repositoryArn": "arn:aws:ecr:us-west-2:<aws-id>:repository/infra-sampl-1ewaboppskux6",
            "createdAt": 1571007114.0,
            "repositoryUri": "<aws-id>.dkr.ecr.us-west-2.amazonaws.com/sample-express-app"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

现在我们已全部登录,可以标记镜像并将其推送到新的 ECR 存储库。repositoryUri使用上面的命令获取存储库describe-repositories。我们将在下面的标记和推送命令中使用它。

$ docker tag sample-express-app <aws-id>.dkr.ecr.us-west-2.amazonaws.com/sample-express-app
$ docker push <aws-id>.dkr.ecr.us-west-2.amazonaws.com/sample-express-app
0574222c01c4: Pushed 
28c9fa7f105e: Pushed 
8942b63b65a1: Pushed 
b91230b492da: Pushed 
6ad739b471d2: Pushed 
954f92adc866: Pushed 
adca1e83b51a: Pushed 
73982c948de0: Pushed 
84d0c4b192e8: Pushed 
a637c551a0da: Pushed 
2c8d31157b81: Pushed 
7b76d801397d: Pushed 
f32868cde90b: Pushed 
0db06dff9d9a: Pushed
Enter fullscreen mode Exit fullscreen mode

太好了,我们的 Docker 镜像已经保存在 ECR 仓库中了。现在我们可以继续部署,并通过 Elastic Container Service (ECS) 运行它了。

设置我们的 ECS 基础设施

在我们可以使用新镜像在 AWS 账户中运行 Docker 容器之前,我们需要创建容器所需的基础设施。在本篇博文中,我们将重点介绍如何在 AWS 提供的 Elastic Container Service (ECS) 上运行容器。

ECS 是 AWS 提供的容器编排服务。它消除了管理容器化工作负载的基础设施、调度和扩展的需要。

ECS 由三个核心术语组成,在考虑服务时务必牢记这些术语。

  • 集群:这是我们容器运行的底层 EC2 实例的逻辑组。我们无法访问这些实例,AWS 会代我们管理它们。
  • 服务:像 Web 服务器或数据库这样的长期运行进程在我们的集群中以服务的形式运行。我们可以定义该服务需要运行多少个容器。
  • 任务定义:这是我们容器的定义,它可以在集群上单独运行,也可以通过服务运行。当任务定义在集群中运行时,我们通常将其称为任务,因此正在运行的容器 === ECS 中的任务。

这些定义的顺序也很重要。我们可以将集群视为服务的最低层级,即容器在其上运行的 EC2 实例。而任务则是在一个或多个实例上运行的容器实例。

记住这些术语后,我们就开始实际配置基础设施吧。我们将更新我们的infrastructure-stack.tsECS 集群,以获取所需的资源。首先,我们需要添加一些其他模块。

$ npm install @aws-cdk/aws-ecs
$ npm install @aws-cdk/aws-ec2
$ npm install @aws-cdk/aws-ecs-patterns
Enter fullscreen mode Exit fullscreen mode

现在我们可以为运行新 Docker 镜像的集群添加所需的资源。infrastructure-stack.ts现在应该如下所示。

import cdk = require('@aws-cdk/core');
import ecr = require('@aws-cdk/aws-ecr');
import ecs = require('@aws-cdk/aws-ecs');
import ec2 = require('@aws-cdk/aws-ec2');
import ecsPatterns = require('@aws-cdk/aws-ecs-patterns');

export class InfrastructureStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ECR repository
    const repository = new ecr.Repository(this, 'sample-express-app', {
      repositoryName: 'sample-express-app'
    });

    // ECS cluster/resources
    const cluster = new ecs.Cluster(this, 'app-cluster', {
      clusterName: 'app-cluster'
    });

    cluster.addCapacity('app-scaling-group', {
      instanceType: new ec2.InstanceType("t2.micro"),
      desiredCapacity: 1
    });

    const loadBalancedService = new ecsPatterns.ApplicationLoadBalancedEc2Service(this, 'app-service', {
      cluster,
      memoryLimitMiB: 512,
      cpu: 5,
      desiredCount: 1,
      serviceName: 'sample-express-app',
      taskImageOptions: {
        image: ecs.ContainerImage.fromEcrRepository(repository),
        containerPort: 8080
      },
      publicLoadBalancer: true
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

我们首先注意到的是,我们首先定义了 ECS 集群。app-cluster接下来,我们需要向集群添加一个实例。app-scaling-group这是一个可自动扩展的实例类型组t2.micro,我们的容器可以在其上运行。然后,我们使用模块提供的负载均衡服务模式ecsPatterns

请注意,我们将 ECS 服务指向托管在 ECR 存储库中的镜像。我们还将 设置为,containerPort因为8080这是我们的 Express 应用在容器内运行的端口。

此模式创建了一个面向公众的负载均衡器,我们可以从 Web 浏览器调用它。该负载均衡器会将调用转发到ECS 服务内部运行的curl端口上的容器。8080

我们通过另一个deploy命令部署这些更改。这次我们将进行指定,--require-approval never以便不会收到有关 IAM 更改的提示。

$ cdk deploy --require-approval never
InfrastructureStack: deploying...
InfrastructureStack: creating CloudFormation changeset...
  0/46 | 16:41:46 | UPDATE_IN_PROGRESS   | AWS::CloudFormation::Stack            | InfrastructureStack User Initiated
  0/46 | 16:42:07 | CREATE_IN_PROGRESS   | AWS::EC2::EIP                         | app-cluster/Vpc/PublicSubnet2/EIP (appclusterVpcPublicSubnet2EIPD0A381A3)
  0/46 | 16:42:07 | CREATE_IN_PROGRESS   | AWS::ECS::Cluster                     | app-cluster (appclusterD09F8E40)
  0/46 | 16:42:07 | CREATE_IN_PROGRESS   | AWS::EC2::InternetGateway             | app-cluster/Vpc/IGW (appclusterVpcIGW17A11835)
  0/46 | 16:42:07 | CREATE_IN_PROGRESS   | AWS::EC2::EIP                         | app-cluster/Vpc/PublicSubnet1/EIP (appclusterVpcPublicSubnet1EIP791F54CD)
...
....
.....
44/46 | 16:46:04 | CREATE_COMPLETE      | AWS::Lambda::Permission               | app-cluster/DefaultAutoScalingGroup/DrainECSHook/Function/AllowInvoke:InfrastructureStackappclusterDefaultAutoScalingGroupLifecycleHookDrainHookTopic2C88B6D3 (appclusterDefaultAutoScalingGroupDrainECSHookFunctionAllowInvokeInfrastructureStackappclusterDefaultAutoScalingGroupLifecycleHookDrainHookTopic2C88B6D3036C2EFB)
 45/46 | 16:46:33 | CREATE_COMPLETE      | AWS::ECS::Service                     | app-service/Service (appserviceServiceA5AB3AA1)
 45/46 | 16:46:37 | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack            | InfrastructureStack
 46/46 | 16:46:38 | UPDATE_COMPLETE      | AWS::CloudFormation::Stack            | InfrastructureStack

Outputs:
InfrastructureStack.appserviceLoadBalancerDNS0A615BF5 = Infra-appse-187228PB273DW-1700265048.us-west-2.elb.amazonaws.com
InfrastructureStack.appserviceServiceURL90EC0456 = http://Infra-appse-187228PB273DW-1700265048.us-west-2.elb.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

通过使用适用于 Elastic Container Service 的 AWS CDK 模块,我们创建了新集群所需的所有资源。从输出中可以看到,系统已为我们创建了一个包含关联子网的新 VPC。CDK 这样的工具的一大优势在于,它为我们的新集群创建了合理的默认设置,无需我们手动指定。

现在,我们应该看到我们已经创建了一个新的 ECS 集群,我们的服务正在运行当前的任务定义。CDK 帮助我们输出了服务负载均衡器的 URL。让我们获取该 URL,并通过 curl 访问该 URL 来检查我们的容器是否正在运行并接受流量。

$ curl Infra-appse-187228PB273DW-1700265048.us-west-2.elb.amazonaws.com
Sample Endpoint
Enter fullscreen mode Exit fullscreen mode

下一步该怎么做

现在我们的初始容器正在我们的 ECS 集群中运行,我们可以退一步,探索从这里可以去哪里。

我们的 ECS 任务定义目前已设置为指向latest我们发布的 Docker 镜像的标签。这意味着我们可以更新镜像,并将更改部署到集群。让我们更新 API 返回的响应。

api.get('/', (req, res) => {
  res.send('New Response\n');
});
Enter fullscreen mode Exit fullscreen mode

现在让我们构建并推送新版本的 Docker 镜像。

$ docker build -t sample-express-app .
$ `aws ecr get-login --no-include-email`
$ docker tag sample-express-app <aws-id>.dkr.ecr.us-west-2.amazonaws.com/sample-express-app:latest
$ docker push <aws-id>.dkr.ecr.us-west-2.amazonaws.com/sample-express-app:latest
9a7704a19307: Pushed 
03a86aeeb52b: Layer already exists 
c14651828ff6: Layer already exists 
4ecb552d7aff: Layer already exists 
6ad739b471d2: Layer already exists 
954f92adc866: Layer already exists 
adca1e83b51a: Layer already exists 
73982c948de0: Layer already exists 
84d0c4b192e8: Layer already exists 
a637c551a0da: Layer already exists 
2c8d31157b81: Layer already exists 
7b76d801397d: Layer already exists 
f32868cde90b: Layer already exists 
0db06dff9d9a: Layer already exists 
latest: digest: sha256:ed82982bfa5fe6333c4b67afaae0f36e3208a588736c9586ff81dbdd7e1bc0f5 size: 3256
Enter fullscreen mode Exit fullscreen mode

现在,我们已将新版本的镜像推送到代码库。但它尚未部署到集群中运行的服务。为了在集群中应用更改,我们需要重启服务,以便它能够拉取latest镜像的标签。

幸运的是,我们可以通过命令行中的一次 AWS CLI 调用来实现这一点。


$ aws ecs update-service --force-new-deployment --cluster app-cluster --service sample-express-app
Enter fullscreen mode Exit fullscreen mode

一旦我们的新图像推广到服务(这可能需要几分钟),我们就可以curl再次访问我们的端点并查看我们的新响应。

$ curl Infra-appse-187228PB273DW-1700265048.us-west-2.elb.amazonaws.com
New Response
Enter fullscreen mode Exit fullscreen mode

结论

🙌 我们已经完成了构建 Docker 镜像、配置 ECS 集群以及在集群中运行镜像的完整练习。我们甚至演示了如何更新镜像并将新版本部署到正在运行的服务中。

接下来,您可以有很多方向。DevOps 的下一个阶段是设置 CI/CD 管道,以便将新镜像持续部署到 ECS 集群。我有一篇博客文章,重点介绍如何使用 AWS CodePipeline 和 CodeBuild 构建 Docker 镜像,帮助您入门。

从这一点来看,在 ECS 集群中启动新镜像时,缩短部署时间或许是值得的。之所以无法立即完成部署,是因为与我们的服务关联的负载均衡器启用了连接耗尽功能。这使我们能够逐步向服务推出新版本,而无需立即关闭现有服务。这对于不引人注意的部署来说是理想的选择,但这确实意味着我们的部署时间会更长一些。

想看看我的其他项目吗?

我是 DEV 社区的忠实粉丝。如果您有任何疑问,或者想讨论关于重构的不同想法,请在 Twitter 上联系我,或在下方留言。

除了写博客之外,我还创建了一门“通过使用 AWS 学习 AWS”课程。课程将重点讲解如何实际使用 Amazon Web Services 来托管、保护和交付静态网站。这是一个简单的问题,有很多解决方案,但它非常适合加深你对 AWS 的理解。我最近为该课程添加了两个新的附加章节,分别侧重于“基础设施即代码”和“持续部署”。

我还精心策划了每周的新闻通讯。“ Learn By Doing”新闻通讯每周都包含精彩的云计算、编程和 DevOps 文章。注册即可在您的邮箱中接收。

鏂囩珷鏉ユ簮锛�https://dev.to/kylegalbraith/how-to-run-docker-containers-via-aws-elastic-container-service-p47
PREV
如何通过外包发展自由职业业务 外包的好处 如何外包
NEXT
获得 AWS 认证如何改变您的职业生涯?