【AWS】用語を整理しながら学ぶAWS - 番外編-EC2上にRails6環境を構築(一部Terraform利用)

# はじめに

この記事では AWS Cloud Tech を通して IaC を学習して実践していく記事です。
主な内容としては実践したときのメモを中心に書きます。（忘れやすいことなど）
誤りなどがあれば書き直していく予定です。

**※AWS Cloud Tech では CloudFormation が基礎から学習できます。**
**※今回チョットだけ扱う Terraform は AWS Cloud Tech で学習しません。(2021/02/08 Mon 現在)**

**※この記事で紹介する Rails 環境のパラメータ（パスワードやユーザ名、権限）などはご自身の環境に**
**合わせていただくようお願い致します。**

また、今回は一度も使っていませんが
今後はプライベートネットワークにも何かしら置く予定です。

## Terraform とは

HashiCorp 社 が提供する製品の名前
巷で話題のインフラをコード化するということが各クラウドでできます。
今回は AWS を題材に使いますが、Azure,GCP でもできます。

## なぜ、Terraform なのか

私が思う Terraform を採用する理由

- インフラをコードという形で実現できる
- 他のクラウドでインフラを構築しようと思った際にコードがインフラの設計図になる
- 他のクラウドにも対応しているので構築するインフラ毎にコードを書いてハイブリッドにインフラを構築することができる

AWS なら CloudFormation があるので最初の 2 つはあまり理由になりませんが

ハイブリッドにパブリッククラウドを繋ぐときに複数のクラウドサービスのIaCにも対応している為
一気通貫でインフラの柔軟な構築が可能になる。
極端なことを言えば、AWS,Azure,GCPの環境をコマンド1行で建てられる！

## Terraform のここが難しい

- HCL という前述の HashiCorp 社が提供する言語を多少学習する必要がある
- 書こうと思って調べると**古いドキュメントや記事が多くて惑わされる**

  今回は VPC 構築からターミナルソフトで SSH ログインするところまで作成しましたが
  かなり Web 上をさまよいました。

## 実際に書いたコード

実際は環境変数を利用してアクセスキーを隠したりしますが
ローカル環境でコードを書いていた為
key.tf にまとめました。

**_アクセスキーの利用は慎重に_**

key.tf

```
variable "access_key" {
  default = "アクセスキー"
}
variable "secret_key" {
  default = "シークレットキー"
}
```

main.tf

