0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

MinecraftサーバをAWSで立ててみた #2

Posted at

はじめに

この記事は MinecraftサーバをAWSで立ててみた の第2回の記事となります。

今回は前回作成したインフラ環境のIaC化を実施してみます。
AWSのインフラ環境のIaCといえば CDK ですね。
ということでCDKの作成をやってみましたのでまとめておきます。

今回は、CDK全般のまとめでそこそこの量になったので、Minecraftサーバのためのサブネットやリソースと詳細まとめは次回にします。

目次

  1. モチベーション
  2. CDK作成
  3. 参考文献

モチベーション

前回が事実上Minecraftサーバの動作確認だけだったのに、早速CDKを作っていく理由的なものを残しておこうと思います。

これから個人でサーバ構築・運用を試していく上で何より重要視していることは

「いかにお金を掛けないか」

です。

サービスが止ろうが、サーバがぶっ壊れようが大した問題ではありません。
AWS上のリソースを利用することでかさんでいくランニングコストを如何に抑えつつ、様々なことを試していくかに重きを置きました。

検証を行わないときはインスタンスの停止ではなく削除をすることでコスト削減できます。
またテスト的にサーバの設定を変更し、壊れてしまっても一度環境を捨てて、すぐに動作する環境を作成できることは、精神衛生上も好ましいです。

CDK作成

CDKを書いてみるにあたってClassmethodさんの記事をめちゃくちゃ参考にさせてもらいました。
実践!AWS CDK の記事シリーズは本当に最高です!
今回作成したCDKのソースはこの記事をベースにしており、サーバ構成やNatGatewayの部分を主に変更して作成しました。なのでソースの細かな部分は上記の記事を参照いただければと思います。

自分が変更した部分をピックアップしてまとめます。
(一応、CDKで実装した構成図を再掲します)

minecraft_server.jpg

実行環境・言語

実践!AWS CDK ではCDKの実装言語にTypescriptを選定されています。自分はTypeScriptには明るくないのでPythonで書くことにしました。

また、CDKではデフォルトでvirtualenvでのcdkコマンドの実行するようなチュートリアルとなっていますが、Pipenvのほうが使いやすいので、こちらを使用するように変更しました。
よって実行環境はざっくり以下となりました。

環境 バージョン
aws cli 2.3.7
aws cdk 2.10.0
python 3.9.12
Pipenv -

ディレクトリ構成

ディレクトリ構成は実践!AWS CDK で紹介されていたものを利用していますが、簡単に紹介します。

.
├── Pipfile
├── Pipfile.lock
├── README.md
├── app.py # mainのソースファイル
├── cdk.json 
├── cdk.out/ # Cfnのファイル出力先
├── docs/ # README.md関連のドキュメント格納用
│   
├── minecraft_server/
│   ├── resources/ # リソースごとの定義ファイルを格納
│   │   ├── abstract/
│   │   │   └── resource.py
│   │   ├── ec2.py
│   │   ├── ...
│   └── stacks/ # スタックごとの定義ファイルを格納
│       ├── ec2_stack.py
│       ├── ...
├── requirements-dev.txt
├── requirements.txt
├── scripts/ # 各EC2作成時に実行するuserData.shを格納
└── tests/ # cdkのテストコード格納先(テストコードは未作成)

リソース定義の実装ルール

実践!AWS CDK #9 リファクタリングでも紹介されていますが、リソース定義する際に同じクラスのインスタンス化をベタ書きすることを避け、リソースのパラメータがわかりやすい形でまとめるようにしました。

  • 作成するリソースごとにファイル(クラス)を分割する
    • 例)internetGateway.ts(InternetGateway クラス)
  • すべてのリソースクラスは抽象クラス Resource を継承する
  • 「あるリソースの生成に必要となるリソース」はコンストラクタに渡す
    • 渡すオブジェクトは CfnXXX クラスのインスタンス
  • リソースクラス内で複数のリソースを生成する場合はループで回す
    • new CfnXXX() を何度も実行しない
    • ResourceInfo インタフェースを用意してプロパティの変動部分のみを書き出す
  • 生成したリソースはリソースクラス内の public メンバ変数に格納し、外部クラスから参照可能とする

上記ルールをPythonでも実現できるようにコードを修正しています。

resources/vpc.py
from .abstract import Resource
from dataclasses import dataclass

# vpcでは出現しないが、リソースのパラメータが値がクラスとなっているような場合(クラスのネスト)
# 普通のdataclassではdict->dataclassの変換ができない
# 上記を達成するために validated_dcを pip installし、ResourceInfoで継承する
from validated_dc import ValidatedDC

from aws_cdk import (
    aws_ec2 as ec2,
)
from constructs import Construct

# リソースのパラメータはdataclassに格納できるようにResourceInfoクラスを定義
@dataclass
class ResourceInfo(ValidatedDC):
    id: str
    cidr_block: str
    resource_name: str
    _assign_name: str

