Terraformを始めよう②【インフラエンジニアへの道２日目】

インフラエンジニアへの道２日目です:hatched_chick:

## 今回取り組むこと
* Session Managerを利用してみる
* IAMロールをTerraformで設定する
* セキュリティグループをTerraformで設定する

## この記事内での目標
AWS Systems Manager Session Managerを使用して、Amazon EC2インスタンスにSSH接続せずにアクセスし、Webサーバー（Apache HTTP Server）をセットアップする。

## 1. インスタンスの設定
IAM Roleを設定して、AWS Systems Manager Session Managerから操作が出来るようにしてみます。
iam.tfを作成し、IAMRoleに必要な定義を記述します。
このIAMRoleはEC2インスタンスに紐づけるため、EC2サービスからのアクセス許可を定義しました。
```tf
data "aws_iam_policy_document" "instance-assume-role-policy"{
    statement {
        actions = ["sts:AssumRole"]

        principals = {
            type        = "Service"
            identifiers = ["ec2.amazonaws.com"]
        }
    }
}
```
これは信頼ポリシーと呼ばれるもので、IAM Roleが何からのアクセスを引き受けるかを定義します。
最初に`data`と記載しています。これは参照専用でリソースの作成や削除は行わず、外部リソースの情報を取得してTerraform上で利用することができます。
[1日目](https://qiita.com/ichisak/items/1eadc75e0417899f737b)の ec2.tf で`resource`と記載しています。これはAWSリソースを定義し、作成や削除を行います。
次にIAM Roleを定義します。
```tf
resource "aws_iam_role" "demo"{
    name = "demo_role"
    assume_role_policy = data.aws_iam_policy_document.instance_assume-role-policy.json
}
```
`assume_role_policy`の行で、先ほど定義した信頼ポリシーの情報を参照しています。
続いてIAM Policyを定義します。
AWSの事前定義済ポリシーである`AmazonSSMMnagedInstanceCore`を`data`を使って参照し、ARN（※）を取得して、Roleに紐づけます。
※ARN…Amazon Resource Name．AWSのリソースを一意に識別するための識別子（ID）
一般的なARNの形式：`arn:partition:service:region:account-id:resource`
    各部分の意味：
    | 部分 | 説明 |
|:-:|:-:|
|`arn`|  すべてのARNに共通のプレフィックス |
| `partition`  |  AWSのパーティション（通常 aws） |
| `service`  |  リソースのサービス（例: iam, s3, ec2） |
| `region`  |  リソースのリージョン（グローバルなら空） |
| `account-id`  |  AWSアカウントID（AWS管理のものは aws） |
| `resource`  |  リソース識別子（サービスごとに異なる） |
```tf
data "aws_iam_policy" "ssm_core"{
    name = "AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "demo" {
    role = aws_iam_role.demo.name
    policy_arn = data.aws_iam_policy.ssm_core.arn
}
```

次にインスタンスプロファイルを定義します。
インスタンスプロファイルはIAM RoleとEC2インスタンスを紐づけるコネクタのようなものです。
```tf
resource "aws_iam_instance_profile" "demo" {
    name = "demo_role"
    role = aws_iam_role.demo.name
}
```
ここまでで、iam.tfは以下のようになりました。
```tf:iam.tf
data "aws_iam_policy_document" "instance-assume-role-policy"{
    statement {
        actions = ["sts:AssumeRole"]

        principals {
            type        = "Service"
            identifiers = ["ec2.amazonaws.com"]
        }
    }
}

resource "aws_iam_role" "demo"{
    name = "demo_role"
    assume_role_policy = data.aws_iam_policy_document.instance-assume-role-policy.json
}

data "aws_iam_policy" "ssm_core"{
    name = "AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "demo" {
    role = aws_iam_role.demo.name
    policy_arn = data.aws_iam_policy.ssm_core.arn
}


resource "aws_iam_instance_profile" "demo" {
    name = "demo_role"
    role = aws_iam_role.demo.name
}

```
ec2.tfでインスタンスプロファイルを使用するように定義を編集します。
```tf:ec2.tf
resource "aws_instance" "example" {
  ami           = "ami-09896cc5f96ec689a"  # Amazon Linux 2 AMI
  instance_type = "t2.micro"  # t2.micro（無料枠対象）
  
  #この行を追加
  aws_iam_instance_profile = aws_iam_instance_profile.demo.name
  
  tags = {
    Name = "tf_demo"
  }
}
```
これで準備OKです。
## 2. インスタンスの再作成
`plan`と`apply`を実行し、リソースを作成します。
```sh
terraform plan
terraform apply
```
EC2インスタンスが再作成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/2d992026-edad-47e1-bbad-eeaf96409d3f.png)
Session Managerのコンソールを開きます。正しく設定できている場合は、ターゲットインスタンスに作成したインスタンスが表示されて、セッションを開始することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/40a89c48-80bb-4813-8a7b-a633fe27a140.png)
※1日目で使っていたAMI（ami-00561c77487da40c1）だとSSM AgentがインストールされていなかったためAMIをami-09896cc5f96ec689aに変更しました。
## 3. Webサーバの起動
ターゲットインスタンスを選択して、「Start Session」からセッションを開始します。
セッションマネージャーにコマンドを打ってみます。
```sh
sudo yum install -y httpd
sudo systemctl start httpd
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/026eb8e9-b6d6-44dd-82a2-f9dde833e596.png)
Webサーバを起動しましたが、ポートを解放していないためアクセスすることができません。外部からWebサーバへアクセス出来るように、セキュリティグループを作成します。
```tf:securitygroup.tf
resource "aws_security_group" "demo" {
    name = "tf_demo"

    tags = {
        Name = "tf_demo"
    }
}

