cloudformationでec2インスタンス(ubuntu22.04)を生成する

cloudformationでec2インスタンス(ubuntu22.04)を生成する

2023/10/17 21:00:00
Program
Ubuntu, Aws

前提 #

awsリソース構成 #

以下の通りのサービス構成とする

作成手順 #

  1. template.yml 作成
    cloudformation用のyamlでaws cli を使ってデプロイする
  2. paramters.json 作成
    template.yml に引き渡す外部パラメータを指定する
  3. デプロイ
    template.yml, paramters.json を使用してaws cli でデプロイを実行する
  4. ssh 接続
    生成したec2にssh接続する

template.yaml を作成 #

必要最低限のawsサービス設定とec2で起動するubuntu22.04の初期設定を実施するためのyamlを記載してある。
また、外部パラメータファイルから指定した内容も反映させる実験も含めているので、!Sub となっている箇所は、外部パラメータ参照している箇所となる。
外部パラメータファイルは別途添付してある。

実験ではこの設定を使って redmine5.0 を手動で構築し起動するかを確認済みの内容である
redmine5.0 の構築手順は以下が参考になるかもしれない
https://blog.oya3.net/posts/2023/10/15/00_redmine/

$ emacs template.yml

AWSTemplateFormatVersion: 2010-09-09
Description: A simple EC2 instance

# parameters.json に外部指定可能なパラメータ
# - parameters.json にはすべて文字列で記載する必要がる様子
Parameters:
  MyAppName:
    Type: String
    Default: TestApp
  MyInstanceType:
    Type: String
    Default: t2.micro
  MyVolumeSize:
    Type: Number
    Default: 10
  LatestUbuntuFocalAMI: # 最新のami:ubuntu22.04を取得するため用
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/canonical/ubuntu/server/focal/stable/current/amd64/hvm/ebs-gp2/ami-id

Resources:
  # vpc 設定
  MyVPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub "${MyAppName}-VPC"

  # インターネット接続するため InternetGateway を紐づけておく
  MyInternetGateway:
    Type: 'AWS::EC2::InternetGateway'

  AttachGateway:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref MyInternetGateway

  MyRouteTable:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref MyVPC

  MyRoute:
    Type: 'AWS::EC2::Route'
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref MyRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway

  MySubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.1.0/24
      Tags:
        - Key: Name
          Value: !Sub "${MyAppName}-Subnet"

  SubnetRouteTableAssociation:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref MySubnet
      RouteTableId: !Ref MyRouteTable

  # SSH,http,https をinboundで許可しておく
  MySecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: My security group
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub "${MyAppName}-SG"

  # こので作成した秘密鍵は AWS Systems Manager (SSM) のパラメータストアに
  # /ec2/keypair/{key_pair_id} という名前で作成される。任意の名前は指定不可
  # - https://ap-northeast-1.console.aws.amazon.com/systems-manager
  MyKeyPair:
    Type: 'AWS::EC2::KeyPair'
    Properties:
      KeyName: !Sub "${MyAppName}-KeyPair"

  # Elastic IP を生成して ec2 に割り当てる
  MyEIP:
    Type: "AWS::EC2::EIP"
    Properties:
      Domain: "vpc"
      Tags:
        - Key: Name
          Value: !Sub "${MyAppName}-EIP"

  # EC2 インスタンスの生成
  MyEC2Instance:
    Type: "AWS::EC2::Instance"
    Properties:
      ImageId: !Ref LatestUbuntuFocalAMI
      # ImageId: ami-0d52744d6551d851e # ubuntu
      InstanceType: !Ref MyInstanceType
      KeyName: !Ref MyKeyPair
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          GroupSet:
            - !Ref MySecurityGroup
          SubnetId: !Ref MySubnet
      BlockDeviceMappings:
        - DeviceName: "/dev/sda1"
          Ebs:
            VolumeSize: !Ref MyVolumeSize
            DeleteOnTermination: "true"
            VolumeType: "gp2"
      # ubuntu22.04 起動時に設定する内容。Dockerfile みたいなもの。
      UserData:
        Fn::Base64: |
          #!/bin/bash
          apt update && sudo apt upgrade -y
          apt install git curl -y
          apt install apache2 apache2-dev -y
          apt install subversion git -y

          # for swapfile
          dd if=/dev/zero of=/swapfile bs=1M count=1024
          chmod 600 /swapfile
          mkswap /swapfile
          swapon /swapfile
          echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab

          # for rbenv
          apt install -y build-essential zlib1g-dev libssl-dev libreadline-dev libyaml-dev libcurl4-openssl-dev libffi-dev
          apt install -y imagemagick fonts-takao-pgothic

          # for mariadb(mysql)
          apt install -y mariadb-server libmysqlclient-dev          
      Tags:
        - Key: Name
          Value: !Sub "${MyAppName}-EC2"

  EIPAssociation:
    Type: "AWS::EC2::EIPAssociation"
    Properties:
      EIP: !Ref MyEIP
      InstanceId: !Ref MyEC2Instance

