В Bright Inventions мы часто используем CloudFormation для настройки инфраструктуры, так как это позволяет нам легко версионировать и отслеживать изменения. Первая часть инфраструктуры, которая нам нужна, — это кластер ECS. Кластер — это логическая группа задач/контейнеров, работающих внутри ECS. В частности, поскольку мы будем использовать Тип запуска EC2, кластер также можно рассматривать как группу экземпляров EC2 с установленным агентом ECS.

"ECSMainCluster": {
    "Type": "AWS::ECS::Cluster",
    "Properties": {
        "ClusterName": "app-stack-main"
    }
},
"ECSAutoScalingGroup": {
    "Type": "AWS::AutoScaling::AutoScalingGroup",
    "Properties": {
        "VPCZoneIdentifier": [
            { "Ref": "PrivateASubnet" },
            { "Ref": "PrivateBSubnet" }
        ],
        "LaunchConfigurationName": {
            "Ref": "ContainerHostInstances"
        },
        "MinSize": "1",
        "MaxSize": "6",
        "DesiredCapacity": "1",
        "Tags": [
            {
                "Key": "Name",
                "Value": "app-stack-ecs",
                "PropagateAtLaunch": true
            }
        ]
    },
    "CreationPolicy": {
        "ResourceSignal": {
            "Timeout": "PT5M"
        }
    },
    "UpdatePolicy": {
        "AutoScalingReplacingUpdate": {
            "WillReplace": "true"
        }
    }
}

Как вы можете видеть выше, ECSMainCluster в основном является объявлением. Далее следует группа автоматического масштабирования, которая будет запускать экземпляры EC2 и управлять ими. В VPCZoneIdentifier перечислены 2 подсети VPC, созданные в отдельных зонах доступности. Это жизненно важно для доступности, так как экземпляры EC2 работают на физически отдельном оборудовании. Для краткости я опустил их конфигурацию в этом посте. Однако, если вам интересна эта тема, переходите в этот пост. Указанный LaunchConfigurationName с именем ContainerHostInstances описывает, как должен выглядеть экземпляр EC2.

"ContainerHostInstances": {
    "Type": "AWS::AutoScaling::LaunchConfiguration",
    "Properties": {
        "ImageId": "ami-880d64f1",
        "SecurityGroups": [
            { "Ref": "ECSSecurityGroup" }
        ],
        "InstanceType": "t2.medium",
        "IamInstanceProfile": { "Ref": "ECSHostEC2InstanceProfile" },
        "KeyName": "private-key-pair",
        "UserData": {
            "Fn::Base64": {
                "Fn::Join": [
                    "",
                    [
                        "#!/bin/bash -xe\n",
                        "echo ECS_CLUSTER=",
                        {
                            "Ref": "ECSMainCluster"
                        },
                        " >> /etc/ecs/ecs.config\n",
                        "yum install -y aws-cfn-bootstrap\n",
                        "/opt/aws/bin/cfn-signal -e $? ",
                        "         --stack ",
                        {
                            "Ref": "AWS::StackName"
                        },
                        "         --resource ECSAutoScalingGroup ",
                        "         --region ",
                        {
                            "Ref": "AWS::Region"
                        },
                        "\n"
                    ]
                ]
            }
        }
    }
},
"ECSHostEC2InstanceProfile": {
    "Type": "AWS::IAM::InstanceProfile",
    "Properties": {
        "Path": "/",
        "Roles": [
            {
                "Ref": "ECSHostEC2Role"
            }
        ]
    }
},

Первым важным свойством является ImageId, который использует Amazon ECS-оптимизированный Linux AMI ID. Далее у нас есть группа безопасности, которая добавляет правила для входящего трафика на портах приложений. Затем у нас есть IamInstanceProfile, который ссылается на профиль экземпляра ECSHostEC2InstanceProfile, который, в свою очередь, предполагает политику ролей, требуемую агентом ECS для развертывания и настройки контейнеров. Внутри UserData мы определяем сценарий оболочки, который информирует агент ECS о кластере, в котором он работает. Я опускаю определение ECSHostEC2Role, так как оно хорошо описано в документации.

С учетом вышеизложенного мы теперь готовы развернуть кластер ECS с помощью шаблона CloudFormation. Однако кластер без контейнеров довольно бессмысленен.