```
terraform {
  required_version = "0.14.0"
  required_providers {
    aws = {
      version = ">= 3.21.0"
      source  = "hashicorp/aws"
    }
  }
}

## ユーザとリージョンの設定
variable "region" {
  default = "ap-northeast-1"
}

provider "aws" {
  region     = var.region
  access_key = var.access_key
  secret_key = var.secret_key

}

## VPC領域の作成
resource "aws_vpc" "rails_vpc" {
  cidr_block           = "10.0.0.0/21"
  instance_tenancy     = "default"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
  tags = {
    Name = "VPC領域"
  }
}

## パブリックサブネットの作成
resource "aws_subnet" "PublicSubnetA" {
  vpc_id            = aws_vpc.rails_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "PublicSubnetA"
  }
}

## プライベートサブネットの作成
resource "aws_subnet" "PrivateSubnetA" {
  vpc_id            = aws_vpc.rails_vpc.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "PrivateSubnetA"
  }
}

##ルートテーブルの追加(0.0.0.0/0)
resource "aws_route_table" "public-route" {
  vpc_id = aws_vpc.rails_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.yamada-web_GW.id
  }
}

##ルートテーブルの追加(1a)
resource "aws_route_table_association" "public-a" {
  subnet_id      = aws_subnet.PublicSubnetA.id
  route_table_id = aws_route_table.public-route.id
}

##ゲートウェイの設定
resource "aws_internet_gateway" "yamada-web_GW" {
  vpc_id = aws_vpc.rails_vpc.id
}

## Rails用セキュリティグループの設定
resource "aws_security_group" "rails-web" {
  name        = "rails-web"
  description = "rails-web"
  vpc_id      = aws_vpc.rails_vpc.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   = 3000
    to_port     = 3000
    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"]
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    ipv6_cidr_blocks = ["::/0"]
  }

  tags = {
    Name = "rails-web"
  }
}

## EC2 インスタンスの設定
variable "ami" {
  default = "ami-0992fc94ca0f1415a"
}

variable "instance_type" {
  default = "t2.micro"
}

variable "volume_size" {
  default = 8
}

##EC2(rails-web01)
resource "aws_instance" "rails-web01" {
  ami               = var.ami
  availability_zone = "ap-northeast-1a"
  instance_type     = var.instance_type
  key_name          = aws_key_pair.key_pair.id
  private_ip        = "10.0.1.10"

  disable_api_termination = false
  vpc_security_group_ids  = [aws_security_group.rails-web.id]
  subnet_id               = aws_subnet.PublicSubnetA.id

  root_block_device {
    volume_type = "gp2"
    volume_size = var.volume_size
  }

  tags = {
    Name = "rails-web01"
  }
}

##EIP(rails-web01)
resource "aws_eip" "rails-web01" {
  instance = aws_instance.rails-web01.id
  vpc      = true
}

## EC2 key pairの名前を変数に保存
variable "key_name" {
  description = "keypair name"
  default     = "rails-web01"
}

## EC2 key pair の生成 PATH を指定
locals {
  public_key_file  = "./.ssh/id_rsa.pub"
  private_key_file = "./.ssh/id_rsa"
}

## 鍵の生成をRSAに設定
resource "tls_private_key" "keygen" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

## 秘密鍵ファイルを作成
resource "local_file" "private_key_pem" {
  filename = local.private_key_file
  content  = tls_private_key.keygen.private_key_pem
  directory_permission ="600"
  file_permission      = "0600"

}

## 公開鍵ファイルを作成
resource "local_file" "public_key_openssh" {
  filename = local.public_key_file
  content  = tls_private_key.keygen.public_key_openssh
  directory_permission ="600"
  file_permission      = "0600"

}

## EC2に登録するキーペア名を出力
output "key_name" {
  value = var.key_name
}

## 秘密鍵ファイルPATH（このファイルを利用してサーバへアクセスする。）
output "private_key_file" {
  value = local.private_key_file
}

## 秘密鍵内容
output "private_key_pem" {
  value = tls_private_key.keygen.private_key_pem
}

## 公開鍵ファイルPATH
output "public_key_file" {
  value = local.public_key_file
}

## 公開鍵内容（サーバの~/.ssh/authorized_keysに登録して利用する。）
output "public_key_openssh" {
  value = tls_private_key.keygen.public_key_openssh
}

## EC2にキーペアを登録
resource "aws_key_pair" "key_pair" {
  key_name   = var.key_name
  public_key = tls_private_key.keygen.public_key_openssh
}

```

## 各項目の説明

### ユーザとリージョンの設定

ここで設定する情報

- VPC のアベイラビリティゾーンをどのリージョンに置くか
- アクセスキーの情報
- シークレットキーの情報

ちなみにところどころで出ている「variable」というのは変数のことです。

### VPC 領域の作成

VPC 領域という名前で VPC ネットワークを作成します。
※名前はなんでも構いません

## プライベートサブネットの作成

データベースなどの重要情報を配置するいわゆる内部ネットワークを構築する。

## パブリックサブネットの作成

インターネットの公開しても良いサブネットを作成します。

### ルートテーブルの追加(0.0.0.0/0)

インターネット向けのルーティングを追加します。

### ルートテーブルの追加(1a)

パブリックサブネットを「VPC 領域」に関連付けします。（サブネットの関連付け）

### ゲートウェイの設定

VPC にインターネットゲートウェイを設定します。

