AWSでランディングページを立ち上げた話【１．導入編】

お取引先様のコンバージョン率（お問合せ・成約のこと）を上げるため、**ランディングページ**（メールフォームとトランザクションメール配信付）を作ったときの話をする。

当方、ホームページ屋（Web制作）は本業では無いが、専門業者に頼むほどでもないので、自分がついでにやることになった。
ランディングページではイメージ画像を多用するので、お取引先様が契約する [Adobe Stock](https://stock.adobe.com/jp/) から素材を購入する。

# ランディングページとは
WEB業界用語である。コーポレートサイト（自社ホームページ）とは異なる、商品（サービス）を売るために特化した自己完結型のページのことで、**LP**と略される。
ページが縦長で、縦スクロールで読み進めるのが特徴。営業のセールストークを１枚に書き連ねた感じ。
ページ移動があると訪問者が離脱しやすいので、他ページへのリンクは極力つけないのが一般的。

# サーバ手配
WordPressだけ動けば良いので、エックスサーバやさくらサーバなどの国産レンタルサーバ（共用タイプ）でも事足りるが、自由度（root権限は欲しい）と拡張性を重視し、AWSで構築する。
とはいえ、[Amazon EC2](https://aws.amazon.com/jp/ec2/) では流石にオーバースペックなので、月額 **$3.50** の固定料金から始められる [Amazon Lightsail](https://aws.amazon.com/jp/lightsail/) を採用する。サンドボックスとしても使えるお手軽なサーバである。低価格ながら、VPSタイプなので自由度が高く、LightsailインスタンスのスナップショットからAMIを作成できるため、将来EC2に載せ替えるのも容易で、拡張性も問題ない。

この「小さく作って大きく育てる」ソリューションが筆者好み。必要になったら継ぎ足していけば良い。クラウドファーストの利点である。

# ドメイン購入
[Amazon Route 53](https://aws.amazon.com/jp/route53/) でもドメインは購入できるが、[お名前.com](https://www.onamae.com/)の方が安く買えるので後者から購入。
ネームサーバを Route53 に変更し、AWSからゾーン編集できるようにする。ドメイン自体の管理と更新費用はお名前.com、ネームサーバの管理はAWS、という棲み分けとした。
ちなみにOffice365もお名前.comで買う方が若干安い。

# セットアップ手順
WordPress付きのAWS公式イメージを選択すると10分も掛からずに構築できるのだが、自分の預かり知らぬところでセットアップが終わるのは好きでは無いので、今回はOSのみのイメージを選択した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/aa1950a7-41e3-8871-1b05-521607db0edb.png)
:small_red_triangle_down: 今回は「OSのみ」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/389c2d5b-9994-35ea-40fa-02ab709a30c3.png)
筆者はずっと長いこと Red Hat Enterprise Linux（RHEL）ばかり弄ってきたため、同じ感覚で使える CentOS を選びがちであったが、LTSが10年に延長されたことを機に Ubuntu に乗り換えている。ここは自身がやり易いOSを選べば良いだろう。

## 静的IPアドレスの作成
IPアドレスを固定化し、インスタンスにアタッチする。

## ファイアウォールの設定
SSH（22/TCP）、HTTP（80/TCP）、HTTPS（443/TCP）の接続を許可する。

## DNSゾーンの作成
お名前.comで購入したドメインを【登録済みドメインの入力】に設定し、【DNSゾーンの作成】をクリックする。
このとき表示されるネームサーバはすべてメモしておこう。

続いて、**Aレコード**をゾーンに追加する。今回はサブドメインが無いので、サブドメインの欄は「@」とする。
解決先の欄には、IPアドレスを直接指定することは避け、先ほど作成した静的IP名を指定しよう。デフォルトのままなら名前は「StaticIp-1」になっているはずだ。

## ネームサーバの変更
お名前.comにログインし、前項でメモしたAWSのネームサーバを設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/82ffd4a8-9f94-6cba-39f3-ce24b984226c.png)
これで、DNSレコードの管理をAWSに引き渡したことになる。

## サーバに接続
インスタンス作成時にダウンロードしたSSHキーでサーバに接続する。

### パッケージを最新に更新