AWS CloudFormationのYAMLファイルについて説明。

  • MyVPC: これは、AWS::EC2::VPCというタイプのリソースを作成します。これは、Amazon Virtual Private Cloud (VPC)を作成するためのもので、CidrBlockプロパティにより10.0.0.0/16というIPアドレス範囲が割り当てられます⁴。
  • MyInternetGateway: AWS::EC2::InternetGatewayというタイプのリソースを作成します。これは、VPCとインターネットとの間に位置するゲートウェイを作成するためのものです⁴。
  • AttachGateway: AWS::EC2::VPCGatewayAttachmentというタイプのリソースを作成します。これは、作成したVPC (MyVPC)とインターネットゲートウェイ (MyInternetGateway)を関連付けるためのものです⁴。
  • MyRouteTable: AWS::EC2::RouteTableというタイプのリソースを作成します。これは、ネットワークトラフィックがどこにルーティングされるべきかを決定するルートテーブルを作成するためのものです⁴。
  • MyRoute: AWS::EC2::Routeというタイプのリソースを作成します。これは、ルートテーブル (MyRouteTable)にルートを追加するためのもので、DestinationCidrBlockプロパティにより全てのIPアドレス (0.0.0.0/0)が指定され、GatewayIdプロパティによりインターネットゲートウェイ (MyInternetGateway)が指定されます²⁴。 インターネットゲートウェイが不要な場合、つまりVPC内のリソースがインターネットと直接通信する必要がない場合は、MyRouteは不要
    ただし、VPC内のリソースがAWSの他のサービス(例えばS3やDynamoDBなど)と通信する必要がある場合は、VPCエンドポイントを使用することで、インターネットを経由せずにこれらのサービスと通信することができる。
    この場合でも、適切なルート設定が必要になるため、MyRouteのようなルート設定は引き続き必要になる場合がある。
  • MySubnet: AWS::EC2::Subnetというタイプのリソースを作成します。これは、VPC内にサブネットを作成するためのもので、CidrBlockプロパティにより10.0.1.0/24というIPアドレス範囲が割り当てられます⁴。
  • SubnetRouteTableAssociation: AWS::EC2::SubnetRouteTableAssociationというタイプのリソースを作成します。これは、サブネット (MySubnet)とルートテーブル (MyRouteTable)を関連付けるためのものです²⁹。
  • MySecurityGroup: AWS::EC2::SecurityGroupというタイプのリソースを作成します。これは、VPC内でEC2インスタンスなどが通信する際の許可や拒否のルールを定義するセキュリティグループを作成するためのものです⁴。この例では、SSH (ポート22) および HTTP/HTTPS (ポート80および443) のインバウンドトラフィックが許可されています。
  • MyKeyPair¹²: AWS::EC2::KeyPairリソースタイプを使用して、EC2インスタンスで使用するキーペアを作成します。キーペアの名前は、${MyAppName}-KeyPairという形式で指定されます。
  • MyEIP¹⁵¹⁷: AWS::EC2::EIPリソースタイプを使用して、Elastic IP (EIP) アドレスを作成します。このEIPは、後でEC2インスタンスに関連付けられます。
  • MyEC2Instance¹³¹⁴: AWS::EC2::Instanceリソースタイプを使用して、EC2インスタンスを作成します。このインスタンスは、指定されたAMI、インスタンスタイプ、キーペア、ネットワークインターフェース、ブロックデバイスマッピング(EBSボリューム設定)、およびユーザーデータ(起動時に実行されるスクリプト)を使用します。
  • EIPAssociation¹⁵¹⁸: AWS::EC2::EIPAssociationリソースタイプを使用して、作成したEIPをEC2インスタンスに関連付けます。