### Rails 用セキュリティグループの設定

rails server 実行時にはデフォルト 3000 番ポートが利用されるので 3000 番を開けておきます。
インターネット上にサーバを公開できているかを確認する為、nginx 用に 80 番を開けておきます。

EC2は自 PC からSSHと秘密鍵を利用して接続します。
※AWS には AWS SSM というサービスがあるので秘密鍵が無くても EC2 にアクセスできます。(IAM 権限が必要)

### EC2 インスタンスの設定

ここで AMI、インスタンスタイプ、EBS ボリュームのサイズを変数にしておきます。
アベイラビリティゾーンやプライベート IP も変数にしたほうが見栄えが良いかも。。。。

### EC2(rails-web01)

ami : AMI のイメージ ID を代入
availability_zone: アベイラビリティゾーンを設定
instance_type:インスタンスタイプを設定(今回は t2.micro)
key_name:秘密鍵の名前を設定
private_ip:EC2 のプライベート IP アドレスを設定

root_block_device : EBS を設定

tags:EC2 の名前を決定

### EIP(rails-web01)

たぶん、ElasticIP の設定

### EC2 key pair の名前を変数に保存

SSH にログインするときに必要な秘密鍵の名前を変数に保存する。

### EC2 key pair の生成 PATH を指定

SSH にログインするときに必要な秘密鍵を変数に保存する。

### 鍵の生成を RSA に設定

RSA アルゴリズムでキーペアを作成するように設定する。

### 秘密鍵ファイルを作成

EC2 に SSH 接続時に利用する秘密鍵を作成する。

### 公開鍵ファイルを作成

EC2 に SSH 接続時に利用する公開鍵を作成する。

### EC2 にキーペアを登録

EC2 に公開鍵を登録

### Ruby on Rails の環境を構築(準備)

せっかくなので Ruby on Rails の環境を構築する。
その前に nginx でパブリック IP の接続確認
まずは管理者ログインを行い 、yum update -y

```shell
sudo su -
sudo yum update -y
```

必要なパッケージをダウンロード

```shell
sudo yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel sqlite-devel git wget gem

```

git config の user.name と user.email を変更

```shell
git config --global user.name "じぶんのアカウント名"
git config --global user.email "じぶんのメールアドレス"
```

今のままだと時刻が UTC なので
時刻を JST に合わせる。

```shell
sudo cp -r /usr/share/zoneinfo/Japan /etc/localtime
```

vi で JST に設定できるように中身を編集

```shell
sudo vim /etc/sysconfig/clock
```

記載内容

```shell
ZONE="Asia/Tokyo"
UTC=true
```

確認がてら date コマンドを打つ

```shell
date
 Sun Feb  7 15:24:27 JST 2021
```

日本標準時で返ってくれば OK

次に EC2 がインターネットと接続されているかを nginx で確認する。

```shell
sudo amazon-linux-extras install -y nginx1
sudo systemctl list-unit-files -t service |grep "ngin*"

sudo systemctl start nginx.service
sudo systemctl enable nginx.service

```

nginx を止める。
今回作成した EC2 を再起動して使いまわす場合は disable も忘れずに

```shell

sudo systemctl stop nginx.service
sudo systemctl list-unit-files -t service |grep "ngin*"

## disable 確認
sudo systemctl disable nginx.service
sudo systemctl list-unit-files -t service |grep "ngin*"

```

### Ruby on Rails の環境を構築(構築)

rbenv を git コマンドでクローンしてくる。

```shell

sudo git clone https://github.com/sstephenson/rbenv.git ~/.rbenv

```

rbenv 用にシェルファイルを作成する。

```shell
sudo vim /etc/profile.d/rbenv.sh

```

記載内容

```shell

export RBENV_ROOT="$HOME/.rbenv"
export PATH="${RBENV_ROOT}/bin:${PATH}"
eval "$(rbenv init -)"

```

ruby-build を git clone

