1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

WAF+ALB+EC2+RDSの構成をテンプレート化してみた

Last updated at Posted at 2024-03-24

はじめに

本記事では、AWSで使用頻度が高いアーキテクチャーである、
WAF+ALB+EC2+RDSの構成をCDKでテンプレートしました。

テンプレート化したCDKを使用してAWSのインフラ環境を自動構築することで、
構築時間を短縮することができます。

記事の後半に、変更頻度が高いパラメータを各サービス毎にまとめましたので、
本記事で紹介したCDKを参考にして、パラメータ変更する際に役立てていただけると幸いです。

インフラの仕事をし続けて困ったこと

 WAF+ALB+EC2+RDSの構成は、よく案件で構築することが多いのですが、案件が開始するたびに、ゼロからコンソールを使用して手作業で構築してました。手作業で実施していると、動作確認中に1回以上はミスが見つかり、調査とパラメータ修正に時間を要します。
 今回のブログを通じて、AWSのテンプレートをCDK作成することで、手作業で構築する手間を少なくし、一定の動作を保証した環境を作成します。

構成図とコードの紹介

WAF+ALB+EC2+RDSの構成は、下記の構成図になります。
cdk.drawio.png

こちらを、CDKでコード化したものが、下記の「waf-alb-ec2-rds-stack.ts」になります。

waf-alb-ec2-rds-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';


//[PARAMETER] VPC
let VPC_CIDR = '20.0.0.0/16';
let NATGW = 1; //NATGatewayの個数

//[PARAMETER] EC2
let EC2_INSTANCE_SPEC = 't3.micro';

//[PARAMETER] AutoScaling
let ASG_DESI = 1;
let ASG_MAX = 2;
let ASG_MIN = 1;

//[PARAMETER] RDS
let RDS_INSTANCE_CLASS = ec2.InstanceClass.T2;
let RDS_INSTANCE_SIZE = ec2.InstanceSize.SMALL;
let MULTI_AZ =  true;


const app = new cdk.App();
const stack = new cdk.Stack(app, 'MyStack');

