【脱レンタルサーバ】AWS EC2でLAMP Stackサーバを作成する手順

## はじめに
この投稿は、EC2をベースとしたLAMP Stackサーバの構築手順をまとめた記事です。

* EC2でサーバ構築の方法がわからない
* 具体的な手順がわからない
* レンタルサーバしか使ったことがない

といった方々に、少しでもお役に立てればと思います。
※インフラ構築については片手間で行う程度なので、手順に不足点があるかもしれないので、気づいた方がいらっしゃいましたらご指摘頂けると助かります！

## AWS 構成
本記事で取り扱うサービスは次のとおりです。各サービスごとに構築・設定手順を説明します。

- AWS EC2
- AWS Certificate Manager
- AWS ALB
- AWS WAF

## サーバソフトウェアについて
最初からサーバソフトウェアをインストールしていくのは、正直つらすぎるので、AWSマーケットプレイスに公開されているBitnami製イメージを利用します。

[LAMP Stack（LAMP with PHP 7.1 Certified by Bitnami）](https://aws.amazon.com/marketplace/pp/B072JNJZ5C)
インストールされているソフトウェアは以下の通り。

> LAMP with PHP 7.1 Certified by Bitnami
> Pre-installed and configured components include FastCGI, OpenSSL, phpMyAdmin, ModSecurity, SQLite, Varnish (TM), ImageMagick, xDebug, Xcache, OpenLDAP, ModSecurity, Memcache, OAuth, PEAR, PECL, APC, GD, cURL and more.
> Use any of the pre-installed PHP frameworks right out of the box, including Zend, Symfony, CodeIgniter, CakePHP, Smarty, and Laravel.

## 大まかな手順

1. EC2セットアップ
2. Elastic IP 設定
3. ドメイン（AWS Route53）設定
4. AWS Certificate Manager（SSL 証明書）作成
5. ロードバランサー（ALB）設定
6. WAF 設定

## EC2セットアップ

1. マネジメントコンソールで`EC2`を選択
2. `AWS Marketplace` > "lamp"で検索
3. "LAMP with PHP 7.1 Certified by Bitnami "を選択
   ![ec2-001.png](https://qiita-image-store.s3.amazonaws.com/0/22445/cb488024-4d62-e57e-7afa-e6bdd99aab13.png)
4. インスタンスの詳細はとくに編集の必要は無し。ストレージについては必要であろう容量を入力する
   ![ec2-002.png](https://qiita-image-store.s3.amazonaws.com/0/22445/08a8e3e5-3acb-bebc-d0a9-bce5da7f0286.png)![ec2-003.png](https://qiita-image-store.s3.amazonaws.com/0/22445/c2148266-3f30-2c92-90ed-f8f3a6465e52.png)
5. インスタンスと`確認と作成`ボタンをクリック
6. キーペアを作成したいない場合は新規作成が必要になる。任意の名前（英数小文字）をつけて、`キーペアのダウンロード`を押してダウンロードする。ダウンロード後インスタンス作成を続行する
  ![ec2-004.png](https://qiita-image-store.s3.amazonaws.com/0/22445/149f2c8b-2551-c7bf-83bf-2f5a1e06ff38.png)

7. 一覧にインスタンスが表示される。インスタンス一覧の情報画面 > `IPv4 パブリック IP`をブラウザで表示、確認できるかを確認
8. bash でログインできることを確認できたら作業完了

```bash
# キーペアの権限を変更
chmod 400 xxxxxxxx.pem

# SSH接続
ssh -i "xxxxxxxx.pem" ubuntu@ec2-xxxxxxxx.xxxxxx.xxxxxx.com

The authenticity of host 'ec2-xxxxxxxx.xxxxxx.xxxxxx.com (xx.xxx.xxx.xxx)' can't be established.
ECDSA key fingerprint is SHA256:xxxxxxxxxxxxx/xxxxxxx.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-xxxxxxxx.xxxxxx.xxxxxx.com' (ECDSA) to the list of known hosts.
Welcome to Ubuntu xx.xx.x LTS (GNU/Linux xxxx-aws x86_64)
*** System restart required ***
       ___ _ _                   _
      | _ |_) |_ _ _  __ _ _ __ (_)
      | _ \ |  _| ' \/ _` | '  \| |
      |___/_|\__|_|_|\__,_|_|_|_|_|

  *** Welcome to the Bitnami LAMP 7.x.xx ***
  *** Documentation:  https://docs.bitnami.com/aws/infrastructure/lamp/ ***
  ***                 https://docs.bitnami.com/aws/ ***
  *** Bitnami Forums: https://community.bitnami.com/ ***
bitnami@ip-xxxxxxxx:~$
```

### セキュリティグループについて

EC2 に関連付けされているセキュリティポリシーが初期設定によっては ssh のみしか有効になっていない可能性があります。その場合パブリック IP でアクセスできないため、EC2 のメニュー欄のセキュリティポリシーから、対象の EC2 インスタンスに紐づくセキュリティグループの`インバウンド`設定を開き、必要なプロトコルの設定を追加してください。
![ec2-005.png](https://qiita-image-store.s3.amazonaws.com/0/22445/7a41f247-7807-3159-5f2b-d534fdc2cf84.png)

## Elastic IP の設定
EC2 は初期状態ではパブリック IP となっており、インスタンスを再起動すると IP が変わってしまいます。ドメインとの関連付けを永続化するために、静的 IP を指定する必要があります。

1. EC2 メニュー内の`Elastic IP`を選択
2. `新しいアドレスの割り当て`を選択
3. `IPv4 address pool > Amazon pool`を選択して次へ（リージョンによっては Scope VPC のみしか出てこない）
4. アドレスの関連付けで`リソース > インスタンス`、`インスタンス`に作成した EC2 インスタンスの ID を指定（作成したインスタンスがセレクトボックスで表示される）、設定が完了したら`関連付け`ボタンをクリック
5. 作成完了、IP 有効後に一覧画面の説明欄に表示される`Elastic IP`でブラウザアクセス可能かどうかを調べる

### 参考
[AWS EC2 インスタンスに Elastic IP(固定グローバル IP アドレス)を割り当てる](https://ac-5.net/aws/aws_elasticip_allocation)

## 作業用ユーザ作成

### 新規ユーザ作成
SSH/SFTP 接続可能なユーザを新たに作成します。この時点では SFTP に接続を絞るなどは行いません。

```bash
# 特権ユーザに切り替え
sudo su

# ユーザ作成
sudo useradd -m {作成user名}
sudo passwd {作成user名}

# .sshフォルダ作成
sudo su - {作成user名} #ユーザ切り替え
mkdir .ssh # 秘密鍵・公開鍵の作成（id_rsa, id_rsa.pub)

# 公開鍵・秘密鍵の作成
cd .ssh
ssh-keygen -t rsa #チュートリアルに従って作成

# 秘密鍵・公開鍵設定
mv id_rsa.pub authorized_keys # 公開鍵をauthorized_keysにリネーム
chmod 600 authorized_keys # パーミッション変更
cat id_rsa # 秘密鍵のテキストが表示されるので、コピペしてxxxx.pemとして保存

```
xxx.pem を元に ssh、sftp でログインできるかを確認してください。

```
# サーバログイン
chmod 400 xxx.pem
ssh -i "xxx.pem" {作成user名}@ecX-XX-XX-XX-XX.xxxxxxx.compute.amazonaws.com
```

### ユーザの権限変更

この状態ではドキュメントルートにアクセスしてもユーザディレクトリの中以外、何も編集できない状態です。
作業ユーザ向けに書き込み許可を与えるため、以下の手順でドキュメントルートを共同編集ディレクトリとして設定します。

```bash
id bitnami #所属するグループ等を確認

# ログイン後に特権ユーザに切り替え
sudo su

# htdocsの共同編集用グループを作成
groupadd {グループ名}

# htdocsディレクトリの管理グループを変更
chgrp {グループ名} /home/bitnami/htdocs/

# Permissionを設定
chmod 775 /home/bitnami/htdocs/

# ユーザをグループに追加
gpasswd -a {追加したいユーザ名} {グループ名}

#  SGID（Set Group ID）を設定する
chmod g+s /home/bitnami/htdocs/

# 新規ファイル・ディレクトリ作成時のアクセス権の設定
# ※通常値は0022。それを0002（グループなら書き込み、読み込みが出来るファイルを標準で作成）にする
# ユーザログイン後、umask 0022 でも対応可能だが、ログイン後に設定がリセットされるため、.bashrcに記述する
sudo sh -c "echo umask 022 >> /home/{ユーザ名}/.bashrc" # 各ユーザごとに.bashrcへ追記
sudo sh -c "echo umask 022 >> /etc/skel/.bashrc" # ユーザが新規に追加される際に自動的に記述

# 既存ディレクトリのアクセス権とグループの設定
chgrp {グループ名} -R /home/bitnami/htdocs/ # -Rオプション(--recursive)でディレクトリ内のグループも変更
chmod g+w -R /home/bitnami/htdocs/ # -Rオプション(--recursive)でグループに書き込み権限を付与
find . -type d -print | xargs chmod g+w # ディレクトリのみを探索して、一括して権限を書き換える

# 書き換え後にgroupが変更になっているかを確認
ls -al /home/bitnami/htdocs/

drwxrwsr-x  4 bitnami {グループ名} 4096 xxx 00 xx:xx .
drwxr-sr-x 14 root    root    4096 xxx  0 xx:xx ..
-rw-rw-r--  1 bitnami {グループ名} 1292 xxx 00 xx:xx xxx.html
-rw-rw-r--  1 bitnami {グループ名} 4837 xxx 00 xx:xx xxx.css
-rw-rw-r--  1 bitnami {グループ名} 1150 xxx 00  xxxx xxx.ico
-rw-rw-r--  1 bitnami {グループ名} 1292 xxx 00 xx:xx xxx.html

# シンボリックリンク作成
ln -s /home/bitnami/htdocs htdocs

```

#### 参考

- [【/etc/passwd】Linuxでユーザーの一覧を確認する方法](https://uxmilk.jp/8530)
- [SGID（Set Group ID） - 特殊なアクセス権](https://kazmax.zpp.jp/linux_beginner/setgroupid.html)
- [複数ユーザーでディレクトリ管理](https://apecell.com/2008/06/20/id/53)
- [グループ開発時の権限設定](https://qiita.com/soramugi/items/53478cbee857815c4a37)


## ドメイン設定
ここでは、ドメインをAWS Route53で管理せず、DNSはお名前.comなどの外部サービスで管理している、という想定で説明します。

### Certificate Manager の設定（SSL 設定）

1. Certificate Managerの画面から`今すぐ始める`を選択
2. 証明書のリクエストでドメイン名の追加を行う。
![acm-001.png](https://qiita-image-store.s3.amazonaws.com/0/22445/8c4f8b7d-388c-09df-0a97-ec9db3b1ddc1.png)
3. DNS認証かemail認証かを選んで続行。`検証保留中`というステータスになったことを確認
![acm-002.png](https://qiita-image-store.s3.amazonaws.com/0/22445/3ecb4b98-0958-91ab-daa8-2e9f94c7810d.png)
4. 承認後、`承認完了`というステータスになると、各サービスでACMを利用できるようになる
![acm-003.png](https://qiita-image-store.s3.amazonaws.com/0/22445/12d2da39-ea78-5d8a-b02c-66ab4cf53bc8.png)

### 参考
- [AWS Certificate Manager(ACM)に承認されるまで数日かかった話](https://qiita.com/mongamae-nioh/items/7629f66797e3bc7a752d)
- [【AWS+WordPress】お名前.com で取得したドメインを常時 SSL 化する](https://www.kt1985.com/aws-wordpress-ssl-oname-com/)

## phpMyAdmin の設定
bitmani Lampstack ではデフォルトで phpMyAdmin が動作します。``http://{ElasticIPで作成した静的IP}/phpmyadmin/``でアクセスできますが、初期設定のセキュリティではアクセス許可がありません。
以下のファイルを編集することでアクセスが可能になります。

### phpMyAdmin の開通

```bash
# viで開く
vi /opt/bitnami/apps/phpmyadmin/conf/httpd-app.conf

# 以下の記述を書き換える
>> Allow from 127.0.0.1 >> Allow from all
>> Require local >> Require all granted

# サーバの再起動
sudo /opt/bitnami/ctlscript.sh restart apache
```

### 初期パスワードについて

EC2 インスタンスの起動直後であれば、EC2 画面のインスタンス`アクション > インスタンスの設定 > システムログの取得`で、**Setting Bitnami application password to 'xxxxxxxxxxx'**と表示されている xxxxxxxxxxx の部分が該当します。
あとで確認する場合は、bitnami ユーザの**bitnami_credentials**をcatして確認できます。

```bash
cd home/bitnami
cat bitnami_credentials  #bitnami_credentialsを開いてパスワードを確認

Welcome to the Bitnami LAMP Stack

******************************************************************************
The default password is 'xxxxxxxxxxx'.
******************************************************************************

You can also use this password to access the databases and any other component the stack includes.

Please refer to https://docs.bitnami.com/ for more details.
```

初期 ID とパスワードは以下の通り。

```
id: root
pass: 上述のpassword
```

### MySQLの作業用ユーザ追加

初期パスワードをそのまま開発に使うのは問題なので、DB操作用ユーザ作成を作成します。ここでは PHPMyAdmin を通してユーザを作成する方法を解説します。

1. phpMyAdmin にログイン
2. ルート画面で`User accounts`を選択
![pma-001.png](https://qiita-image-store.s3.amazonaws.com/0/22445/1cb43cb6-d9d0-9ab4-f77e-0785c1cf2143.png)
3. `Add user account`を選択
4. `User name`や`Password`を入力して、実行をクリックし、新規ユーザを作成
![pma-002.png](https://qiita-image-store.s3.amazonaws.com/0/22445/a16aa154-f969-4d97-91c3-407b9eea03bf.png)
5. ログインし直しでアクセスできるか確認

#### [OPTION] mysql コマンドでユーザ作成済みかを確認

コマンドで念の為ユーザが作成されているかを確認します。

```bash
sudo su # ルートユーザに権限変更
mysql -u root -p # mysqlにログイン
select user, host from mysql.user; # mysqlの全ユーザを確認

+---------------+-----------+
| user          | host      |
+---------------+-----------+
| mysql.session | localhost |
| mysql.sys     | localhost |
| root          | localhost |
| {作成ユーザ名}  | localhost |
+---------------+-----------+
4 rows in set (0.00 sec)
```

## ロードバランサー（ALB）の設定

従来は CloudFront 経由でのみしか設定できなかったAWS WAFですが、**ALB(Application Load Balancer)**を使うことで簡単に利用可能です。

1. EC2 メニュー>ロードバランサーを選択
2. `Application Load Balancer` を選択して作成
   ![alb-001.png](https://qiita-image-store.s3.amazonaws.com/0/22445/4ff7982d-ed2f-31a4-5ad0-bbb50435a316.png)
3. 任意の`名前`を入力し、ロードバランサーのプロトコルは http,https を選択
   ![alb-002.png](https://qiita-image-store.s3.amazonaws.com/0/22445/ac71252e-f0c1-a9c1-890c-7dfc6e41650a.png)
4. 上述で作成済みの ACM 証明書を選択する。セキュリティポリシーは指定がない場合は Default で OK
   ![alb-003.png](https://qiita-image-store.s3.amazonaws.com/0/22445/8d8addbd-f65e-c242-31a2-a075e03d2668.png)
5. セキュリティポリシーを選択。EC2を作成するときに生成されたセキュリティポリシーを利用する
   ![alb-004.png](https://qiita-image-store.s3.amazonaws.com/0/22445/6d827651-549e-d4de-e2b8-48c049140088.png)
6. ルーティングの設定。1ルーティングにつき、1ターゲットグループが指定できる
   ![alb-005.png](https://qiita-image-store.s3.amazonaws.com/0/22445/d8b057e3-b493-2bee-c254-87909a2befb4.png)
7. ターゲットのEC2インスタンスを指定。チェックを入れて`登録済みに追加`ボタンをクリック
   ![alb-006.png](https://qiita-image-store.s3.amazonaws.com/0/22445/7d7e6030-d574-081d-d679-58a5ae82ed9e.png)
8. 作成完了。ロードバランサー一覧画面に戻り、作成したDNS名をメモして、Route53や外部DNSサービスとの関連付けを行う
   ![alb-007.png](https://qiita-image-store.s3.amazonaws.com/0/22445/6091ed73-2006-1dec-4998-9f4fe02a2b99.png)


### セキュリティグループの設定
EC2とALBとを紐付けることができましたが、今のままではブラウザからEC2のパブリックIPで、サイトやアプリケーションが表示できてしまいます。
{{ 以下執筆中 }}


### 参考
* [AWSの新しいELB(AWS Application Load Balancer)を試してみた](https://qiita.com/papix/items/2b9949a61924c02628c9)

## AWS WAF設定
IP 制限とディレクトリに対してのアクセス制限の設定方法を解説します。大まかに以下の手順で設定を行います。※WAFはALB(Application Load Balancer)に対してのみ設定が可能です。

1. 制限・設定したい内容（Condition）の反映（IPのフィルタリング、アクセスを制限するフォルダー等）
2. Conditionを元にRulesを設定
3. Rulesを使ってWebACL（Web Access Condition List）を作成
4. ロードバランサー(ALB)に設定反映

今回は**特定のディレクトリに対して、指定のIPでのみアクセスが可能**という設定を行います。

### 制限IP指定

- `IP addresses`を指定
- IP Version は`IP v4'
- Address にアクセスを制限したい IP を入力※CIDR 形式でしか入力できない

#### CIDR 形式について
たとえば自身の IP が`192.168.0.1`だった場合、`192.168.0.1/32` と入力する必要があります。
CIDRの説明については[こちら](https://wa3.i-3-i.info/word11990.html)を参考にして下さい。
<small>※登録できるアドレスは /8, /16, /24, /32 の CIDR アドレスのみ</small>

### 制限対象ディレクトリを設定
正規表現によるディレクトリのマッチング設定も可能。今回は静的なディレクトリに指定で簡単に行う。

1. `String and regex matching`を選択してCreateを押す
2. 単純にディレクトリにアクセス制限を課したい場合は`Type > String match`を選択
3. Filter settingsは`Part of the request to filter on`を`URI`、Match typeは`contain`、Transformationは`URI decode`を選択
   ![waf-001.png](https://qiita-image-store.s3.amazonaws.com/0/22445/cb699cd6-611f-b34e-8626-ba1abbdd528b.png)
4. Add Filterを押して、filterに追加する。問題なければCreateを押して作業完了


### Roules設定

1. `Rules`の`Create Rule`を選択
2. `Name`、`CloudWatch metric name`を指定、`Rule type`は何も指定がなければ、`Regular rule`を設定
  ![waf-002.png](https://qiita-image-store.s3.amazonaws.com/0/22445/e1f2e5fe-4aed-a16b-f1da-9e3395ee1c36.png)
3. Add conditionsについては上記で設定済みのIPとディレクトリ指定設定を利用する。`does not` `originate from an IP address in`で、IPが管理者や特定の会社のIPでない場合と指定、Andで `does` `match at least one of the filters in the string match condition`としてディレクトリ設定を行う。この設定で、**制限IP以外から指定ディレクトリにアクセスがあった場合、アクセスを制限する**というルールが策定できる
   ![waf-003.png](https://qiita-image-store.s3.amazonaws.com/0/22445/e6b12def-01d9-681b-94c7-bbc2a1c76287.png)
4. `Create`を押して作業完了

### Web ACL 作成

1. `Web ACLs > Create Web ACL`を選択
2. `Name web ACL`画面に移動。`Resource type to associate with web ACL`でApplication load balancerを、`AWS resource to associate`では作成済みのロードバランサー(ALC)を選択
   ![waf-004.png](https://qiita-image-store.s3.amazonaws.com/0/22445/f74e45ba-3b5f-3e0e-d7b6-f50287380a3e.png)
3. Create conditions はすでに condition を作成済みなのでスキップ
4. `Create rules`で、設定済みのルールを `Add rules to a web ACL`から選択・追加。`Allow all requests that don't match any rules`を`Allow all requests that don't match any rules`に設定
  ![waf-005.png](https://qiita-image-store.s3.amazonaws.com/0/22445/839cb703-3e33-d0a3-4fae-050be340b355.png)
5. `Review and create`で`Confirm and Create`を押すと作成完了
6. 設定したディレクトリにアクセス制限がかかっているかどうかを確認


### 参考
- [[素朴な手順]Cloudfront に対して AWS WAF で地域制限をかけつつ特定 IP からアクセス許可を入れてみました](https://dev.classmethod.jp/cloud/waf-cloudfront-geographic-match-and-ip-match/)
- [AWS WAF を使って、特定の URL への接続をブロックしてみた](https://dev.classmethod.jp/cloud/aws/aws-waf-url/)

## Auto SnapshotによるEC2の自動バックアップ（オプション）
自動バックアップの方法は自力でLambdaを書くなどの方法もありますが、もっとも簡単な方法として、CloudWatchのルールを使った自動バックアップ、最近だと**Amazon Data Lifecycle Manager (Amazon DLM)**を使った方法があります。詳しくは各参考記事を確認してください。

2018年12月現時点では、世代管理や何世代から破棄するなどの設定を柔軟にできるAmazon DLMよるバックアップを推奨します。

### Amazon DLMよるバックアップ方法
[【新サービス】EBSスナップショットのライフサイクルを自動化するAmazon DLMが登場！](https://dev.classmethod.jp/cloud/aws/amazon-dlm-ebs-snapshot-lifecycle/)

### Cloudwatchによるバックアップ方法
* [EBS 自動スナップショット作成について](https://www.s-style.co.jp/blog/2017/11/1280/)
* [CloudWatch イベント を使用した、自動化された Amazon EBS スナップショットのスケジュール](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/TakeScheduledSnapshot.html) 

## 終わりに

個人的にはレンタルサーバとほぼ同等の構成・構築をAWSで行なうのであれば、[Lightsail](https://aws.amazon.com/jp/lightsail/)で十分だと考えています。一方でロードバランサーの細かい設定、WAF（Web Application Firewall）の設定等、オプションを簡単に導入していきたい、というケースが想定される場合、EC2で作るケースが多いです。

今回の構築調査・実作業の感想として、マーケットプレイスのディスクイメージを使うと、思った以上に構築作業のコストが下げられることがわかりました。ただ一方、比較的簡単とはいえ最初からサーバを立ち上げるのは、小規模な会社やインフラ専属のエンジニアが在籍していないと難しいことに変わりません。その後のセキュリティ対策・メンテナンスコストもかかるので、なおさら難しい..と思いました（苦笑

AWSに縛られず、在籍しているエンジニアのスキル、アクセス負荷やユーザ見込み数、その後の運用コストを考えて、プロジェクトに最適なインフラ環境を立ち上げられるのが理想だと思います。