```shell
sudo -i
apt -y update
apt -y upgrade
```

### スワップファイルの作成
物理メモリの少ないマシンなのでスワップ領域を作成する。MySQLがメモリ不足で落ちることがあるからだ。
領域確保にはスワップパーティションを切る方法もあるが、クラウドによる動的な資源割り当てが簡単になった今日では、スワップファイルが一般的になりつつある。

`dd`コマンドは時間が掛かるので、代わりに`fallocate`コマンドを使っている。どちらでも良い。

```shell
fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
```

スワップイン、スワップアウトが頻繁に起こっているようであれば（`vmstat`コマンドで確認可）、物理メモリを増やすことを検討しよう。

### 日本語化とホスト名の変更
これは必須では無いので必要に応じて対応するで良い。

```shell
apt -y install language-pack-ja-base language-pack-ja ibus-mozc
localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja"
source /etc/default/locale
apt -y install manpages-ja manpages-ja-dev
timedatectl set-timezone Asia/Tokyo
```

```shell
hostnamectl set-hostname ホスト名
```

一度リブートし、スワップメモリが効いているか念の為に確認する。

```shell
$ swapon -s
ファイル名                              タイプ          サイズ  使用済み        優先順位
/swapfile                               file            1048572 22784   -2

$ free -m
              total        used        free      shared  buff/cache   available
Mem:            479         208          14          18         256         240
Swap:          1023          22        1001
```

ここでスナップショットを取っておくと後でやり直すときにラクだが、最低の20GBプランでも月額100円程度の維持費が掛かるので、どうしても安く済ませたいなら無理にやる必要は無い。

### Apache導入

```shell
apt -y install apache2
systemctl start apache2 && systemctl enable apache2
```

ブラウザからサーバにアクセスし、テストページが表示されることを確認。
追々 Nginx に移行するかもしれない。

### PHP7導入

```shell
apt -y install php7.2 php7.2-mysql php7.2-mbstring php7.2-xml php7.2-curl
```

* `php7.2-mbstring`は、WordPressプラグイン *WP Multibyte Patch* で必要。
* `php7.2-xml`と`php7.2-curl`は、WordPressプラグイン *WP Offload Media* で必要。

### php.ini 設定
アップロードできるファイルサイズ上限の変更、マルチバイト文字列の設定をする。

```ini:/etc/php/7.2/apache2/php.ini
post_max_size = 30M
upload_max_filesize = 30M
date.timezone = "Asia/Tokyo"
mbstring.language = Japanese
mbstring.encoding_translation = Off
mbstring.detect_order = auto
```

