AWS Route53フェイルオーバー

  • 2019.11.14
  • AWS
AWS Route53フェイルオーバー

はじめに

AWSのDNSサービス Route53を使ったフェイルオーバーを試してみたいと思います。テストのために無料のドメインサービスも利用します。

最初、ELBを使った冗長化と何が違うのか迷っていましたが、次のようなことだとわかりました。

ELBによる冗長化は複数のEC2インスタンスのうち一部が障害によってダウンしてしまった場合に、生きているEC2インスタンスのみに振り分けるようにする。Route53を使用したフェイルオーバーの場合は、例えばEC2インスタンス上のWEBサービスがダウンしたらS3上の静的ページを表示するなど、そういった用途に使うことができます。

無料ドメインを取得する

DNSフェイルオーバーを行うには、Route53を使用します。ここでドメインを登録する必要があるのですが、今回はテストですので、

無料ドメインを発行してくれるサービスFreenomを使用します。

ここのNameserverを取得するためにAWS の Route53を作成します。

AWS Route53 作成

取得するドメイン名を入力し、[作成] をクリックします。

NSレコードが4件作成されます。

Freenomの続き

と、ここへきてどうも私のgmailのメールアドレスでは購入できないようで、Googleアカウントでログインすることにしました。そのため、またやり直しです。

その後、次のエラーが表示されて購入できません。。。

しばらく時間を置いてから試してみます。

別の回線に切り替えてみたら購入できました。

残りのNameServerを追加していきます。

メインサイト (EC2 インスタンス)構築

EC2インスタンスにApacheを入れてメインサイトを構築します。

{
    "ImageId": "ami-0064e711cbc7a825e",
    "InstanceType": "t2.micro",
    "KeyName": "myawskey-tokyo",
    "MaxCount": 1,
    "SecurityGroupIds": [
        "sg-04b4e7642a8fc6efe"
    ],
    "SubnetId": "subnet-0b99e52d17314979d"
}
$ aws ec2 run-instances --cli-input-json file://ec2-amazonlinux.json
$ ssh -i $key ec2-user@ec2-hoge.ap-northeast-1.compute.amazonaws.com
[ec2-user@ip-hoge ~] sudo yum install httpd
[ec2-user@ip-hoge ~] sudo systemctl start httpd

メインページを作成しておきます。

$ sudo vi /var/www/html/index.html
<html>
<head><title>Test Page</title></head>
<body>
<h1 style="background-color:green;color:white;padding:5px;">
Test Main Page!
</h1>
<span style="font-size:60px;line-height:30px;">
□■□<br>
□□■<br>
■■■
</span>
</body>
</html>

これでブラウザでアクセスするとTest Main Pageが表示されます。

メインサイトのHTTPS化

ELBを使用してメインサイトをHTTPS化します。構成としてはつぎのようになります。

ACMから証明書発行

次のコマンドで証明書を発行できます。

$ aws acm request-certificate --domain-name waku-test01.tk --validation-method DNS --idempotency-token 123456 --options CertificateTransparencyLoggingPreference=DISABLED
{
    "CertificateArn": "arn:aws:acm:ap-northeast-1:281230433728:certificate/6ddf6b39-4bba-43ca-b22a-3a694598b97f"
}

$ cert_arn=arn:aws:acm:ap-northeast-1:281230433728:certificate/6ddf6b39-4bba-43ca-b22a-3a694598b97f

DNS検証用にDNSレコードを取得しておきます。

