withコロナな連休に向けて。

[mediba Advent Calendar 2020](https://qiita.com/advent-calendar/2020/mediba) 5日目を担当させていただく t-kusakabeです。がんばります。お願いします。
medibaには10月からお世話になっており、SRE UNIT兼スマートパスというプロダクトを担当させていただいております。

今年も早いもので気づけばアドベントカレンダーの季節です。
カレンダーを見てみると28日を挟んで正月休み。有給を使ってクリスマスが終われば仕事納めだ！という方も少なくないのではないしょうか。

本来はクリスマスを楽しみにするイベントですが、連休が楽しみでしかたないですね！
しかし、そうなると気になるのはコロナ問題です。
今年は緊急事態宣言や不要不急などで外出する機会が多くはなかった印象です。
そして気になるのは第三波。

そのため自宅でも楽しめる何かが必要ですよね。
自宅で無限に遊べるものと言えばゲームです。
今年もたくさん時間が溶けるタイトルが発表されました。
おそらくこの記事が公開されるときにも、あつ森や桃鉄などを夜通し楽しまれている方もいることでしょう。

無限に遊べるゲームと言えば忘れてはいけないタイトルがあります。
そうです、Minecraftです。

https://www.minecraft.net/ja-jp
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108495/24f26109-bae0-844c-93e9-4d191bb66c64.png)



みなさんも一度はプレイされたことがあるのではないしょうか。
整地をしたりPvPをしたりなど、色々な遊び方が出来て楽しいですよね！

というわけで今回は、連休を楽しく過ごすためにMinecraft環境を構築してみましょう！

## 使用技術

- ECS(Fargate)
- CDK

僕が担当させていただいてるプロダクトではEKSやTerraformを使っていますが、たまには違った技術をということでECSとCDKでやっていこうと思います。
(ほんとはGCPでやりたかったけど、CDKがよかったのでAWSにしました)

~~料金が気になる？大丈夫ボーナスとお年玉があります。~~

## 環境構築

まずは準備をしていきましょう。

※ あらかじめNode.jsをinstallしておいてください。

まずは作業用のディレクトリを用意します。

```
mkdir advent-calendar2020
cd advent-calendar2020
```

cdkもinstallしておきます。

```
npm i -g aws-cdk
```

雛形を作るため、initしてみます。
今回はtypescriptでやっていこうと思います。

```
cdk init app --language typescript
npm i
```

initを実行すると以下のようなものが作られます。
今回はあまり難しいことはせず、lib以下のみをよしなに触っていこうと思います。

```
❯ tree -L 1
.
├── README.md
├── bin
├── cdk.json
├── cdk.out
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json
```


## VPC

まずはVPCから作っていきましょう。
CDKにはいろいろと便利なclassが用意されています。
VPC関連は [このあたり](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.Vpc.html) を見てれば簡単に作れます。

MainStackにどんどん書き連ねてもいいですが、肥大化するのがいやなので最低限いい感じに分割していこうと思います。

lib以下にfileを作ります。

```
├── advent-calendar2020-stack.ts
└── recipes
    └── VPC
        └── multi_az.ts
```

VPCを作るためにまず、EC2のパッケージをinstallします。

```
npm i @aws-cdk/aws-ec2
```

早速CDKを書いていきましょう。

```lib/recipes/VPC/multi_az.ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';

interface IRecipesVpcMultiAzProps {
  context: cdk.Stack;
}

const RecipesVpcMultiAz = (props: IRecipesVpcMultiAzProps) => {
  // === VPC ===
  const vpc = new ec2.Vpc(props.context, 'vpc', {
    cidr: '10.0.0.0/16',
    natGateways: 0
  })

  return {
    vpc
  };
};

export default RecipesVpcMultiAz;
```