以上が各リソースの役割。このYAMLファイルはAWS CloudFormationサービスが解釈し、AWSリソースを自動的に作成・設定される。

外部パラメータファイルを作成 #

このファイルでは以下の設定をしている。  

  • アプリ名(MyAppName) を “Redmine50”
  • インスタンスタイプ(MyInstanceType) を “t3.micro”
  • 起動するecインスタンスの容量(MyVolumeSize) を “20GB”

今回は外部パラメータとして上記3か所を外部パラメータとしたが他にも追加することはもちろん可能

$ emacs paramters.json

[
  {
    "ParameterKey": "MyAppName",
    "ParameterValue": "Redmine50"
  },
  {
    "ParameterKey": "MyInstanceType",
    "ParameterValue": "t3.micro"
  },
  {
    "ParameterKey": "MyVolumeSize",
    "ParameterValue": "20"
  }
]

デプロイ #

template.yml, paramters.json を使用してaws cli でデプロイを実行する

  • –stack-name(スタック名)を “Redmine50Stack” としている。 これは aws cloudformation のスタック名として利用される。(aws console で確認できる)
  • –profile(aws profile名) を “developer” としてる。
    aws cli 設定に追加されてあるプロファイル名を指定する。defaultの場合は無指定でも構わない
$ aws cloudformation create-stack --stack-name Redmine50Stack --template-body file://template.yml --parameters file://parameters.json --profile developer

更新 #

template.yml や paramters.json を編集後、以下の内容を実施することで更新できる。
ただし、注意が必要で、大幅更新をすると ec2 インスタンス自体が再生成される場合があり、すべて破棄(初期化)されるケースもあるようなので注意が必要。
たとえば、インスタンスタイプを変更すると ec2 インスタンスが破棄され、再生成されるらしい。

$ aws cloudformation update-stack --stack-name Redmine50Stack --template-body file://template.yml --parameters file://parameters.json --profile developer

注意 #

更新で、ami imageid が変更されると大幅更新となり、確実にインスタンス自体を再生成することになるので注意すること。
(公式サイトにも記載があるので注意)

対応方法としては、ami imageid を固定しておけばいい。
template.yml の Paramters 節の以下の設定を変更すればいいと思う。

現状は最新取得するようになっている。

  LatestUbuntuFocalAMI: # 最新のami:ubuntu22.04を取得するため用
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/canonical/ubuntu/server/focal/stable/current/amd64/hvm/ebs-gp2/ami-id

以下に変更すれば特定のimageidを指定できる

  LatestUbuntuFocalAMI: # 固定のimageid
    Type: String
    Default: ami-022c3cb73fbed1cc7

aws imageid を取得する方法は以下の通り

# -query の配列の意味
#  [-1]: 最古
#  [0]: 最新
#  [:10]: 最新10個
# --owners
#  amazon は amazonが開発元
#  099720109477 は ubuntu開発元