### MariaDB（MySQL）導入
規模が大きくなってきたらマネージドデータベース（[Amazon RDS](https://aws.amazon.com/jp/rds/)とか）に移行するのもあり。

```shell
apt -y install mariadb-server mariadb-client
systemctl start mariadb && systemctl enable mariadb
```

セキュリティ対策を行う。

```shell
mysql_secure_installation

Enter current password for root (enter for none): ← enter
Set root password? [Y/n] ← n
Remove anonymous users? [Y/n] ← y
Disallow root login remotely? [Y/n] ← y
Remove test database and access to it? [Y/n] ← y
Reload privilege tables now? [Y/n] ← y
```

WordPress用データベースを作成する。

```shell
mysql -u root
CREATE DATABASE wpdb;
CREATE USER 'wpdbuser'@'localhost' IDENTIFIED BY 'PASSWORD';
GRANT ALL PRIVILEGES ON wpdb.* TO 'wpdbuser'@'localhost';
FLUSH PRIVILEGES;
exit;
```

### WordPress導入

```shell
cd /tmp && wget https://ja.wordpress.org/latest-ja.tar.gz
tar zxvf latest-ja.tar.gz
mv wordpress/* /var/www/html/
cd /var/www/html
rm index.html
chown -R www-data:www-data .
find . -type d -exec chmod 775 {} \;
find . -type f -exec chmod 664 {} \;

mv wp-config-sample.php wp-config.php
sed -i s/database_name_here/wpdb/g wp-config.php
sed -i s/username_here/wpdbuser/g  wp-config.php
sed -i s/password_here/PASSWORD/g  wp-config.php
```

https://api.wordpress.org/secret-key/1.1/salt/ で生成した秘密鍵を `wp-config.php` に貼り付ける。

### htaccessを許可

```apache:/etc/apache2/apache2.confに追加
<Directory /var/www/html>
    AllowOverride All
</Directory>
```

### モジュール有効化

```shell
a2enmod rewrite
a2enmod ssl
a2dissite 000-default

apache2ctl configtest

systemctl restart apache2
```

ブラウザからサーバにアクセスし、WordPressの管理者アカウントを登録。

## Amazon SES の設定
お問い合わせフォームに入力があると、お問い合わせをした本人、および営業窓口のメールアドレスに通知される。
このように、顧客が何らかのアクションを起こした際に、システムから自動で配信される確認メールのことを**トランザクションメール**という。**迷惑メール防止法**では、広告・宣伝を目的とする電子メール送信に関して事前に受信者から同意を得ることを義務付けているが、トランザクションメールは広告に該当しないため、受信者から**オプトイン**による同意を得ずに配信できる。許可を得ていないメールは**スパム**と見做される。一方、**オプトアウト**とは配信解除のことで、受信者がオプトアウトを簡便に行えるよう解除方法等などをメール本文に記載することも義務付けている。受信者から解除通知を受けた場合は以降のメール配信は行えない。

前置きが長くなったが、AWSには [Amazon SES](https://aws.amazon.com/jp/ses/) というフルマネージドのメール送信サービスがあるので、利用できるよう設定をする。

### ドメインをSESで承認する
検証済ドメインIDリストに登録したドメインを From とするメールの送信を許可させる。これには、SESから指示されたTXTレコードをDNSに追加すれば良い。

[Amazon SES コンソール](https://console.aws.amazon.com/ses/) の *Domains* `>` *Verify a New Domain* からドメインを入力し、送信元メールのドメイン検証を行う。

:::note
新コンソールでは Configuration > Verified identities > Create identity
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/5772d871-130d-6212-134b-c78dd7d42c5f.png)
**DKIM(DomainKeys Identified Mail)** の仕組みでは、送信側で、差出人とメール本文を秘密鍵で電子署名し、受信側で、電子署名を公開鍵で照合するため、DNSにCNAMEレコード（公開鍵）の追加が必要になる。*Generate DKIM Settings* にチェックを入れるとドメインのDKIM署名を有効にできる。

**SPF(Sender Policy Framework)** の仕組みでは、`MAIL FROM`コマンドで入力されたドメインがSPFレコードのチェックに使われるわけだが、Amazon SESでは、`MAIL FROM`コマンドにamazonses.comのサブドメイン、エンベロープFromに登録ドメインがセットされるため、SPFレコードをDNSに追加しなくてもデフォルトでPASSされる。[RFC4408](https://tools.ietf.org/html/rfc4408)でSPFレコードに設定するinclude回数は10回までと制限されているので、貴重な回数を消費せずに済んでいる。

より厳格な**DMARC**に準拠させるならカスタムドメインのDNSにSPFレコードを追加することになる。

:::note warn
キャリアメールに完全対応させるには **カスタム MAIL FROM ドメイン** も設定し、検証済ドメインのサブドメインにMXレコードとSPFレコードを追加する
:::

*Download Record Set as CSV* をクリックし、DNSレコードの設定値をダウンロードする。
MXレコードは、SESを介してメールを受信したい場合のみ設定すれば良い。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/8ff59612-14a3-3d19-9257-601ac5247700.png)
ステータス *"pending verification"* （検証待ち）のドメインIDリストに追加されたことを確認したら、ドメインのDNSレコードを編集し、`dig`コマンドで反映を確認する。

```
dig TXT _amazonses.example.com.
dig CNAME xxxxxxxxxxxxxxxxxxxxxxx._domainkey.example.com.
```

しばらくしたら再度ドメインIDリストを確認し、ステータスが *"verified"* （検証済）に変わっていればOKだ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/bd6ccff9-e724-af0d-c845-8f07e5aa46c9.png)

### メールアドレスをSESで承認する
検証済メールアドレスリストに登録したメールアドレスを From とするメールの送信を許可させる。これには、SESから送信される確認メールのリンクをクリックすれば良い。

**サンドボックス**（後述）の外では、*Domains* でドメインを許可されていればそのドメインのメールアドレスを改めて許可する必要は無いが、サンドボックス内では、送信元メールアドレスと送信先メールアドレスは検証済みである必要がある。

*Email Addresses* `>` *Verify a New Email Address* からメールアドレスを入力し、検証を行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/0688ae31-5bfb-1b2d-81f6-1c35f84cac4f.png)
検証済メールアドレスリストから From に使うメールアドレスを選択し、*Send a Test Email* でメール送信テストを行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/4a415cfa-243b-abf8-bf6f-0708313b44a8.png)

