今まではそんなに頻繁にAWSアカウント上にWordPressの環境を立ち上げるということはなかったんですが、立ち上げたり削除したりの頻度が上がってきたのでIaCでさくっと出来たらいいなということで、AWS-CDKを使ってやってみました。
半年ほど前にやった記録なので情報が古いかもしれません。
AWS-CDK
TypeScriptやPythonでAWSの構成を記述できるキットです。
その実態はCloudFormationのラッパーのようです。
開発版ということなので、APIがしょっちゅう変わります。
(この記事のソースは1.36.0を使っています)
詳細の説明はしませんが、ワークショップが用意されているのでおすすめです。
https://dev.classmethod.jp/articles/cdk-workshop-typescript/
作りたい構成
とりあえずWordPressを動かすのに最低限必要な、
- EC2
- RDS
- ALB (SSL化用)
だけで良いかと。
必要なものが出てきたら随時追加という形を取ります。
作った
スタックの分割がいまいちですが。。
VPC
VPCは他のリソースで利用されることが多いので、引き回せられるようにしておくと良いと思います。
クローズドのサブネットでIGWを作らないときは、SubnetType.ISOLATED
とするんだそうです。
import {Stack, Tag,Construct} from '@aws-cdk/core';
import {SubnetType, Vpc} from '@aws-cdk/aws-ec2';
import MyStackProps from './my-stack-props';
export default class NetWorkStack extends Stack {
public readonly vpc: Vpc;
constructor(scope: Construct, id: string, props: MyStackProps) {
super(scope, id, props);
this.vpc = new Vpc(this, 'Vpc', {
enableDnsHostnames: true,
enableDnsSupport: true,
natGateways: 0,
maxAzs: 2,
subnetConfiguration: [
{
name: 'Public',
subnetType: SubnetType.PUBLIC,
cidrMask: 24,
},
{
name: 'Isolated',
subnetType: SubnetType.ISOLATED,
cidrMask: 24
},
],
});
this.vpc.node.applyAspect(new Tag('Name', `${props.prefix}Vpc`));
}
}
Iam
EC2用のIamを作成しておきます。
主にSSMでのアクセス目的。
CodeDeployを使う場合もこちらにポリシーをアタッチする必要があります。
import { Stack, Construct } from "@aws-cdk/core"
import { IRole, Role, ServicePrincipal, ManagedPolicy } from "@aws-cdk/aws-iam";
import { MyStackProps } from ".";
export default class IamStack extends Stack {
public readonly instanceRole: IRole;
constructor(scope: Construct, id: string, props: MyStackProps) {
super(scope, id, props);
this.instanceRole = new Role(this, 'IamRoleInstance', {
roleName: `${props.prefix}InstanceRole`,
assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
],
});
}
}
Security Group
今回立てるサーバーは3台なので、3台分のSGを作成します。
import { Stack, Construct } from "@aws-cdk/core";
import { MyStackProps } from ".";
import { IVpc, SecurityGroup, Peer, Port } from "@aws-cdk/aws-ec2";
interface SecurityGroupStackProps extends MyStackProps {
vpc: IVpc;
}
export default class SecurityGroupStack extends Stack {
public readonly albSecurityGroup: SecurityGroup;
public readonly ec2SecurityGroup: SecurityGroup;
public readonly rdsSecurityGroup: SecurityGroup;
constructor(scope: Construct, id: string, props: SecurityGroupStackProps) {
super(scope, id, props);
// ALBのSG
this.albSecurityGroup = new SecurityGroup(this, 'AlbSecurityGroup', {
allowAllOutbound: true,
securityGroupName: 'alb-sg',
vpc: props.vpc,
});
if (!!props.ssl) {
this.albSecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(443));
} else {
this.albSecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(80));
}
// EC2のSG
this.ec2SecurityGroup = new SecurityGroup(this, 'Ec2SecurityGroup', {
allowAllOutbound: true,
securityGroupName: 'ec2-sg',
vpc: props.vpc,
});
this.ec2SecurityGroup.addIngressRule(this.albSecurityGroup, Port.tcp(80));
// RDSのSG
this.rdsSecurityGroup = new SecurityGroup(this, 'RdsSecurityGroup', {
allowAllOutbound: true,
securityGroupName: 'rds-sg',
vpc: props.vpc,
});
this.rdsSecurityGroup.addIngressRule(this.ec2SecurityGroup, Port.tcp(3306));
this.rdsSecurityGroup.addEgressRule(this.ec2SecurityGroup, Port.tcp(3306));
}
}
ALB, EC2
インスタンスサイズは t2.micro
で充分でしょう。
SSL証明書を取得した時用にポートは80と443を選択できるようにしておきました。
import MyStackProps from "./my-stack-props";
import { Stack, Construct, Duration } from "@aws-cdk/core";
import { InstanceIdTarget } from '@aws-cdk/aws-elasticloadbalancingv2-targets';
import { ApplicationListener, ApplicationLoadBalancer } from '@aws-cdk/aws-elasticloadbalancingv2';
import { IVpc, Instance, InstanceType, InstanceClass, InstanceSize, AmazonLinuxImage, AmazonLinuxGeneration, SubnetType, SecurityGroup } from "@aws-cdk/aws-ec2";
import { IRole } from "@aws-cdk/aws-iam";
interface WebStackProps extends MyStackProps {
vpc: IVpc;
instanceRole: IRole;
albSecurityGroup: SecurityGroup;
ec2SecurityGroup: SecurityGroup;
}
export default class WebStack extends Stack {
public readonly instance: Instance;
public readonly alb: ApplicationLoadBalancer;
constructor(scope: Construct, id: string, props: WebStackProps) {
super(scope, id, props);
this.instance = new Instance(this, 'Ec2', {
instanceName: `${props.prefix}Ec2`,
vpc: props.vpc,
instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MICRO),
machineImage: new AmazonLinuxImage({
generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
}),
allowAllOutbound: true,
keyName: props.params.keyName,
vpcSubnets: {subnetType: SubnetType.PUBLIC},
role: props.instanceRole,
securityGroup: props.ec2SecurityGroup,
});
this.instance.instance.creditSpecification = {cpuCredits: 'standard'};
this.instance.instance.blockDeviceMappings = [{deviceName: '/dev/xvda', ebs: {volumeSize: 100, volumeType: 'gp2'}}];
this.alb = new ApplicationLoadBalancer(this, 'Alb', {
loadBalancerName: `${props.prefix}Alb`,
vpc: props.vpc,
internetFacing: true,
idleTimeout: Duration.minutes(10),
securityGroup: props.albSecurityGroup,
});
let appListener: ApplicationListener;
let targets: InstanceIdTarget[];
targets = [new InstanceIdTarget(this.instance.instanceId, 80)];
if (!!props.ssl) {
appListener = this.alb.addListener('AppListener', {port: 443});
appListener.addCertificates('AppCertificateArns', [{certificateArn: props.certificateArn}]);
} else {
appListener = this.alb.addListener('AppListener', {port: 80});
}
appListener.addTargets('AppTarget', {
targetGroupName: `${props.prefix}AppTarget`,
port: 80,
targets: targets,
healthCheck: {
path: '/health_check.php',
}
});
}
}
RDS
こちらも弱くてOK。
import { Stack, Construct, RemovalPolicy } from "@aws-cdk/core";
import { IVpc, IInstance, InstanceType, InstanceClass, InstanceSize, SubnetType, SecurityGroup } from "@aws-cdk/aws-ec2";
import { DatabaseInstance, DatabaseInstanceEngine} from "@aws-cdk/aws-rds";
import MyStackProps from "./my-stack-props";
interface RdsStackProps extends MyStackProps {
vpc: IVpc;
instance: IInstance;
rdsSecurityGroup: SecurityGroup;
}
export default class RdsStack extends Stack {
constructor(scope: Construct, id: string, props: RdsStackProps){
super(scope, id, props);
const instance: DatabaseInstance = new DatabaseInstance(this, 'Rds', {
instanceIdentifier: `${props.project}-${props.deployEnv}`,
databaseName: `${props.prefix}Db`,
engine: DatabaseInstanceEngine.MYSQL,
engineVersion: '5.7.30',
instanceClass: InstanceType.of(InstanceClass.T2, InstanceSize.MICRO),
masterUsername: 'pman',
vpc: props.vpc,
vpcPlacement: {subnetType: SubnetType.ISOLATED},
deletionProtection: false,
removalPolicy: RemovalPolicy.DESTROY,
securityGroups: [props.rdsSecurityGroup],
});
}
}
Github
一応、Githubにもあげてあります。
https://github.com/ShotaroMuraoka/wordpress-cdk-sample