# リージョン:ap-northeast-1(東京)で利用可能な amazon linux 最新1個
$ aws ec2 describe-images --region ap-northeast-1 --owners amazon \
 --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" \
 --query "reverse(sort_by(Images, &CreationDate))[0].[ImageId,CreationDate,Name]" \
 --output text

# リージョン:ap-northeast-1(東京)で利用可能な ubuntu20.04 最新10個
$ aws ec2 describe-images --region ap-northeast-1 --owners 099720109477 \
 --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64*" \
 --query "reverse(sort_by(Images, &CreationDate))[:10].[ImageId,CreationDate,Name]" \
 --output text

# リージョン:ap-northeast-1(東京)で利用可能な ubuntu22.04 最新10個
$ aws ec2 describe-images --region ap-northeast-1 --owners 099720109477 \
 --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64*" \
 --query "reverse(sort_by(Images, &CreationDate))[:10].[ImageId,CreationDate,Name]" \
 --output text

# 実行例
ami-07c589821f2b353aa   2023-12-07T03:26:14.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231207
ami-0289dadfee5556302   2023-11-28T04:30:01.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231128
ami-0dadff2c5c2f5f1b7   2023-11-21T02:51:12.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231121
ami-0a90bf37d2fa7fd4c   2023-11-17T04:18:52.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231117
ami-0495363ae63d1182e   2023-10-30T21:28:18.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231030
ami-0640caada745d9cca   2023-10-25T06:15:06.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231025
ami-066b74b421294f8ec   2023-10-21T04:13:41.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20231021
ami-09a81b370b76de6a2   2023-09-19T05:30:46.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20230919
ami-09937bb9852528c94   2023-09-07T04:27:28.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20230907
ami-06f25f372d5d98da3   2023-08-29T15:56:00.000Z        ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20230829

破棄 #

スタック名に紐づくすべてのawsリソースが破棄される。
ただし、aws console 等で手動でリソース内容を変更している場合、破棄されない場合がある様子。
この場合、手動で1個ずつawsリソースを破棄する必要がある。

$ aws cloudformation delete-stack --stack-name Redmine50Stack --profile developer

ssh 接続 #

  1. 秘密鍵をダウンロードする
    aws console にログインして、作成した秘密鍵を AWS Systems Manager (SSM) のパラメータストアから手動でダウンロードする。
    (ssm 設定済みの場合、aws cli で取得することも可能のはずで、手動でダウンロードする必要はないはず)
    https://ap-northeast-1.console.aws.amazon.com/systems-manager
    ダウンロードしたファイルは redmine50.key.pem として保存し、属性を600としておくこと。

    $ chmod 600 redmine50.key.pem
    

    aws console で ssm にアクセスした対象のkeypairを選択
    対象のkeypairが不明な場合、ec2インスタンスから確認できる


    値を表示状態にし、redmine50.key.pem ファイルとしてコピー&ペースとして保存する

  2. 生成したec2インスタンスのグローバルIPアドレスを確認
    aws コンソールにログインして生成した ec2 インスタンスのグローバルIPを確認する   ここでは、12.345.67.89 としておく。(画像は加工済みのため一致しない)
    aws console の ec2 にアクセス


    ec2 インスタンス(起動中)一覧を確認

    対象の ec2 一覧を選択しIPアドレスを確認

  3. 生成したec2インスタンスにssh 接続する

    $ ssh -i redmine50.key.pem ubuntu@12.345.67.89
    

.ssh/config に追記する場合 #

  1. .ssh/config に接続情報追記

    $ emacs ~/.ssh/config

    Host Redmine50
      HostName 12.345.67.89
      Port 22
      User ubuntu
      IdentityFile ~/.ssh/redmine50.key.pem
    
  2. redmine50.key.pem を ~/.ssh/ に配置
    $ cp redmine50.key.pem ~/.ssh/.
    
  3. ssh 接続
    $ ssh redmine50
    

その他 #

参考URL #