$ aws acm describe-certificate --certificate-arn $cert_arn
{
    "Certificate": {
        "CertificateArn": "arn:aws:acm:ap-northeast-1:281230433728:certificate/6ddf6b39-4bba-43ca-b22a-3a694598b97f",
        "DomainName": "waku-test01.tk",
        "SubjectAlternativeNames": [
            "waku-test01.tk"
        ],
        "DomainValidationOptions": [
            {
                "DomainName": "waku-test01.tk",
                "ValidationDomain": "waku-test01.tk",
                "ValidationStatus": "PENDING_VALIDATION",
                "ResourceRecord": {
                    "Name": "_8a4196262f13fe9ee3a5c8f25cd67b8a.waku-test01.tk.",
                    "Type": "CNAME",
                    "Value": "_92f2c311aa108a201bbf34db9c1d7ec7.kirrbxfjtw.acm-validations.aws."
                },
                "ValidationMethod": "DNS"
            }
        ],
        "Subject": "CN=waku-test01.tk",
        "Issuer": "Amazon",
        "CreatedAt": 1573607022.0,
        "Status": "PENDING_VALIDATION",
        "KeyAlgorithm": "RSA-2048",
        "SignatureAlgorithm": "SHA256WITHRSA",
        "InUseBy": [],
        "Type": "AMAZON_ISSUED",
        "KeyUsages": [],
        "ExtendedKeyUsages": [],
        "RenewalEligibility": "INELIGIBLE",
        "Options": {
            "CertificateTransparencyLoggingPreference": "DISABLED"
        }
    }
}

ResourceRecordの Name, Type, value を Route53に追加します。

まずは現在のDNS情報を確認します。

$ aws route53 list-hosted-zones
{
    "HostedZones": [
        {
            "Id": "/hostedzone/Z2BEP1Z1Q3YA66",
            "Name": "waku-test01.tk.",
            "CallerReference": "FF2550B0-089F-264E-AF04-0DADB8C6F6B7",
            "Config": {
                "PrivateZone": false
            },
            "ResourceRecordSetCount": 2
        }
    ]
}

DNSレコード追加用のJSONファイルを作成しておきます。

{
            "Comment": "waku-test01.tk Validation ",
            "Changes": [{
            "Action": "CREATE",
                        "ResourceRecordSet": {
                                    "Name": "_8a4196262f13fe9ee3a5c8f25cd67b8a.www.waku-test01.tk.",
                                    "Type": "CNAME",
                                    "TTL": 300,
                                 "ResourceRecords": [{ "Value": "_92f2c311aa108a201bbf34db9c1d7ec7.kirrbxfjtw.acm-validations.aws."}]
}}]
}

次のコマンドでDNSレコードを追加します。

$ aws route53 change-resource-record-sets --hosted-zone-id /hostedzone/Z2BEP1Z1Q3YA66 --change-batch file://dns-record.json
{
    "ChangeInfo": {
        "Id": "/change/C1VYU66AJ0YAEA",
        "Status": "PENDING",
        "SubmittedAt": "2019-11-13T02:55:15.394Z",
        "Comment": "waku-test01.tk Validation "
    }
}

しばらくして PENDING が SUCCESS に変われば検証OKです。

ELB作成

SSL化するためELBを作成します。

SSLを通過させるためのセキュリティグループを作成します。

$ aws ec2 create-security-group --group-name ElbSSLGroup --description "Elb SSL SecurityGroup" --vpc-id $vpc_id
{
    "GroupId": "sg-047e60bba0349e2c9"
}
$ security_id=sg-047e60bba0349e2c9
$ aws ec2 authorize-security-group-ingress --group-id $security_id --protocol tcp --port 443 --cidr 0.0.0.0/0

ELBに所属させるサブネットの情報をしらべておきます。

$ aws ec2 describe-subnets --filter Name=vpc-id,Values=$vpc_id
{
    "Subnets": [
        {
            "AvailabilityZone": "ap-northeast-1c",
            "AvailabilityZoneId": "apne1-az1",
            "AvailableIpAddressCount": 251,
            "CidrBlock": "10.1.1.0/24",
            "DefaultForAz": false,
            "MapPublicIpOnLaunch": true,
            "State": "available",
            "SubnetId": "subnet-0f2edab09ccc98c0f",
            "VpcId": "vpc-04e72e6413b624d29",
            "OwnerId": "281230433728",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "SubnetArn": "arn:aws:ec2:ap-northeast-1:281230433728:subnet/subnet-0f2edab09ccc98c0f"
        },
        {
            "AvailabilityZone": "ap-northeast-1a",
            "AvailabilityZoneId": "apne1-az4",
            "AvailableIpAddressCount": 249,
            "CidrBlock": "10.1.0.0/24",
            "DefaultForAz": false,
            "MapPublicIpOnLaunch": true,
            "State": "available",
            "SubnetId": "subnet-0b99e52d17314979d",
            "VpcId": "vpc-04e72e6413b624d29",
            "OwnerId": "281230433728",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "SubnetArn": "arn:aws:ec2:ap-northeast-1:281230433728:subnet/subnet-0b99e52d17314979d"
        }
    ]
}

