ECS+ロードバランサー的なことをEC2で実現して、コスト削減

dockerで開発している場合に、ECSとロードバランサーを遣うと、SSL環境でのサブドメインステージング環境とかあっという間に立てられますよね。
なれてないときはそんなのでポコポコサーバーを立てていたいのですが、ロードバランサー毎に費用がかかったりして立ち上げ期とかで開発予算が少ないときはなにげにボディーブローとなります。

そこで、極小のリソースでワードプレスをAWS上に立ててみた方法をまとめます

## 要件

* 規模が大きくなってきたときにECSを遣うことを想定してそれに近い環境を構築する
* ほぼほぼ、それに近い形の開発環境でdocker-composeで実現する。というか、開発環境のdocker-composeをできる限りAWS上で実現する
* 取得済みのサブドメインでアクセスできる様にする
* 当然SSL接続を実現する

## 手順
ということで、wordpress のdocker環境をawsに立てる手順をまとめます

1. amazon linux2 のEC2イメージを立てる
2. 1のEC2イメージにdocker-compose可能な環境を構築する
3. route53にドメインが登録されていることを前提に、1のEC2イメージにサブドメインでアクセスできる様にする
4. 1のEC2イメージに無料のSSLをインストールする
5. 1のEC2イメージにwordpressのdocker環境を作成して、81版ポートでアクセスできる様に設定する
6. まずは sslなしのポート直接指定でアクセスする
7. 1のEC2イメージのポートフォワード設定をし、ssl接続を可能にする

## 1. amazon linux2 のEC2イメージを立てる

AWSのアカウントはお持ちでしょうか？
AWSって、サインアップから12ヶ月間の無料枠が結構充実しているんですよね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/297e6f3d-bbd6-62b8-c523-af1d0bf06f0c.png)

AWSのハンズオンイベントに参加すると、新規アカウントを作って初年度の無料クレジットを使ってハンズオンする手順が前提になっていたりします。

ということで、AWSのアカウントを作って12ヶ月以上経っている場合も異なるメールアドレスで新たにアカウント作成して無料枠で色々試してみるということが半ば公式になっていると考えていもいいかもですね。。

開発環境のdockerイメージのとして、centosやubuntuを選択していることが多いかと思いますし、最近だとalpineイメージをベースにして極限までdockerimageを減らす方法もおおい気もします。

* https://hub.docker.com/_/alpine/

で、docker imageを動かすことを前提にEC2環境を構築するとなると、EC2タイプのECSと同じ環境を構築すればいいのかなと。それが amazon-linux-2の様で、下を見ると別のECSに特化しているわけではなさそうです。中身は redhatとのことで、運用に当たって web上でも情報が検索出来る感じになっています
なにげに何か必要なものが出てきたら yum install でインストールして行きます

* https://aws.amazon.com/jp/amazon-linux-2/

今回必要なEC2イメージは 将来ECSに移行したときに再テストの懸念を極小にすることを念頭に置いているので、ECS向けのEC2構築と同じ手順で立ててみます。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/getting-started-ecs-ec2.html

2021年1月現在、上の手順の真ん中辺にある「Amazon ECS-optimized AMI」がそれに当たります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/039f0f4b-96eb-752e-00f2-32466681718a.png)

AMIイメージにたどり着くのが結構大変なのですが

1. https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-optimized_AMI.html
2. Amazon ECS-optimized Amazon Linux 2 AMI  バリアントという情報を知る
3. AMI IDをAWS CLIで取得する（AWS CLIのインストールが必要・・）
4. と書いてあるものの、こちらのページで最新情報がリストされていると見つける https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-ami-versions.html#ecs-ami-versions-linux
5. Amazon ECS-optimized Amazon Linux 2 AMI の最新IDを見つけます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/8f28c5f6-5eb1-068d-b19a-329348aee261.png)

