インフラ素人が３週間でWebサービスのインフラを0から構築するためにやったこと

#目次
[1. インフラ設計](#1-インフラ設計)
[2. ネットワーク構築](#2-ネットワーク構築)
[3. サーバ・DB構築](#3-サーバdb構築)
[4. Lambdaによる自動化](#4-lambdaによる自動化)
[5. Amazon SES・SNS](#5-amazon-sessns)
[6. 開発環境構築(docker)](#6-開発環境構築docker)
[7. jenkins・phpmyadminなど開発ツール設定](#7-jenkinsphpmyadminなど開発ツール設定)
[8. VPN接続](#8-vpn接続)
[9. 起きた変化](#9-起きた変化)
[10. おまけ](#10-おまけ)


# はじめに
インフラ素人同然レベルだった私が実務において、Webアプリケーションのインフラを０から構築することになりました。

期間は約３週間、**インフラ設計から行い、構築したインフラ上でWebサービスが稼働できる状態**まで持っていくことがゴールになります。　

約３週間の間、AWSやミドルウェア周りの技術に触れ、今まで考えてもみなかった知識の習得や、多くのつまづきを通して貴重な経験をすることができました。

チームメンバーと分担しながら進めたので、あくまで自分のやったことベースにはなりますが、３週間どんなことをやったのか、特につまづいたことを中心に書いていきたいと思います。

---
※基本的なインフラ構成はほぼ全てAWSのリソースを使用しました。
【使用技術】
・ AWS（EC2, RDS, VPC, Route53, Lambda, IAM, SES, SNS, ACM, CloudWatch, etc.)
・ docker 
・ Nginx(Webサーバ）
・ Puma(アプリケーションサーバ）
・ Ruby on Rails 5.2


## 開始前の筆者のレベルを３行で！
・自社開発エンジニア歴５ヶ月の駆け出しエンジニア
・AWS Cloud Practitionerの資格は持っているが実務でのインフラ（AWS）経験は無し
・ミドルウェア周りの知識はほぼゼロ（Webサーバとアプリケーションサーバってどう違うの？レベル）


# 1. インフラ設計
まずやったこととして、Webサービスを稼働させる上でどのようなインフラ環境が必要か理解し、可視化することです。

今回、実装するのは、Web開発では基本となる **開発環境(development)、ステージング環境(staging)、本番環境(production)** からなる構成です。
なんとなくの理解だったので、各環境の役割について、改めて理解するのに良い記事があったので共有します。

https://qiita.com/suin/items/004679b27d4a1f8bcb36

## 事前学習
とはいっても、
VPCってなんぞや？　サブネットの切り方ってどうするの？　NATゲートウェイとは？　ALBとロードバランサーなにが違うの？

など、私にとってネットワーク設計やサーバ構築が初めての経験だったので、知識の習得のため最初の数日はとにかくインプット量を意識して手を動かしながら学習しました。

VPC構築、サブネットの設定、EC2構築など実際にAWSマネジメントコンソール上でポチポチやりながら、「こことここが繋がるのか〜」「こうすれば良いのね」といった感じで、体系的にAWSのリソースを理解しながら、知識を蓄えていったところが大きいです。
一部、以下**AWSCloudTech**のサービスを使ったりなんかもしました。

https://kws-cloud-tech.com/


## 実装するインフラ構成を図に落とし込む
理解が少し曖昧なところがありながらもなんとか、今回実装するインフラ構成を図に落とし込みました。
ここまででおおよそ３日です。

Webサーバはアクセスを考慮して、現時点では冗長構成にせず単一のインスタンスのみで運用を行い、batchサーバの役割も持たせます。
本番環境では、バウンスメール等はSES,SNSを使用し、通知を行いレートの管理をします。

他ネットワークからのアクセスも可能にするため、AWSClientVPNを使用したVPN接続も実装する予定です。


![インフラ設計図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/2bc680c7-69ed-8593-76ed-ae7ad9ab8440.png)


## Excelにインフラ設計情報の詳細をドキュメント化
インフラ設計情報として、開発する上で必要になってくる細かな設定に関しては、都度Excelに記載し、後ほど忘れないためにドキュメント化し管理しました。
・RDSの詳細設定
・各インスタンスの詳細設定
・pemキー情報、IAM情報、各種ログイン情報　
・ssh接続コマンドチートシート　などなど


# 2. ネットワーク構築　
ここから実際に手を動かしていきます。上記インフラ構成を実現するためにまずは、サーバを設置する箱となるネットワークの構築（VPC)から始めました。
ざっくり手順は以下のとおり実現しました。

1.VPC設定
2.VPC内にサブネット構築
3.インターネットゲートウェイの設置
4.NATゲートウェイの設置
5.VPC間のピアリング接続の設定
6.サブネットのルートテーブルの設定

具体的な設定についてここでは詳細省きますが、これでおおよそ必要なるネットワーク構成は出来あがります。

1つ注意点として、
VPCやサブネットを作成する際の**IPv4CIDRブロックは、以下のプライベートIPアドレスの範囲内で作成する**必要があります。

プライベートアドレスは以下のように範囲が定められており、この範囲外のIPアドレスはグローバルアドレスとなってしまうからです。

| クラス | 範囲 | ネットワーク数 |
|:--|:--|:--|
| クラスA | 10.0.0.0 ~ 10.255.255.255 (10.0.0.0/8) | 1 | 
| クラスB | 172.16.0.0 ~ 172.31.255.255 (172.16.0.0/12) | 16 | 
| クラスC | 192.168.0.0 ~ 192.168.255.255 (192.168.0.0/16) | 256 |

また、AWSで設定できるCIDRの最小単位は　`/28`となっており、
例えば、クラスBで設定する場合、`172.16.10.0/29`などは設定できないのでご注意を！

## パブリックサブネットとプラベートサブネット
自分も勉強していく中で、パブリックサブネットとプライベートサブネットってどう違うのと疑問に思っていました。
サブネット作成のコンソール画面でも**パブリック/プライベート**の選択などなかったはずです。

**【結論】**
サブネットに関連づけるルートテーブルへの記述内容で変わる

**・パブリックサブネット**
すべてのIPアドレス宛のターゲットがインターネットゲートウェイに向いているもの。

| 送信先 | ターゲット |
|:--|:--|
| 0.0.0.0/0 | インターネットゲートウェイ　|


**・プライベートサブネット**
すべてのIPアドレス宛のターゲットがNATゲートウェイに向いているもの。
プライベートサブネットがインターネットと通信するためには、NATゲートウェイがあるサブネットのルートテーブルへの記述に、↓のように`0.0.0.0/0`がインターネットゲートウェイに向かうように設定する必要があります。

| 送信先 | ターゲット |
|:--|:--|
| 0.0.0.0/0 | NATゲートウェイ　|



# 3. サーバ・DB構築　
サブネット内に必要となるEC2インスタンス、またはRDSを作成していきます。
（EC2,RDSの詳細な作成設定については記述を省略します）

パブリックサブネットに配置するインスタンスには、インターネットに接続するため、ElasticIPを関連づける必要があります。

パブリックサブネットに配置したインスタンスは通常、パブリックIPv4アドレスが割り当てられます。
しかし、これだとインスタンスの起動停止のたびに毎回IPアドレスが変わってしまいます。

それでは大変なので、EIPを取得し、インスタンスと関連づけることでEIPを固定のパブリックIPアドレスとして使用することができます。

## EC2インスタンス作成とNginxインストール
必要となるEC2インスタンスを作成します。
アプリケーションを動かすインスタンスには、RailsアプリケーションサーバであるPumaと通信するためのWebサーバ（Nginx)を事前にインストールします。
アプリケーションデプロイ後には、NginxとPumaを通信させる必要があります。

https://qiita.com/tamorieeeen/items/07743216a3662cfca890

## EIPの上限はデフォルトだと５個な話
インスタンスにEIPを割り当てるときに、画像のようなエラーがでてきました。
これはEIPの数が制限に達したという警告です。

EIPは、サポートプランにも寄りますが、Developerプランではデフォルトでは5個までしか使用できません。（デフォルトVPC内に２つ使用されているので、実質３個）

５個以上使用する場合には、**Service Quatas**のダッシュボードから申請を行う必要があります。
https://ap-northeast-1.console.aws.amazon.com/servicequotas

なお、一気に20個とか申請すると、**「申請する理由を詳しく教えてください」**などとAWS側から怒られてしまうので、まずは５個ぐらいから始めると良いと思います。（※申請は全て英語です）

私は、デフォルトVPCに関連づいているEIP２個を外したのと、追加申請５個で足りました。

![スクリーンショット 2022-01-09 15.45.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/d1af9caf-33fd-7c57-8d7c-f194199f36ce.png)


## RDSのフェイルオーバー設定
RDSを作成していきます。
RDSの詳細設定に関しては、各自RDS使用用途によって異なるかと思うので、詳細は省きます。

可用性の確保のため、RDSはマルチAZ構成にします。
障害時にRDSを他AZで起動させる**フェイルオーバー**をさせる必要があります。
RDSのダッシュボードから**サブネットグループ**の作成を行うことで、複数のRDSをグループ化し、フェイルオーバー時にサブネットグループ内のRDSが起動します。

フェイルオーバーの確認については、以下記事を参考にテストしてみました。

https://dev.classmethod.jp/articles/multiaz-rds-failback/

## EC2-RDS間の疎通確認
アプリケーションが動く、EC2インスタンスからRDSへ接続ができることを確認します。
RDSのエンジンとしてMySQLを選択していた場合、EC2インスタンス上で、以下コマンドでRDS内のMySQLと接続ができます。

RDSに設定しているセキュリグループのインバウンドルールに、接続元EC2からのMySQL通信（３３０６番ポート）の許可を忘れずに！

```bash:接続元EC2インスタンス 
$ mysql -u マスターユーザー名 -pパスワード -h エンドポイント
```

マスターユーザー名、パスワードは、それぞれRDS作成時に決定したものです。
エンドポイントはRDS作成後に確認できます。

## ALBの使用
一台のEC２ならロードバランサーは使用しなくても良いのでは？　と思ったのですが、AWSのロードバランサーは単なる負荷分散以外の機能も提供してくれるので、そちらのメリットを加味してALBを使用します。

https://dev.classmethod.jp/articles/benefit_elb_with_one_ec2/

またALBには、Amazon Certificate Manager(ACM）からSSL証明書を取得し、HTTPSでの通信を設定します。
これでALBまでの通信は、HTTPSでの通信が可能となり、ALB以降のWebインスタンスなどへの通信は、HTTPでの通信となります。
こうすることで、暗号化をなくした通信(HTTP）により、安全性を保ちつつWebサーバへの通信負担を軽減することができます。

https://recipe.kc-cloud.jp/archives/11084


# 4. Lambdaによる自動化
Lambda,EventBridge, CloudWatch, AWSChatBot, SNS このあたりのサービスを使用して開発する上で管理しやすくなる自動化を行いました。


## Lambda✖︎EventBridgeによるEC2インスタンス起動・停止の自動化
開発ツール系のインスタンスや使用頻度の高いインスタンスについは起動・停止を自動化すると便利です。
私の場合は、開発期間中は、9:00~21:00で自動起動・停止するようにEventBridgeの設定を行いました。
↓記事を参考に、実装しました。

https://qiita.com/YK0214/items/59bc0e5ae89f68af74b3

## 環境ごとのEC2・RDSの停止忘れ防止
staging環境、production環境（運用開始まで）に属するインスタンス、RDSについては毎日↑記事の**Lambdaの停止スクリプト**のみを実行し、停止忘れ防止策をこうじました。

## AWS ChatbotによるSlack通知
Lambdaの実行結果は、CloudWatchLogsに蓄積されます。
インスタンスの停止スクリプトなどはエラー通知を即座に把握したいため、実務のコミュニケーションツールとして使用しているSlackに通知するように設定しました。
すごく便利！！

https://masakimisawa.com/lambda_error_notification_slack_from_chatbot/

# 5. Amazon SES,SNS　
本番環境や開発環境において、AWSからメールを送信するにはAmazon SimpleEmailService(SES)を使用します。
ここでもざっくりと手順を説明すると以下になります。

1.メール送信権限付きIAMユーザー作成後、アクセスキーを取得
2.Railsアプリコード内の　`~アプリ名/config/environments/development.rb, staging.rb, production.rb `配下にアクセスキーを記載
3.SESでドメイン、メールアドレスの認証
4.認証したドメイン・アドレスからのメール送信の確認
5.SNSでメール通知設定

## メール送信権限付きユーザーのアクセスキーを取得
メール送信権限を持つIAMユーザーを作成します。（既存のユーザーでも大丈夫です）
アタッチするポリシーは、`AmazonSesSendingAccess`というデフォルトポリシーがあるのでそれをユーザーにアタッチすれば大丈夫です。

```json:AmazonSesSendingAccessのjsonファイル
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ses:SendRawEmail",
            "Resource": "*"
        }
    ]
}
```
 
## テストメール送信
ドメイン・メールアドレスの認証を行います。
認証からテストメールの送信の仕方までは↓記事が参考になります。

※認証するドメインのメールアドレスに関しては、Route53のほうで事前に認証済みのドメインでないとSES上で認証されないので注意が必要です。（経験あり😢）

https://qiita.com/kanadeee/items/dbc412f85f83bd2efc60

バウンスメールのテストをしたい場合については、上記テストメールの送信先を`bounce@simulator.amazonses.com`に設定してあげると、バウンスレートを上げることなくバウンスメールのテストを行なってくれます。
その他、通常の成功メールや苦情メールも同様で、宛先を↓に記載のアドレスにして送信するとレートとは無関係にテストができるのでオススメです。

https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-email-simulator.html

バウンスメール・苦情メール等については、SNSとSESを連携させて通知設定を行います。
これも↓記事が参考になるので、簡単に設定できます。

https://dev.classmethod.jp/articles/ses-bounce-check/


ここまでだいたい２週間ぐらいでした。
この時点で、記述はしてないですが、セキュリティグループやネットワーク周りの設定、インスタンス内の操作など、たくさんAWSリソースに触れたのでだいぶ理解が深まりました。

やっぱり実際に触ってみることで、知識とはまた別の理解が深まり、良いですね。そして何より楽しい☺️

# 6. 開発環境構築(docker)　
ローカルの開発環境と検証用のdevelopment環境はdockerで構築を行いました。

`Dockerfile`, `docker-compose.yml`を作成し、シェルスクリプト内にそれらファイルを参照することで設定できる、開発環境構築用のスクリプトを用意しました。
開発環境構築には、このスクリプトを叩くだけで、dockerでの開発環境ができあがります。
合わせて、アプリケーションに初期データとして必要なデータを投入するrakeタスクも実装しました。

ざっと内容を紹介

```shell:setup.sh
#!/bin/bash
# 作業用ディレクトリ作成
if [ ! -d ./workspace ]; then
  mkdir workspace
fi

# 作業用dir配下にプロジェクトをclone
git clone git@~~

# キャッシュを使用せず、imageをbuild
docker-compose build --no-cache
# 必要な初期rakeタスクを流す
docker-compose run web bundle exec rake rakeタスク
# 停止済みのコンテナを削除
docker container prune -f
 
```


## ridgepole（RailsのGem）導入時にハマったこと
今回、アプリケーションは`migrationファイル`での管理ではなく`ridgepole`を使用した`Schemafile`管理で行います。
私自身初めてのridgepole導入だったので少し苦戦しました。

詳細はこちら↓

https://qiita.com/yokoo-an209/items/e9859d1105de49c175d3


# 7. jenkins・phpmyadminなど開発ツール設定　
開発ツール用のインスタンスにjenkins, phpmyadminをそれぞれインストールします。

それぞれのインスタンスには、`EC2FullAccess`などのポリシーを持ったアクセスキーを配置する必要があります。

```bash:jenkinsインスタンス
# 認証情報を設定
$ aws configure
# 4つ聞かれるのでそれぞれ入力
1. アクセスキー
2. パスワード
3. region
4. output形式

# この配下に認証情報（アクセスキー等がある）
$ cd ~/.aws 
$ ls -la
=> config, credentials
```

## jenkinsのジョブが失敗する
アクセスキーの設置が終わり、jenkinsのジョブを作成し実行たところ、エラーになりました。。。
jenkisnのコンソール出力を見ると以下のようなエラーメッセージが

```
unable to locate credentials. you can configure credentials by running "aws configure".
```

おいおい、アクセスキーはちゃんと配置したはずだぞ🤔
設定を確認してもきちんと登録されているはず・・・

```bash
# アクセスキー設定情報を確認
aws configure list

=> aws_access_key_id = *****************
=> aws_secret_access_key = *****************
```

【結論】
jenkinsのジョブを実行しているのは、ec2の中のjenkinsユーザー
だからjenkinsユーザーに認証情報を設定してあげる必要がある（これまでは、ec2ユーザーとrootユーザーに設定していた）


```bash
# rootユーザーになる
$ sudo su  

# jenkinsユーザーになる
$ su - jenkins

# 認証情報を確認すると・・・なにも設定されていなかった😢
$ aws configure list
=> aws_access_key_id
=> aws_secret_access_key

# 再度認証情報を設定
$ aws configure 
```

無事、ジョブが成功した！

余談ですが、画面上で作成したjenkinsのジョブはサーバ内jenkinsユーザーの以下に格納されているので、確認できます。

```bash:jenkinsインスタンス
# rootユーザーになる
sudo su
# jenkinstユーザーになる
su - jenkins
cd workspace
ls -la
=> 作成したjenkinsジョブ一覧が表示
```


詳しくは別途記事にしました↓

https://qiita.com/yokoo-an209/items/4415cc2654af8543bc06

# 8. VPN接続　
別のネットワークから、この環境へのアクセスを可能にするためAWSClientVPNを使用し、VPN接続を試みます。

パブリックサブネット内のNATゲートウェイにアタッチしたEIPを固定IPアドレスとして、インターネットに接続できることをゴールとします。
↓記事を参考にするとできるかと思います。

が、私がハマったことで一つ注意点！

**ClientVPVEndpointと関連づけるサブネットはプライベートサブネットでないといけない**
これに気づかず、数時間ハマりました。。。

また、VPN接続するときに作成されるENIは、接続のたびに新しいものが作成されます。
ENIに割り当てられるパブリックIPアドレスは接続のたびに毎回変わりますが、それをNATゲートウェイのEIPで固定化するというイメージです。


![AWS Client VPN２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/24bbea25-1f93-3013-3f88-581850450a85.png)


https://dev.classmethod.jp/articles/aws-client-vpn-with-static-ip/

https://blog.serverworks.co.jp/tech/2019/06/10/awsclientvpn/


# 9. 起きた変化
以上作業を約３週間で行いました。

インフラにほとんど触ったことの無かった素人が、３週間のインフラ構築経験を通して、どのような変化があったのか、**技術的な側面と価値観・考え方の面**から感じたことを書いていきたいと思います。

## 技術面
【AWS】
・AWSを使用して、迷わず基本的なアーキテクチャの設計ができるようになった
・AWS上のサービスをドキュメントを読むだけで使いこなせるようになった
・AWSリソースの管理能力が向上した


【docker】
・dockerを使用して、開発環境の構築ができるようになった
・Dockerfile, docker-compose.ymlなど記述内容を理解し、自分の実装したいように改修できるようになった

【その他】
・Webサーバやアプリケーションサーバのどのレイヤーに対してエラーが発生しているのか感覚的にわかるようになった
・ログを見る習慣/ログからエラーの原因を特定する力がついた
・Linuxサーバ内のディレクトリ構造や必要ファイルなどが把握でき、行いたい修正を加えられるようになった
・Webの３層構造を体系的に理解することができた

---
ざっとこんな感じになります。
特に自分として変化を感じたことは、基本的なアーキテクチャ設計をする際のAWSリソースに対して**わからない・これどうやるんだ**という感情がなくなったことです。

今までは、事前知識としてなかったのはもちろんですがどこかAWSを気軽に触れないものだと思っていました。
ですが、毎日AWSを触りながら理解していくことで、AWSに対するハードルが低くなっていくのを強く感じました。

今では、初めて使用するAWS上のサービスでも **まずは触ってみる**ということを大切にし、ドキュメントや参考記事を読めばだいたいのことはなんとかなるようになったと思います。

わずか３週間ですが、自分のエンジニアとしての知識・技術が３週間前と比較し格段に上がったのを感じました！

## 価値観・考え方
インフラ構築を経験してみることで、インフラの楽しさを感じたのと同時に、改めて、AWSの素晴らしさに感動しました。

マネジメントコンソールをいじるだけでサーバ・ネットワークを作れたり、あらゆるサービスを連携することでできることの無限さを感じました。

インフラというのはどこか、モヤに隠れた分かりづらいものという意識があった中、自分で０から設計し図を書き、それを実際に構築するという経験のおかげで構造的にインフラを捉えられるようになりました。

**AWSを理解するにはまず図を書いてみる**　とよく聞きますが、これは本当にそうだと思います。

図を書きながら、「これってどうやって通信してるの？」「この部分にはどういう制御が必要なんじゃないか」と頭の中で考え、それを実際に手を動かしてみることで必要な処理や構成が感覚的に理解できるようになってきます。

この経験をきっかけとして、これからもインフラに関する知識・技術を勉強し、今後はSAA,さらにはその上のSAPの取得を目指していきたいと思います。
(※追記：この3週間の経験から１ヶ月後、AWS SAAに合格しました)

そして、実務としてこのような素晴らしい挑戦の機会をいただけたことに感謝し、これからも成長できるように頑張っていきたいと思います。


# 10. おまけ
約３週間インフラ構築をする上で、ミドルウェアの操作についてはコマンドラインが主となるので、自然とコマンドを使う機会が多くなりました。
そこで備忘録がてら、よく使用したコマンドを残していきたいと思います。
## よく使ったコマンドたち

```shell
■ コマンド
#　ファイル削除
rm -rf ディレクトリ
# 検索
grep -r 正規表現
# プロセス確認
ps aux | grep XXX
# 環境変数確認
env

■ systemctl
# start, stop, restart, statusなど
# Nginxの起動状態確認
systemctl nginx status
# MySQLの起動状態確認
systemctl mysql status

■ crontab
# サーバの初期設定をするcrontabの内容確認
crontab -l
# crontabの内容編集
crontab -e

■ Git
# 接続先のremoteリポジトリを確認
git remote -v
# remoteリポジトリを設定
git remote set-url origin git@~~~

■ ssh
ssh -i ~/.ssh/キー.pem ec2-user@IPアドレス


```