2
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?

【IaC超入門】30日でAWS CloudFormationとTerraformをマスターするロードマップ - 8日目: CloudFormationでEC2インスタンスをデプロイする

Posted at

CloudFormationでEC2インスタンスをデプロイする

はじめに

前回は、CloudFormationのパラメータとOutputs機能を使って、テンプレートの再利用性を高めました。今回は、いよいよS3バケットよりも複雑なリソースであるEC2インスタンスをデプロイしてみましょう。EC2インスタンスをデプロイするには、VPC、サブネット、セキュリティグループといった複数のリソースが必要になります。

実際のWebサーバーとして動作するEC2インスタンスを作成し、CloudFormationでのインフラ構築の全体像を理解しましょう。

前提条件

  • AWSアカウントが作成済みであること
  • EC2キーペアが作成済みであること(SSH接続用)
  • CloudFormationの基本概念を理解していること

ステップ1:テンプレートの作成

今回は、実際に動作するWebサーバーを構築するテンプレートを作成します。ファイル名は ec2-webserver-template.yaml としましょう。

AWSTemplateFormatVersion: "2010-09-09"
Description: "A template to create a secure EC2 instance running a web server with proper network setup."

Parameters:
  EnvironmentName:
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - staging
      - prod
    Description: "Environment name for resource tagging"

  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
      - t3.micro
      - t3.small
      - t3.medium
    Description: "EC2 instance type"
    ConstraintDescription: "Must be a valid EC2 instance type"

  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: "Name of an existing EC2 KeyPair for SSH access"
    ConstraintDescription: "Must be the name of an existing EC2 KeyPair"

  SSHLocation:
    Type: String
    Default: "0.0.0.0/0"
    Description: "The IP address range that can SSH to the EC2 instances"
    AllowedPattern: "^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$"
    ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x"

  LatestAmiId:
    Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
    Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
    Description: "The latest AMI ID for Amazon Linux 2"

Resources:
  # VPC Configuration
  WebServerVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-WebServer-VPC"
        - Key: Environment
          Value: !Ref EnvironmentName

  # Public Subnet
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref WebServerVPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs ""]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-Public-Subnet"
        - Key: Environment
          Value: !Ref EnvironmentName

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-IGW"
        - Key: Environment
          Value: !Ref EnvironmentName

  # Attach Internet Gateway to VPC
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref WebServerVPC
      InternetGatewayId: !Ref InternetGateway

  # Public Route Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref WebServerVPC
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-Public-RT"
        - Key: Environment
          Value: !Ref EnvironmentName

  # Public Route
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Associate Public Subnet with Route Table
  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

  # Security Group for Web Server
  WebServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${EnvironmentName}-WebServer-SG"
      GroupDescription: "Security group for web server - allows HTTP and SSH access"
      VpcId: !Ref WebServerVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: "HTTP access from anywhere"
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
          Description: "HTTPS access from anywhere"
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref SSHLocation
          Description: "SSH access from specified IP range"
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
          Description: "All outbound traffic"
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-WebServer-SG"
        - Key: Environment
          Value: !Ref EnvironmentName

  # IAM Role for EC2 Instance
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${EnvironmentName}-EC2-WebServer-Role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName

  # Instance Profile
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub "${EnvironmentName}-EC2-WebServer-Profile"
      Roles:
        - !Ref EC2InstanceRole

  # EC2 Instance
  WebServerInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref LatestAmiId
      KeyName: !Ref KeyPairName
      IamInstanceProfile: !Ref EC2InstanceProfile
      SubnetId: !Ref PublicSubnet
      SecurityGroupIds:
        - !Ref WebServerSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          
          # Create a simple HTML page
          cat > /var/www/html/index.html << 'EOF'
          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>CloudFormation Web Server</title>
              <style>
                  body { 
                      font-family: Arial, sans-serif; 
                      text-align: center; 
                      margin: 50px; 
                      background-color: #f0f8ff;
                  }
                  .container { 
                      max-width: 600px; 
                      margin: 0 auto; 
                      padding: 20px; 
                      border-radius: 10px; 
                      background-color: white;
                      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                  }
              </style>
          </head>
          <body>
              <div class="container">
                  <h1>🎉 CloudFormation Web Server</h1>
                  <p>Congratulations! Your EC2 instance is running successfully.</p>
                  <p><strong>Environment:</strong> ${EnvironmentName}</p>
                  <p><strong>Instance ID:</strong> <span id="instance-id">Loading...</span></p>
                  <p><strong>Region:</strong> ${AWS::Region}</p>
                  <hr>
                  <p><em>This server was deployed using AWS CloudFormation</em></p>
              </div>
              
              <script>
                  // Get instance metadata
                  fetch('http://169.254.169.254/latest/meta-data/instance-id')
                      .then(response => response.text())
                      .then(data => {
                          document.getElementById('instance-id').textContent = data;
                      })
                      .catch(error => {
                          document.getElementById('instance-id').textContent = 'Unable to fetch';
                      });
              </script>
          </body>
          </html>
          EOF
          
          # Set proper permissions
          chown apache:apache /var/www/html/index.html
          chmod 644 /var/www/html/index.html
          
          # Send success signal to CloudFormation
          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
    CreationPolicy:
      ResourceSignal:
        Count: 1
        Timeout: PT10M
    Tags:
      - Key: Name
        Value: !Sub "${EnvironmentName}-WebServer"
      - Key: Environment
        Value: !Ref EnvironmentName