けど、image idが分からないんですよね。。とはいえ、ここでは、`20201209`というキーワードが見つかるので、"ECS optimized 20201209"というキーワードで検索してみると・・
コミュニティAMIで一件見つかりました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/8d6c3ac1-34d3-d1a6-e5ad-d141eba207dc.png)
 
正解は "amzn2-ami-ecs-hvm x86_64 20201209" とかで投げた結果が今の時点ではよさそうです。

もっと確実なのは、ちなみに AWS-CLIでコマンドを投げて返ってくる情報
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/463110e5-bff9-8b4e-ce7b-4762d9cb7e69.png)

この結果を基にすると `ami-021541b9f47c3c5cc`により、マシンイメージが特定されるわけです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/6f11e5a9-c075-b5de-d3b8-f0420b8f55da.png)

違うのが返ってくるけど、まあ、amazon-ecs-optimizedとなっているのでどちらでも大丈夫そう・・

今回はAWS-CLIコマンドで返ってくるimage-idで作成します。

* EC2ダッシュボード → インスタンスを起動 →image-idで検索 → t2.microを選択 → "インスタンスの詳細設定", "ストレージの追加" それぞれデフォルトで進む(IAMロールとか特に設定しなくても目的は達せられます)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/88183436-51a0-7069-07b9-e0d5862343f2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/952221ff-c34b-7b62-fe2a-2b07549a0d28.png)

* セキュリティグループ設定：とりあえずは ssh, http, httpsを必要に応じて設定しましょう。ロードバランサーを遣わないので、このインスタンスに外部から直接つながる様に設定する必要があります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/065d21cc-cceb-8808-3790-5aa40e17dd4b.png)

## 2. 1のEC2イメージにdocker-compose可能な環境を構築する

1. userの作成(下の例では someuser) とsudo権限付与
    * 'sudo adduser someuser'
    * 'sudo visudo'
        * %wheel ALL=(ALL) NOPASSWD: ALL のコメントを外す
    * 'sudo usermod -aG wheel someuser'
2. dockerのインストール→インストール済みだった。。
3. 起動時にdockerが立ち上がっているようにする  'sudo systemctl enable docker'
4. docker-composeの取得（最新版だとGLIBCのエラーになるのでその場合はバージョンを下げます)
    * sudo curl -L "https://github.com/docker/compose/releases/download/1.28.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    * sudo chmod +x /usr/local/bin/docker-compose

## 3. route53にドメインが登録されていることを前提に、1のEC2イメージにサブドメインでアクセスできる様にする

### 3-1. 固定ipをわりあてる。
起動しているEC2インスタンスに対してelastic ipを使う場合は料金が発生しないとのこと!

    2. Elastic IPアドレスの割り当て
    3. Elastic IPアドレスの関連付け
    4. 上で起動したインスタンスを選択して「関連付ける」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/5a0f0f49-2161-2b10-7389-883644c8bc2c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/63b3b327-df8f-93c7-058d-1b160424fa71.png)

### 3-2. DNS(Route53)でサブドメインを割り当てる

(既にホストゾーンが作成されていることを前提としています）
* 上で取得したIPアドレスをAレコードとして、サブドメン設定を追加する
    * レコード名：サブドメイン ○○.sample.jp とか
    * レコードタイプ：A
    * 値：上で取得したipアドレス
    * その他 デフォルト値で大丈夫かと (ex. TTL=300)

## 4. 1のEC2イメージに無料のSSLをインストールする

ここからは1で作成したEC2イメージに ssh接続しての作業になります
インスタンス概要の「接続」の説明等が参考になります
色々とわかりやすい記事もたくさんあるので、こちらでは割愛します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/bf49f125-2657-6c9f-8f78-52b2d13a5f34.png)

### 4-1. apacheにバーチャルホストを設定する

大事なのは、次のプロセスで、certbot というツールを使って、証明書を取得するのですが、その際に証明書を取得したいアドレスでhttp接続出来ることが必要なのです。

