はじめに
本記事では、AWSで使用頻度が高いアーキテクチャーである、
WAF+ALB+EC2+RDSの構成をCDKでテンプレートしました。
テンプレート化したCDKを使用してAWSのインフラ環境を自動構築することで、
構築時間を短縮することができます。
記事の後半に、変更頻度が高いパラメータを各サービス毎にまとめましたので、
本記事で紹介したCDKを参考にして、パラメータ変更する際に役立てていただけると幸いです。
インフラの仕事をし続けて困ったこと
WAF+ALB+EC2+RDSの構成は、よく案件で構築することが多いのですが、案件が開始するたびに、ゼロからコンソールを使用して手作業で構築してました。手作業で実施していると、動作確認中に1回以上はミスが見つかり、調査とパラメータ修正に時間を要します。
今回のブログを通じて、AWSのテンプレートをCDK作成することで、手作業で構築する手間を少なくし、一定の動作を保証した環境を作成します。
構成図とコードの紹介
WAF+ALB+EC2+RDSの構成は、下記の構成図になります。
こちらを、CDKでコード化したものが、下記の「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による環境構築は、自動で行われるため、構築中は他の作業を同時に進められます。これらのメリットは、オンプレでは味わえない、クラウド固有のいいところだど思いました。