AWS-CDKでWordPress用スタックを立ち上げる方法

今まではそんなに頻繁に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化用)

だけで良いかと。
必要なものが出てきたら随時追加という形を取ります。

wordpress構成

作った

スタックの分割がいまいちですが。。

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

タイトルとURLをコピーしました