Как запускать Docker контейнеры через сервис AWS Elastic Container

  • 28 октября, 15:27
  • 6422
  • 0

В Amazon Web Services существует множество различных вычислительных сервисов. В AWS Lambda есть серверный маршрут, где вы можете подготовить свою рабочую нагрузку и запускать ее только тогда, когда вам это нужно. Elastic Compute Cloud (EC2) позволяет вам запускать любую рабочую нагрузку на виртуальных машинах, за которую вы платите каждый час.

Как запускать Docker контейнеры через сервис AWS Elastic Container

Но сегодня многие люди создают контейнерные рабочие нагрузки с помощью Docker. Итак, какие у вас есть варианты для запуска ваших контейнеров в AWS? В этом посте мы создадим контейнер Docker. Затем мы собираемся создать инфраструктуру AWS для размещения этого образа и запустить его через AWS Elastic Container Service (ECS). Оттуда мы рассмотрим, как вы можете развернуть новые версии вашего образа прямо с вашего терминала.

Давайте начнем с создания примера контейнера Docker, который мы можем развернуть.

Образец Docker Image

Для нашей цели давайте создадим пример приложения 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: 

Как только проект настроен через 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.

Теперь мы можем настроить конечную точку, добавив в файл 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}`);

Теперь, когда у нас есть наш пример expressAPI, давайте продолжим и настроим наш Dockerfile. Вот как Dockerfile в итоге будет выглядеть:

FROM node:10

WORKDIR /src/api

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

RUN yarn install

COPY . .

EXPOSE 8080
CMD ["node", "server.js"]

Чтобы проверить себя, давайте создадим этот образ и запустим контейнер с ним.

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

Мы видим, что когда мы запускаем контейнер через порт 8080, мы можем вызвать нашу конечную точку через curl и получить ответ Sample Endpoint.

Теперь, когда у нас есть образ Docker для сборки и развертывания, давайте настроим реестр контейнеров в AWS, в который мы можем отправлять наши образы.

Публикация Docker Image в Elastic Container Repository (ECR)

Чтобы продолжить изучение темы AWS с его использованием, мы собираемся настроить репозиторий ECR в AWS. Мы будем использовать этот репозиторий для размещения нашего образа Docker. Прежде чем мы сможем это сделать, необходимо установить и настроить интерфейс командной строки AWS . 

У вас установлены и настроены интерфейс командной строки AWS и CDK? Отлично, давайте инициализируем наш проект CDK, чтобы начать представлять нашу инфраструктуру в Typescript. Создайте новый каталог с именем infrastructure в корне вашего репозитория. Затем инициализируйте новый проект через 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

Теперь у нас есть проект CDK, заглушенный внутри нашей infrastructure папки. Ключевой файл, который мы собираемся добавить в нашу инфраструктуру: lib/infrastructure-stack.ts. 

Для начала давайте добавим наш ресурс репозитория ECR. Во-первых, нам нужно добавить модуль ECR в наш проект CDK.

$ npm install @aws-cdk/aws-ecr

Теперь мы можем подготовить наш репозиторий 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'
    });  
  }
}

Чтобы развернуть инфраструктуру 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

Теперь в нашей учетной записи 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"
        }
    ]
}

Теперь, когда вошли в систему, мы можем пометить и отправить наше изображение в наш новый репозиторий 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

Отлично, наш образ Docker находится в нашем репозитории ECR. Теперь мы можем перейти к его развертыванию и запуску через Elastic Container Service (ECS).

Настройка инфраструктуры ECS

Прежде чем мы сможем запустить контейнер Docker в нашей учетной записи AWS, используя наш новый образ, нам нужно создать инфраструктуру, на которой он будет работать. Мы сосредоточимся на запуске нашего контейнера в Elastic Container Service (ECS), предоставляемой AWS.

ECS - это сервис оркестровки контейнеров, предоставляемый AWS. Это устраняет необходимость в управлении инфраструктурой, планировании и масштабировании для контейнерных рабочих нагрузок.

ECS состоит из трех основных терминов, которые важно знать.

  1. Кластер: это логическая группа базовых экземпляров EC2, на которых работают наши контейнеры. У нас нет доступа к этим экземплярам, и AWS управляет ими от нашего имени.
  2. Сервис: длительный процесс, такой как веб-сервер или база данных, работает как сервис в нашем кластере. Мы можем определить, сколько контейнеров должно быть запущено для этого сервиса.
  3. Определение задачи: это определение нашего контейнера, который может запускаться в кластере индивидуально или через службу. Когда определение задачи выполняется в нашем кластере, мы часто называем его задачей, поэтому работающий контейнер === задача в ECS.

Порядок этих определений также имеет значение. Мы можем думать о том, что кластер - это самый низкий уровень сервиса, когда EC2 запускает наши контейнеры. В то время как задача является экземпляром нашего контейнера, запущенного на одном или нескольких из этих экземпляров.

Давайте перейдем к обеспечению нашей инфраструктуры. Мы собираемся обновить наш, infrastructure-stack.ts чтобы иметь ресурсы, которые нам нужны для нашего кластера ECS. Во-первых, нам нужно добавить несколько других модулей.

$ npm install @aws-cdk/aws-ecs
$ npm install @aws-cdk/aws-ec2
$ npm install @aws-cdk/aws-ecs-patterns

Теперь мы можем добавить ресурсы, необходимые для нашего кластера, который будет запускать новый образ 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
    });
  }
}

Первым , что мы замечаем, что мы сначала определим наш ECS кластер app-cluster. Далее нам нужно добавить экземпляр в наш кластер app-scaling-group. Это автомасштабирующая группа t2.microтипов экземпляров, на которой могут работать наши контейнеры. Затем мы используем шаблон обслуживания с балансировкой нагрузки, предоставляемый ecsPatterns модулем.

Мы также устанавливаем значение containerPort 8080 в качестве порта, на котором работает наше экспресс-приложение внутри контейнера.

Этот шаблон создает общедоступный балансировщик нагрузки. Этот балансировщик будет переадресовывать вызовы в наш контейнер через порт 8080, работающий внутри нашей службы ECS.

Мы внедряем эти изменения с помощью другой 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             c2C88B6D3 
 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

Используя модуль AWS CDK для Elastic Container Service, мы создали все ресурсы, необходимые для нашего нового кластера. Как вы можете видеть из вывода, новый VPC со связанными подсетями был создан от нашего имени. Это хорошее преимущество такого инструмента, как CDK, он создал разумные значения по умолчанию для нашего нового кластера без необходимости их указания.

Теперь мы должны увидеть, что мы создали новый кластер ECS с нашим сервисом, в котором выполняется текущее определение задачи. CDK помог нам, выдав URL для нашего сервисного балансировщика нагрузки. Давайте возьмем этот URL и проверим, что наш контейнер работает и принимает трафик.

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

Наше определение задачи ECS в настоящее время настроено так, чтобы указывать на latest тег изображения Docker, которое мы публикуем. Это означает, что мы можем обновить наш образ, а изменения можно внедрить в наш кластер. Давайте обновим ответ, который мы возвращаем для нашего API.

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

Теперь давайте создадим и запустим новую версию нашего образа 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

Теперь у нас есть новая версия нашего образа, помещенная в репозиторий. Но он не был развернут на сервисе, работающем в нашем кластере. Чтобы получить изменения, нам нужно перезапустить службу, чтобы она вытягивала latest тег нашего изображения.

К счастью, мы можем сделать это с помощью одного вызова AWS CLI из нашей командной строки.

$ aws ecs update-service --force-new-deployment --cluster app-cluster --service sample-express-app

Как только наш новый образ будет запущен в службу (что может занять несколько минут), мы снова сможем подключиться к нашей конечной точке и увидеть наш новый ответ.

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

Мы прошли полное упражнение по созданию образа Docker, подготовке кластера ECS и запуску нашего образа в кластере. Мы даже продемонстрировали, как мы можем обновить наш образ и развернуть новые версии на работающем сервисе.


0 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все