これだけで、MultiAZなVPCやSubnetを作成してくれます(InternetGatewayやその他諸々)。
今回はめんどくさいのでPublicなSubnetにコンテナを作ろうと思うので、NatGatewayを作らないようにします。
(しっかりやりたい方は、PrivateSubnetに置いたり、ELBを用意したりしてみてください)
VPCのConstructに `natGateways` というのが用意されているので0にすることでNatGatewayを作らないように出来ます。

また、分割するものはNestedStackとして用意してもいいのですが、CDKのNestedStackには変更点が差分として表示されない罠がありました。
個人的なものを作る時には困りませんが業務で利用したりを想定すると、予想外の変更が起きてしまうのはかなり怖いので今回はNestedStackとして切り出さずに関数として用意し、MainStackから呼び出すことで1枚のCFnテンプレートを作ることで差分を確認出来るようにしています。

## ECR

事前にECSで使うImageを用意しておきましょう。
今回は [こちらのImage](https://hub.docker.com/r/itzg/minecraft-server) をお借りします。
DockerHubからImageを取得することも出来ますが、せっかくなのでECRに置いておきましょう。

ECRはPublicでもPrivateでもどちらでもよいですが、Privateの場合は認証許可が必要なため以下のコマンドで対応しておきます。

```
aws ecr get-login-password | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com
```

tagをつけてECRにpushしておきましょう。
事前にrepositoryを作っておいてください。


```
docker tag itzg/minecraft-server:latest ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/minecraft-server:latest
docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/minecraft-server:latest
```


## ECS

VPCが出来たのでECSを用意していきましょう。
Fargateが好きなのでFargateしていきます。

ECSには大きく4つくらいの概念があります。
- Cluster
- Service
- TaskDefinition
- CotnainerDefinition

これらをCDKで作っていきます。

```
lib
├── advent-calendar2020-stack.ts
└── recipes
    └── ECS
        └── fargate.ts
```

必要なパッケージをinstallします。

```
npm i @aws-cdk/aws-ecs
npm i @aws-cdk/aws-ecr
```

まずはClusterを用意しましょう。
ECSは [このあたり](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ecs.Cluster.html) を見ればなんとかなります。

```lib/recipes/ECS/fargate.ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as ecr from '@aws-cdk/aws-ecr';

interface IRecipesEcsFargateProps {
  context: cdk.Stack;
  vpc: ec2.IVpc;
}

const RecipesEcsFargate = (props: IRecipesEcsFargateProps) => {
  // === Cluster ===
  const cluster = new ecs.Cluster(props.context, 'EcsFargateCluster', {
    clusterName: 'advent-calendar2020-ecs-cluster',
    vpc: props.vpc
  });
};

export default RecipesEcsFargate;
```

これだけでECS Clusterが作られます。

次にTaskDefinitionを用意しましょう。
Minecraftを動かす際にはある程度のCPU・メモリがないといけないのでひとまず適当に設定してあります(もっとパワフルにしたい！という方は適宜調整してください)


```lib/recipes/ECS/fargate.ts

  // 省略

  // === Cluster ===
  const cluster = new ecs.Cluster(props.context, 'EcsFargateCluster', {
    clusterName: 'advent-calendar2020-ecs-cluster',
    vpc: props.vpc
  });

  // 追記
  // === Task Definition ===
  const taskDefinition = new ecs.FargateTaskDefinition(props.context, 'EcsFargateTaskDefinition', {
    cpu: 512,
    memoryLimitMiB: 4096
  });

  // 省略
```

次はContainerDefinitionです。
ECSで動かすコンテナを定義していきます。
あらかじめ用意しておいたECRを参照し、ContainerDefinitionに組み込みます。

```lib/recipes/ECS/fargate.ts
  // 省略

  // === Task Definition ===
  const taskDefinition = new ecs.FargateTaskDefinition(props.context, 'EcsFargateTaskDefinition', {
    cpu: 512,
    memoryLimitMiB: 4096
  });

  // 追記
  // === ECR ===
  const repository = ecr.Repository.fromRepositoryName(props.context, 'Ecr', 'minecraft');

  // === Container Definition ===
  const containerDefinition = new ecs.ContainerDefinition(props.context, 'MineCraft', {
    image: ecs.ContainerImage.fromEcrRepository(repository),
    taskDefinition: taskDefinition,
    environment: {
      EULA: 'TRUE'
    }
  });

  containerDefinition.addPortMappings({
    containerPort: 25565,
    protocol: ecs.Protocol.TCP
  });

  // 省略
```

最後にServiceです。
Minecraft用のportもついでに開けておきます。

```lib/recipes/ECS/fargate.ts
  // 省略

  // === ECR ===
  const repository = ecr.Repository.fromRepositoryName(props.context, 'Ecr', 'minecraft');

  // === Container Definition ===
  const containerDefinition = new ecs.ContainerDefinition(props.context, 'MineCraft', {
    image: ecs.ContainerImage.fromEcrRepository(repository),
    taskDefinition: taskDefinition,
    environment: {
      EULA: 'TRUE'
    }
  });

  containerDefinition.addPortMappings({
    containerPort: 25565,
    protocol: ecs.Protocol.TCP
  });

  // 追記
  // === Security Group ===
  const securityGroup = new ec2.SecurityGroup(props.context, 'SecurityGroup', {
    vpc: props.vpc,
    securityGroupName: 'ecs-sg'
  });

  securityGroup.addIngressRule(
      ec2.Peer.ipv4('0.0.0.0/0'),
      ec2.Port.tcp(25565)
  );

  // === Service ===
  const service = new ecs.FargateService(props.context, 'EcsFargateService', {
    cluster: cluster,
    taskDefinition: taskDefinition,
    assignPublicIp: true,
    securityGroup: securityGroup,
    vpcSubnets: props.vpc.selectSubnets({
      subnetType: ec2.SubnetType.PUBLIC
    })
  });

  // 省略
```

これで、ECSの準備が出来ました。


最後にMainStackから呼び出してみましょう。

```lib/advent-calendar2020-stack.ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';

import RecipesVpcMultiAz from './recipes/VPC/multi_az';
import RecipesEcsFargate from './recipes/ECS/fargate';

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

    // === VPC ===
    const vpcResource = RecipesVpcMultiAz({
      context: this
    });

    // === ECS ===
    RecipesEcsFargate({
      context: this,
      vpc: vpcResource.vpc
    });
  }
}
```

## CDK実行

CDKを用意出来たのであとは実行するだけです。

```
cdk deploy
```

CDKを実行するかどうかを聞かれるので y でdeployしてください。
CloudFormationのStackが作られるので、あとは気長に待つだけです。
deployに失敗した場合はCloudFormationのイベントを眺めてみてください。



## 遊んでみる

これでECS上にMinecraftを用意することが出来ました。
というわけで実際に遊んでいきましょう。

コンテナにPublicIPを割り振っているので、直接接続してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108495/9730b9be-6c86-5206-1fb9-724cb5d46162.png)

無事に繋がりました！！！
特に制限はしていないので、IPを伝えればいつでもマルチでわいわいMinecraftをプレイすることが出来ます！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108495/1f2919a1-e6cd-eb15-3e29-3ba18b7e354e.png)

ECSは柔軟にスケーリングが出来るので人が増えた場合も簡単に対応することが出来ます。
クラウドは便利でいいですね。


## まとめ

というわけで連休に向けて時間を忘れて楽しく遊べるものをSREっぽくクラウド上で用意してみました。
実際はECSとCDKを触りたかっただけなので、アクセス出来たところで満足です。
(プレイデータや設定周りを外部に出しておきたい場合はEFSとかをMountしてあげればなんとかなります)

12月の1週目という比較的早めな日を担当させていただけたので、みなさんも今年最後のInputにECSやCDKを試して、連休はMinecraft上で集まりわいわいしてみるのも楽しいのではないでしょうか。