export class AwsCdkEc2TamplateStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    //VPC, Subnet, NAT Gatewayの作成
    const vpc = new ec2.Vpc(this, 'MyVpc', {
      cidr: VPC_CIDR,
      natGateways: NATGW, 
      availabilityZones: ['ap-northeast-1a', 'ap-northeast-1c'], 
      subnetConfiguration: [
        {
          subnetType: ec2.SubnetType.PUBLIC,
          name: 'Public',
          cidrMask: 24,
        },
        {
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
          name: 'Private',
          cidrMask: 24,
        },
        {
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
          name: 'DB',
          cidrMask: 24,
        },
      ],
    });
  
    // ALB用のセキュリティグループを作成
    const albSecurityGroup = new ec2.SecurityGroup(this, 'ALBSecurityGroup', {
      vpc,
      allowAllOutbound: true,    
    });
  
    // ALBを作成
    const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
      vpc,
      internetFacing: true,
      securityGroup: albSecurityGroup,
    });
  
    // ALBアクセスログ用バケットを作成
    const logBucket = new s3.Bucket(this, "LogBucket", {});
  
    // ALBアクセスログ設定
    alb.logAccessLogs(logBucket);
  
    // EC2インスタンス用のセキュリティグループを作成
    const ec2SecurityGroup = new ec2.SecurityGroup(this, 'EC2SecurityGroup', {
      vpc,
      allowAllOutbound: true,    
    });
    ec2SecurityGroup.addIngressRule(albSecurityGroup, ec2.Port.tcp(80));

    //DBのサブネットグループを作成
    const rdsSecuriyGroup = new ec2.SecurityGroup(this, 'RDSSecuriyGroup', {
      vpc,
      allowAllOutbound: true, 
    });
    rdsSecuriyGroup.addIngressRule(ec2SecurityGroup, ec2.Port.tcp(3306));
  
    
    const userDataEC2 = ec2.UserData.forLinux({ shebang: '#!/bin/bash' })
    userDataEC2.addCommands(
      'yum update -y',
      'yum install -y httpd',
      'systemctl start httpd',
      'systemctl enable httpd',
      'touch /var/www/html/index.html',
      'echo "AWS TEST" | tee -a /var/www/html/index.html'
    )
    

    const asg = new autoscaling.AutoScalingGroup(this, "Ec2Autoscaling", {
        vpc,
        vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
        instanceType: new ec2.InstanceType(EC2_INSTANCE_SPEC),
        machineImage: ec2.MachineImage.latestAmazonLinux2023(),
        desiredCapacity: ASG_DESI,
        maxCapacity: ASG_MAX,
        minCapacity: ASG_MIN,
        userData: userDataEC2,
        securityGroup: ec2SecurityGroup,
        associatePublicIpAddress: false,
        ssmSessionPermissions: true,
        
    });
    cdk.Tags.of(asg).add('Mode', 'autoscaling');//Auto Scaling Group Tag

    
    const targetGroup = new cdk.aws_elasticloadbalancingv2.ApplicationTargetGroup(
      this,
      "TargetGroup",
      {
        vpc: vpc,
        port: 80,
        targetType: cdk.aws_elasticloadbalancingv2.TargetType.INSTANCE,
        targets: [asg],
        healthCheck: {
          path: '/',
        },
      }
    );

    //リスナー設定
    const listener = alb.addListener("Listener", {
      port: 80,
      protocol: cdk.aws_elasticloadbalancingv2.ApplicationProtocol.HTTP,
      defaultTargetGroups: [targetGroup],
    });

    listener.addTargetGroups('TargetGroup', {
      targetGroups: [targetGroup],
    });
    
  
    // RDSサブネットグループを作成
    const subnetGroup = new rds.SubnetGroup(this,'MySubnetGroup', {
      vpc,
      description: 'MySubnetGroup',
      subnetGroupName: 'MySubnetGroup',
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
       }
    });
  
    
    // DBインスタンス(RDS MySQL)を作成
    
    const dbServer = new rds.DatabaseInstance(this, "WordPressDB", {
      vpc,
      engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_31 }),
      instanceType: ec2.InstanceType.of(RDS_INSTANCE_CLASS, RDS_INSTANCE_SIZE),
      databaseName: "wordpress",
      multiAz: MULTI_AZ,
      subnetGroup: subnetGroup,
      securityGroups:  [rdsSecuriyGroup],
    });
    


    // WAF
    const webACL = new cdk.aws_wafv2.CfnWebACL(this,"MyWebACL-main",{
      defaultAction: {
        allow: {}
      },
      scope: "REGIONAL",
      visibilityConfig: {
        cloudWatchMetricsEnabled: false,
        metricName: "MyWebACL-metrics",
        sampledRequestsEnabled: true,
      },
      name: "MyWebACL-managed-1",
      rules: [
        {
          name: 'AWS-AWSManagedRulesCommonRuleSet',
          priority: 0,
          statement: {
            managedRuleGroupStatement: {
              name:'AWSManagedRulesCommonRuleSet',
              vendorName:'AWS',
              excludedRules: [
                {name: 'SizeRestrictions_BODY'},
                {name: 'NoUserAgent_HEADER'},
                {name: 'UserAgent_BadBots_HEADER'},
                {name: 'SizeRestrictions_QUERYSTRING'},
                {name: 'SizeRestrictions_Cookie_HEADER'},
                {name: 'SizeRestrictions_BODY'},
                {name: 'SizeRestrictions_URIPATH'},
                {name: 'EC2MetaDataSSRF_BODY'},
                {name: 'EC2MetaDataSSRF_COOKIE'},
                {name: 'EC2MetaDataSSRF_URIPATH'},
                {name: 'EC2MetaDataSSRF_QUERYARGUMENTS'},
                {name: 'GenericLFI_QUERYARGUMENTS'},
                {name: 'GenericLFI_URIPATH'},
                {name: 'GenericLFI_BODY'},
                {name: 'RestrictedExtensions_URIPATH'},
                {name: 'RestrictedExtensions_QUERYARGUMENTS'},
                {name: 'GenericRFI_QUERYARGUMENTS'},
                {name: 'GenericRFI_BODY'},
                {name: 'GenericRFI_URIPATH'},
                {name: 'CrossSiteScripting_COOKIE'},
                {name: 'CrossSiteScripting_QUERYARGUMENTS'},
                {name: 'CrossSiteScripting_BODY'},
                {name: 'CrossSiteScripting_URIPATH'}
              ]
            }
          },
          visibilityConfig: {
            cloudWatchMetricsEnabled: true,
            metricName:'AWS-AWSManagedRulesCommonRuleSet',
            sampledRequestsEnabled: true,
          },
          overrideAction: {
            none: {}
          },
        }
      ]
    });

    const wafPolicy = new wafv2.CfnWebACLAssociation(this, 'MyWebACL-arn', {
      webAclArn: webACL.attrArn,
      resourceArn: alb.loadBalancerArn
    });

  }
}

パラメータの解説

サービスの設定において、変更頻度が高いパラメータを、
サービス毎に表を作成してにまとめました。

VPC

変数名 パラメータの概要
VPC_CIDR VPCのサイダーブロック 20.0.0.0/16
NATGW NATGatewayの個数 1

EC2

変数名 パラメータの概要
EC2_INSTANCE_TYPE EC2のインスタンスタイプ t3.micro

EC2 AutoScaing

変数名 パラメータの概要
ASG_DESIRED_CAPACITY 起動時に必要なインスタンス数 1
ASG_MAX インスタンスの最大数 2
ASG_MIN インスタンスの最小数 1

RDS

変数名 パラメータの概要
RDS_INSTANCE_CLASS RDSのインスタンスクラス ec2.InstanceClass.T2
RDS_INSTANCE_SIZE RDSのインスタンスサイズ ec2.InstanceSize.SMALL
MULTI_AZ マルチAZの有効 true

作成したCDKをAWS環境にデプロイ

AWSアカウントで初めてCDKを使用してデプロイする際は、下記のコマンドを使用して、
Bootstrapを作成します。(2回目以降のデプロイからは省略できます。)

cdk bootstrap

CDKからCloudFormationスタックを生成します。

cdk synth

リソースをデプロイします。

cdk deploy

不使用になった環境はcdk destoryで一括削除

無料アカウントで作っている場合、放置してるとお金がかかります。
CDKで作成した環境を消すときは、

cdk destroy

を実行することで、AWS上からすべて削除できます。消し忘れの心配もありません。

感想

使用頻度が高いAWS構成をCDKでテンプレート化しておくことで、構築工数の削減、調整頻度が高いパラメータの設計に集中することができ、品質向上を図ることができました。また、CDKによる環境構築は、自動で行われるため、構築中は他の作業を同時に進められます。これらのメリットは、オンプレでは味わえない、クラウド固有のいいところだど思いました。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?