```shell

sudo git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

```

ec2-user のグループを wheel グループに変更してファイルのアクセス権限を変更
※人によってはここで別のユーザを作成して環境構築に入りますが工程が増えるので興味のある方は調べてみてください。

```shell
sudo usermod -G wheel ec2-user | grep wheel /etc/group
sudo chown -R ec2-user:wheel ~/.rbenv
sudo chmod -R 775 ~/.rbenv
```

権限とグループの確認

```shell
ls -la
```

rbenv を用いて ruby をインストール
自分の PC とバージョンを合わせる為、バージョンは 2.5.8 を選択

※ちなみに Ruby on Rails を動かす為の Ruby のバージョンは 2.5 以上とのこと

```shell
source /etc/profile.d/rbenv.sh
rbenv --version
rbenv install -l
rbenv install -v 2.5.8
rbenv rehash
rbenv global 2.5.8
```

バージョン確認

```shell
ruby -v
 ruby 2.5.8p224 (2020-03-31 revision 67882) [x86_64-linux]
```

次に NVM
ザックリ言えば Nodejs のバージョン管理ツールを導入

```shell
git clone https://github.com/creationix/nvm.git ~/.nvm
source ~/.nvm/nvm.sh
```

bash_profile の設定

```shell
vi .bash_profile
```

記載内容

```shell
# nvm
if [[ -s ~/.nvm/nvm.sh ]] ; then
  source ~/.nvm/nvm.sh ;
fi
```

Node.js のバージョンは自分の PC と合わせる為
v12.19 を選択

バージョンがあるかを確認する。

```shell
nvm ls-remote | grep "v12.19.*"
```

Node.js のインストールとデフォルトバージョンの設定

```shell
nvm install v12.19.0
nvm use v12.19.0

```

Node.js のバージョン確認

```shell
node -v

```

Rails6 あたりから yarn というパッケージマネージャが必要になったので導入する。
実は yarn って npm より高速に動くらしい。

```shell
sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo
sudo curl --silent --location https://rpm.nodesource.com/setup_12.x | bash -
sudo yum -y install -y nodejs yarn
yarn install --check-files

```

Rails の DB には mariadb を採用
理由としては今後、RDS で MySQL を利用する為

※gem インストールを忘れずに！！

```shell
sudo yum -y install mariadb-server
sudo systemctl start mariadb
sudo yum -y install mysql-devel
gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'
```

仕上げに bundler と 大本命の Rails をインストール

```shell
gem install bundler
gem install rails -v 6.0.3
```

そしてお待ちかねの起動コマンド

```shell
rails new app
cd app
bundle install
rails server -p 80 -b 0.0.0.0
# 起動はOK
```

起動はできるが開いてみるとエラーになる。
どうやらデータベース周りのようだ。
![RailsSQLiteエラー.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/d2085054-c6ed-b1ae-c86d-b70f83c5a285.jpeg)

### エラーの原因を探す

mariadb の生存確認

```
sudo systemctl status mariadb
```

実行結果

> [root@ip-10-0-1-10 app]# sudo systemctl status mariadb
> mariadb.service - MariaDB database server
> Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled)
> Active: active (running) since Mon 2021-02-08 21:09:46 JST; 14min ago
> Process: 28789 ExecStartPost=/usr/libexec/mariadb-wait-ready $MAINPID (code=exited, status=0/SUCCESS)

mariadb はダイジョブ

Rails Server で読み込まれるデータベースはどうやら SQLite が標準みたいなので
./config/database.yml を編集する。

**書き間違えて戻せなくなると怖いのでコピーを取る**

```
$ [root@ip-10-0-1-10 /app] # pwd
/root/app
$ [root@ip-10-0-1-10 /app] cp ./config/database.yml ./config/sqlite3_database.yml
$ [root@ip-10-0-1-10 /app] rm -f ./config/database.yml
$ [root@ip-10-0-1-10 /app] vi ./config/database.yml
```