Outputs:
  InstanceId:
    Description: "The Instance ID of the EC2 instance"
    Value: !Ref WebServerInstance
    Export:
      Name: !Sub "${AWS::StackName}-InstanceId"

  PublicIp:
    Description: "Public IP address of the EC2 instance"
    Value: !GetAtt WebServerInstance.PublicIp
    Export:
      Name: !Sub "${AWS::StackName}-PublicIp"

  WebsiteURL:
    Description: "URL of the web server"
    Value: !Sub "http://${WebServerInstance.PublicIp}"
    Export:
      Name: !Sub "${AWS::StackName}-WebsiteURL"

  SSHCommand:
    Description: "SSH command to connect to the instance"
    Value: !Sub "ssh -i your-key.pem ec2-user@${WebServerInstance.PublicIp}"

  VPCId:
    Description: "VPC ID of the created network"
    Value: !Ref WebServerVPC
    Export:
      Name: !Sub "${AWS::StackName}-VPCId"

  SubnetId:
    Description: "Subnet ID where the instance is deployed"
    Value: !Ref PublicSubnet
    Export:
      Name: !Sub "${AWS::StackName}-SubnetId"

  SecurityGroupId:
    Description: "Security Group ID attached to the instance"
    Value: !Ref WebServerSecurityGroup
    Export:
      Name: !Sub "${AWS::StackName}-SecurityGroupId"

ステップ2:テンプレートの詳細解説

このテンプレートでは、セキュリティとベストプラクティスを考慮した完全なWebサーバー環境を構築しています。

パラメータセクション