ということで、まずはhttpサーバーを立てます。ここではapacheをインストールして、サービス登録します
(この先rootユーザーではなく、ステップ2で作ったユーザーでログインしている前提なので注意）

```
sudo yum install -y httpd
sudo systemctl start httpd
sudo systemctl enable httpd
```
この環境だと、/etc/httpd/conf配下に httpd.confファイルがあるので、設定ファイル httpd.confを編集し、接続したいサブドメインをバーチャルホストとして設定します
設定の例はこちら
https://httpd.apache.org/docs/2.2/ja/vhosts/examples.html

```
sudo vi /etc/httpd/conf/httpd.conf
```

42行目あたりの `Listen 80` を見つけて、その下あたりに次の様な設定を記載する
sub.sample.jp のあたりを書き換えてください。
ここでは、/var/www/html 配下のファイルを sub.sample.jpでアクセスできる様に設定しています
ちなみに、viコマンドで行番号を表示する場合 esc で `:set number`で表示されます
<VirtualHost *:80>
DocumentRoot /var/www/html
ServerName sub.sample.jp

# Other directives here

</VirtualHost>
書き換えたら　`sudo systemctl restart httpd` で再起動します。
エラーがでたら、httpd.confの記載を見直してください

### 4-2.certbotをインストールして証明書を導入
 
次の順番でcertbotがインストール出来ました

```
sudo yum install -y wget
sudo yum install -y yum-config-manager
sudo wget -r --no-parent -A 'epel-release-*.rpm' http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/
sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm
sudo amazon-linux-extras install epel -y
sudo yum install -y certbot python2-certbot-apache
```
apacheに対して、SSL設定を導入します

`sudo certbot`
すると下のメッセージが得られるので、サブドメインを入力します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/d116f9c6-6c46-1d3c-ec64-559061e5e966.png)

ここまで出来ていたら、certbotコマンドで証明書がインストール出来るはずなので、詳細は割愛します。

詳しくはcertbot apacheとかで検索してみて下さい

参考：https://avinton.com/academy/creating-ssl-certificate-by-certbot/

インストールすると、/etc/httpd/conf 下に　httpd-le-ssl.confが生成され、
/etc/httpd/conf/httpd.confのバーチャルホスト設定に以下が追記されるようです

```
RewriteEngine on
RewriteCond %{SERVER_NAME} =sub.sample.jp
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
```
と最後の行に
`Include /etc/httpd/conf/httpd-le-ssl.conf`

### 4-3. 証明書更新をスケジュールする
この状態だと3ヶ月で証明書が切れてしまうので、スケジューラー cronで自動更新するように設定します

`crontab -e`
でcron設定のエディタを起動し
`00 04 01 * * root /bin/certbot renew --webroot-path /var/www/html/ --post-hook "systemctl reload httpd"`と入力します

で、https://sub.sample.jpとかにアクセス出来るか確認してみましょう

## 5. 1のEC2イメージにwordpressのdocker環境を作成して、81版ポートでアクセスできる様に設定する

wordpress環境を立てるためには、簡単に言うとこんな感じ。詳しくは割愛します。ここでの主目的はともかく amazon linux 2 ECS optimized image上で、docker-composeを行い、そこに外部からアクセスするところなので。

### 5-1. wordpressのインストールまで

1. RDSを mysqlの最小構成で作成する
    * 2021年1月末時点では、RDS > データベースの作成 > 簡単作成 > MySQLを選択 > 無料利用枠 という流れで無料枠対象のDBが作成出来ます
    * わかりやすいDBインスタンス名(wordpress-devとか)をつける
    * 推測されにくいマスターユーザー名とパスワードをつける
2. RDSのセキュリティグループを設定する。EC2から 3306でアクセスできる様にする(EC2からアクセス出来れば、EC2上のdockerインスタンスからもアクセスが可能)
3. 開発環境で動作確認した docker-compose環境をEC2にコピーする (私は bitbucket経由でやるのが好き)
4. EC2にsshでログインして、3でコピーしたdocker-compose.yml があるディレクトリに移動する