database.yml の中身

```yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  timeout: 5000
  username: root
  password: test
  socket: /var/lib/mysql/mysql.sock

development:
  <<: *default
  database: rails_todo_tutorial_development

test:
  <<: *default
  database: rails_todo_tutorial_test

production:
  <<: *default
  database: rails_todo_tutorial_production
```

注意点：socket のパスを間違えると mariadb が起動できなくなります。(Socket エラー)

各環境ごとにテーブルの名前をつけてください。
今回はわかりやすいように末尾に環境名を付けました。

また、当然ですが
実際にはパスワードは厳密に決める必要があります。
加えて、root 権限でアプリをデプロイすることなどしません。
適切な権限を持ったアカウントを作成してコンフィグに載せましょう。

コンフィグを変更したので起動したいところですが
この時点では gem が SQLite になっていますので mysql2 に変更

変更後の Gemfile

```gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.8'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3'

# Use sqlite3 as the database for Active Record
# gem 'sqlite3', '~> 1.4'
  gem 'mysql2'

# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
```

そしてもう一度、bundle install

```shell
$ [root@ip-10-0-1-10 /app] bundle install

Don t run Bundler as root. Bundler can ask for sudo if it is needed, and installing your bundle as root will break this application
for all non-root users on this machine.
Fetching gem metadata from https://rubygems.org/............
Resolving dependencies...
.
.
.

Using method_source 1.0.0
Using mysql2 0.5.3
Using puma 4.3.7
.
.
.
```

特にバージョン指定はしませんでしたが
バンドルされましたね。

> Using mysql2 0.5.3

これでようやく起動かと思いきや
今度はデータベースのパスワード決めていません。
![Railsデータベースアクセスのエラー.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/ecea5709-664c-d6f1-f708-3583433d9452.jpeg)

root 権限にパスワードを設定してあげましょう。

```shell
$ [root@ip-10-0-1-10 /app] mysql
MariaDB [(none)]> show databases;
MariaDB [(none)]> use mysql;
MariaDB [(none)]>  show tables;
MariaDB [(none)]> SET PASSWORD FOR root@localhost = password('test');
MariaDB [(none)]> select user,password from user;

```

まだまだやることがあります。
今起動するとこんなエラーが表示されるはずです。

![Railsテーブルエラー.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/4e750fe6-6b83-2c15-c825-0c5ef6d32a4c.jpeg)

最後にやることは 3 つ

- yarn との依存関係を解決する
- DB を作成する
- DB をマイグレーション

```shell
$ [root@ip-10-0-1-10 /app] yarn install --check-files
$ [root@ip-10-0-1-10 /app] rails db:create
$ [root@ip-10-0-1-10 /app] rails db:migrate

```

何度目かの正直

```shell
rails server -p 80 -b 0.0.0.0
```

![Railsの画面.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527543/141abdac-b0e4-6f4e-1c34-aa52528ca5af.jpeg)

## 宣伝

AWS Cloud Tech では AWS の知識を幅広く学習することが可能です。
一緒に学習できるメンバーもいるので
「一人で学習して挫折する」ということをうまく避けながら学習ができます。

一緒に勉強できる仲間を募集中です。
最近ですが、Yamada 主催のもくもく会を
AWS Cloud Tech 内で随時開催する予定ですので
もし、ご興味がございましたらAWS Cloud Techの門を叩いていただけたらと思います。

リンクは[こちら](https://aws-cloud-tech.com/)


## おわり

Terraform で EC2 を建てるところまでできたなら
他のデータベースのセットアップなども自動でやりたい感じがする。
まぁ、データベースに関しては素直に Amazon RDS を使えば良いんですけどね。
とりあえず、環境構築はこの手順でできるとわかったので最後に「terraform destory」

これだけ書いたけど用語がありすぎて。。。アリストテレスになりそうなので用語集を書きたいですね。

あ^全部コード化したいんじゃあ^