### SMTP認証情報の作成
*SMTP Settings* `>` *Create My SMTP Credentials* より、SMTPサーバ名などを控える。

:::note
新コンソールでは Account dashboard > Simple Mail Transfer Protocol (SMTP) settings > Create SMTP credentials
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/38534b2b-61f6-2f2c-66fa-226cb1bce378.png)
IAMユーザ名を入力し、[作成] をクリックする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/8c203a87-521c-3a97-dfd1-192be748e4e0.png)
SMTPユーザー名とSMTPパスワードを控えるか、CSVでダウンロードする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/4d3620f4-a638-1c55-5c72-e3ce1e88f92b.png)

### サンドボックスの外に移動
初期状態ではサンドボックス（試用環境）の中にいるため、不特定のメールアドレスに送信できない。
理由は、SESから送ったメールが**バウンス**したり**苦情**の対象になると、発信元のSESに問題があると判断され、SESがブラックリストに登録されてしまうからだ。
バウンスとは、送信メールが何らかのエラーによって送信者に差し戻されることで、苦情とは、メールの受信者がメッセージをスパムと報告することである。
SESでは、バウンス率や苦情率が一定の水準を超えるとそのアカウントのメール配信が停止されるので、運用には注意が必要だ。

運用（ユースケース）に問題が無ければ、次の手順でサンドボックスの外に移動（制限解除）できる。

*Sending Statistics* より *Edit your account details* をクリックする。

:::note
新コンソールでは Account dashboard > Request production access
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/eedff96e-6167-0943-a87a-6c30cea184da.png)
必要事項を入力する。肝になるのは *Use case description* で、メール送信プロセスや手順について可能な限り詳しい情報を記載する。日本語で構わない。例えば、受信者リストのメンテナンス方法、バウンスやオプトアウトの管理方法、メール送信頻度などについて書くと良いだろう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/bf2d26a7-1672-8296-2773-c0429a572919.png)
不備が無ければ24時間以内に申請が承認され、サンドボックスの外に移動される。

### テスト送信
#### mailxコマンドを使う場合
`mailx`コマンドを使うと、postfixなどでMTAを構築しなくても直接SMTPサーバに転送できる。

```shell:コマンド例
mailx -v \
-S smtp=smtp://email-smtp.ap-northeast-1.amazonaws.com:587 \
-S smtp-use-starttls \
-S smtp-auth=login \
-S ssl-verify=ignore \
-S smtp-auth-user=AKIXXXXXXXXXXXXXXXXX \
-S smtp-auth-password=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
-S nss-config-dir=/etc/pki/nssdb/ \
-s "subject" \
-r no-reply@example.com \
-c cc_address@example.com \
-b bcc_address@example.com \
to@example.com < /tmp/body.txt
```

#### Pythonコードの場合

```python:Pythonコード例
#!/usr/bin/python3
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formatdate

class SendMail():
	Subject = u'テスト'
	Charset = 'utf-8'

	FromAddress = 'no-reply@example.com'
	User        = 'AKIXXXXXXXXXXXXXXXXX'
	Pass        = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
	SmtpServer  = 'email-smtp.ap-northeast-1.amazonaws.com:587'

	def send(self, to_address, text):
		msg = MIMEText(text.encode(self.Charset), 'plain', self.Charset)
		msg['Subject'] = Header(self.Subject, self.Charset)
		msg['From']    = self.FromAddress
		msg['To']      = to_address
		msg['Date']    = formatdate(localtime=True)
		smtp = smtplib.SMTP(self.SmtpServer)
		smtp.starttls()
		smtp.login(self.User, self.Pass)
		smtp.sendmail(self.FromAddress, to_address, msg.as_string())
		smtp.close()

if __name__ == '__main__':
	smail = SendMail()
	smail.send('to@example.com', u'メール送信テスト\nこれはテストです')
```