参考に、こんな感じのdocker-compose.ymlにしています

```
services:
  wp-php:
    build:
      context: ../php
      dockerfile: ./Dockerfile
    # privileged: true
    tty: true
    stdin_open: true 
    env_file: ../php/local.env
    volumes:
      - ../php/etc:/usr/local/etc
      - ../php/httpd:/etc/httpd/conf
      - ../php/work:/root/work
      - ../../wordpress:/var/www/html
    ports:
      - "81:80"
```

フォルダ構成としてはこんな感じ

```
├home
　　├ git
　　│　├ docker
　　│　│　　├ docker-compose.yml
　　│　│　　├ php
　　│　│　　│  ├ Dockerfile
　　│　│　　│  
　　│　├ wordpress
　　│　│　　├ wp-config.php
　　│　│ 
```

上のdocker-compose.ymlの場合、EC2インスタンスの 81ポートでwordpressがアクセス出来るはずです。

## 6. まずは sslなしのポート直接指定でアクセスする

* 一時的にEC2インスタンスのセキュリティグループを編集して、81版ポートを空けましょう
* 動作確認するので、"ソース"を「任意の場所」にしてもいいのですが、「マイIP」として制限かけることをお勧めします
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42407/cfa5629f-f517-81a6-5f04-c1e7e0fd885e.png)

httpポートでRoute53で設定したサブドメインでアクセス出来ましたか？
例： http://sub.sample.jp:81

出来ない場合はdocker周りの設定がおかしいことが考えられます
* dockerインスタンスからwordpressのソースコードが参照出来ていない
* docker-composeで指定している環境変数 env設定がおかしい

等

## 7. 1のEC2イメージのポートフォワード設定をし、ssl接続を可能にする
さて、最後にssl接続をするための設定をします

手順4-2でcertbotにより作成された、httpd-le-ssl.confを以下の様に書き換えます

```
<IfModule mod_ssl.c>
<VirtualHost *:443>
#    DocumentRoot "/var/www/html"
    ServerName "sub.sample.jp"
    ServerAlias "www.subs.sample.jp"
SSLCertificateFile /etc/letsencrypt/live/sub.sample.jp/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/sub.sample.jp/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
    <Location />
      ProxyPass http://localhost:81
      ProxyPassReverse http://localhost:81
      RequestHeader set X-Forwarded-Proto https
      RequestHeader set X-Forworderd-Port 443
    </Location>
</VirtualHost>
</IfModule>
```

この設定により、
* EC2側のapacheで受信した 443ポートへのssl通信が、localhost:81に転送されます。(ProxyPass)
* dockerインスタンスから呼び出された https://sub.sample.jp の呼出も http://localhost:81 に転送されます (ProxyPassReverse)
* dockerインスタンスには http通信が転送されるのですが、「本当はhttps通信で443ポートから来ていたものだったのよー」とリクエストヘッダーに情報を追加します(RequestHeaderの2行）

このままだと、wordpress側の方で「sub.sample.jpとして呼び出された」と言うことが分からないので、以下の設定を wp-config.phpに行う必要があります。

```
if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
    $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
    $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
}
  define('WP_HOME', 'sub.sample.jp');
  define('WP_SITEURL', 'sub.sample.jp');
  define('FORCE_SSL',true);
  define('FORCE_SSL_ADMIN', true);
  $_SERVER['HTTPS']='on';
```

* HTTP_X_FORWARDED_HOST, HTTP_HOST, SERVER_NAME, HTTPSはサーバー変数の変更が必要
    * → 詳しい説明はこちら：https://www.php.net/manual/ja/reserved.variables.server.php
* WP_HOME, WP_SITEURL, FORCE_SSL, FORCE_SSL_ADMIN はwordpressの変数です
    * → 詳しい説明はこちら： https://ja.wordpress.org/support/article/editing-wp-config-php/


以上です！お疲れ様でした