pulumiでAWSにWardPressの環境を構築する

# はじめに
前回、pulumiでwebアプリケーションを動かすための基本構成(ec2+rdsの環境)をAWSに構築しました[前回の記事](https://qiita.com/yufuku/items/1c8ca01494e77924ab48)。

今回は、ALB+ec2の動的webサイトの構成をpulumiで構築します。
また、pulumiで構築したAWS環境にWordPress(docker container)を動作させます。

# 構成図
<img width="529" alt="スクリーンショット 2020-07-28 0.38.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/bd44eb77-1722-4ad4-ed97-d748cc18b2f1.png">

# 実際のコード
## コード全体

今回作成した、pulumiのコードになります

```python:__main__.py
import pulumi
import pulumi_aws as aws

# VPCの作成
vpc = aws.ec2.Vpc(
    "pulumi-vpc",
    cidr_block="10.0.0.0/16",
    tags={
        "Name": "pulumi-vpc",
    })

# Subnetの作成
public_subnet_a = aws.ec2.Subnet(
    "pulumi-public-subnet-a",
    cidr_block="10.0.1.0/24",
    availability_zone="ap-northeast-1a",
    tags={
        "Name": "pulumi-public-subnet-a",
    },
    vpc_id=vpc.id)

public_subnet_c = aws.ec2.Subnet(
    "pulumi-public-subnet-c",
    cidr_block="10.0.2.0/24",
    availability_zone="ap-northeast-1c",
    tags={
        "Name": "pulumi-public-subnet-c",
    },
    vpc_id=vpc.id)

private_subnet_a = aws.ec2.Subnet(
    "pulumi-private-subnet-a",
    cidr_block="10.0.3.0/24",
    availability_zone="ap-northeast-1a",
    tags={
        "Name": "pulumi-private-subnet-a",
    },
    vpc_id=vpc.id)

private_subnet_c = aws.ec2.Subnet(
    "pulumi-private-subnet-c",
    cidr_block="10.0.4.0/24",
    availability_zone="ap-northeast-1c",
    tags={
        "Name": "pulumi-private-subnet-c",
    },
    vpc_id=vpc.id)

# InternetGatewayの作成
igw = aws.ec2.InternetGateway(
    "pulumi-igw",
    tags={
        "Name": "pulumi-igw",
    },
    vpc_id=vpc.id)

# EIPの作成
ngw_eip_a = aws.ec2.Eip("pulumi-ngw-eip-a")

ngw_eip_c = aws.ec2.Eip("pulumi-ngw-eip-c")

# NatGatewayの作成
ngw_a = aws.ec2.NatGateway(
    "pulumi-ngw-a",
    allocation_id=ngw_eip_a.id,
    subnet_id=public_subnet_a.id)

# NatGatewayの作成
ngw_c = aws.ec2.NatGateway(
    "pulumi-ngw-c",
    allocation_id=ngw_eip_c.id,
    subnet_id=public_subnet_c.id)

# RouteTableの作成
public_route_table_a = aws.ec2.RouteTable(
    "pulumi-public-route-table-a",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "gateway_id": igw.id,
        },
    ],
    tags={
        "Name": "pulumi-public-route-table-a",
    },
    vpc_id=vpc.id)

public_route_table_c = aws.ec2.RouteTable(
    "pulumi-public-route-table-c",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "gateway_id": igw.id,
        },
    ],
    tags={
        "Name": "pulumi-public-route-table-c",
    },
    vpc_id=vpc.id)

private_route_table_a = aws.ec2.RouteTable(
    "pulumi-private-route-table-a",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_a.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-a",
    },
    vpc_id=vpc.id)

private_route_table_c = aws.ec2.RouteTable(
    "pulumi-private-route-table-c",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_c.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-c",
    },
    vpc_id=vpc.id)

# RouteTableAssociationの作成
route_table_association_public_a = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-public-a",
    subnet_id=public_subnet_a.id,
    route_table_id=public_route_table_a.id)

route_table_association_public_c = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-public-c",
    subnet_id=public_subnet_c.id,
    route_table_id=public_route_table_c.id)

route_table_association_private_a = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-a",
    subnet_id=private_subnet_a.id,
    route_table_id=private_route_table_a.id)

route_table_association_private_c = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-c",
    subnet_id=private_subnet_c.id,
    route_table_id=private_route_table_c.id)

# SecurityGroupの作成
alb_sg = aws.ec2.SecurityGroup(
    "pulumi-alb-sg",
    ingress=[
        {
            "from_port": 80,
            "protocol": "TCP",
            "to_port": 80,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    egress=[
        {
            "from_port": 0,
            "protocol": "TCP",
            "to_port": 65535,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    tags={
        "Name": "pulumi-alb-sg",
    },
    vpc_id=vpc.id)

ec2_sg = aws.ec2.SecurityGroup(
    "pulumi-ec2-sg",
    ingress=[
        {
            "from_port": 80,
            "protocol": "TCP",
            "to_port": 80,
            "security_groups": [alb_sg.id]
        },
    ],
    egress=[
        {
            "from_port": 0,
            "protocol": "TCP",
            "to_port": 65535,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    tags={
        "Name": "pulumi-ec2-sg",
    },
    vpc_id=vpc.id)

rds_sg = aws.ec2.SecurityGroup(
    "pulumi-rds-sg",
    ingress=[
        {
            "from_port": 3306,
            "protocol": "TCP",
            "to_port": 3306,
            "security_groups": [ec2_sg.id]
        },
    ],
    egress=[
        {
            "from_port": 0,
            "protocol": "TCP",
            "to_port": 65535,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    tags={
        "Name": "pulumi-rds-sg",
    },
    vpc_id=vpc.id)

# KeyPairの作成
key_pair = aws.ec2.KeyPair(
    "pulumi-keypair",
    public_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 email@example.com",
    tags={
        "Name": "pulumi-keypair",
    })

#  TargetGroupの作成
target_group = aws.alb.TargetGroup(
    "pulumi-target-group",
    health_check={
        "healthyThreshold": 5,
        "interval": 30,
        "matcher": "200,302",
        "path": "/",
        "protocol": "HTTP",
        "timeout": 5,
        "unhealthyThreshold": 2
    },
    name="pulumi-target-group",
    port=80,
    protocol="HTTP",
    tags={
        "Name": "pulumi-target-group",
    },
    target_type="instance",
    vpc_id=vpc.id)

# ユーザデータの読み込み
file = open("./user-data")
user_data = file.read()

# LaunchConfigurationの作成
launch_conf = aws.ec2.LaunchConfiguration(
    "pulumi-launch-conf",
    image_id="ami-0ee1410f0644c1cac",
    instance_type="t2.micro",
    associate_public_ip_address=True,
    key_name=key_pair.key_name,
    security_groups=[ec2_sg.id],
    user_data=user_data)

file.close()

# AutoScalingGroupの作成
autoscaling_group = aws.autoscaling.Group(
    "pulumi-autoscaling-group",
    availability_zones=["ap-northeast-1a", "ap-northeast-1c"],
    health_check_type="ELB",
    desired_capacity=1,
    launch_configuration=launch_conf.name,
    max_size=1,
    min_size=1,
    target_group_arns=[target_group.arn],
    vpc_zone_identifiers=[
        private_subnet_a.id,
        private_subnet_c.id
    ])

# LoadBalancerの作成
alb = aws.alb.LoadBalancer(
    "pulumi-alb",
    load_balancer_type="application",
    name="pulumi-alb",
    security_groups=[alb_sg.id],
    subnets=[
        public_subnet_a.id,
        public_subnet_c.id
    ],
    tags={
        "Name": "pulumi-alb",
    })

# Listenerの作成
alb_listener = aws.alb.Listener(
    "pulumi-alb-listener",
    default_actions=[{
        "target_group_arn": target_group.arn,
        "type": "forward",
    }],
    load_balancer_arn=alb.arn,
    port="80",
    protocol="HTTP")

# RDS SubnetGroupの作成
rds_subnet = aws.rds.SubnetGroup(
    "pulumi-rds-subnet",
    subnet_ids=[
        private_subnet_a.id,
        private_subnet_c.id,
    ],
    tags={
        "Name": "pulumi-rds-subnet",
    })

#RDSインスタンスの作成
rds = aws.rds.Instance(
    "pulumi-rds",
    allocated_storage=20,
    db_subnet_group_name=rds_subnet.name,
    engine="mysql",
    engine_version="5.7",
    identifier="pulumi-rds",
    instance_class="db.t2.micro",
    name="pulumi",
    parameter_group_name="default.mysql5.7",
    password="password",
    skip_final_snapshot=True,
    storage_type="gp2",
    tags={
        "Name": "pulumi-rds",
    },
    username="admin",
    vpc_security_group_ids=[rds_sg.id])
```

```shell:user-data
#!/bin/bash

yum install docker -y

service docker start

docker pull wordpress

docker run -p 80:80 -d wordpress
```
## NAT Gateway、Route Table（private subnet用）
今回は、アプリケーションをホストするEC2をprivate subnetで起動する
EC2がインターネットに抜けられるようにNAT Gatewayとそれに向けたRoute Tableを作成した

```python
# NAT Gatewayに紐付けるEIPの作成
ngw_eip_a = aws.ec2.Eip("pulumi-ngw-eip-a")

ngw_eip_c = aws.ec2.Eip("pulumi-ngw-eip-c")

# NatGatewayの作成
ngw_a = aws.ec2.NatGateway(
    "pulumi-ngw-a",
    allocation_id=ngw_eip_a.id,
    subnet_id=public_subnet_a.id)

# NatGatewayの作成
ngw_c = aws.ec2.NatGateway(
    "pulumi-ngw-c",
    allocation_id=ngw_eip_c.id,
    subnet_id=public_subnet_c.id)

# private subnet用のRouteTableの作成
# NAT Gateway向けのrouteを追加している
private_route_table_a = aws.ec2.RouteTable(
    "pulumi-private-route-table-a",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_a.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-a",
    },
    vpc_id=vpc.id)

private_route_table_c = aws.ec2.RouteTable(
    "pulumi-private-route-table-c",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_c.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-c",
    },
    vpc_id=vpc.id)

# RouteTableとsubnetの紐付け
route_table_association_private_a = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-a",
    subnet_id=private_subnet_a.id,
    route_table_id=private_route_table_a.id)

route_table_association_private_c = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-c",
    subnet_id=private_subnet_c.id,
    route_table_id=private_route_table_c.id)
```

## LaunchConfiguration、AutoScalingGroup、TargetGroup
LaunchConfigurationとAutoScalingGroupを作成する
また、ALBとAutoScalingGroupを紐づけるためのTargetGroupを作成する

```python
#  TargetGroupの作成
target_group = aws.alb.TargetGroup(
    "pulumi-target-group",
    health_check={
        "healthyThreshold": 5,
        "interval": 30,
        "matcher": "200,302",
        "path": "/",
        "protocol": "HTTP",
        "timeout": 5,
        "unhealthyThreshold": 2
    },
    name="pulumi-target-group",
    port=80,
    protocol="HTTP",
    tags={
        "Name": "pulumi-target-group",
    },
    target_type="instance",
    vpc_id=vpc.id)

# ユーザデータの読み込み
file = open("./user-data")
user_data = file.read()

# LaunchConfigurationの作成
launch_conf = aws.ec2.LaunchConfiguration(
    "pulumi-launch-conf",
    image_id="ami-0ee1410f0644c1cac",
    instance_type="t2.micro",
    associate_public_ip_address=True,
    key_name=key_pair.key_name,
    security_groups=[ec2_sg.id],
    user_data=user_data)

file.close()

# AutoScalingGroupの作成
autoscaling_group = aws.autoscaling.Group(
    "pulumi-autoscaling-group",
    availability_zones=["ap-northeast-1a", "ap-northeast-1c"],
    health_check_type="ELB",
    desired_capacity=1,
    launch_configuration=launch_conf.name,
    max_size=1,
    min_size=1,
    target_group_arns=[target_group.arn],
    vpc_zone_identifiers=[
        private_subnet_a.id,
        private_subnet_c.id
    ])
```
TargetGroupの`health_check`の設定について、
WordPressの`/`にアクセスした際にredirectされるため、`matcher`の設定値にredirectのステータスコード（302）も追加している

LaunchConfigurationのuser_dataについては、別ファイルから読み込むようにしている
user_dataの中身については後述する

## ALB
ALBの作成とListenerを作成する。

```python
# LoadBalancerの作成
alb = aws.alb.LoadBalancer(
    "pulumi-alb",
    load_balancer_type="application",
    name="pulumi-alb",
    security_groups=[alb_sg.id],
    subnets=[
        public_subnet_a.id,
        public_subnet_c.id
    ],
    tags={
        "Name": "pulumi-alb",
    })

# Listenerの作成（HTTP）
alb_listener = aws.alb.Listener(
    "pulumi-alb-listener",
    default_actions=[{
        "target_group_arn": target_group.arn,
        "type": "forward",
    }],
    load_balancer_arn=alb.arn,
    port="80",
    protocol="HTTP")
```
今回は、HTTPでListenerを作成したが、HTTPSのListenerを作成する場合は以下のように証明書などの設定も必要となる

```python
# Listenerの作成（HTTPS）
alb_listener = aws.alb.Listener(
    "pulumi-alb-listener",
    certificate_arn="arn:aws:iam::187416307283:server-certificate/test_cert_rab3wuqwgja25ct3n4jdj2tzu4",
    default_actions=[{
        "target_group_arn": target_group.arn,
        "type": "forward",
    }],
    load_balancer_arn=alb.arn,
    port="443",
    protocol="HTTPS",
    ssl_policy="ELBSecurityPolicy-2016-08")
```

## ユーザーデータ
今回は、Docker hubに登録されているWordPressのimageを起動させる
ユーザデータで、dockerのインストールと起動、WordPressコンテナの起動を行う

```shell:user-data
#!/bin/bash

# dockerのインストール
yum install docker -y

# dockerデーモンの起動
service docker start

# WordPressのimageをpull
docker pull wordpress

# WordPressのコンテナを起動
docker run -p 80:80 -d wordpress
```
# まとめ
今回は、動的なWebサイトを動作させるための代表的なAWS構成をpulumiで作成し、WordPressのdockerコンテナを起動してみました。

前回の記事で作成したAWSリソースについては今回は詳しく説明していないので、よろしければ[前回の記事](https://qiita.com/yufuku/items/1c8ca01494e77924ab48)も参考にしてみてください。

# おまけ
実際にアクセスしてみる

言語の設定
<img width="567" alt="スクリーンショット 2020-07-28 1.46.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/fab1081b-ce31-15b4-adb1-a1aca2df032d.png">

データベース情報の設定
ここでは、先ほど作成したRDSの設定を入力してください。
<img width="906" alt="スクリーンショット 2020-07-28 1.47.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/3172da85-0dd4-7780-41b9-d7f7c2eb4436.png">

ユーザー情報の設定
<img width="767" alt="スクリーンショット 2020-07-28 1.48.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/62e0ac11-f9e7-f787-70b3-2f022d11ce92.png">

WordPressログイン後の画面
<img width="1430" alt="スクリーンショット 2020-07-28 1.49.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680074/4a3dd98a-4ebf-b81e-29b3-58549cc030f1.png">