AWS: Application Load Balancer – настройка

Автор: | 26/10/2016
 

aws-logo-square-02Обзор

Elastic Load Balancing позволяет распределять трафик между несколькими инстансами EC2 и зонами доступности AWS, что повышает отказоустойчивость приложения.

С недавних пор – AWS предоставляет два типа балансировщиков – Application Load Balancer и Classic Load Balancer.

Вкратце – классический ELB работал на 4-ом и 7-ом уровнях OSIApplication Load Balancer – на 7-ом и позволяет строить более гибкие правила за счёт работы на уровне приложений.

Подробнее – тут>>> и тут>>>.

Создание ELB

Запуск EC2

Задача – создать два EC2, поднять Application Load Balancer и настроить балансировку нагрузки по health-проверке инстансов.

Запускать будем из имеющего AMI, который собран Packer-ом.

$ aws ec2 describe-images --image-ids ami-369bd545 --output text
IMAGES  x86_64  2016-10-21T15:58:49.000Z                xen     ami-1398d660    947191746595/aws-api-test-ami   machine aws-api-test-ami        947191746595    False   /dev/sda1       ebs     simple  available       hvm
BLOCKDEVICEMAPPINGS     /dev/sda1
EBS     True    False   snap-0df2dbd2d5f9711b7  8       gp2

Нам требуется запустить два EC2 в двух разных подсетях, в двух availability zones.

Создаём подсеть A в VPC, в зоне eu-west-1a:

$ aws ec2 create-subnet --vpc-id vpc-43402c27 --cidr-block 10.0.1.0/24 --availability-zone eu-west-1a
{
    "Subnet": {
        "VpcId": "vpc-43402c27", 
        "CidrBlock": "10.0.1.0/24", 
        "State": "pending", 
        "AvailabilityZone": "eu-west-1a", 
        "SubnetId": "subnet-422f5134", 
        "AvailableIpAddressCount": 251
    }
}

И подсеть B в зоне eu-west-1b:

$ aws ec2 create-subnet --vpc-id vpc-43402c27 --cidr-block 10.0.2.0/24 --availability-zone eu-west-1b
{
    "Subnet": {
        "VpcId": "vpc-43402c27", 
        "CidrBlock": "10.0.2.0/24", 
        "State": "pending", 
        "AvailabilityZone": "eu-west-1b", 
        "SubnetId": "subnet-c7c2409f", 
        "AvailableIpAddressCount": 251
    }
}

(не забываем добавить Internet Gateway и настроить Route Tables, тут пропущено, см тут>>>)

Запускаем два инстанса – по одному в каждой подсети:

$ aws ec2 run-instances --image-id ami-369bd545 --key-name aws-test --count 1 --instance-type t2.medium --user-data file://api_start.sh --security-group-ids sg-9225eef4 --subnet-id subnet-422f5134
$ aws ec2 run-instances --image-id ami-369bd545 --key-name aws-test --count 1 --instance-type t2.medium --user-data file://api_start.sh --security-group-ids sg-9225eef4 --subnet-id subnet-c7c2409f

Теперь у нас имеется два инстанса, которые мы включим в ELB:

$ aws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId]' --filters Name=instance-state-name,Values=running --output text
i-0fb346245a14ff8bc
i-0aac76eab30ec53a9

Скрипт api_start.sh, который передаётся в userdata инстанса – просто запускает Docker контейнеры с сервисами приложения при старте нового инстанса (потребуется позже для Auto Scaling):

#!/usr/bin/env bash

sleep 30

SPRING_PROFILE="dev"

DOCKER_REGISTRY="project.jfrog.io"
DOCKER_IMAGE_TAG="aws-test-1.0"

DOCKER_IMAGE_OAUTH="project-oauth2-authserver:${DOCKER_IMAGE_TAG}"
DOCKER_IMAGE_PROFILE="project-profile:${DOCKER_IMAGE_TAG}"
DOCKER_IMAGE_CONFIGURATION="project-configuration:${DOCKER_IMAGE_TAG}"
DOCKER_IMAGE_PRODUCERS="project-producers:${DOCKER_IMAGE_TAG}"
DOCKER_IMAGE_GATEWAY="project-api-gateway:${DOCKER_IMAGE_TAG}"

