Имеется Application Load Balancer, за которым находятся три хоста: два фронтенда, и один master.
Задачи следующие:
- добавить редиректы HTTP => HTTPS
- добавить редиректы доменов dev.admin.dme.example.com на master-хост, а остальных – на frontend-хосты
- добавить редиректы www <=> non-www
Содержание
Редирект HTTP на HTTPS
Тут столкнулся с очередным “подарком” от AWS CloudFormation:
After checking our internal information, the newly released action “Redirects” and “Fixed Responses” [1] does not have support with the CloudFormation yet. We have received similar cases from other customers, and our internal team is working towards making this feature available to the customers as soon as possible. Unfortunately, as a support engineer, I do not have an ETA for this feature to be available for use to the customers.
При том, что в Terraform уже неделю как “завезли” эту возможность (такая же история была с cross-region peering для VPC – в Terraform опция region
есть, в CloudFormation – нет).
В общем – пока делаем руками, потом обновим CloudFormation шаблон.
У ресурса ALB есть два Listener-а – HTTP, и HTTPS.
HTTP Listener в CloudFormation сейчас выглядит так:
... "AlbHTTPListener": { "Type" : "AWS::ElasticLoadBalancingV2::Listener", "Properties" : { "DefaultActions": [{ "Type": "forward", "TargetGroupArn": { "Ref": "AppFrontTargetGroup" } }], "LoadBalancerArn" : { "Ref": "LoadBalancer" }, "Port" : 80, "Protocol" : "HTTP" } }, ...
Переходим в Console, и для HTTP-листенера редактируем Default action – удаляем правило Forward to dme-dev-front-target, и задаём Redirect to HTTPS:
Сохраняем:
Проверяем:
[simterm]
$ curl -LvI dev.dme.example.com ... * Connected to dev.dme.example.com (18.218.148.182) port 80 (#0) ... > Host: dev.dme.example.com ... < HTTP/1.1 301 Moved Permanently HTTP/1.1 301 Moved Permanently < Server: awselb/2.0 Server: awselb/2.0 ... < Location: https://dev.dme.example.com:443/ Location: https://dev.dme.example.com:443/ ... * Issue another request to this URL: 'https://dev.dme.example.com:443/' ... * Connected to dev.dme.example.com (52.14.15.235) port 443 (#1) ... * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 ... < HTTP/2 200 HTTP/2 200
[/simterm]
Запрос приходит по HTTP, попадает на HTTP Listener Load Balancer-а, который возвращает 301-й редирект на HTTPS, который перенаправляет запрос на HTTPS Listener этого же балансировщика.
Увы, т.к. автоматизация для этого решения не работает, а добавлять AWS Lambda только ради редиректов желания нет – то HTTP => HTTPS редирект в продакшене пока буду делать по старинке – на стороне NGINX.
Master и Front редиректы
Следующей задачей будет перенаправление трафика в зависимости от имени хоста.
Т.е. запросы типа dev.admin.dme.example.com – надо направлять на один хост за балансировщиком, а все остальные запросы – на другие хосты, фронтеды.
Для настройки редиректов у ALB есть два фильтра – host-header
и path-based
. Используем host-header
, и две отдельные target-группы – одна будет для master хоста, вторая – для фронтендов.
Таргет-группа в шаблоне CloudFormation для фронтов выглядит так:
"AppFrontTargetGroup" : { "Type" : "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties" : { "HealthCheckIntervalSeconds": 30, "HealthCheckProtocol": "HTTP", "HealthCheckTimeoutSeconds": 10, "HealthyThresholdCount": 4, "Matcher" : { "HttpCode" : "200" }, "Name": { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "front-target"] ] }, "Port": 80, "Protocol": "HTTP", "TargetGroupAttributes": [ { "Key": "deregistration_delay.timeout_seconds", "Value": "20" }, { "Key": "stickiness.enabled", "Value": true } ], "Targets": [ { "Id": {"Ref" : "Front1EC2Instance"}, "Port": 80 }, { "Id": {"Ref" : "Front2EC2Instance"}, "Port": 80 } ], "UnhealthyThresholdCount": 3, "VpcId": {"Ref" : "VPC"}, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "dme-front-targets"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } },
И для мастера – так:
"AppMasterTargetGroup" : { "Type" : "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties" : { "HealthCheckIntervalSeconds": 30, "HealthCheckProtocol": "HTTP", "HealthCheckTimeoutSeconds": 10, "HealthyThresholdCount": 4, "Matcher" : { "HttpCode" : "200" }, "Name": { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "master-target"] ] }, "Port": 80, "Protocol": "HTTP", "TargetGroupAttributes": [ { "Key": "deregistration_delay.timeout_seconds", "Value": "20" }, { "Key": "stickiness.enabled", "Value": true } ], "Targets": [ { "Id": {"Ref" : "MasterEC2Instance"}, "Port": 80 } ], "UnhealthyThresholdCount": 3, "VpcId": {"Ref" : "VPC"}, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "dme-master-targets"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } },
В блоке Targets:
... "Targets": [ { "Id": {"Ref" : "MasterEC2Instance"}, "Port": 80 } ], ...
Перечисляем инстансы, которые будут подключены этой таргет-группе.
Далее – настраиваем фильтр на ALB Listener, используя AWS::ElasticLoadBalancingV2::ListenerRule
:
"HTTPSListenerRule": { "Type": "AWS::ElasticLoadBalancingV2::ListenerRule", "Properties": { "Actions": [{ "Type": "forward", "TargetGroupArn": { "Ref": "AppMasterTargetGroup" } }], "Conditions": [{ "Field": "host-header", "Values": [ "dev.admin.dme.example.com" ] }], "ListenerArn": { "Ref": "AlbHTTPSListener" }, "Priority": 1 } },
Тут в Actions
указываем редирект на AppMasterTargetGroup, в которую включён Master-хост, а в Conditions
используем host-header
, в котором проверяем значение, и если оно == dev.admin.dme.example.com – то выполняем Actions:Type:forward
на мастер-таргет.
Обновляем стек:
Проверяем – добавляем на Мастер хосте:
[simterm]
root@ip-10-0-7-43:/home/admin# echo "This is Master" > /var/www/html/index.nginx-debian.html
[/simterm]
И на обоих фронтах:
[simterm]
root@ip-10-0-7-12:/home/admin# echo "This is Front1" > /var/www/html/index.nginx-debian.html root@ip-10-0-7-28:/home/admin# echo "This is Front2" > /var/www/html/index.nginx-debian.html
[/simterm]
Выполняем запрос к dev.admin.dme.example.com:
[simterm]
$ curl -L dev.admin.dme.example.com This is Master
[/simterm]
И просто к Dev:
[simterm]
$ curl -L dev.dme.example.com This is Front2 $ curl -L dev.dme.example.com This is Front1
[/simterm]
ОК – всё работает.
Редиректы www <=> non-www
Тут делаем аналогично редиректу на HTTPS – в настройках HTTPS Listener добавляем условие редиректа по host-header
: только выбираем Custom host, path, query:
Опять-таки, т.к. CloudFormation такие редиректы пока не поддерживает – то в продакшен будем выходить с редиректами на стороне NGINX, но сама по себе опция полезна, и потом перенесу редиректы на ALB.
UPD: зачем, если есть CNAME
на DNS/Route53?
Проверяем:
[simterm]
$ curl -LvI https://www.dev.dme.example.com ... < HTTP/2 301 HTTP/2 301 < server: awselb/2.0 server: awselb/2.0 < date: Thu, 30 Aug 2018 10:01:51 GMT date: Thu, 30 Aug 2018 10:01:51 GMT < content-type: text/html content-type: text/html < content-length: 150 content-length: 150 < location: https://dev.dme.example.com:443/ location: https://dev.dme.example.com:443/ ... < HTTP/2 200 HTTP/2 200
[/simterm]
Готово.