新しく追加されたパラメータ

  • EnvironmentName: 環境の識別とタグ付けのため
  • KeyPairName: SSH接続用のキーペア(必須
  • SSHLocation: SSH接続を許可するIPアドレス範囲(セキュリティ向上)

改善されたパラメータ

  • InstanceType: 使用可能なインスタンスタイプを制限
  • 各パラメータに適切な制約と説明を追加

ネットワークリソース

VPC設定

  • 全リソースに適切なタグを付与
  • DNS解決とホスト名の有効化

サブネット設定

  • MapPublicIpOnLaunch: trueでパブリックIP自動割り当て
  • アベイラビリティーゾーンを自動選択

セキュリティ設定

セキュリティグループ

  • HTTPS(443番ポート)も追加
  • SSH接続元IPを制限可能
  • 各ルールに説明を追加

IAMロール

  • EC2インスタンス用のIAMロールを追加
  • Session Manager経由でのアクセスが可能
  • セキュアな管理を実現

EC2インスタンス設定

User Data

  • Apache Webサーバーの自動インストール・設定
  • カスタムHTMLページの作成
  • インスタンスメタデータを表示する動的コンテンツ
  • CloudFormationシグナルによる起動完了通知

Creation Policy

  • インスタンスの正常起動を確認
  • タイムアウト設定で無限待機を防止

ステップ3:スタックのデプロイ

CloudFormationコンソールでのデプロイ

  1. 前準備:キーペアの作成

    • EC2コンソールでキーペアを作成
    • キーペア名をメモしておく
  2. スタックの作成

    • CloudFormationコンソールで「スタックの作成」をクリック
    • ec2-webserver-template.yamlファイルをアップロード
    • スタック名(例: MyWebServerStack)を入力
  3. パラメータの設定

    • KeyPairName: 作成済みのキーペア名を選択
    • SSHLocation: 必要に応じてSSH接続元IPを制限(例: 123.456.789.0/32
    • その他はデフォルトでOK
  4. デプロイ実行

    • 「AWS CloudFormationによってIAMリソースが作成される場合があることを承認します」にチェック
    • 「スタックの作成」をクリック

AWS CLIでのデプロイ(推奨)

# パラメータファイルの作成
cat > parameters.json << 'EOF'
[
  {
    "ParameterKey": "EnvironmentName",
    "ParameterValue": "dev"
  },
  {
    "ParameterKey": "InstanceType",
    "ParameterValue": "t2.micro"
  },
  {
    "ParameterKey": "KeyPairName",
    "ParameterValue": "your-key-pair-name"
  },
  {
    "ParameterKey": "SSHLocation",
    "ParameterValue": "0.0.0.0/0"
  }
]
EOF

# スタックのデプロイ
aws cloudformation create-stack \
  --stack-name MyWebServerStack \
  --template-body file://ec2-webserver-template.yaml \
  --parameters file://parameters.json \
  --capabilities CAPABILITY_NAMED_IAM

ステップ4:デプロイ結果の確認

基本的な確認

  1. CloudFormationコンソールでの確認

    • スタックのステータスがCREATE_COMPLETEになることを確認
    • 「出力」タブでWebサイトのURLを確認
  2. Webサイトへのアクセス

    • 出力されたWebsiteURLにブラウザでアクセス
    • カスタムHTMLページが表示されることを確認
  3. SSH接続のテスト

    ssh -i your-key.pem ec2-user@<PublicIP>
    

高度な確認

# スタック情報の取得
aws cloudformation describe-stacks --stack-name MyWebServerStack

# Webサイトのレスポンス確認
curl -I http://<PublicIP>

# インスタンスの状態確認
aws ec2 describe-instances --instance-ids <InstanceId>

セキュリティベストプラクティス

1. SSH接続の制限

# 特定のIPアドレスからのみSSH接続を許可
SSHLocation:
  Type: String
  Default: "203.0.113.0/32"  # あなたのパブリックIPに変更

2. Session Managerの活用

# SSHの代わりにSession Managerを使用
aws ssm start-session --target <InstanceId>

3. セキュリティグループの最小権限化

  • 必要なポートのみを開放
  • 信頼できるIPアドレスからのアクセスのみ許可

トラブルシューティング

よくある問題と解決法

1. キーペアが見つからないエラー

Parameter validation failed: Parameter value 'non-existent-key' does not exist
  • 解決策:EC2コンソールでキーペアの存在を確認

2. インスタンス起動の失敗

CREATE_FAILED - Received 0 SUCCESS signal(s) out of 1
  • 解決策:User Dataスクリプトのログを確認
  • /var/log/cloud-init-output.logでエラーを調査

3. Webサイトにアクセスできない

  • セキュリティグループの設定を確認
  • インスタンスのステータスチェックを確認
  • Apache の起動状態を確認
# SSH接続後の確認コマンド
sudo systemctl status httpd
sudo journalctl -u httpd

リソースのクリーンアップ

スタックの削除

# CloudFormation経由での削除(推奨)
aws cloudformation delete-stack --stack-name MyWebServerStack

# 削除の確認
aws cloudformation describe-stacks --stack-name MyWebServerStack

手動削除が必要な場合

  • Elastic IPアドレス(アタッチされている場合)
  • 追加で作成したセキュリティグループ
  • カスタムで作成したキーペア

まとめ

今回は、EC2インスタンスという複数の依存関係を持つリソースを、CloudFormationで安全にデプロイしました。この実践を通じて、以下の重要な概念を学びました:

学習のポイント

  1. 複合リソースの管理:VPC、サブネット、セキュリティグループなど、関連するリソースを一括管理
  2. セキュリティの考慮:IAMロール、セキュリティグループ、SSH接続制限などの実装
  3. 自動化の活用:User Dataによるソフトウェアの自動インストール・設定
  4. 依存関係の管理DependsOn!Refを使った適切なリソース順序制御
  5. 監視と検証:Creation Policyによる起動確認

次のステップ

  • Auto Scaling:負荷に応じたインスタンスの自動スケーリング
  • Load Balancer:複数インスタンスでの負荷分散
  • データベース統合:RDSとの連携
  • CI/CDパイプライン:自動デプロイの実装

次回は、テンプレートの更新と変更セットの活用方法について解説します。既存のインフラを安全に更新する手法を学びましょう。お楽しみに!

参考リンク

2
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
2
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?