ELBを作成します。

$ aws elbv2 create-load-balancer --name elb-ssl --subnets subnet-0f2edab09ccc98c0f subnet-0b99e52d17314979d --security-groups $security_id
{
    "LoadBalancers": [
        {
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:loadbalancer/app/elb-ssl/53f4f24d85c04821",
            "DNSName": "elb-ssl-hoge.ap-northeast-1.elb.amazonaws.com",
            "CanonicalHostedZoneId": "Z14GRHDCWA56QT",
            "CreatedTime": "2019-11-13T01:36:19.400Z",
            "LoadBalancerName": "elb-ssl",
            "Scheme": "internet-facing",
            "VpcId": "vpc-04e72e6413b624d29",
            "State": {
                "Code": "provisioning"
            },
            "Type": "application",
            "AvailabilityZones": [
                {
                    "ZoneName": "ap-northeast-1a",
                    "SubnetId": "subnet-0b99e52d17314979d",
                    "LoadBalancerAddresses": [
                        {}
                    ]
                },
                {
                    "ZoneName": "ap-northeast-1c",
                    "SubnetId": "subnet-0f2edab09ccc98c0f",
                    "LoadBalancerAddresses": [
                        {}
                    ]
                }
            ],
            "SecurityGroups": [
                "sg-047e60bba0349e2c9"
            ],
            "IpAddressType": "ipv4"
        }
    ]
}

$ elb_arn=arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:loadbalancer/app/elb-ssl/53f4f24d85c04821

ターゲットグループを作成します。

$ aws elbv2 create-target-group --name elb-ssl-target --protocol HTTP --port 80 --vpc-id $vpc_id
{
    "TargetGroups": [
        {
            "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:targetgroup/elb-ssl-target/4273e5aea70ecfe3",
            "TargetGroupName": "elb-ssl-target",
            "Protocol": "HTTP",
            "Port": 80,
            "VpcId": "vpc-04e72e6413b624d29",
            "HealthCheckProtocol": "HTTP",
            "HealthCheckPort": "traffic-port",
            "HealthCheckEnabled": true,
            "HealthCheckIntervalSeconds": 30,
            "HealthCheckTimeoutSeconds": 5,
            "HealthyThresholdCount": 5,
            "UnhealthyThresholdCount": 2,
            "HealthCheckPath": "/",
            "Matcher": {
                "HttpCode": "200"
            },
            "TargetType": "instance"
        }
    ]
}

$ target_arn=arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:targetgroup/elb-ssl-target/4273e5aea70ecfe3

ターゲットグループにEC2インスタンスを登録します。

$ aws elbv2 register-targets --target-group-arn $target_arn --targets Id=$ins_id

リスナーを作成します。

$ aws elbv2 create-listener --load-balancer-arn $elb_arn --protocol HTTPS --port 443 --certificates CertificateArn=$cert_arn --default-actions Type=forward,TargetGroupArn=$target_arn
{
    "Listeners": [
        {
            "ListenerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:listener/app/elb-ssl/53f4f24d85c04821/54c40c75ddefb147",
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:loadbalancer/app/elb-ssl/53f4f24d85c04821",
            "Port": 443,
            "Protocol": "HTTPS",
            "Certificates": [
                {
                    "CertificateArn": "arn:aws:acm:ap-northeast-1:281230433728:certificate/6ddf6b39-4bba-43ca-b22a-3a694598b97f"
                }
            ],
            "SslPolicy": "ELBSecurityPolicy-2016-08",
            "DefaultActions": [
                {
                    "Type": "forward",
                    "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:targetgroup/elb-ssl-target/4273e5aea70ecfe3"
                }
            ]
        }
    ]
}