class Vpc(Resource):
    def __init__(self) -> None:
        # リソースのパラメータを定義
        self._resources = [
            {
                'id': 'Vpc',
                'cidr_block': '10.8.0.0/16',
                'resource_name': 'Vpc',
                '_assign_name': 'vpc',
            }
        ]
        # パラメータのdictをdataclassに変換
        self.resources = [ResourceInfo(**resource) for resource in self._resources]

    def create_resources(self, scope: Construct):
        for resource_info in self.resources:
            vpc = self.__create_vpc(scope, resource_info)
            # 実践!AWS CDK では、パラメータのオブジェクトに無名関数を定義して
            # 自分自身のpublicメンバにリソースをsetしている
            # 同様な形でPythonでも再現できそうだが、一旦'_assign_name'をメンバ変数名として
            # setattrする形で実現した
            setattr(self, resource_info._assign_name, vpc)
    
    def __create_vpc(self, scope: ec2.CfnVPC, resource_info: ResourceInfo) -> ec2.CfnVPC:
        return ec2.CfnVPC(scope,
            resource_info.id,
            cidr_block=resource_info.cidr_block,
            tags=[{'key':'Name', 'value':self._create_resource_name(scope, resource_info.resource_name)}],
        )

Nat Instance

実践!AWS CDK ではNatGatewayを各AZに1台作成しています。

NatGatewayは高いです

NatGatewayは稼働時間料金で33.48USD/月、これに加えてデータ処理および転送料金が発生します。一方でNatInstanceはt3.microのオンデマンドで10.12USD/月程度です。
検証目的であればNatInstanceで十分で、費用も抑えれます。

今回は2AZの構成ですが、NatInstanceは1台のみで運用します。

NatInstanceをCDKで作成する場合、L2: High-level constructsVPCクラスでリソースを作成すれば簡単に作成できます。しかし、L2: High-level constructsのクラスを使用すると、こちらで明示的に定義していないリソースも勝手に作成されてしまいます。(良くも悪くも、AWSのおすすめの構成が自動的に作られます)
CDKにおけるレイヤーの理解は重要なので実践!AWS CDK #1 導入を一読することをおすすめします。

ということで、1からNatInstanceを定義することになるのですが、リソース定義をresources/nat_instance.pyに実装しました。NatInstanceは実際ただのEC2インスタンスなのですが、役割としてはVpcStackに所属すべきだと考え、VpcStackのリソースとしてインスタンス化しています。

ただ、VpcStackに所属させるために、一つのファイルに、NatInstanceの定義と、NatInstanceのためのSecurityGroupの定義が混じってしまっています。
ここは改善したいポイントです。

resources/nat_instance.py
# (略)

# ここから NatInstance の定義
@dataclass
class ResourceInfo(ValidatedDC):
    id: str
    availability_zone: str
    iam_instance_profile: str
    resource_name: str
    image_id: str
    instance_type: str
    source_dest_check: bool
    subnet_id: str
    eip_id: str
    _assign_name: str

class NatInstance(Resource):
    def __init__(self, 
    vpc: ec2.CfnVPC,
    iam_role: IAMRole,
    subnet_public_1a: ec2.CfnSubnet, 
    elastic_ip: ec2.CfnEIP) -> None:

        self._vpc = vpc

        self._resources = [
            {
                'id': 'Ec2InstanceNat',
                'availability_zone': 'us-east-1a',
                'iam_instance_profile': iam_role.instance_profile_general_ec2.ref,
                'resource_name': 'EC2-NatIns-1a',
                # Nat Instance
                'image_id': 'ami-0cc6fa590dc4d36eb',
                'instance_type': 't2.micro',
                'source_dest_check': False,
                'subnet_id': subnet_public_1a.ref,
                'eip_id': elastic_ip.ref,
                '_assign_name': 'natins_1a'
            },
        ]

        self.resources = [ResourceInfo(**resource) for resource in self._resources]

    def create_resources(self, scope: Construct):

        security_group = SecurityGroup(self._vpc)
        security_group.create_resources(scope)

# (略)

# ここから SecurityGroup の定義
@dataclass
class SecurityGroupIngressInfo(ValidatedDC):
    ip_protocol: str
    cidr_ip: str
    from_port: int
    to_port: int

@dataclass
class IngressInfo(ValidatedDC):
    id: str
    security_group_ingress_info: SecurityGroupIngressInfo

@dataclass
class NSResourceInfo(ValidatedDC):
    id: str
    group_description: str
    ingresses: List[IngressInfo]
    resource_name: str
    _assign_name: str

class SecurityGroup(Resource):
    def __init__(self, vpc: ec2.CfnVPC) -> None:
        self._vpc = vpc

        self._resources = [
            {
                'id': 'SecurityGroupNatInstance',
                'group_description': 'For NatInstance',
                'ingresses': [
                    {
                        'id': 'SecurityGroupIngressNatInstance1',
                        'security_group_ingress_info': {
                            'ip_protocol': 'tcp',
                            'cidr_ip': '10.8.0.0/16',
                            'from_port': 80,
                            'to_port': 80,
                        },
                    },
                    {
                        'id': 'SecurityGroupIngressNatInstance2',
                        'security_group_ingress_info': {
                            'ip_protocol': 'tcp',
                            'cidr_ip': '10.8.0.0/16',
                            'from_port': 443,
                            'to_port': 443,
                        },
                    },
                ],
                'resource_name': 'Sg-NatIns',
                '_assign_name': 'natins'
            },
        ]

        self.resources = [NSResourceInfo(**resource) for resource in self._resources]

    def create_resources(self, scope: Construct):
        for resource_info in self.resources:

# (略)

作成したCDK

完成したソースをgithubで公開しています。
クレデンシャル等は入っていないと思うのですが、もしこのファイルは公開しないほうが良いよといったことがあれば是非コメントでご指摘ください。

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?