docker run -d -p 9999:9999 -e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} --restart unless-stopped ${DOCKER_REGISTRY}/${DOCKER_IMAGE_OAUTH}
docker run -d -p 8083:8083 -e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} --restart unless-stopped ${DOCKER_REGISTRY}/${DOCKER_IMAGE_PROFILE}
docker run -d -p 8084:8084 -e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} --restart unless-stopped ${DOCKER_REGISTRY}/${DOCKER_IMAGE_CONFIGURATION}
docker run -d -p 8090:8090 -e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} --restart unless-stopped ${DOCKER_REGISTRY}/${DOCKER_IMAGE_PRODUCERS}
docker run -d -p 8080:8080 -e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} --restart unless-stopped ${DOCKER_REGISTRY}/${DOCKER_IMAGE_GATEWAY}

Создание Application Load Balancer

За создание ELB отвечают две одинаковые опции AWS CLIcreate-load-balancer. Только для классического ELB – она вызывается из aws.elb, а для Application Load Balancer – из aws.elbv2.

С помощью create-load-balancer создаём:

$ aws elbv2 create-load-balancer --name api-test --subnets subnet-422f5134 subnet-c7c2409f --security-groups sg-9225eef4 --scheme internet-facing
{
    "LoadBalancers": [
        {
            "VpcId": "vpc-43402c27", 
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-west-1:947191746595:loadbalancer/app/api-test/0af98b76b2b99307", 
            "State": {
                "Code": "provisioning"
            }, 
            "DNSName": "api-test-607244287.eu-west-1.elb.amazonaws.com", 
            "SecurityGroups": [
                "sg-9225eef4"
            ], 
            "LoadBalancerName": "api-test", 
            "CreatedTime": "2016-10-24T14:33:21.160Z", 
            "Scheme": "internet-facing", 
            "Type": "application", 
            "CanonicalHostedZoneId": "Z32O12XQLNTSW2", 
            "AvailabilityZones": [
                {
                    "SubnetId": "subnet-422f5134", 
                    "ZoneName": "eu-west-1a"
                }, 
                {
                    "SubnetId": "subnet-c7c2409f", 
                    "ZoneName": "eu-west-1b"
                }
            ]
        }
    ]
}

--scheme internet-facing можно не укзывать, т.к. internet-facing используется по умолчанию.

Добавление Target Group

Для того, что бы ELB распределял трафик между несколькими EC2 инстансами – их необходимо объединить в target group.

Создаём с помощью create-target-group:

$ aws elbv2 create-target-group --name api-test --protocol HTTP --port 8080 --vpc-id vpc-43402c27
{
    "TargetGroups": [
        {
            "HealthCheckPath": "/", 
            "HealthCheckIntervalSeconds": 30, 
            "VpcId": "vpc-43402c27", 
            "Protocol": "HTTP", 
            "HealthCheckTimeoutSeconds": 5, 
            "HealthCheckProtocol": "HTTP", 
            "UnhealthyThresholdCount": 2, 
            "HealthyThresholdCount": 5, 
            "TargetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:947191746595:targetgroup/api-test/3f0ff2ce8d667243", 
            "Matcher": {
                "HttpCode": "200"
            }, 
            "HealthCheckPort": "traffic-port", 
            "Port": 8080, 
            "TargetGroupName": "api-test"
        }
    ]
}

Добавляем к этой группе оба созданных инстанса, указав порт 8080:

$ aws elbv2 register-targets --target-group-arn arn:aws:elasticloadbalancing:eu-west-1:947191746595:targetgroup/api-test/3f0ff2ce8d667243 --targets Id=i-0fb346245a14ff8bc,Port=8080 Id=i-0aac76eab30ec53a9,Port=8080

Добавление Listener

Следующим – с помощью create-listener добавляем listener, который будет слушать указанный порт, и распределять обращения к этом порту по созданной ранее target group.

$ aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:eu-west-1:947191746595:loadbalancer/app/api-test/0af98b76b2b99307 --protocol HTTP --port 8080 --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:eu-west-1:947191746595:targetgroup/api-test/3f0ff2ce8d667243
{
    "Listeners": [
        {
            "Protocol": "HTTP", 
            "DefaultActions": [
                {
                    "TargetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:947191746595:targetgroup/api-test/3f0ff2ce8d667243", 
                    "Type": "forward"
                }
            ], 
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-west-1:947191746595:loadbalancer/app/api-test/0af98b76b2b99307", 
            "Port": 8080, 
            "ListenerArn": "arn:aws:elasticloadbalancing:eu-west-1:947191746595:listener/app/api-test/0af98b76b2b99307/c5ab6c413ae29a1b"
        }
    ]
}

Вот так это всё выглядит в панели управления:

elb_1

elb_2

И проверяем:

$ curl api-test-607244287.eu-west-1.elb.amazonaws.com/health
{"description":"Spring Cloud Eureka Discovery Client","status":"UP"}

Готово.