#インバウンドルール
resource "aws_security_group_rule" "ingress_http" {
    type = "ingress"
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cider_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.demo.id
}

#アウトバウンドルール
resource "aws_security_group_rule" "egress_all" {
    type = "egress"
    from_port = 0
    to_port = 0
    protocol = "all"
    cider_blocks = ["0.0.0.0/0"]
    security_group_id = aws_security_group.demo.id
}
```
`plan` , `apply`を実行してセキュリティグループを作成します。
```sh
terraform plan
terraform apply
```
`plan`の結果を見てみるとtfファイルに記載したことが反映されているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/57b99e6e-e5d5-4b3b-95ca-09ee34d46ddb.png)

セキュリティグループをEC2に紐づけるため、`ec2.tf` を編集します。
```tf:ec2
resource "aws_instance" "example" {
  ami           = "ami-09896cc5f96ec689a"  # Amazon Linux 2 AMI
  instance_type = "t2.micro"  # t2.micro（無料枠対象）
  iam_instance_profile = aws_iam_instance_profile.demo.name
  
  vpc_security_group_ids = [
    aws_security_group.demo.id
  ]
  
  tags = {
    Name = "tf_demo"
  }
}
```
再び、`plan` を実行し、どのような影響があるか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/63db1d07-5bb5-440d-99ba-fca9c0703264.png)
元々セキュリティグループの定義がなかったため、デフォルトセキュリティグループが紐づいていましたが、新しく作成したセキュリティグループを使用するように定義したため、changeが出力されました。
リソースの再作成を伴わない変更の場合、変更箇所は`~`で表示されます。
`apply`で反映すると、先ほどは見れなかったWebページにApacheのデフォルトページが表示されるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/4b6b113a-7650-41c0-b5c1-77681cc87584.png)
Webサーバの起動に成功したと言っていいでしょう。

## 4. リソースの再作成を伴う変更
先程リソースの再作成を伴わない変更の動作を確認したので、次はリソースの再作成を伴う動作を確認します。
`userdata`ディレクトリを作成し、Webサーバを起動するスクリプトを配置します。
```sh
mkdir userdata
cat <<EOF > userdata/demo.sh
> #!/bin/bash
> yum install -y httpd
> systemctl start httpd
> EOF
```
ec2.tfを編集し、作成したスクリプトをUserDataとして使用するように定義します。
Terraformの`file`関数でスクリプトを渡すことができます。
```tf:ec2.tf
resource "aws_instance" "example" {
  ami           = "ami-09896cc5f96ec689a"  # Amazon Linux 2 AMI
  instance_type = "t2.micro"  # t2.micro（無料枠対象）
  iam_instance_profile = aws_iam_instance_profile.demo.name
  
  vpc_security_group_ids = [
    aws_security_group.demo.id
  ]

  user_data = file("./userdata/demo.sh") #この行を追加
  
  tags = {
    Name = "tf_demo"
  }
}
```
`plan`コマンドを実行して確認してみます。
user_dataが追加されており、public_ipも新しくなるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/1f583082-41f4-41e9-b47b-30350067d395.png)
今回定義したuserdataはインスタンスの起動時にのみ適用できるパラメータのため、Terraformはリソースを再作成して適用しようとします。
`apply`を実行して適用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/cc3c8d19-9c4d-4037-ba29-efbc5250f0f4.png)
インスタンスが削除、再作成されました。インスタンス起動時にuserdataでWebサーバを起動するように設定したので新しいインスタンスのパブリックIPアドレスにアクセスして動作を確認してみます。
先程と同じApacheのテストページが開きました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3734886/2ada8bca-ad80-40fb-bd3a-7a62bb2c1c98.png)

最後に、忘れずに環境を削除します。

## まとめ
この記事では、AWS Systems Manager Session Managerを使用して、Amazon EC2インスタンスにSSH接続せずにアクセスし、Webサーバー（Apache HTTP Server）をセットアップする方法を学びました。

### 学んだこと
* AWS Systems Manager Session Manager
AmazonSSMManagedInstanceCoreポリシーと、IAM Role、インスタンスプロファイルを使用して、EC2インスタンスに対してSession Managerが利用できるように設定しました。
* IAM Roleの設定
EC2インスタンスに対してIAM Roleを適用し、そのRoleに適切なポリシー（例えば、SSM用のAmazonSSMManagedInstanceCore）を関連付ける方法を理解しました。
* セキュリティグループの設定
セキュリティグループでインバウンドおよびアウトバウンドルールを設定し、EC2インスタンスがHTTPトラフィックを受け取れるようにする方法を学びました。
* User Dataスクリプト
EC2インスタンスの起動時に実行されるスクリプトを利用して、インスタンスが自動的にセットアップされるように設定しました。
Terraformのfile関数を使用して、外部のスクリプトファイルをインスタンスに渡す方法を学びました。
* Terraformでのインフラ管理
Terraformでインフラをコードとして管理し、planとapplyコマンドを使ってリソースを作成・変更・削除するプロセスを理解しました。変更を加えた際に、リソースがどのように影響を受けるのか、変更の内容をplanで確認する方法も学びました。