Terraform初心者の備忘録　〜IaCを使うときに学んだあれやこれ〜

## はじめに
グループワークにおいてawsを使う必要があったけど金かからんようにするためだけに毎回GUI操作をログインしてEC2起動して停止して、、、ってやるの面倒だったからIaC使ってみたって話。できたこと、できなかったこともまとめる。

## 今回の課題
- apacheからの動画ストリーミングと動画アップロードを受け取り、保存するサーバの構築
- Go言語でのデータベース操作のAPIサーバの構築
- MySQLデータベースのサーバ構築

### そもそもIaCってなんぞや？
聞いたことあっても自信を持ってこれ！！って説明できなかったからこの際にしっかりと。

参考：[RedHat IaC (Infrastructure as Code) とは](https://www.redhat.com/ja/topics/automation/what-is-infrastructure-as-code-iac)

**IaC** : Infrastructure as a Codeの略

IaC によるインフラストラクチャのプロビジョニングを自動化することで、開発者は、アプリケーションを開発またはデプロイするたびに、サーバー、オペレーティングシステム、ストレージ、およびその他のインフラストラクチャ・コンポーネントのプロビジョニングと管理を手動で行う必要がなくなります。インフラストラクチャを体系化すれば、プロビジョニングをテンプレート化できます。

### IaCのメリット
- コスト削減
- デプロイメントの高速化
- エラーの減少 
- インフラストラクチャの一貫性の向上
- 構成ドリフトの排除

とのことなので、今回は人的エラーの減少とインフラストラクチャの一貫性の向上(コード化することで毎回同じインフラを構築すること)を目的にTerraformを使ってみよう。



というわけで、本題。

## 今回行うこと

![Solution (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3473187/7ddc9265-017f-1ae8-8ec6-eac31b4636e9.png)


これをTerraformで作ります。(SecurityGroupが一個だったり、AZが一個だったり、、色々ツッコミどころはありますが、、今回はこれで事足りるのでこれを作ります。＆無料分で自分ができる最大限がこれでした。)

細かな仕様はこんな感じ

#### 詳細仕様
- VPC
    - CIDR：10.0.0.0/16
- Public Subnet
    - VPC（上記のもの）
    - CIDR：10.0.3.0/24
    - IPの自動割り当て：有効化
- IGW
    - VPC（上記のもの）
- Route table
    - VPC（上記のもの）
    - CIDR：0.0.0.0/0
    - IGW（上記のもの）
    - Subnet（上記のもの）
- Security Group（ガバガバなので非推奨）
    - インバウンドルール
        - 22
            - sshが好ましい。ミスって今回tcpになってました
            - CIDR：0.0.0.0/0
        - 80
            - tcp
            - CIDR：0.0.0.0/0
        - 5000
            - tcp
            - CIDR：0.0.0.0/0
        - 443
            - tcp
            - CIDR：0.0.0.0/0
        - 9090
            - tcp
            - CIDR：0.0.0.0/0
        - 3306
            - tcp
            - CIDR：0.0.0.0/0
    - アウトバウンドルール
        - 任意の場所
- EC2
    - Apacheサーバ
        - t2.micro
        - ami:amazonlinux
        - subnet,SecurityGroup（上記のもの）
        - ボリューム:13GB
    - MySQLサーバ
        - t2.micro
        - ami：amazonlinux2
        - subnet,SecurityGroup（上記のもの）
        - ボリューム：8GB
    - Goサーバ
        - t2.micro
        - ami:amazonlinux
        - subnet,SecurityGroup（上記のもの）
        - ボリューム：8GB

:::note warn
**ssh**と**tcp**の違いはなんなのか？

sshもtcpの一種

**レイヤーの違い**
ssh：OSI第7層（アプリケーション層）
tcp：OSI第4層（トランスポート層）
**目的の違い**
tcp：信頼性のあるデータ転送を提供
ssh：安全なリモートアクセスと通信を提供
:::

さらっと出てきてるけどCIDRについてもまとめる

### CIDRとはなんぞや？

参考：[「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word11989.html)
CIDR:Classless Inter-Domain Routingの略
アドレスクラスの概念を気にしないことで、IPアドレスの割り当てや経路選択などの自由度を上げる仕組み


参考：[AWS CIDRとは？](https://aws.amazon.com/jp/what-is/cidr/)

インターネット上のデータルーティング効率を向上させる IP アドレス割り当て方法です。インターネットに接続するすべてのマシン、サーバー、およびエンドユーザーデバイスには、IP アドレスと呼ばれる固有の番号が関連付けられています。デバイスは、これらの IP アドレスを使用して相互に検索し、通信します。組織は CIDR を使用して、ネットワークに IP アドレスを柔軟かつ効率的に割り当てます。

### CIDRのメリット
- IP アドレスの浪費を削減

- データをすばやく送信

- 仮想プライベートクラウドを作成する

- スーパーネットを柔軟に作成

ここから先は趣旨がズレるので省きます、、

ようやくTerraformでの実装！！！

## Terraformファイルの作成
今回は変数ファイルなどを使わずにそのまま書き込みます。
また、解説は要所要所のみ行います。

#### プロバイダーの設定
```terraform
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }

  required_version = ">= 1.8.0"
}
```

`terraform`：実行環境のバージョン指定やプロバイダー指定を行うブロック
`required_providers`：terraformがインストールして実行できるように利用するプロバイダーを指定する
`required_version`：terraform自体のバージョンを指定する


#### プロバイダーの詳細設定
```terraform
provider "aws" {
  region     = "ap-northeast-1" # 適宜変更
  access_key = "*********"
  secret_key = "*****************"
}
```
`region`：awsのサービスを展開するリージョンを指定
`access_key`：IAMユーザのアクセスキー
`secret_key`：IAMユーザのシークレットキー

#### リソースブロック
```terraform
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "TerraformVPC"
  }
}
```
`resource`：サービスの種類、任意の名前で指定
`resource "aws_vpc" "main"`はVPCをmainという名前で作成する
`cidr_block`：CIDR範囲を指定
`tags`：GUIから確認した時の名前を決める

#### セキュリティグループリソースブロックの作成
```terraform
resource "aws_security_group" "allow_ssh_http" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "TerraformSecurityGroup"
  }
}
```

`ingress`：インバウンドルールの作成
`egress`：アウトバウンドルールの作成

#### EC2リソースブロックの作成
```terraform
resource "aws_instance" "Apache" {
  ami             = "ami-02a405b3302affc24" # 適宜変更
  instance_type   = "t2.micro"
  subnet_id       = aws_subnet.public.id
  security_groups = [aws_security_group.allow_ssh_http.id]
  key_name        = "mojito"

  root_block_device {
    volume_size = 13
  }
  user_data = <<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              EOF
  tags = {
    Name = "Apache"
  }
}
```
`ami`：イメージの指定
`instance_type`：インスタンスタイプを指定
`subnet_id`：どこのサブネットに置くか指定
`security_groups`：適応するセキュリティグループの指定
`root_block_device`：今回はvolume_size（容量）のみ指定
`user_data`：インスタンス作成時に実行するスクリプトを書き込む（前提としてsudoで実行される）

#### outputブロック
```terraform
output "Apache_public_ip" {
  description = "The public IP address of the Apache instance"
  value       = aws_instance.Apache.public_ip
}
```
`output`：`terraform apply`コマンド実行後に指定された出力を行う
今回の場合、ターミナルに「The public IP address of the Apache instance」と文字列と作成されたApacheのインスタンスのipアドレスが出力される

てな感じで、、出来上がったのがこちら。
```main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }

  required_version = ">= 1.8.0"
}

provider "aws" {
  region     = "ap-northeast-1" # 適宜変更
  access_key = "***********"
  secret_key = "***************"
}

# VPCの作成
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "TerraformVPC"
  }
}

# パブリックサブネットの作成
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.3.0/24"
  map_public_ip_on_launch = true

  tags = {
    Name = "TerraformPublicSubnet"
  }
}


# インターネットゲートウェイの作成
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "TerraformIGW"
  }
}

# ルートテーブルの作成とインターネットゲートウェイへのルート追加
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "TerraformPublicRouteTable"
  }
}

# サブネットとルートテーブルの関連付け
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# セキュリティグループの作成
resource "aws_security_group" "allow_ssh_http" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 5000
    to_port     = 5000
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 9090
    to_port     = 9090
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "TerraformSecurityGroup"
  }
}

# EC2インスタンス（13GBのEBSボリュームを持つt2.microインスタンス）
resource "aws_instance" "Apache" {
  ami             = "ami-02a405b3302affc24" # 適宜変更
  instance_type   = "t2.micro"
  subnet_id       = aws_subnet.public.id
  security_groups = [aws_security_group.allow_ssh_http.id]
  key_name        = "MY_KEY"

  root_block_device {
    volume_size = 13
  }

  user_data = <<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "AddType video/mp4 .mp4" >> /etc/httpd/conf/httpd.conf
              systemctl restart httpd

              yum install -y git

              # githubからコードを取得
              git clone https://github.com/mrAkito/python-apache-open.git
              cd python-apache-open
              chmod +x app.py
              chmod +x changePermission.sh
              mkdir /var/www/html/uploads
              chmod 777 /var/www/html/uploads
              cd /var/www/html

              # ダミー動画ファイルのダウンロード
              curl -O https://*********

              # Flaskのインストール
              yum install -y python3
              yum install -y python3-pip
              pip3 install flask
              EOF
  tags = {
    Name = "Apache"
  }
}

# EC2インスタンス（8GBのEBSボリュームを持つt2.microインスタンス）
resource "aws_instance" "MySQL" {
  ami             = "ami-08c07d6e7c1274e2a"
  instance_type   = "t2.micro"
  subnet_id       = aws_subnet.public.id
  security_groups = [aws_security_group.allow_ssh_http.id]
  key_name        = "MY_KEY"

  root_block_device {
    volume_size = 8
  }

  user_data = <<-EOF
              #!/bin/bash
              sudo yum update -y
              sudo yum remote -y mariadb
              sudo yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm
              sudo yum install -y --enablerepo=mysql80-community mysql-community-server
              sudo yum install -y --enablerepo=mysql80-community mysql-community-devel
              yum list installed | grep mysql
              sudo touch /var/log/mysqld.log
              sudo systemctl start mysqld
              sudo systemctl enable mysqld
              # MySQLのパスワード設定をオフにする
              echo "[mysqld]" | sudo tee -a /etc/my.cnf
              echo "skip-grant-tables" | sudo tee -a /etc/my.cnf
              echo "bind-address = 0.0.0.0" | sudo tee -a /etc/my.cnf
              sudo systemctl restart mysqld
              # MySQLにデータベースを作成
              mysql -u root -e "CREATE DATABASE DATABASE;"
              mysql -u root SolutionDB -e "
              CREATE TABLE IF NOT EXISTS 
              （中略）
              "
              sudo sed -i '/skip-grant-tables/d' /etc/my.cnf
              sudo systemctl restart mysqld
              echo "MYSQL_PASSWORD=$(sudo grep 'temporary password' /var/log/mysqld.log |sudo awk '{print $NF}')" >> .bash_profile
              mysql -u root -p $MYSQL_PASSWORD -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'NEWPASSWORD';"
              # 全ての権限を持つユーザを作成
              mysql -u root -p NEWPASSWORD -e "CREATE USER 'USER'@'%' IDENTIFIED BY 'DATABASE_PASSWORD';"
              mysql -u root -p NEWPASSWORD -e "GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' IDENTIFIED BY 'solution2024_MySQL'; FLUSH PRIVILEGES;"
              mysql -u root -p NEWPASSWORD -e "GRANT ALL PRIVILEGES ON SolutionDB.* TO 'admin'@'%'; FLUSH PRIVILEGES;"
              sudo systemctl restart mysqld
              # スワップファイルの作成
              sudo dd if=/dev/zero of=/swapfile bs=1M count=4096
              sudo chmod 600 /swapfile
              sudo mkswap /swapfile
              sudo swapon /swapfile
              EOF
  tags = {
    Name = "MySQL"
  }
}

# EC2インスタンス（8GBのEBSボリュームを持つt2.microインスタンス）
resource "aws_instance" "Go" {
  ami             = "ami-02a405b3302affc24" # 適宜変更
  instance_type   = "t2.micro"
  subnet_id       = aws_subnet.public.id
  security_groups = [aws_security_group.allow_ssh_http.id]
  key_name        = "MY_KEY"

  root_block_device {
    volume_size = 8
  }

  user_data = <<-EOF
              #!/bin/bash
              yum update -y
              yum remove golang
              curl -O https://dl.google.com/go/go1.22.0.linux-amd64.tar.gz
              cd
              tar -xvf go1.22.0.linux-amd64.tar.gz
              mv go /usr/local
              cd /home/ec2-user
              echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc
              source ~/.bash_rc

              yum install -y git

              git clone https://github.com/mrAkito/SolutionDev
              cd SolutionDev
              go mod tidy
              echo "DB_HOST=${aws_instance.MySQL.public_ip}" > .env
              echo "DB_USER=USER" >> .env
              echo "DB_PASSWORD=DATABASE_PASSWORD" >> .env
              echo "DB_NAME=DATABASE" >> .env
              echo "DB_PORT=3306" >> .env
              sudo shutdown -r now
              EOF
  tags = {
    Name = "GoAPI"
  }
}

output "Apache_public_ip" {
  description = "The public IP address of the Apache instance"
  value       = aws_instance.Apache.public_ip
}

output "Go_public_ip" {
  description = "The public IP address of the Go instance"
  value       = aws_instance.Go.public_ip
}

output "MySQL_public_ip" {
  description = "The public IP address of the MySQL instance"
  value       = aws_instance.MySQL.public_ip
}
```

## Terraformの実行

上記のファイルが出来上がったら、Terraformコマンドを実行してインフラを構築していく！
#### 構築編
```terminal
terraform init
```
構成ファイルを含むディレクトリで行うことでTerraformでの環境構築に必要な情報を作成してくれる。

```terminal
terraform plan
```
この構成ファイルで構築するインフラを構築前に確認することができる。クラウドベンダーだとミスるとお金かかるやつもあるから必須。
```terminal
terraform apply
```
`terraform plan`で確認したインフラを構築することができる。

#### 破棄編
```terminal
terraform destroy
```
このコマンドで`terraform apply`で作成したインフラを全削除することができる。


### できなかったこと、わからなかったことまとめ
- pythonのインストール、flaskのインストールまで終わったがその後pythonプログラムを起動させようとしたが`Internal Server Error`が起きて起動できなかった。
    - バックエンドプロセスになってなかったからシェルが切れたタイミングで止まってしまったのかもしれない。nohupコマンドとかscreenを使ったらもしかしたらできた？？
- goをインストールしたが下記コマンドをuser_dataで打ち込んで起動しても`go -version`を使ってもパスが通っていないことが確認された。`bash_profile`と`bashrc`の違いが影響あったっぽい。（要勉強）
```terminal
echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc
source ~/.bash_rc
```
- AWS RDSを使ってデータベース側を実装しようとしたけど、terraform側でシングルAZでRDSとEC2の連結ができなかった。


というわけでなんやかんやあーだこーだあったけどTerraformで都度都度簡単に同じインフラを組めるのとコードが作業ログの一部になってるのが便利だったって話。pythonの実行とかGoの実行とか気になるから近いうちに再挑戦しようかな、、、ネットワークの知識の復習になったのは意外と嬉しい！「ここ間違ってるよ！」ってあったら優しく教えてくれると嬉しいです。