v1.0 / 作成日: 2026-06-07 / IaC入門 / VPC + パブリックサブネット × 2 + EC2 × 2(Nginx)/ AWSマネジメントコンソール操作
インフラの構成をコード(テキストファイル)として記述し、そのコードを実行することでAWSリソースを自動的に作成・変更・削除する仕組みです。
同じテンプレートから何度でも同じ構成を再現できます。開発・ステージング・本番を同一テンプレートで管理できます。
テンプレートをGitで管理することで「誰が・いつ・何を変えたか」がコードの差分として追跡できます。
スタックを削除するだけで関連するすべてのリソースを一括削除できます。リソースの削除漏れが防げます。
手動でコンソールをポチポチする代わりに、テンプレートを流すだけで数十個のリソースを一括作成できます。
| 用語 | 説明 | 例え |
|---|---|---|
| テンプレート | 作成するリソースを定義したYAML/JSONファイル | 設計図・レシピ |
| スタック | テンプレートから作成されたリソースのグループ | 設計図から建てた建物 |
| リソース | スタックに含まれるAWSのリソース(VPC・EC2など) | 建物の各部屋・設備 |
| パラメーター | スタック作成時に外から渡せる変数 | 建物の広さを注文時に指定 |
| アウトプット | スタック作成後に参照できる値(IPなど) | 完成後の住所・電話番号 |
| 変更セット | スタック更新前に「何が変わるか」をプレビュー | リフォーム前の見積もり |
| ドリフト | コンソールで手動変更されテンプレートとズレた状態 | 設計図と実際の建物が違う状態 |
① テンプレート(YAML)の構造を理解する
② コンソールからスタックを作成しリソースが一括作成されることを体験する
③ 変更セットを使ってスタックを安全に更新する方法を学ぶ
④ スタック削除で全リソースが一括削除されることを体験する
| リソース | 論理ID(テンプレート内の名前) | 設定値 |
|---|---|---|
| VPC | VPC | 10.0.0.0/16 / DNS有効 |
| Internet Gateway | InternetGateway | VPCにアタッチ |
| パブリックサブネット(1a) | PublicSubnet1a | 10.0.1.0/24 / パブリックIP自動割り当て |
| パブリックサブネット(1c) | PublicSubnet1c | 10.0.2.0/24 / パブリックIP自動割り当て |
| ルートテーブル | PublicRouteTable | 0.0.0.0/0 → IGW |
| セキュリティグループ | WebServerSG | HTTP:80 from 0.0.0.0/0 |
| IAMロール | EC2SSMRole | AmazonSSMManagedInstanceCore(Session Manager用) |
| EC2(WebServer1) | WebServer1 | t2.micro / 1a / Nginx自動インストール |
| EC2(WebServer2) | WebServer2 | t2.micro / 1c / Nginx自動インストール |
合計 12個 のリソースをテンプレート1つで一括作成します。
| 関数 | 記述例 | 説明 |
|---|---|---|
| !Ref | !Ref PublicSubnet1a | リソースのIDまたはパラメーターの値を返す。EC2の !Ref はインスタンスID、サブネットの !Ref はサブネットIDを返す |
| !Sub | !Sub ${EnvironmentName}-vpc | 文字列内の変数(${変数名})を展開する。命名規則に便利 |
| !GetAtt | !GetAtt WebServer1.PublicIp | リソースの属性値を取得する。EC2の PublicIp や DNSName など |
| Fn::Base64 | Fn::Base64: !Sub | #!/bin/bash... |
文字列をBase64エンコード。EC2のUserDataに使用 |
| DependsOn | DependsOn: IGWAttachment | 明示的な依存関係を宣言。CFnが自動で依存解決できない場合に使用 |
リソースタイプは AWS::サービス名::リソース種別 の形式です。
| リソースタイプ | 作成されるリソース |
|---|---|
AWS::EC2::VPC | VPC |
AWS::EC2::Subnet | サブネット |
AWS::EC2::SecurityGroup | セキュリティグループ |
AWS::EC2::Instance | EC2インスタンス |
AWS::IAM::Role | IAMロール |
AWS::RDS::DBInstance | RDSインスタンス |
AWS::ElasticLoadBalancingV2::LoadBalancer | ALB |
# AMI IDをハードコードせず、SSM Parameter StoreからAmazon Linux 2023の最新AMIを取得 ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}'
UserData: Fn::Base64: !Sub | #!/bin/bash dnf update -y dnf install -y nginx systemctl enable nginx systemctl start nginx echo "<h1>${EnvironmentName} - WebServer 1</h1>" \ > /usr/share/nginx/html/index.html
Fn::Base64 でBase64エンコードして渡します。!Sub でパラメーターの値(${EnvironmentName})を埋め込めます。PublicRoute: Type: AWS::EC2::Route DependsOn: IGWAttachment # IGWがVPCにアタッチされてからルート作成 Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway
!Ref や !GetAtt で自動依存解決しますが、この場合はアタッチメントが間接的な依存のため DependsOn で明示します。| 項目 | 内容 |
|---|---|
| AWSアカウント | AdministratorAccess権限を持つIAMユーザー |
| リージョン | 東京(ap-northeast-1)― コンソール右上で必ず確認 |
| ブラウザ | Chrome / Edge / Firefox(最新版) |
| テンプレートファイル | cfn-handson.yaml(本手順書と同梱)と cfn-handson-v2.yaml(更新用) |
本手順書と同梱の cfn-handson.yaml をローカルPC上に保存します。テンプレートの全内容は以下のとおりです(コピー&貼り付けも可能です):
AWSTemplateFormatVersion: '2010-09-09' Description: 'CFn Handson v1 - VPC + Public Subnets + EC2 x2 (Nginx)' Parameters: EnvironmentName: Type: String Default: handson Description: リソース名のプレフィックス InstanceType: Type: String Default: t2.micro AllowedValues: - t2.micro - t2.small - t3.micro Resources: # VPC VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-vpc # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${EnvironmentName}-igw IGWAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway # Subnets PublicSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.1.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-subnet-public-1a PublicSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.2.0/24 AvailabilityZone: ap-northeast-1c MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-subnet-public-1c # Route Table PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName}-rt-public PublicRoute: Type: AWS::EC2::Route DependsOn: IGWAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway Subnet1aRouteAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1a RouteTableId: !Ref PublicRouteTable Subnet1cRouteAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1c RouteTableId: !Ref PublicRouteTable # Security Group WebServerSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Web server security group VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub ${EnvironmentName}-sg-web # IAM Role (Session Manager) EC2SSMRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${EnvironmentName}-ec2-ssm-role AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore EC2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref EC2SSMRole # EC2 Instance 1 (1a) WebServer1: Type: AWS::EC2::Instance Properties: ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}' InstanceType: !Ref InstanceType SubnetId: !Ref PublicSubnet1a SecurityGroupIds: - !Ref WebServerSG IamInstanceProfile: !Ref EC2InstanceProfile Tags: - Key: Name Value: !Sub ${EnvironmentName}-web-1a - Key: Version Value: v1 UserData: Fn::Base64: !Sub | #!/bin/bash dnf update -y dnf install -y nginx systemctl enable nginx systemctl start nginx echo "<h1>${EnvironmentName} - WebServer 1 (1a) - v1</h1>" \ > /usr/share/nginx/html/index.html # EC2 Instance 2 (1c) WebServer2: Type: AWS::EC2::Instance Properties: ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}' InstanceType: !Ref InstanceType SubnetId: !Ref PublicSubnet1c SecurityGroupIds: - !Ref WebServerSG IamInstanceProfile: !Ref EC2InstanceProfile Tags: - Key: Name Value: !Sub ${EnvironmentName}-web-1c - Key: Version Value: v1 UserData: Fn::Base64: !Sub | #!/bin/bash dnf update -y dnf install -y nginx systemctl enable nginx systemctl start nginx echo "<h1>${EnvironmentName} - WebServer 2 (1c) - v1</h1>" \ > /usr/share/nginx/html/index.html Outputs: VPCId: Description: 作成されたVPCのID Value: !Ref VPC Export: Name: !Sub ${EnvironmentName}-VpcId WebServer1IP: Description: WebServer1のパブリックIP Value: !GetAtt WebServer1.PublicIp WebServer2IP: Description: WebServer2のパブリックIP Value: !GetAtt WebServer2.PublicIp WebServer1URL: Description: WebServer1のURL Value: !Sub http://${WebServer1.PublicIp} WebServer2URL: Description: WebServer2のURL Value: !Sub http://${WebServer2.PublicIp}
| 項目 | 値 |
|---|---|
| テンプレートソース | テンプレートファイルのアップロード |
| ファイル | cfn-handson.yaml を選択してアップロード |
| 項目 | 値 |
|---|---|
| スタック名 | cfn-handson-stack |
| EnvironmentName(パラメーター) | handson(デフォルトのまま) |
| InstanceType(パラメーター) | t2.micro(デフォルトのまま) |
Parameters セクションで定義した変数がここに表示されます。スタック作成時に値を変えることで同じテンプレートから異なる構成を作れます。このページはデフォルトのまま変更不要です。
次へページ最下部の 「機能」 セクションにある以下のチェックボックスにチェックを入れます:
AWS::IAM::Role が含まれているためです。各リソースの作成状況が時系列で表示されます。全リソースが CREATE_COMPLETE になるまで待ちます(約3〜5分)。
| ステータス | 意味 |
|---|---|
CREATE_IN_PROGRESS | リソース作成中 |
CREATE_COMPLETE | リソース作成完了 |
CREATE_FAILED | 作成失敗 → 「理由」列でエラー内容を確認 |
ROLLBACK_COMPLETE | 失敗後にロールバック完了 |
テンプレートに定義した12個のリソースが一覧表示されます。各行の「物理ID」をクリックするとそのリソースのコンソールページに直接ジャンプできます。
| 論理ID(テンプレートの名前) | リソースタイプ | 確認内容 |
|---|---|---|
| VPC | AWS::EC2::VPC | handson-vpc が作成されているか |
| WebServer1 | AWS::EC2::Instance | handson-web-1a が「実行中」か |
| WebServer2 | AWS::EC2::Instance | handson-web-1c が「実行中」か |
| EC2SSMRole | AWS::IAM::Role | handson-ec2-ssm-role が作成されているか |
テンプレートの Outputs セクションで定義した値が表示されます:
| キー | 表示される値 |
|---|---|
| WebServer1URL | http://xx.xx.xx.xx(WebServer1のURL) |
| WebServer2URL | http://yy.yy.yy.yy(WebServer2のURL) |
| WebServer1IP | WebServer1のパブリックIPアドレス |
| WebServer2IP | WebServer2のパブリックIPアドレス |
| VPCId | 作成されたVPCのID(vpc-xxxxxxxx) |
WebServer1URL の値(http://xx.xx.xx.xx)をブラウザで開きます。
テンプレートを修正してスタックを更新します。変更セット(Change Set) を使うことで、実際に更新を適用する前に「何が変わるか」を安全に確認できます。
同梱の cfn-handson-v2.yaml では以下の3箇所を変更しています:
| 変更箇所 | 変更種別 | 内容 | インスタンス置き換え |
|---|---|---|---|
| WebServerSG のインバウンドルール | 変更 | HTTPS(TCP:443)のルールを追加 | なし(インプレース更新) |
| WebServer1 / WebServer2 のタグ | 変更 | Version: v1 → Version: v2 |
なし(インプレース更新) |
| Outputs に StackVersion を追加 | 追加 | 新しい出力値 v2 を追加 |
— |
| 項目 | 値 |
|---|---|
| テンプレートソース | テンプレートファイルのアップロード |
| ファイル | cfn-handson-v2.yaml を選択してアップロード |
パラメーターはデフォルトのまま → 次へ → オプションはデフォルトのまま → 次へ
IAM承認のチェックを入れて → 変更セット名 cfn-handson-changeset-v2 を入力 → 変更セットの作成
変更セット一覧 → cfn-handson-changeset-v2 を選択 → 「変更」タブで変更内容を確認します:
| アクション | 論理ID | リソースタイプ | 置き換え |
|---|---|---|---|
| Modify | WebServerSG | AWS::EC2::SecurityGroup | False(置き換えなし) |
| Modify | WebServer1 | AWS::EC2::Instance | False(置き換えなし) |
| Modify | WebServer2 | AWS::EC2::Instance | False(置き換えなし) |
内容を確認したら 変更セットの実行 をクリックします。
スタックのステータスが UPDATE_IN_PROGRESS → UPDATE_COMPLETE に変わることを確認します。
「出力」タブに StackVersion: v2 が追加されていれば更新成功です。
CFnの最大の利点のひとつ:スタックを削除するだけで作成したすべてのリソースが自動的に削除されます。
「スタックの削除」ダイアログで 削除 をクリックします。
ステータスが DELETE_IN_PROGRESS → DELETE_COMPLETE(リストから消える)に変わります。
EC2コンソール → 「インスタンス」で handson-web-1a と handson-web-1c が「終了済み」になっていることを確認します。
VPCコンソール → 「お使いのVPC」で handson-vpc が削除されていることを確認します。
スタック削除前に、わざとEC2のタグを手動で変更した後にドリフト検出を実行することで、CFnとAWSの実態のズレを検出できます。
EC2コンソール → handson-web-1a → タグ を編集 → Version タグの値を v2 → v99 に変更します。
検出完了後 → スタックアクション → 「ドリフトの表示」で WebServer1 が 「MODIFIED」(修正済み)と表示されることを確認します。