#### ヘッダ確認
送信ドメイン認証の判定結果は `Authentication-Results`ヘッダにあるので、`spf=pass`と`dkim=pass`があるか確認しよう。
Gmailでは「メッセージのソースを表示」からも確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/67a2d132-7277-62c9-fd3a-cdb2dd37d459.png)

## WordPress の設定
### wp-cliの導入
これは必須では無いが、開発でがっつりWordPressを使うなら、コマンドラインでWordPressを操作できる`wp-cli`の導入すると捗る。

```
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
```

### テーマの追加
[外観] `>` [テーマ] の [新規追加] から、ランディングページ向きのテーマをアップロードする。
ランディングページにはサイト型とブログ型があるが、今回は、かつてコンテンツマーケティングで一世を風靡した[バズ部](https://bazubu.com/)が提供するサイト型テーマ [Xeory Extension](https://xeory.jp/template/xeory-extension/) を使うことにした。機能がシンプルなのでカスタマイズしやすい。

スターターテーマとかブランクテーマと呼ばれる、最初からカスタマイズ前提のテーマを使う手もある。今回はローンチまで１日しか無かったので諦めたが、[Sage](https://roots.io/sage/)（Bootstrap4やSassが使える！）などのエンジニアに人気のテーマも時間があれば試してみたい。

### 子テーマを作成
Xeory Extension を親テーマとする子テーマを作成し、子テーマをカスタマイズしていくことになる。
親テーマを直接カスタマイズしてしまうと、テーマのバージョンアップによって変更内容が上書きされてしまうからだ。
テーマのカスタマイズにはPHPやCSSの知識が必須になるが、フロントエンドエンジニアなら朝飯前だろう。

以下に子テーマの基本形を示す。子テーマには「child」というサフィックスを付けるのが作法になっている。

```php:/var/www/html/wp-content/themes/xeory_extension_child/functions.php
<?php
add_action( 'wp_enqueue_scripts', 'theme_enqueue_styles' );
function theme_enqueue_styles() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
    wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css',
        array('parent-style')
    );
}
```

```css:/var/www/html/wp-content/themes/xeory_extension_child/style.css
@charset "utf-8";
/*
Theme Name: xeory_extension_child
Template: xeory_extension
*/
```

SSHユーザの `ubuntu` を `www-data` に所属させると、開発環境によってはリモート編集が容易になるだろう。

```
usermod -aG www-data ubuntu
```

### 子テーマの有効化
[外観]→[テーマ] から xeory_extension_child を選択し、有効化をクリックする。

### デバッグログを有効にする
`functions.php`の改造や、独自プラグインを開発する場合、次のようにして`wp-content/debug.log`にデバッグログを出力すると良い。

```php:wp-config.php
define('WP_DEBUG', true);
if ( WP_DEBUG ) {
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);
    @ini_set('display_errors', 0);
}
```

このとき、ログに出力する日時がずれているのを嫌うならタイムゾーンを修正する。

```php:wp-settings.php
date_default_timezone_set( 'Asia/Tokyo' );
```

### WP Mail SMTP プラグインの設定
WP Mail SMTP プラグインをインストール後、メーラーに「その他のSMTP」を選択。
同じ行にある「Amazon SES」はPro版（有償）でのみ選択できるようだが、何が違うのか良く分からない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/8e657070-8c5a-a687-e9d4-96ad0a02b3af.png)
Amazon SES で控えたSMTP情報（SMTPホスト名、SMTPポート、暗号化方法、SMTPユーザ名、SMTPパスワード）を入力する。

# さいごに
導入編はここまで。
次回以降、ACMのサーバ証明書とか、[Amazon CloudFront](https://aws.amazon.com/jp/cloudfront/) とか、Googleアナリティクスなどの話をする予定。