ぶろとよ

ネットワーク系クラウドエンジニア(AWS)の技術ブログ。自動化に興味があるためAWS CLIを勉強&アウトプット中。

AWS CLI:VPC~EC2の構築をCLIだけでやってみる!(PowerShell版)

f:id:toyokky:20211010103144p:plain


更新履歴
2021/10/16, 構成図のNameタグを修正。Nameの頭にシステム名 blog- を付与。
2021/10/16, IDを変数へ格納する際、変数の中身を表示する処理を追加。


AWSアカウントの1年無料期間が過ぎてしまったので、以前のアカウントを削除して新規でAWSアカウントを採取しました。
そのため、これまでのAWS環境を放棄、、、。


今後も僕は無料期間が終わる1年毎にAWSアカウントを作り変えるのでしょう、、、、それならば新規で作りなおすための記事を書こう!どうせならAWS CLIだけで構築してみよう!と思い立ちました(汗


本ページに記載した構成図の内容を、可能な限りAWS CLIで書いてみようと思います。
僕はWindowsをメインで使用しているため、PowerShell版のAWS CLIで操作します。

※最近、AWSのCloudShell(Amazon Linux)が気に入っているので、そちらもいつか別記事で書きたい。


この記事を読むことで出来ること

- <mark>AWS CLIでVPC~EC2環境を構築できる。</mark>
- 付随するAWSリソースもAWS CLIで構築できる。
    - 作れるリソースは`目次`を確認してください。
- PowerShell版AWS CLIの使い方の参考になる、かも。

目次

検証環境

  • 検証日: 2021/10/16
  • PC環境:
    • Windows10 Home バージョン 21H1
      • AWS CLI: aws-cli/2.1.34 Python/3.8.8 Windows/10 exe/AMD64 prompt/off
        • -- profile default : 読み取り専用
        • -- profile aws_RW : 書き込み権限あり
          • ※ 誤操作防止のため、変更操作時のみ-- profileオプションを付けてます。
      • PowerShell: Ver7.1.5

注意事項

  • 本記事の内容はWindows PowerShellで検証しています。
  • AWS CLIはバージョン2を使用しています。
    • 本記事のコマンド出力でYAMLを使用してますが、これはバージョン2からの機能です。
  • コマンド内の変数やパラメータやは検証で使用したものを記載しています。ご自身の環境に合わせ、書き換えて使用してください。
  • 個人で検証しているため実行結果に責任は持てません。必ずご自身でも検証してから使用してください。
  • AWSサービス自体の説明は書かず、AWS CLIコマンドを中心に書いています。


事前準備

AWS CLI v2のインストール

[https://blog.toyokky.com/entry/2021/04/04/162137:embed:cite]

本記事で使用しているAWSリソースの命名規則

[https://blog.toyokky.com/entry/20210911_aws_meimeikisoku:embed:cite]


1. 全体構成

1.1. 構成図

[f:id:toyokky:20211016193448p:plain]

1.2. 作成するAWSリソースの一覧

リソース種別 AZ 個別情報 Name CIDR, その他
VPC - - blog-P1x-Vpc 10.0.0.0/16
サブネット 1a パブリック blog-P1a-Sub-Pub01 10.0.11.0/24
1a プライベート blog-P1a-Sub-Pri01 10.0.12.0/24
1a プロテクト blog-P1a-Sub-Pro01 10.0.13.0/24
1c パブリック blog-P1c-Sub-Pub01 10.0.21.0/24
1c プライベート blog-P1c-Sub-Pri01 10.0.22.0/24
1c プロテクト blog-P1c-Sub-Pro01 10.0.23.0/24
Internet   Gateway - - blog-P1x-Igw -
Route Table - パブリック blog-P1x-Rtb-Pub01 -
- プライベート blog-P1x-Rtb-Pri01 -
- プロテクト blog-P1x-Rtb-Pro01 -
IAMロール - EC2をCW,SSMで管理 blog-P1x-Irl-Mnt-Cw-And-Ssm AmazonEC2RoleforSSM
     CloudWatchAgentAdminPolicy
     CloudWatchAgentServerPolicy
- VPCフローログ用 blog-P1x-Irl-Mnt-Vpcflowlogs インラインポリシーを作成
VPCフローログ - すべて記録 blog-P1x-Vfl-All -
CloudWatchロググループ - VPCフローログ用 /aws/blog-P1x-Vpc/flowlogs 保管期間 1ヶ月
Key Pair - 共通利用 blog-Pxx-Key-Toyokey -
SecurityGroup(共有) - 共通利用 blog-P1a-Sgr-Mnt-Share -
EC2インスタンス 1a パブリック blog-P1a-Ec2-Nat-NatServer -
EBS 1a /dev/xvda blog-P1a-Ebs-Nat-NatServer-xvda 16GB, gp3
ENI 1a eth0 blog-P1a-Eni-Nat-NatServer-Eth0 10.0.11.4/32
SecurityGroup(個別) 1a - blog-P1a-Sgr-Nat-NatServer -
EC2インスタンス 1a パブリック blog-P1a-Ec2-Srv-TestUser -
EBS 1a /dev/sda1 blog-P1a-Ec2-Srv-TestUser-Sda1 50GB, gp3
ENI 1a eth0 blog-P1a-Ec2-Srv-TestUser-Eth0 10.0.12.4/32
SecurityGroup(個別) 1a - blog-P1a-Sgr-Srv-TestUser -
Auto Recovery - Ec2-Nat-NatServer blog-P1a-Cwa-Nat-NatServer-Autorecovery -
- Ec2-Srv-TestUser blog-P1a-Cwa-Srv-TestUser-Autorecovery -


2. VPC、サブネット

2.1 VPCの作成

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# VPC作成
${profile} = "aws_RW"
${vpc_name} = "blog-P1x-Vpc"
${vpc_cidr} = "10.0.0.0/16"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-vpc `
    --profile ${profile} `
    --cidr-block ${vpc_cidr} `
    --instance-tenancy default `
    --tag-specifications "ResourceType=vpc, `
        Tags=[{Key=Name,Value=${vpc_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

2.2 サブネットの作成

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 変数(共通利用)
${profile} = "aws_RW"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

# VPC-IDを変数へ格納する(共通利用)
${vpc_id} = aws ec2 describe-vpcs `
    --filters "Name=tag:Name,Values=${vpc_name}" `
    --query "Vpcs[].VpcId" `
    --output text `
    ; ${vpc_id}


# -------------------------------------------
# サブネット作成: AZ-1a パブリック
${sub_name} = "blog-P1a-Sub-Pub01"
${sub_cidr} = "10.0.11.0/24"
${az} = "ap-northeast-1a"

aws ec2 create-subnet `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --availability-zone ${az} `
    --cidr-block ${sub_cidr} `
    --tag-specifications "ResourceType=subnet, `
        Tags=[{Key=Name,Value=${sub_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"


#"# -------------------------------------------
# サブネット作成: AZ-1a プライベート
${sub_name} = "blog-P1a-Sub-Pri01"
${sub_cidr} = "10.0.12.0/24"
${az} = "ap-northeast-1a"

aws ec2 create-subnet `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --availability-zone ${az} `
    --cidr-block ${sub_cidr} `
    --tag-specifications "ResourceType=subnet, `
        Tags=[{Key=Name,Value=${sub_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"


#"# -------------------------------------------
# サブネット作成: AZ-1a プロテクト
${sub_name} = "blog-P1a-Sub-Pro01"
${sub_cidr} = "10.0.13.0/24"
${az} = "ap-northeast-1a"

aws ec2 create-subnet `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --availability-zone ${az} `
    --cidr-block ${sub_cidr} `
    --tag-specifications "ResourceType=subnet, `
        Tags=[{Key=Name,Value=${sub_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"


#"# -------------------------------------------
# サブネット作成: AZ-1c パブリック
${sub_name} = "blog-P1c-Sub-Pub01"
${sub_cidr} = "10.0.21.0/24"
${az} = "ap-northeast-1c"

aws ec2 create-subnet `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --availability-zone ${az} `
    --cidr-block ${sub_cidr} `
    --tag-specifications "ResourceType=subnet, `
        Tags=[{Key=Name,Value=${sub_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"


#"# -------------------------------------------
# サブネット作成: AZ-1c プライベート
${sub_name} = "blog-P1c-Sub-Pri01"
${sub_cidr} = "10.0.22.0/24"
${az} = "ap-northeast-1c"

aws ec2 create-subnet `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --availability-zone ${az} `
    --cidr-block ${sub_cidr} `
    --tag-specifications "ResourceType=subnet, `
        Tags=[{Key=Name,Value=${sub_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"


#"# -------------------------------------------
# サブネット作成: AZ-1c プロテクト
${sub_name} = "blog-P1c-Sub-Pro01"
${sub_cidr} = "10.0.23.0/24"
${az} = "ap-northeast-1c"

aws ec2 create-subnet `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --availability-zone ${az} `
    --cidr-block ${sub_cidr} `
    --tag-specifications "ResourceType=subnet, `
        Tags=[{Key=Name,Value=${sub_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

2.3. 確認コマンド

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# VPC
## すべて表示
aws ec2 describe-vpcs

## Nameタグを指定して表示
${vpc_name} = "blog-P1x-Vpc"
aws ec2 describe-vpcs --filters "Name=tag:Name,Values=${vpc_name}"


# -------------------------------------------
# サブネット
## すべて表示
aws ec2 describe-subnets

## Nameタグを指定して表示
${sub_name} = "blog-P1a-Sub-Pub01"
aws ec2 describe-subnets --filters "Name=tag:Name,Values=${sub_name}"


3. Gateway、RouteTable、ルート追加

3.1. InternetGatewayの作成

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# InternetGateway作成
${profile} = "aws_RW"
${igw_name} = "blog-P1x-Igw"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-internet-gateway `
    --profile ${profile} `
    --tag-specifications "ResourceType=internet-gateway, `
        Tags=[{Key=Name,Value=${igw_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

3.2. InternetGatewayとVPCを関連付ける

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# VPC-IDを変数へ格納
${vpc_name} = "blog-P1x-Vpc"

${vpc_id} = aws ec2 describe-vpcs `
    --filters "Name=tag:Name,Values=${vpc_name}" `
    --query "Vpcs[].VpcId" `
    --output text `
    ; ${vpc_id}

# InternetGateway-IDを変数へ格納
${igw_name} = "blog-P1x-Igw"

${igw_id} = aws ec2 describe-internet-gateways `
    --filters "Name=tag:Name,Values=${igw_name}" `
    --query "InternetGateways[].InternetGatewayId" `
    --output text `
    ; ${igw_id}

# InternetGatewayとVPCを関連付ける
${profile} = "aws_RW"

aws ec2 attach-internet-gateway `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --internet-gateway-id ${igw_id}

3.3. RouteTableの作成

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 変数(共通利用)
${profile}        = "aws_RW"
${tag_env}        = "Product"
${tag_system}     = "HatenaBlog"

# VPC-IDを変数へ格納する(共通利用)
${vpc_name} = "blog-P1x-Vpc"

${vpc_id} = aws ec2 describe-vpcs `
    --filters "Name=tag:Name,Values=${vpc_name}" `
    --query "Vpcs[].VpcId" `
    --output text `
    ; ${vpc_id}

# -------------------------------------------
# RouteTable作成: パブリック
${rtb_name} = "blog-P1x-Rtb-Pub01"

aws ec2 create-route-table `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --tag-specifications "ResourceType=route-table, `
        Tags=[{Key=Name,Value=${rtb_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

#"# -------------------------------------------
# RouteTable作成: プライベート
${rtb_name} = "blog-P1x-Rtb-Pri01"

aws ec2 create-route-table `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --tag-specifications "ResourceType=route-table, `
        Tags=[{Key=Name,Value=${rtb_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

#"# -------------------------------------------
# RouteTable作成: プロテクト
${rtb_name} = "blog-P1x-Rtb-Pro01"

aws ec2 create-route-table `
    --profile ${profile} `
    --vpc-id ${vpc_id} `
    --tag-specifications "ResourceType=route-table, `
        Tags=[{Key=Name,Value=${rtb_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

3.4. RouteTableへルートを追加する

本構成で追加するルートは、以下の2本です。

  • パブリック
    • blog-P1x-Rtb-Pub01
      • 宛先: 0.0.0.0/0 , ターゲット: blog-P1x-Igw (InternetGateway)
  • プライベート
    • blog-P1x-Rtb-Pri01
      • 宛先: 0.0.0.0/0 , ターゲット: Ec2-Nat-NatServer (NATインスタンス)

ただ、NATインスタンスはまだ未作成のためプライベート側のルートは追加できません。NATインスタンス作成後に改めて設定します。

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# RouteTable-IDを変数へ格納する
${rtb_name} = "blog-P1x-Rtb-Pub01"

${rtb_id} = aws ec2 describe-route-tables `
    --filters "Name=tag:Name,Values=${rtb_name}" `
    --query "RouteTables[].RouteTableId" `
    --output text `
    ; ${rtb_id}

# InternetGateway-IDを変数へ格納
${igw_name} = "blog-P1x-Igw"

${igw_id} = aws ec2 describe-internet-gateways `
    --filters "Name=tag:Name,Values=${igw_name}" `
    --query "InternetGateways[].InternetGatewayId" `
    --output text `
    ; ${igw_id}

# Route追加: パブリック
${profile} = "aws_RW"
${dest_cidr} = "0.0.0.0/0"

aws ec2 create-route `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --destination-cidr-block ${dest_cidr} `
    --gateway-id ${igw_id}

3.5. RouteTableとサブネットの関連付け

関連付けの組み合わせは以下です。

  • パブリック
    • blog-P1x-Rtb-Pub01
      • blog-P1a-Sub-Pub01 (AZ-1a)
      • blog-P1c-Sub-Pub01 (AZ-1c)
  • プライベート
    • blog-P1x-Rtb-Pri01
      • blog-P1a-Sub-Pri01 (AZ-1a)
      • blog-P1c-Sub-Pri01 (AZ-1c)
  • プロテクト
    • blog-P1x-Rtb-Pro01
      • blog-P1a-Sub-Pro01 (AZ-1a)
      • blog-P1c-Sub-Pro01 (AZ-1c)

1つのRouteTableへ2つ以上のサブネットを関連付けする場合、関連付けコマンドassociate-route-tableはサブネット数分の実行が必要です。同時に2つは付けられない。

また、対象のサブネットがDefault RouteTableに紐付いている必要があります。他のRouteTableへ明示的に関連付けしているサブネットは別の方法で付け替えますが、方法が面倒になるためGUIで付け替えたほうが楽です。

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# --- パブリック ---
# RouteTable(パブリック)とサブネットを関連つける
## RouteTable-IDを変数へ格納する
${rtb_name} = "blog-P1x-Rtb-Pub01"

${rtb_id} = aws ec2 describe-route-tables `
    --filters "Name=tag:Name,Values=${rtb_name}" `
    --query "RouteTables[].RouteTableId" `
    --output text `
    ; ${rtb_id}

## Subnet-IDを変数へ格納する
${sub_name_1a} = "blog-P1a-Sub-Pub01"
${sub_name_1c} = "blog-P1c-Sub-Pub01"

${sub_id_1a} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name_1a}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id_1a}

${sub_id_1c} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name_1c}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id_1c}

## RouteTableへサブネットを関連付ける
${profile} = "aws_RW"

aws ec2 associate-route-table `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --subnet-id ${sub_id_1a}

aws ec2 associate-route-table `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --subnet-id ${sub_id_1c}


# -------------------------------------------
# ---プライベート ---
# RouteTable(プライベート)とサブネットを関連つける
## RouteTable-IDを変数へ格納する
${rtb_name} = "blog-P1x-Rtb-Pri01"

${rtb_id} = aws ec2 describe-route-tables `
    --filters "Name=tag:Name,Values=${rtb_name}" `
    --query "RouteTables[].RouteTableId" `
    --output text `
    ; ${rtb_id}

## Subnet-IDを変数へ格納する
${sub_name_1a} = "blog-P1a-Sub-Pri01"
${sub_name_1c} = "blog-P1c-Sub-Pri01"

${sub_id_1a} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name_1a}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id_1a}

${sub_id_1c} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name_1c}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id_1c}

## RouteTableへサブネットを関連付ける
${profile} = "aws_RW"

aws ec2 associate-route-table `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --subnet-id ${sub_id_1a}

aws ec2 associate-route-table `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --subnet-id ${sub_id_1c}


# -------------------------------------------
# --- プロテクト ---
# RouteTable(プロテクト)とサブネットを関連つける
## RouteTable-IDを変数へ格納する
${rtb_name} = "blog-P1x-Rtb-Pro01"

${rtb_id} = aws ec2 describe-route-tables `
    --filters "Name=tag:Name,Values=${rtb_name}" `
    --query "RouteTables[].RouteTableId" `
    --output text `
    ; ${rtb_id}

## Subnet-IDを変数へ格納する
${sub_name_1a} = "blog-P1a-Sub-Pro01"
${sub_name_1c} = "blog-P1c-Sub-Pro01"

${sub_id_1a} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name_1a}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id_1a}

${sub_id_1c} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name_1c}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id_1c}

## RouteTableへサブネットを関連付ける
${profile} = "aws_RW"

aws ec2 associate-route-table `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --subnet-id ${sub_id_1a}

aws ec2 associate-route-table `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --subnet-id ${sub_id_1c}

3.6. 確認コマンド

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# InternetGateway
## すべて表示
aws ec2 describe-internet-gateways

## Nameタグを指定して表示
${igw_name} = "blog-P1x-Igw"
aws ec2 describe-internet-gateways --filters "Name=tag:Name,Values=${igw_name}"

# -------------------------------------------
# RouteTable
## すべて表示
aws ec2 describe-route-tables

## Nameタグを指定して表示
${rtb_name} = "blog-P1x-Rtb-Pub01"
aws ec2 describe-route-tables --filters "Name=tag:Name,Values=${rtb_name}"


4. IAMロール、IAMポリシー

4.1. VPCフローログ用のIAMロール

IAMロールへインラインポリシーとして直接ポリシーを書き込みます。

GUIでVPCフローログを作成する際、ついでにIAMロールの作成が出来ますが、それと同等のものを作成します。

4.1.1 JSONファイルの作成

以下のJSONファイルを作成し、変数 ${work_dir} で指定したフォルダへ出力します。

  • iam_assumerole_vpcflowlogs.json : AssumeRole(信頼されたエンティティ)の設定用
  • iam_policy_vpcflowlogs.json : VPCフローログのインラインポリシー作成用
# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 作業フォルダの作成
${date} = Get-Date -Format yyyy-MM-dd
${work_dir} = "C:\#temp\awscli\${date}"
New-Item "${work_dir}" -itemType Directory -Force

# (参考)作成した作業フォルダを開くコマンド
Invoke-Item "${work_dir}"



# JSONファイルの作成: AssumeRole VPCflowlogs, 文字コード: UTF-8
${json_assume_name} = "iam_assumerole_vpcflowlogs.json"
${service_type} = "vpc-flow-logs"

${json_content} = @"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "${service_type}.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
"@
Write-Output ${json_content} | Out-File -Encoding ascii ${work_dir}\${json_assume_name}


# -------------------------------------------
# JSONファイルの作成: ポリシー, 文字コード: UTF-8
${json_policy_name} = "iam_policy_vpcflowlogs.json"

${json_content} = @"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
"@
Write-Output ${json_content} | Out-File -Encoding ascii ${work_dir}\${json_policy_name}

4.1.2. IAMロールの作成、インラインポリシーの適用

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# IAMロールの作成
${profile} = "aws_RW"
${iam_role_name} = "blog-P1x-Irl-Mnt-Vpcflowlogs"
${iam_role_description} = "Allows VPCflowlogs to call CloudWatch Logs on your behalf."
${date} = Get-Date -Format yyyy-MM-dd
${work_dir} = "C:\#temp\awscli\${date}"
${json_assume_name} = "iam_assumerole_vpcflowlogs.json"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws iam create-role `
    --profile ${profile}  `
    --role-name ${iam_role_name} `
    --description ${iam_role_description} `
    --assume-role-policy-document file://${work_dir}\${json_assume_name} `
    --tags "Key=Name,Value=${iam_role_name}" `
            "Key=EMSEnv,Value=${tag_env}" `
            "Key=EMSSystem,Value=${tag_system}"

# IAMロールへインラインポリシーを適用する
${iam_policy_name} = "blog-P1x-Ipl-Mnt-Vpcflowlogs"
${json_policy_name} = "iam_policy_vpcflowlogs.json"

aws iam put-role-policy `
    --profile ${profile}  `
    --role-name ${iam_role_name} `
    --policy-name ${iam_policy_name} `
    --policy-document file://${work_dir}\${json_policy_name}

4.2. EC2へ適用する管理用のIAMロール(CloudWatch, SystemsManager)

IAMロールへAWS管理ポリシーを割り当てる方法を使用します。

4.2.1 JSONファイルの作成

以下のJSONファイルを作成し、変数 ${work_dir} で指定したフォルダへ出力します。

  • iam_assumerole_ec2.json : AssumeRole(信頼されたエンティティ)の設定用
# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 作業フォルダの作成
${date} = Get-Date -Format yyyy-MM-dd
${work_dir} = "C:\#temp\awscli\${date}"
New-Item "${work_dir}" -itemType Directory -Force

# (参考)作成した作業フォルダを開くコマンド
Invoke-Item "${work_dir}"


# JSONファイルの作成: AssumeRole EC2, 文字コード: UTF-8
${json_assume_name} = "iam_assumerole_ec2.json"
${service_type} = "ec2"

${json_content} = @"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "${service_type}.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
"@
Write-Output ${json_content} | Out-File -Encoding ascii ${work_dir}\${json_assume_name}

4.2.2. IAMロールの作成、インスタンスプロファイルの作成

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# IAMロールの作成
${profile} = "aws_RW"
${iam_role_name} = "blog-P1x-Irl-Mnt-Cw-And-Ssm"
${iam_role_description} = "Allows EC2 to call CloudWatch and SystemsManager on your behalf."
${date} = Get-Date -Format yyyy-MM-dd
${work_dir} = "C:\#temp\awscli\${date}"
${json_assume_name} = "iam_assumerole_ec2.json"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws iam create-role `
    --profile ${profile}  `
    --role-name ${iam_role_name} `
    --description ${iam_role_description} `
    --assume-role-policy-document file://${work_dir}\${json_assume_name} `
    --tags "Key=Name,Value=${iam_role_name}" `
    "Key=EMSEnv,Value=${tag_env}" `
    "Key=EMSSystem,Value=${tag_system}"


# IAMロールへAWS管理ポリシーをアタッチする
aws iam attach-role-policy `
    --profile ${profile} `
    --role-name ${iam_role_name} `
    --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM

aws iam attach-role-policy `
    --profile ${profile} `
    --role-name ${iam_role_name} `
    --policy-arn arn:aws:iam::aws:policy/CloudWatchAgentAdminPolicy

aws iam attach-role-policy `
    --profile ${profile} `
    --role-name ${iam_role_name} `
    --policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy


## インスタンスプロファイルの作成
## ※GUIでは意識して作成しないが、EC2等からIAMロールが見えるようにする設定で、CLI作業では作成が必要。インスタンスプロファイル名はIAMロールの名前を流用で大丈夫です。
aws iam create-instance-profile `
    --profile ${profile}  `
    --instance-profile-name ${iam_role_name}

## IIAMロールとインスタンスプロファイルをアタッチする
aws iam add-role-to-instance-profile `
    --profile ${profile}  `
    --role-name ${iam_role_name} `
    --instance-profile-name ${iam_role_name}

4.3. 確認コマンド

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# IAMロール
## すべて表示する
aws iam list-roles

## 名前を指定して表示
${iam_role_name} = "blog-P1x-Irl-Mnt-Cw-And-Ssm"

aws iam get-role `
    --role-name ${iam_role_name}

## IAMロールにアタッチされているIAMポリシーを表示
aws iam list-attached-role-policies `
    --role-name ${iam_role_name}


# -------------------------------------------
# インスタンスプロファイル
## すべて表示する
aws iam list-instance-profiles

## 名前を指定して表示
${iam_role_name} = "blog-P1x-Irl-Mnt-Cw-And-Ssm"

aws iam get-instance-profile `
    --instance-profile-name ${iam_role_name}


5. CloudWatch Logsロググループ、VPCフローログ

5.1. CloudWatch Logs ロググループの作成

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# ロググループ作成
${profile} = "aws_RW"
${loggroup_name} = "/aws/blog-P1x-Vpc/flowlogs"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"
${retention_days} = "14"
    # 選択値: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653

aws logs create-log-group `
    --profile ${profile} `
    --log-group-name ${loggroup_name} `
    --tags "Name=${loggroup_name}, `
        Env=${tag_env}, `
        System=${tag_system}"

# ログの保管期間(Retention)設定
aws logs put-retention-policy `
    --profile ${profile} `
    --log-group-name ${loggroup_name} `
    --retention-in-days ${retention_days}

5.2. VPCフローログの作成

VPCへVPCフローログを設定します。ログ形式はデフォルトです。

# ログ形式(デフォルト)
`${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}`

# サンプル: VPCフローログ
2021-09-18T08:15:14.000Z 2 123456789012 eni-0ffab6a423aba9790 10.0.11.4 45.76.211.39 123 123 17 1 76 1631952914 1631952973 REJECT OK
2021-09-18T08:15:14.000Z 2 123456789012 eni-0ffab6a423aba9790 10.0.11.4 52.119.219.52 36980 443 6 5 300 1631952914 1631952973 ACCEPT OK
# --- PowerShell ---  ※変数部分は自分用に修正してください。
# VPC-IDを変数へ格納
${vpc_name} = "blog-P1x-Vpc"

${vpc_id} = aws ec2 describe-vpcs `
    --filters "Name=tag:Name,Values=${vpc_name}" `
    --query "Vpcs[].VpcId" `
    --output text `
    ; ${vpc_id}

# ロググループのARNを変数へ格納
${iam_role_name} = "blog-P1x-Irl-Mnt-Vpcflowlogs"

${iam_role_arn} = aws iam get-role `
    --role-name ${iam_role_name} `
    --query "Role.Arn" `
    --output text `
    ; ${iam_role_arn}

# VPCフローログの作成
${profile} = "aws_RW"
${flowlog_name} = "blog-P1x-Vfl-All"
${loggroup_name} = "/aws/blog-P1x-Vpc/flowlogs"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-flow-logs `
    --profile ${profile}  `
    --resource-type VPC `
    --resource-ids ${vpc_id} `
    --traffic-type ALL `
    --log-destination-type cloud-watch-logs `
    --log-group-name ${loggroup_name} `
    --deliver-logs-permission-arn ${iam_role_arn} `
    --tag-specifications "ResourceType=vpc-flow-log, `
        Tags=[{Key=Name,Value=${flowlog_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

5.3. 確認コマンド

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# CloudWatch Logs ロググループ
## すべて表示する
aws logs describe-log-groups

## 名前を指定して表示
${loggroup_name} = "/aws/blog-P1x-Vpc/flowlogs"

aws logs describe-log-groups `
    --log-group-name-prefix ${loggroup_name}

# -------------------------------------------
# VPCフローログ
## すべて表示する
aws ec2 describe-flow-logs

## 名前を指定して表示
${flowlog_name} = "blog-P1x-Vfl-All"

aws ec2 describe-flow-logs `
    --filter "Name=tag:Name,Values=${flowlog_name}" 
    # 「filter」が「s」要らないので注意


6. EC2でNATインスタンス、セキュリティグループ

6.1. SecurityGroup

以下の理由から、SecurityGroupは「共有」「個別」のものを作成します。

  • 監視サーバからのアクセス等の他サーバと共有される通信は、共有のSecurityGroupを修正するだけで全台適用できる。
  • そのサーバだけが必要な通信を許可するために「個別」のSecurityGroupが必要。
  • 2つのSecurityGroupを適用するCLIを書きたかったから。

6.1.1. SecurityGroup(共有)の作成

Security group(共有)の名前: blog-P1a-Sgr-Mnt-Share

方向 タイプ プロトコル ポート範囲 送信元(ソース) 説明
インバウンド すべてのICMP-IPv4 icmp all 10.0.0.0/16 "Allow ICMPipv4 from VPC."
インバウンド SSH(22) tcp 22 10.0.0.0/16 "Allow SSH from VPC."
アウトバウンド すべてのICMP-IPv4 icmp all 10.0.0.0/16 "Allow ICMPipv4 to VPC."
# --- PowerShell ---  ※変数部分は自分用に修正してください。
# VPC-IDを変数へ格納
${vpc_name} = "blog-P1x-Vpc"

${vpc_id} = aws ec2 describe-vpcs `
    --filters "Name=tag:Name,Values=${vpc_name}" `
    --query "Vpcs[].VpcId" `
    --output text `
    ; ${vpc_id}


# SecurityGroup(共有)の作成
${profile} = "aws_RW"
${sgr_name_share} = "blog-P1a-Sgr-Mnt-Share"
${sgr_description} = "This Security Group is shared with other EC2."
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-security-group `
    --profile ${profile} `
    --group-name ${sgr_name_share} `
    --description "${sgr_description}" `
    --vpc-id ${vpc_id} `
    --tag-specifications "ResourceType=security-group, `
        Tags=[{Key=Name,Value=${sgr_name_share}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

6.1.2. SecurityGroup(共有)へルールの記載

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 作成したSecurityGroup-IDを変数へ格納
${sgr_name_share} = "blog-P1a-Sgr-Mnt-Share"

${sgr_id} = aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_share}" `
    --query "SecurityGroups[].GroupId" `
    --output text `
    ; ${sgr_id}


# ルールの記載
## ※コマンドは変数以外もあるため、直接編集が必要です。
${profile} = "aws_RW"

## インバウンド(ingress)
aws ec2 authorize-security-group-ingress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=-1,IpRanges="[{CidrIp=10.0.0.0/16,Description='Allow ICMPipv4 from VPC.'}]"
aws ec2 authorize-security-group-ingress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges='[{CidrIp=10.0.0.0/16,Description="Allow SSH from VPC."}]'

## アウトバウンド(egress)
aws ec2 authorize-security-group-egress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=-1,IpRanges="[{CidrIp=10.0.0.0/16,Description='Allow ICMPipv4 to VPC.'}]"

## ※アウトバウンドにdefaultで記載されている、全て許可ルールを削除します。
aws ec2 revoke-security-group-egress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=-1,IpRanges="[{CidrIp=0.0.0.0/0}]"

6.1.3. SecurityGroup(個別: NATインスタンス用)の作成

Security group(個別)の名前: blog-P1a-Sgr-Nat-NatServer

方向 タイプ プロトコル ポート範囲 送信元(ソース) 説明
インバウンド HTTP(80) tcp 80 10.0.0.0/16 "Allow HTTP from VPC."
インバウンド HTTPS(443) tcp 443 10.0.0.0/16 "Allow HTTPS from VPC."
アウトバウンド HTTP(80) tcp 80 0.0.0.0/0 "Allow HTTP to Internet."
アウトバウンド HTTPS(443) tcp 443 0.0.0.0/0 "Allow HTTPS to Internet."
# --- PowerShell ---  ※変数部分は自分用に修正してください。
# VPC-IDを変数へ格納
${vpc_name} = "blog-P1x-Vpc"

${vpc_id} = aws ec2 describe-vpcs `
    --filters "Name=tag:Name,Values=${vpc_name}" `
    --query "Vpcs[].VpcId" `
    --output text `
    ; ${vpc_id}


# SecurityGroup(個別: NATインスタンス用)の作成
${profile} = "aws_RW"
${sgr_name_nat} = "blog-P1a-Sgr-Nat-NatServer"
${sgr_description} = "This Security Group associates with blog-P1a-Ec2-Nat-NatServer."
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-security-group `
    --profile ${profile} `
    --group-name ${sgr_name_nat} `
    --description "${sgr_description}" `
    --vpc-id ${vpc_id} `
    --tag-specifications "ResourceType=security-group, `
        Tags=[{Key=Name,Value=${sgr_name_nat}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

6.1.4. SecurityGroup(個別: NATインスタンス用)へルールの記載

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 作成したSecurityGroup-IDを変数へ格納
${sgr_name_nat} = "blog-P1a-Sgr-Nat-NatServer"

${sgr_id} = aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_nat}" `
    --query "SecurityGroups[].GroupId" `
    --output text `
    ; ${sgr_id}

# ルールの記載
## ※コマンドは変数以外もあるため、直接編集が必要です。
${profile} = "aws_RW"

## インバウンド(ingress)
aws ec2 authorize-security-group-ingress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges='[{CidrIp=10.0.0.0/16,Description="Allow HTTP from VPC."}]'
aws ec2 authorize-security-group-ingress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=10.0.0.0/16,Description="Allow HTTPS from VPC."}]'

## アウトバウンド(egress)
aws ec2 authorize-security-group-egress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges='[{CidrIp=0.0.0.0/0,Description="Allow HTTP to Internet."}]'
aws ec2 authorize-security-group-egress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0,Description="Allow HTTPS to Internet."}]'

## ※アウトバウンドにdefaultで記載されている、全て許可ルールを削除します。
aws ec2 revoke-security-group-egress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=-1,IpRanges="[{CidrIp=0.0.0.0/0}]"

6.2. NATインスタンスの作成

6.2.1. キーペアの作成

キーペアを作成して、変数work_dirで指定したフォルダにpemファイル(UTF-8)を出力します。
キーペアは大事に保管する必要があるため、作業フォルダから自身がメンテできるフォルダへ移動してください。

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 作業フォルダの作成
${date} = Get-Date -Format yyyy-MM-dd
${work_dir} = "C:\#temp\awscli\${date}"
New-Item "${work_dir}" -itemType Directory -Force

# (参考)作成した作業フォルダを開くコマンド
Invoke-Item "${work_dir}"


## キーペアを作成して、作業ディレクトリ`work_dir`にpemファイル(UTF-8)を出力する。
${profile} = "aws_RW"
${key_name} = "blog-Pxx-Key-Toyokey"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2  create-key-pair `
    --profile ${profile} `
    --key-name ${key_name} `
    --query "KeyMaterial" `
    --output text `
    --tag-specifications "ResourceType=key-pair, `
        Tags=[{Key=Name,Value=${key_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]" `
    | Out-File -Encoding ascii ${work_dir}\${key_name}.pem

6.2.2. NATインスタンスの作成

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数へ格納
## 最新のNATインスタンス用AMI-ID
### NATインスタンス用のAMIは名前に「amzn-ami-vpc-nat-」が付いています。
${ec2_nat_ami} = aws ec2 describe-images `
  --region ap-northeast-1 `
  --query "reverse(sort_by(Images, &CreationDate))[:1].ImageId" `
  --owners amazon `
  --filters 'Name=name,Values=amzn-ami-vpc-nat-*' `
  --output text `
    ; ${ec2_nat_ami}

## EC2を建てるサブネットのID(パブリックを使用)
${sub_name} = "blog-P1a-Sub-Pub01"

${sub_id} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id}

## SecurityGroup-ID、共有
${sgr_name_share} = "blog-P1a-Sgr-Mnt-Share"

${sgr_id_share} = aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_share}" `
    --query "SecurityGroups[].GroupId" `
    --output text `
    ; ${sgr_id_share}

## SecurityGroup-ID、個別: NATインスタンス用
${sgr_name_nat} = "blog-P1a-Sgr-Nat-NatServer"

${sgr_id_nat} = aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_nat}" `
    --query "SecurityGroups[].GroupId" `
    --output text `
    ; ${sgr_id_nat}


# NATインスタンスの作成
${profile} = "aws_RW"
${ec2_name} = "blog-P1a-Ec2-Nat-NatServer"
${ec2_instance_type} = "t2.micro"
${key_name} = "blog-Pxx-Key-Toyokey"
${iam_role_name} = "blog-P1x-Irl-Mnt-Cw-And-Ssm"
${ec2_ip_fixed} = "10.0.11.4"
${ebs_dev_name} = "/dev/xvda"
${ebs_delete} = "true"
${ebs_size} = "16"
${ebs_type} = "gp3"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 run-instances `
    --profile ${profile} `
    --image-id ${ec2_nat_ami} `
    --instance-type ${ec2_instance_type} `
    --key-name ${key_name} `
    --iam-instance-profile Name=${iam_role_name} `
    --subnet-id ${sub_id} `
    --private-ip-address ${ec2_ip_fixed} `
    --security-group-ids ${sgr_id_share} ${sgr_id_nat} `
    --disable-api-termination `
    --monitoring Enabled=true `
    --associate-public-ip-address `
    --block-device-mappings `
        "DeviceName=${ebs_dev_name},Ebs={DeleteOnTermination=${ebs_delete},VolumeSize=${ebs_size},VolumeType=${ebs_type}}" `
    --tag-specifications "ResourceType=instance, `
        Tags=[{Key=Name,Value=${ec2_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

### オプション説明
### [--dry-run] : 実際にEC2を起動する前に、コマンドが正しいか検証します。
### [--disable-api-termination] : 終了保護の有効化を「有効」にして、誤った終了を防止します。
### [--enable-api-termination] : 終了保護の有効化を「無効」にして、誤った終了を防止しません。
### [--monitoring Enabled=true] : CloudWatchの詳細モニタリングを有効化。※無効なら`false`にする。
### [--associate-public-ip-address] : パブリックIPを付与したい時に記載する。
### [--block-device-mappings] : EBS関連の設定をする。
###### [DeleteOnTermination=true] : EC2インスタンス削除時にEBSを残すなら`false`、消すなら`true`にする。

6.2.3. EBS(Elastic Block Store)へタグを付与する

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数に格納する
## EC2インスタンスID
${ec2_name} = "blog-P1a-Ec2-Nat-NatServer"

${ec2_id} = aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}" `
    --query "Reservations[].Instances[].InstanceId" `
    --output text `
    ; ${ec2_id}

# EBS-ID
${ebs_dev_name} = "/dev/xvda"

${ebs_id} = aws ec2 describe-volumes `
    --filters "Name=attachment.instance-id,Values=${ec2_id}" `
              "Name=attachment.device,Values=*${ebs_dev_name}" `
    --query "Volumes[].Attachments[].VolumeId" `
    --output text `
    ; ${ebs_id}

# EBSへタグを付与する
${profile} = "aws_RW"
${ebs_name} = "blog-P1a-Ebs-Nat-NatServer-xvda"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-tags `
    --profile ${profile} `
    --resources ${ebs_id} `
    --tags "Key=Name,Value=${ebs_name}" `
           "Key=EMSEnv,Value=${tag_env}" `
           "Key=EMSSystem,Value=${tag_system}"

6.2.4. ENI(Elastic Network Interface)へタグを付与する

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数に格納する
## EC2インスタンスID
${ec2_name} = "blog-P1a-Ec2-Nat-NatServer"

${ec2_id} = aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}" `
    --query "Reservations[].Instances[].InstanceId" `
    --output text `
    ; ${ec2_id}

## ENI-ID
${ec2_ip_fixed} = "10.0.11.4"

${eni_id} = aws ec2 describe-network-interfaces `
    --filters "Name=attachment.instance-id,Values=${ec2_id}" `
              "Name=addresses.private-ip-address,Values=${ec2_ip_fixed}" `
    --query "NetworkInterfaces[].NetworkInterfaceId" `
    --output text `
    ; ${eni_id}

# ENIへタグを付与する
${profile} = "aws_RW"
${eni_name} = "blog-P1a-Eni-Nat-NatServer-Eth0"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-tags `
    --profile ${profile} `
    --resources ${eni_id} `
    --tags "Key=Name,Value=${eni_name}" `
           "Key=EMSEnv,Value=${tag_env}" `
           "Key=EMSSystem,Value=${tag_system}"

6.2.5. 送信元/送信先チェックを無効にする

NAT, ルーティング, Firewall用途のインターフェースでは、送信元/送信先チェックを無効にする。

Elastic Network Interface - Amazon Elastic Compute Cloud

送信元/送信先チェックを有効または無効にできます。これにより、受信するすべてのトラフィックに関して、そのインスタンスが送信元なのか、あるいたは送信先であるのかを確認できます。送信元/送信先チェックはデフォルトで有効化されています。ネットワークアドレス変換、ルーティング、ファイアウォールなどのサービスを実行するインスタンスでは、送信元/送信先チェックを無効にする必要があります。

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数に格納する
## ENI-ID
${ec2_ip_fixed} = "10.0.11.4"

${eni_id} = aws ec2 describe-network-interfaces `
    --filters "Name=attachment.instance-id,Values=${ec2_id}" `
              "Name=addresses.private-ip-address,Values=${ec2_ip_fixed}" `
    --query "NetworkInterfaces[].NetworkInterfaceId" `
    --output text `
    ; ${eni_id}

# 送信元/送信先チェックを無効にする
${profile} = "aws_RW"

aws ec2 modify-network-interface-attribute `
    --profile ${profile} `
    --network-interface-id ${eni_id} `
    --no-source-dest-check

#### ※有効にする場合は[--source-dest-check]

6.2.6. プライベートRouteTableのデフォルトルートをNATインスタンスにする

RouteTableでのルート設定は「3.4. RouteTableへルートを追加する」で実施したけれど、NATインスタンスが未構築だったためルート設定が残ってました。

プライベートサブネットに構築する他のサーバ達が、インターネットへ抜ける際のNATゲートウェイとして、NATインスタンスを使用するように、プライベートRouteTableへルート情報を追記します。

追加するルートは、以下の1本です。

  • プライベート
    • blog-P1x-Rtb-Pri01
      • 宛先: 0.0.0.0/0 , ターゲット: Ec2-Nat-NatServer(NATインスタンス)のENI
# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数に格納する
## RouteTable-ID
${rtb_name} = "blog-P1x-Rtb-Pri01"

${rtb_id} = aws ec2 describe-route-tables `
    --filters "Name=tag:Name,Values=${rtb_name}" `
    --query "RouteTables[].RouteTableId" `
    --output text `
    ; ${rtb_id}


## ENI-ID
${ec2_ip_fixed} = "10.0.11.4"

${eni_id} = aws ec2 describe-network-interfaces `
    --filters "Name=addresses.private-ip-address,Values=${ec2_ip_fixed}" `
    --query "NetworkInterfaces[].NetworkInterfaceId" `
    --output text `
    ; ${eni_id}


# Route追加: プライベート, NATインスタンスのENI向け
${profile} = "aws_RW"
${dest_cidr} = "0.0.0.0/0"

aws ec2 create-route `
    --profile ${profile} `
    --route-table-id ${rtb_id} `
    --destination-cidr-block ${dest_cidr} `
    --network-interface-id ${eni_id}

6.3. 確認コマンド

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# SecurityGroup
## すべて表示する
aws ec2 describe-security-groups

# テキストで表示するとルールが見やすい
aws ec2 describe-security-groups --output text

## 名前を指定して表示
${sgr_name_nat} = "blog-P1a-Sgr-Nat-NatServer"

aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_nat}"


# -------------------------------------------
# キーペア
## すべて表示する
aws ec2 describe-key-pairs

## 名前を指定して表示
${key_name} = "blog-Pxx-Key-Toyokey"

aws ec2 describe-key-pairs `
    --key-name ${key_name}


# -------------------------------------------
# EC2インスタンス
## すべて表示する
aws ec2 describe-instances

## 名前を指定して表示
${ec2_name} = "blog-P1a-Ec2-Nat-NatServer"

aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}"


7. EC2でWindowsServer、セキュリティグループ

7.1. SecurityGroup

以下の理由から、SecurityGroupは「共有」「個別」のものを作成します。

  • 監視サーバからのアクセス等の他サーバと共有される通信は、共有のSecurityGroupを修正するだけで全台適用できる。
  • そのサーバだけが必要な通信を許可するために「個別」のSecurityGroupが必要。
  • 2つのSecurityGroupを適用するCLIを書きたかったから。

SecurityGroup(共有)は「6.1.1. SecurityGroup(共有)の作成」で作成したため、そちらを利用します。

7.1.1. SecurityGroup(個別: WindowsServer用)の作成

Security group(個別)の名前: blog-P1a-Sgr-Srv-TestUser

方向 タイプ プロトコル ポート範囲 送信元(ソース) 説明
インバウンド RDP(3389) tcp 3389 10.0.0.0/16 "Allow RDP from VPC."
アウトバウンド ALL ALL -1 0.0.0.0/0 "Allow ALL."
# --- PowerShell ---  ※変数部分は自分用に修正してください。
# VPC-IDを変数へ格納
${vpc_name} = "blog-P1x-Vpc"

${vpc_id} = aws ec2 describe-vpcs `
    --filters "Name=tag:Name,Values=${vpc_name}" `
    --query "Vpcs[].VpcId" `
    --output text `
    ; ${vpc_id}

# SecurityGroup(個別: WindowsServer用)の作成
${profile} = "aws_RW"
${sgr_name_win} = "blog-P1a-Sgr-Srv-TestUser"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"
${sgr_description} = "This Security Group associates with blog-P1a-Ec2-Srv-TestUser."

aws ec2 create-security-group `
    --profile ${profile} `
    --group-name ${sgr_name_win} `
    --description "${sgr_description}" `
    --vpc-id ${vpc_id} `
    --tag-specifications "ResourceType=security-group, `
        Tags=[{Key=Name,Value=${sgr_name_win}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

7.1.2. SecurityGroup(個別: WindowsServer用)へルールの記載

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 作成したSecurityGroup-IDを変数へ格納
${sgr_name_win} = "blog-P1a-Sgr-Srv-TestUser"

${sgr_id} = aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_win}" `
    --query "SecurityGroups[].GroupId" `
    --output text `
    ; ${sgr_id}

# ルールの記載
## ※コマンドは変数以外もあるため、直接編集が必要です。
${profile} = "aws_RW"

## インバウンド(ingress)
aws ec2 authorize-security-group-ingress --profile ${profile} --group-id ${sgr_id} --ip-permissions IpProtocol=tcp,FromPort=3389,ToPort=3389,IpRanges='[{CidrIp=10.0.0.0/16,Description="Allow RDP from VPC."}]'

## アウトバウンド(egress)
## ※defaultで設定されているものを利用するため、コマンド設定無し。

7.2. WindowsServerの作成

7.2.1. キーペアの作成

キーペアは「6.2.1. キーペアの作成」で作成したため、そちらを利用します。

7.2.2. WindowsServerの作成

AWSで無料利用枠として用意されているMicrosoft Windows 2019 Datacenter edition. [English]を利用します。
無料で利用するための条件として月の稼働が750時間までインスタンスタイプ t2.microと制限があるため注意が必要です。

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数へ格納
## 最新のWindowsServer用AMI-ID
### WindowsServer用のAMIは名前に「Windows_Server-2019-English-Full-Base-」が付いています。
${ec2_win_ami} = aws ec2 describe-images `
  --region ap-northeast-1 `
  --query "reverse(sort_by(Images, &CreationDate))[:1].ImageId" `
  --owners amazon `
  --filters 'Name=name,Values=Windows_Server-2019-English-Full-Base-*' `
  --output text `
    ; ${ec2_win_ami}

## EC2を建てるサブネットのID
${sub_name} = "blog-P1a-Sub-Pri01"

${sub_id} = aws ec2 describe-subnets `
    --filters "Name=tag:Name,Values=${sub_name}" `
    --query "Subnets[].SubnetId" `
    --output text `
    ; ${sub_id}

## SecurityGroup-ID、共有
${sgr_name_share} = "blog-P1a-Sgr-Mnt-Share"

${sgr_id_share} = aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_share}" `
    --query "SecurityGroups[].GroupId" `
    --output text `
    ; ${sgr_id_share}

## SecurityGroup-ID、個別: WindowsServer用
${sgr_name_win} = "blog-P1a-Sgr-Srv-TestUser"

${sgr_id_win} = aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_win}" `
    --query "SecurityGroups[].GroupId" `
    --output text `
    ; ${sgr_id_win}


# WindowsServerの作成
${profile} = "aws_RW"
${ec2_name} = "blog-P1a-Ec2-Srv-TestUser"
${ec2_instance_type} = "t2.micro"
${key_name} = "blog-Pxx-Key-Toyokey"
${iam_role_name} = "blog-P1x-Irl-Mnt-Cw-And-Ssm"
${ec2_ip_fixed} = "10.0.12.4"
${ebs_dev_name} = "/dev/sda1"
${ebs_delete} = "true"
${ebs_size} = "50"
${ebs_type} = "gp3"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 run-instances `
    --profile ${profile} `
    --image-id ${ec2_win_ami} `
    --instance-type ${ec2_instance_type} `
    --key-name ${key_name} `
    --iam-instance-profile Name=${iam_role_name} `
    --subnet-id ${sub_id} `
    --private-ip-address ${ec2_ip_fixed} `
    --security-group-ids ${sgr_id_share} ${sgr_id_win} `
    --disable-api-termination `
    --monitoring Enabled=true `
    --block-device-mappings `
        "DeviceName=${ebs_dev_name},Ebs={DeleteOnTermination=${ebs_delete},VolumeSize=${ebs_size},VolumeType=${ebs_type}}" `
    --tag-specifications "ResourceType=instance, `
        Tags=[{Key=Name,Value=${ec2_name}}, `
            {Key=Env,Value=${tag_env}}, `
            {Key=System,Value=${tag_system}}]"

### オプション説明
### [--dry-run] : 実際にEC2を起動する前に、コマンドが正しいか検証します。
### [--disable-api-termination] : 終了保護の有効化を「有効」にして、誤った終了を防止します。
### [--enable-api-termination] : 終了保護の有効化を「無効」にして、誤った終了を防止しません。
### [--monitoring Enabled=true] : CloudWatchの詳細モニタリングを有効化。※無効なら`false`にする。
### [--block-device-mappings] : EBS関連の設定をする。
###### [DeleteOnTermination=true] : EC2インスタンス削除時にEBSを残すなら`false`、消すなら`true`にする。

7.2.3. EBS(Elastic Block Store)へタグを付与する

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数に格納する
## EC2インスタンスID
${ec2_name} = "blog-P1a-Ec2-Srv-TestUser"

${ec2_id} = aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}" `
    --query "Reservations[].Instances[].InstanceId" `
    --output text `
    ; ${ec2_id}

# EBS-ID
${ebs_dev_name} = "/dev/sda1"

${ebs_id} = aws ec2 describe-volumes `
    --filters "Name=attachment.instance-id,Values=${ec2_id}" `
              "Name=attachment.device,Values=*${ebs_dev_name}" `
    --query "Volumes[].Attachments[].VolumeId" `
    --output text `
    ; ${ebs_id}

# EBSへタグを付与する
${profile} = "aws_RW"
${ebs_name} = "blog-P1a-Ebs-Srv-TestUser-sda1"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-tags `
    --profile ${profile} `
    --resources ${ebs_id} `
    --tags "Key=Name,Value=${ebs_name}" `
           "Key=EMSEnv,Value=${tag_env}" `
           "Key=EMSSystem,Value=${tag_system}"

7.2.4. ENI(Elastic Network Interface)へタグを付与する

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# 必要なIDを変数に格納する
## EC2インスタンスID
${ec2_name} = "blog-P1a-Ec2-Srv-TestUser"

${ec2_id} = aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}" `
    --query "Reservations[].Instances[].InstanceId" `
    --output text `
    ; ${ec2_id}

## ENI-ID
${ec2_ip_fixed} = "10.0.12.4"

${eni_id} = aws ec2 describe-network-interfaces `
    --filters "Name=attachment.instance-id,Values=${ec2_id}" `
              "Name=addresses.private-ip-address,Values=${ec2_ip_fixed}" `
    --query "NetworkInterfaces[].NetworkInterfaceId" `
    --output text `
    ; ${eni_id}

# ENIへタグを付与する
${profile} = "aws_RW"
${eni_name} = "blog-P1a-Eni-Srv-TestUser-Eth0"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

aws ec2 create-tags `
    --profile ${profile} `
    --resources ${eni_id} `
    --tags "Key=Name,Value=${eni_name}" `
           "Key=EMSEnv,Value=${tag_env}" `
           "Key=EMSSystem,Value=${tag_system}"

7.3. 確認コマンド

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# SecurityGroup
## すべて表示する
aws ec2 describe-security-groups

# テキストで表示するとルールが見やすい
aws ec2 describe-security-groups --output text

## 名前を指定して表示
${sgr_name_win} = "blog-P1a-Sgr-Srv-TestUser"

aws ec2 describe-security-groups `
    --filters "Name=tag:Name,Values=${sgr_name_win}"


# -------------------------------------------
# EC2インスタンス
## すべて表示する
aws ec2 describe-instances

## 名前を指定して表示
${ec2_name} = "blog-P1a-Ec2-Srv-TestUser"

aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}"


8. EC2 AutoRecovery(復旧)

8.1. EC2 AutoRecoveryの設定

EC2インスタンスが載っているホスト側(AWS管理)機器に異常が出たら、EC2を再起動して正常なホスト側機器へ移動するために、AutoRecoveryを設定します。

8.1.1. AutoRecoveryの主要なパラメータ

要約 「1分毎にメトリクスを評価、2分間で2回 異常を検知したら、平均値0.99を超えるのでAutoRecoveryを実行する。」

  • 2分間に2回 異常があったら、平均値 1.0 なのでAutoRecovery実行。
  • 2分間に1回 異常があったら、平均値 0.5 なので何もしない。
  • 3分間に2回 連続で異常があったら、平均値 1.0 なのでAutoRecovery実行。
  • 3分間に1回 異常があったら、評価期間は2分なので平均値 0.5 となり何もしない。
  • 連続2回 異常があったら、AutoRecovery実行。
項目 備考
メトリクス名 StatusCheckFailed_System
統計 平均値
期間 1分 アラームはメトリクスを 1 分あたり 1 回評価する。
評価期間 2分
データポイント 2 期間またはデータポイントの数。
しきい値 StatusCheckFailed_System >= 0.99 StatusCheckFailed_Systemは通常が0、異常時は1

8.1.2. NATインスタンスにAutoRecoveryを設定する

# --- PowerShell ---  ※変数部分は自分用に修正してください。
${autorec_name} = "blog-P1a-Cwa-Nat-NatServer-Autorecovery"
${alarm_description} = "EC2 is automatically restarted when the host device malfunctions."
${profile} = "aws_RW"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

# 必要なIDを変数に格納する
## EC2インスタンスID
${ec2_name} = "blog-P1a-Ec2-Nat-NatServer"

${ec2_id} = aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}" `
    --query "Reservations[].Instances[].InstanceId" `
    --output text `
    ; ${ec2_id}

# CloudWatchアラームの作成
aws cloudwatch put-metric-alarm `
    --profile ${profile} `
    --alarm-actions arn:aws:automate:ap-northeast-1:ec2:recover `
    --alarm-description "${alarm_description}" `
    --alarm-name ${autorec_name} `
    --comparison-operator GreaterThanOrEqualToThreshold `
    --dimensions Name=InstanceId,Value=${ec2_id} `
    --evaluation-periods 2 `
    --datapoints-to-alarm 2 `
    --metric-name StatusCheckFailed_System `
    --namespace AWS/EC2 `
    --period 60 `
    --statistic Average `
    --threshold 0.99 `
    --tags "Key=Name,Value=${autorec_name}" `
            "Key=EMSEnv,Value=${tag_env}" `
            "Key=EMSSystem,Value=${tag_system}"

8.1.3. WindowsServerにAutoRecoveryを設定する

# --- PowerShell ---  ※変数部分は自分用に修正してください。
${autorec_name} = "blog-P1a-Cwa-Srv-TestUser-Autorecovery"
${alarm_description} = "EC2 is automatically restarted when the host device malfunctions."
${profile} = "aws_RW"
${tag_env} = "Product"
${tag_system} = "HatenaBlog"

# 必要なIDを変数に格納する
## EC2インスタンスID
${ec2_name} = "blog-P1a-Ec2-Srv-TestUser"

${ec2_id} = aws ec2 describe-instances `
    --filters "Name=tag:Name,Values=${ec2_name}" `
    --query "Reservations[].Instances[].InstanceId" `
    --output text `
    ; ${ec2_id}

# CloudWatchアラームの作成
aws cloudwatch put-metric-alarm `
    --profile ${profile} `
    --alarm-actions arn:aws:automate:ap-northeast-1:ec2:recover `
    --alarm-description "${alarm_description}" `
    --alarm-name ${autorec_name} `
    --comparison-operator GreaterThanOrEqualToThreshold `
    --dimensions Name=InstanceId,Value=${ec2_id} `
    --evaluation-periods 2 `
    --datapoints-to-alarm 2 `
    --metric-name StatusCheckFailed_System `
    --namespace AWS/EC2 `
    --period 60 `
    --statistic Average `
    --threshold 0.99 `
    --tags "Key=Name,Value=${autorec_name}" `
            "Key=EMSEnv,Value=${tag_env}" `
            "Key=EMSSystem,Value=${tag_system}"

8.3. 確認コマンド

# --- PowerShell ---  ※変数部分は自分用に修正してください。
# AutoRecovery
## CloudWatchアラームをすべて表示する
aws cloudwatch describe-alarms

## 名前を指定して表示
${autorec_name} = "blog-P1a-Cwa-Srv-TestUser-Autorecovery"

aws cloudwatch describe-alarms `
    --alarm-names ${autorec_name}


9. 今後の追加予定

暇を見て追加していく予定のものです。

  • SystemsManagerで踏み台なしでプライベートのサーバへアクセス
  • SystemsManagerパラメータストアを作成してCloudWatch LogsでWindowsイベントログを採取
  • CloudWatch LogsでWinodowsのメトリクス取得