Служба ECS и определение задачи

На жаргоне AWS служба ECS описывает минимальную конфигурацию, необходимую для развертывания и запуска определения задачи. Определение задачи, в свою очередь, описывает, как настроить и запустить набор контейнеров, образующих единый логический компонент.

"EmailSenderService": {
    "Type": "AWS::ECS::Service",
    "Properties": {
        "Cluster": { "Ref": "ECSMainCluster" },
        "DesiredCount": 2,
        "DeploymentConfiguration": { "MinimumHealthyPercent": 50 },
        "Role": { "Ref": "ECSServiceRole" },
        "TaskDefinition": { "Ref": "EmailSenderTask" }
    }
},
"EmailSenderTask": {
    "Type": "AWS::ECS::TaskDefinition",
    "Properties": {
        "Family": "app-stack-email-sender",
        "ContainerDefinitions": [{
            "Name": "app-stack-email-sender",
            "Essential": "true",
            "Image": { "Ref": "EmailSenderTaskDockerImage" },
            "LogConfiguration": {
                "LogDriver": "awslogs",
                "Options": {
                    "awslogs-group": { "Ref": "EmailSenderLogsGroup" },
                    "awslogs-region": { "Ref": "AWS::Region" },
                    "awslogs-stream-prefix": "email-sender",
                    "awslogs-datetime-format": "%Y-%m-%d %H:%M:%S.%L"
                }
            },
            "PortMappings": [{ "ContainerPort": 8080 }],
            "Environment": [{
                "Name": "DEPLOY_ENV",
                "Value": { "Ref": "DeployEnv" }
            }]
        }]
    }
},
"EmailSenderLogsGroup": {
    "Type": "AWS::Logs::LogGroup",
    "Properties": {
        "LogGroupName": "app-stack-email-sender",
        "RetentionInDays": 14
    }
}

EmailSenderService довольно просто понять. EmailSenderTask определяет один контейнер. В определении задачи app-stack-email-sender указано, что Image — это ссылка на параметр, передаваемый в шаблон CloudFormation при создании или обновлении стека. Его значение должно быть именем образа Docker, который может быть извлечен агентом ECS. Репозиторий может быть как публичным, так и приватным. При размещении собственного частного репозитория образов Docker необходимо убедиться, что агент ECS имеет правильные учетные данные. К счастью, существует Elastic Container Registry, который предлагает частные репозитории, которые автоматически настраиваются при использовании ECS, если политика ECSHostEC2Role разрешает действия, связанные с ECR.

Далее у нас есть LogConfiguration, который отправляет журналы контейнеров в группу журналов EmailSenderLogsGroup CloudWatch, чтобы мы могли проверять их через консоль AWS. В PortMappings перечислены порты, открытые работающим контейнером. Обратите внимание, что мы не определили порт хоста, и он будет назначен автоматически. Это важно при запуске нескольких экземпляров одного и того же контейнера. Чуть подробнее опишу в следующем посте. И последнее, но не менее важное: в разделе Environment перечислены переменные среды, которые передаются экземплярам контейнера при запуске. Здесь мы ссылаемся на параметр стека DeployEnv, который позволяет нам информировать приложение, работающее внутри контейнера, о текущей среде развертывания, например. постановка против производства.

Как вы можете видеть выше, требуется несколько шагов, чтобы использовать CloudFormation для развертывания контейнера в ECS. Это правда, что он требует большей настройки, чем Elastic Beanstalk. Однако он позволяет лучше использовать инстансы EC2 и использовать единый подход к развертыванию и настройке независимо от технологии приложений, используемых внутри контейнера. Более того, это больше рассчитано на будущее, так как с небольшими настройками можно будет переключиться в Fargate Launch Mode. Использование этого режима освобождает нас от бремени задач управления кластером EC2 ECS. Для развертывания дополнительных сервисов и задач потребуются отдельные определения ресурсов CloudFormation. Однако с помощью cloudform легко сохранить шаблон CloudFormation СУХИМ.

Пётр Мионсковски,поклонник TDD, стремящийся узнать что-то новое

Личный блог Электронная почта Twitter Github Stackoverflow

Эта статья кросспостирована с личным блогом автора.