ここでブラウザでhttps接続してTestPageが表示されるか確認してみます。

接続できました。

S3で障害発生時のエラーページを構築

次にS3で障害発生時のエラーページを構築していきます。
参考サイト: aws-cliを使ってS3で静的コンテンツを公開する

このとき気をつけなくてはいけないのが、ドメイン名と同じ名前のバケットを作成する必要があるということです。

$ aws s3 mb s3://waku-test01.tk
make_bucket: waku-test01.tk

エラー用のindex.htmlを作成します。

<html>
  <body>
    Error Page!
  </body>
</html>

ファイルを同期します。

$ aws s3 sync . s3://waku-test01.tk
upload: ./index.html to s3://waku-test01.tk/index.html

static web hosting を有効にします。

$ aws s3 website s3://waku-test01.tk --index-document index.html

権限設定用のJSONファイルを作成します。

{
    "Version": "2012-10-17",
    "Id": "PublicRead",
    "Statement": [
        {
            "Sid": "ReadAccess",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::waku-test01.tk/*"
        }
    ]
}

ポリシーを適用します。

$ aws s3api put-bucket-policy --bucket waku-test01.tk --policy file://s3-public.json

ブラウザで表示されることを確認します。

この時のURLは、http://waku-test01.tk.s3-website-ap-northeast-1.amazonaws.com です。

S3障害ページのSSL対応

ACMから証明書を発行

またACMから証明書を発行します。最初に発行したじゃないかと思われるかもしれませんが、今回のはS3をSSL対応化するためのもので、リージョンをバージニア北部にします。CloudFrontで使用するためです。

$ aws acm request-certificate --domain-name waku-test01.tk --validation-method DNS --idempotency-token 654321 --options CertificateTransparencyLoggingPreference=DISABLED --region us-east-1
{
    "CertificateArn": "arn:aws:acm:us-east-1:281230433728:certificate/429693a4-e66c-4cc0-8707-b369914c993b"
}

DNS検証の設定をします。

$ aws acm describe-certificate --certificate-arn $cert_arn2 --region us-east-1
{
    "Certificate": {
        "CertificateArn": "arn:aws:acm:us-east-1:281230433728:certificate/429693a4-e66c-4cc0-8707-b369914c993b",
        "DomainName": "waku-test01.tk",
        "SubjectAlternativeNames": [
            "waku-test01.tk"
        ],
        "DomainValidationOptions": [
            {
                "DomainName": "waku-test01.tk",
                "ValidationDomain": "waku-test01.tk",
                "ValidationStatus": "PENDING_VALIDATION",
                "ResourceRecord": {
                    "Name": "_a696cdc6b443c52dafa7a20d6580bb51.waku-test01.tk.",
                    "Type": "CNAME",
                    "Value": "_63247296075808464f7f305bc524e325.kirrbxfjtw.acm-validations.aws."
                },
                "ValidationMethod": "DNS"
            }
        ],
        "Subject": "CN=waku-test01.tk",
        "Issuer": "Amazon",
        "CreatedAt": 1573627212.0,
        "Status": "PENDING_VALIDATION",
        "KeyAlgorithm": "RSA-2048",
        "SignatureAlgorithm": "SHA256WITHRSA",
        "InUseBy": [],
        "Type": "AMAZON_ISSUED",
        "KeyUsages": [],
        "ExtendedKeyUsages": [],
        "RenewalEligibility": "INELIGIBLE",
        "Options": {
            "CertificateTransparencyLoggingPreference": "DISABLED"
        }
    }
}
{
    "Comment": "waku-test01.tk Validation 2",
    "Changes": [{
        "Action": "CREATE",
        "ResourceRecordSet": {
            "Name": "_a696cdc6b443c52dafa7a20d6580bb51.waku-test01.tk.",
            "Type": "CNAME",
            "TTL": 300,
            "ResourceRecords": [{ "Value": "_63247296075808464f7f305bc524e325.kirrbxfjtw.acm-validations.aws."}]
}}]
}
$ aws route53 change-resource-record-sets --hosted-zone-id /hostedzone/Z2BEP1Z1Q3YA66 --change-batch file://dns-record2.json
{
    "ChangeInfo": {
        "Id": "/change/C31M5I2KA8LY6P",
        "Status": "PENDING",
        "SubmittedAt": "2019-11-13T06:49:06.136Z",
        "Comment": "waku-test01.tk Validation 2"
    }
}

CloudFrontの設定

次のようなJSONファイルを作成します。

参考URL: [AWS] CloudFront Distributionをコマンドライン(CLI)から作成する

{
    "CallerReference": "2019-11-13:16:3",
    "DefaultRootObject": "index.html",
    "Aliases": {
      "Quantity": 1,
      "Items": ["waku-test01.tk"]
    },    
    "Origins": {
        "Quantity": 1,
        "Items": [
            {
                "Id": "S3-waku-test01.tk",
                "DomainName": "waku-test01.tk.s3.amazonaws.com",
                "S3OriginConfig": {
                    "OriginAccessIdentity": ""
                }		
            }
        ]
    },
    "DefaultCacheBehavior": {
        "TargetOriginId": "S3-waku-test01.tk",
        "ForwardedValues": {
            "QueryString": false,
            "Cookies": {
                "Forward": "none"
            }
        },
        "TrustedSigners": {
            "Enabled": false,
            "Quantity": 0
        },	    
        "ViewerProtocolPolicy": "redirect-to-https",
	"MinTTL": 0
    },
    "ViewerCertificate": {
	"CloudFrontDefaultCertificate": false,
        "ACMCertificateArn": "arn:aws:acm:us-east-1:281230433728:certificate/429693a4-e66c-4cc0-8707-b369914c993b",
	"SSLSupportMethod": "sni-only"
    },
    "Enabled": true,
    "Comment": "S3 web"
}

次のコマンドでCloudFrontのDistributionを作成します。

$ aws cloudfront create-distribution --distribution-config file://cloud-front.json

DNSフェイルオーバーの設定

いよいよRoute53を使ってフェイルオーバーの設定をしていきます。ここまで長かったです。

まずはDNSに設定するための情報を取得しておきます。まずはメインサイトとなるELBの設定。

$ aws elbv2 describe-load-balancers
{
    "LoadBalancers": [
        {
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:281230433728:loadbalancer/app/elb-ssl/53f4f24d85c04821",
            "DNSName": "elb-ssl-hoge.ap-northeast-1.elb.amazonaws.com",
            "CanonicalHostedZoneId": "Z14GRHDCWA56QT",
            "CreatedTime": "2019-11-13T01:36:19.400Z",
            "LoadBalancerName": "elb-ssl",
            "Scheme": "internet-facing",
            "VpcId": "vpc-04e72e6413b624d29",
            "State": {
                "Code": "active"
            },
            "Type": "application",
            "AvailabilityZones": [
                {
                    "ZoneName": "ap-northeast-1a",
                    "SubnetId": "subnet-0b99e52d17314979d",
                    "LoadBalancerAddresses": [
                        {}
                    ]
                },
                {
                    "ZoneName": "ap-northeast-1c",
                    "SubnetId": "subnet-0f2edab09ccc98c0f",
                    "LoadBalancerAddresses": [
                        {}
                    ]
                }
            ],
            "SecurityGroups": [
                "sg-047e60bba0349e2c9"
            ],
            "IpAddressType": "ipv4"
        }
    ]
}

ここで必要となるのは、CanonicalHostedZoneIdとDNSNameです。

もう一つ、CloudFrontの設定を確認します。

$ aws cloudfront list-distributions
{
    "DistributionList": {
        "Items": [
            {
                "Id": "E18OSMUYQKYFFB",
                "ARN": "arn:aws:cloudfront::281230433728:distribution/E18OSMUYQKYFFB",
                "Status": "Deployed",
                "LastModifiedTime": "2019-11-13T08:07:51.690Z",
                "DomainName": "d3esnohogecloudfront.net",
                "Aliases": {
                    "Quantity": 1,
                    "Items": [
                        "waku-test01.tk"
                    ]
                },

(途中略)
                "AliasICPRecordals": [
                    {
                        "CNAME": "waku-test01.tk",
                        "ICPRecordalStatus": "APPROVED"
                    }
                ]
            }
        ]
    }
}

ここで必要となるのはCloudFrontへアクセスするためのDomainNameです。

DNSレコード設定用のJSONファイルを作成します。

補足として、ELBのDNSNameの先頭にはdualstack.を付与します。
参考URL: ELB & Route53 Aliasレコード (ipv6も!)

また、CloudFrontのHostedZoneIdは Z2FDTNDATAQYW2 で固定となります。

今回はELBが正常に稼働しているかヘルスチェックしてDNSフェイルオーバーを行います。ELBの場合はヘルスチェックを作成する必要はなく、EvaluateTargetHealthで正常性を確認します。

{
    "Comment": "waku-test01.tk failover",
    "Changes": [{
        "Action": "CREATE",
        "ResourceRecordSet": {
            "Name": "waku-test01.tk.",
            "Type": "A",
            "SetIdentifier": "Primary",
	    "Failover": "PRIMARY",
	    "AliasTarget": {
		"HostedZoneId": "Z14GRHDCWA56QT",
		"DNSName": "dualstack.elb-ssl-hoge.ap-northeast-1.elb.amazonaws.com.",
		"EvaluateTargetHealth": true
	    }
	}
    },
		{
		    "Action": "CREATE",
		    "ResourceRecordSet": {
			    "Name": "waku-test01.tk.",
			    "Type": "A",
			    "SetIdentifier": "Secondary",
			    "Failover": "SECONDARY",
			    "AliasTarget": {
				"HostedZoneId": "Z2FDTNDATAQYW2",
				"DNSName": "d3esnohogecloudfront.net.",
				"EvaluateTargetHealth": false
			    }
		    }
		}
	       ]
}

次のコマンドでDNSレコードの追加をします。

$ aws route53 list-hosted-zones
{
    "HostedZones": [
        {
            "Id": "/hostedzone/Z2BEP1Z1Q3YA66",
            "Name": "waku-test01.tk.",
            "CallerReference": "FF2550B0-089F-264E-AF04-0DADB8C6F6B7",
            "Config": {
                "PrivateZone": false
            },
            "ResourceRecordSetCount": 6
        }
    ]
}

$ aws route53 change-resource-record-sets --hosted-zone-id /hostedzone/Z2BEP1Z1Q3YA66 --change-batch file://dns-record3.json
{
    "ChangeInfo": {
        "Id": "/change/C3OQCWK2N78PP9",
        "Status": "PENDING",
        "SubmittedAt": "2019-11-14T01:54:49.992Z",
        "Comment": "waku-test01.tk failover"
    }
}

動作確認

取得したドメインで正常にメインページが表示されることを確認します。

EC2インスタンスにssh接続し、Apacheを停止してみます。

$ ssh -i $key ec2-user@ec2-hoge.ap-northeast-1.compute.amazonaws.com
Last login: Thu Nov 14 01:07:23 2019 from hoge

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
13 package(s) needed for security, out of 26 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-1-0-62 ~]$ sudo systemctl stop httpd

しばらくの間、このエラーとなってしまいました。1〜2分待つとエラーページが表示されました。

その後も何度か試してみましたが、切り替わるまでは若干時間がかかるようです。ヘルスチェックのチューニングが必要なのかもしれませんが、機能としてはこれで設定できましたので、今回はここまでにしたいと思います。