目次
概要
AWS CloudFormationでインフラ構築を自動化する。
Route 53を使用したDR(Disaster Recovery)環境を構築する。
目的
- CloudFormationに慣れる
- 構築の自動化
- 構築の冪等性の確保
- 構築内容・手順の可視化(IaC)
今回作成する構成
作成したテンプレート
template01.yaml
スタック1、スタック2用のテンプレート。処理が全く同じなので同じテンプレートをを使用する。というか管理する資材(テンプレート)を増やしたくなかったため、マルチリージョン・マルチサイトに対応するよう設計した。
AWSTemplateFormatVersion: "2010-09-09" Description: TBD Parameters: # EC2 Instance Type InstanceType: Description: EC2 Instance type Type: String Default: t2.micro AllowedValues: - t1.micro - t2.nano - t2.micro - t2.small - t2.medium - t2.large ConstraintDescription: must be a valid EC2 instance type Mappings: RegionAmiMap: ap-northeast-1: hvm: ami-001f026eaf69770b4 ap-southeast-1: hvm: ami-0e8e39877665a7c92 RegionAzMap: ap-northeast-1: AZ: ap-northeast-1a ap-southeast-1: AZ: ap-southeast-1a RegionKeypairMap: ap-northeast-1: KeyPair: keypair-ap-northeast-1 ap-southeast-1: KeyPair: keypair-ap-southeast-1 RegionSiteMap: ap-northeast-1: Site: Primary ap-southeast-1: Site: Secondary Resources: # VPC VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 Tags: - Key: Application Value: !Ref AWS::StackId - Key: Name Value: !Sub ${AWS::StackName}-vpc # Subnet Subnet: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC CidrBlock: 10.0.0.0/24 AvailabilityZone: !FindInMap [RegionAzMap, !Ref AWS::Region, AZ] Tags: - Key: Application Value: !Ref AWS::StackId - Key: Name Value: !Sub ${AWS::StackName}-sbunet # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Application Value: !Ref AWS::StackId - Key: Name Value: !Sub ${AWS::StackName}-igw AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway # Route Table RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Application Value: !Ref AWS::StackId - Key: Name Value: !Sub ${AWS::StackName}-rtb # Route Route: Type: AWS::EC2::Route DependsOn: AttachGateway Properties: RouteTableId: !Ref RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway # Subnet Route Table Association SubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref Subnet RouteTableId: !Ref RouteTable # Elastic IP Address EIP: Type: AWS::EC2::EIP DependsOn: AttachGateway Properties: Domain: vpc InstanceId: !Ref EC2Instance Tags: - Key: Name Value: !Sub ${AWS::StackName}-eip AttachEIP: Type: AWS::EC2::EIPAssociation Properties: AllocationId: !GetAtt EIP.AllocationId InstanceId: !Ref EC2Instance # Instance Security Group InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref VPC GroupDescription: Accept SSH, HTTP SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub ${AWS::StackName}-securitygroup # EC2 Instance EC2Instance: Type: AWS::EC2::Instance DependsOn: AttachGateway Properties: ImageId: !FindInMap [RegionAmiMap, !Ref AWS::Region, hvm] AvailabilityZone: !FindInMap [RegionAzMap, !Ref AWS::Region, AZ] InstanceType: !Ref InstanceType KeyName: !FindInMap [RegionKeypairMap, !Ref AWS::Region, KeyPair] NetworkInterfaces: - GroupSet: - Ref: InstanceSecurityGroup AssociatePublicIpAddress: true DeviceIndex: 0 DeleteOnTermination: true SubnetId: !Ref Subnet UserData: Fn::Base64: !Sub - | #!/bin/bash -xe sudo su - yum -y install httpd cat <<EOF > /var/www/html/index.html <html><h1>Here is ${SITE}!</h1></html> EOF cat /var/www/html/index.html systemctl enable httpd.service --now systemctl status httpd.service curl http://localhost/ exit exit - { SITE: !FindInMap [RegionSiteMap, !Ref AWS::Region, Site] } Tags: - Key: Name Value: !Sub ${AWS::StackName}-instance Outputs: EIP: Value: !Ref EIP Export: Name: !Join ["", [!FindInMap [RegionSiteMap, !Ref AWS::Region, Site], EIP] ]
template02.yaml
スタック3用のテンプレート。
AWSTemplateFormatVersion: "2010-09-09" Description: TBD Parameters: # Hosted Zone HostedZoneName: Type: String Description: DNS Name to create Default: example.co.jp AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-) ConstraintDescription: must be a valid DNS zone name # Secondary Elastic IP SecondaryEIP: Type: String Description: Secondary Elastip IP Default: _SecondaryEIP_ Resources: # Hosted Zone HostedZone: Type: AWS::Route53::HostedZone Properties: Name: !Ref HostedZoneName HostedZoneTags: - Key: Name Value: !Sub ${AWS::StackName}-hostedzone # Primary DNS Record PrimaryDNSRecord: Type: AWS::Route53::RecordSet DependsOn: HostedZone Properties: HostedZoneId: !Ref HostedZone Comment: DNS Name for Elastic IP Address Name: !Join ["", [www, ".", !Ref HostedZoneName, "."] ] Type: A TTL: 60 ResourceRecords: - !ImportValue PrimaryEIP Failover: PRIMARY HealthCheckId: !Ref HealthCheck SetIdentifier: ec2-primary # Secondary DNS Record SecondaryDNSRecord: Type: AWS::Route53::RecordSet DependsOn: HostedZone Properties: HostedZoneId: !Ref HostedZone Comment: DNS Name for Elastic IP Address Name: !Join ["", [www, ".", !Ref HostedZoneName, "."] ] Type: A TTL: 60 ResourceRecords: #- !ImportValue SecondaryEIP - !Ref SecondaryEIP Failover: SECONDARY #HealthCheckId: !Ref HealthCheck SetIdentifier: ec2-secondary # HealthCheck HealthCheck: Type: AWS::Route53::HealthCheck Properties: HealthCheckConfig: Type: HTTP IPAddress: !ImportValue PrimaryEIP Port: 80 FullyQualifiedDomainName: !Join ["", [www, ".", !Ref HostedZoneName] ] ResourcePath: /index.html RequestInterval: 30 FailureThreshold: 3 HealthCheckTags: - Key: Name Value: !Sub ${AWS::StackName}-healthcheck
前提条件
- プライマリサイトとセカンダリサイトのリージョンは異なる場所にする。DRなので。
キーペア名 | リージョン |
---|---|
keypair-ap-norheast-1 | ap-norheast-1 |
keypair-ap-southeast-1 | ap-southeast-1 |
- ドメイン名はexample.co.jpとする。
- スタック1とスタック3は同一リージョンのCloudFormationで作成する。
- Elastic IPの値をスタック1からExportし、スタック3にImportするため。Export/Importは同一リージョン内でのみ可能。
- スタック2はリージョンが異なるためExport/Importができない。そのため、手動で値をコピーする方式とした。自動化の検討ポイントである。
スタックの作成
今までは1つのスタックだけだったため気にする必要がなかったが、今回は3つのスタックを順序性を意識して実行する。
- 1と2は順不同。並列実行も可能。
- 3の開始条件は「1の完了」かつ「2の完了」。順序性を意識するのはここだけ。
1.プライマリサイトの構築
- プライマリサイトのリージョン(ap-northeast-1)のCloudFormationでスタック1を作成する。
- テンプレートはtemplate01.yml。
- スタック名は任意。
2.セカンダリサイトの構築
- セカンダリサイトのリージョン(ap-southeast-1)のCloudFormationでスタック2を作成する。
- テンプレートはtemplate01.yml。
- スタック名は任意。
3.フェイルオーバールーティングの設定
- スタック2の出力より「SecondaryEIP」の値をコピーする。
- プライマリサイトのリージョン(ap-northeast-1)のCloudFormationでスタック3を作成する。
- テンプレートはtemplate02.yml。
- スタック名は任意。
- パラメータ「SecondaryEIP」にはコピーしておいた「SecondaryEIP」の値を入力する。
参考
これらをAWS CLIとLinuxコマンドで表したものを掲載しておく。
# 1.プライマリサイトの構築 aws cloudformation create-stack --stack-name stack01 --region ap-northeast-1 --template-body file://template01.yml aws cloudformation describe-stacks --stack-name stack01 --region ap-northeast-1 # 2.セカンダリサイトの構築 aws cloudformation create-stack --stack-name stack02 --region ap-southeast-1 --template-body file://template01.yml aws cloudformation describe-stacks --stack-name stack02 --region ap-southeast-1 # 3.フェイルオーバールーティングの設定 _SecondaryEIP=`aws cloudformation describe-stacks --stack-name stack02 --region ap-southeast-1 --output text | grep OUTPUTS | grep SecondaryEIP | awk '{print $4}'` sed "s/_SecondaryEIP_/${_SecondaryEIP}/g" template02.yml > template02_tmp.yml diff template02.yml template02_tmp.yml aws cloudformation create-stack --stack-name stack03 --region ap-northeast-1 --template-body file://template02_tmp.yml aws cloudformation describe-stacks --stack-name stack03 --region ap-northeast-1
スタック作成後の作業
3.動作確認
http://www.example.co.jpにアクセスし、以下を確認する。
- プライマリサイトの正常稼働時は「Here is Primary!」と表示されること。
- プライマリサイトを停止すると「Here is Secondary!」に表示が変わること。
- プライマリサイトを復旧すると「Here is Primary!」に表示が変わること。
あとがき
まだまだ自動化の余地がある。スタックが完了したら次のスタックを起動するなどの処理はAWSのサービスで実現可能なはず。