Copilot CLIでWordPress on Docker

コンテナ使ってWordPressのインフラ構築作ろうかなと言うことで、AWSのFargateとECSを使うことに。
最初はTerraformでゴリゴリ作っていこうと考えていたが、Copilot CLIでほぼできそうなのでそれをつかうことにした。かなり実用に耐えるようになってきたとも聞くので。

CloudFrontとSSL証明書の取得はCopilot CLIではできないので別途Terraformを使う。この記事では割愛。

前提・方針

  • ステージング環境・本番環境で動かす
  • ローカル開発環境
  • 公式のDockerイメージをベースに作る
  • 開発環境のみdocker-composeを使う
  • DBはAurora Serverless
  • メディアファイルはS3
  • ログはCloudWatch Logs

Copilot CLIとは

公式ドキュメント分かりやすくまとまっている。

インストール

参考: Copilotのインストール

手元のマシンはWindows11でWSL2のUbuntuでの作業。

curl -Lo copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux && chmod +x copilot && sudo mv copilot /usr/local/bin/copilot && copilot --help

チュートリアル

参考: 最初のアプリケーションをデプロイしよう

export APP_PROFILE=hoge-profile
copilot init
copilot app delete

Dockerfileを準備してcopilot initする。チュートリアル通りに対話式の質問に回答していくとVPCやらALBやら色々と作ってくれる。
そしてcopilot app deleteですべて削除される。

ここでは動きを見てみるだけなので、ひとまず以下の一行だけでもOK。

FROM wordpress:5.9.3

URLにアクセスしてDB接続エラーの画面が表示されればWordPressがデプロイされたことは確認できるのでcopilot app delete。

Concepts

なんとなく動きが分かったらここでコンセプトを一読しておくと良い。

Applicationをウェブサイト名で作り、その下にServiceとしてWordPressがあるってかんじ。
Environmentはstagingやproductionといった環境で、その下にサービスがデプロイされる。

最初のDockerfileの準備

Dockerfile

Dockerfileはステージングと本番で共通のものを利用する(別々のDockerfileを使うこともできる)
今はやはりとりあえず一行。

FROM wordpress:5.9.3

リソースの作成

チュートリアルのようにcopilot initしてしまうとtestというenvができる。これは不要で無駄なリソースが作られるだけなのでapp、env、svcと順を追ってinitする

copilot app init

まずはApplicationの作成。ここではmy-wordpressという名前で作る。

export APP_PROFILE=hoge-profile
copilot app init my-wordpress

copilot env init

ステージングと本番のEnvironmentを作る。

copilot env init --name staging --profile=hoge-profile --default-config
copilot env init --name production --profile=hoge-profile --default-config

export APP_PROFILEしててもprofileを指定しないといけないのがちょっと謎。コマンドで指定しなくても対話式で選択を促されるのでそこで選択すればOK。
–default-configを指定しない場合は以下の質問が出てくる。既存のVPCなどを使いたい場合はここで指定する(VPCを指定するためのコマンドラインオプションもある)

Would you like to use the default configuration for a new environment?
  - A new VPC with 2 AZs, 2 public subnets and 2 private subnets
  - A new ECS Cluster
  - New IAM Roles to manage services and jobs in your environment
[Use arrows to move, type to filter]
> Yes, use default.
  Yes, but I'd like configure the default resources (CIDR ranges, AZs).
  No, I'd like to import existing resources (VPC, subnets).

copilot svc init

Serviceを作る。

copilot svc init --name wordpress --svc-type "Load Balanced Web Service" --dockerfile ./Dockerfile --port 80

CloudFrontをかぶせる前提なのでポートは80のままで作る。

copilot storage init

S3、DynamoDB、Auroraが作れるのでDBとしてAurora、WordPressの管理画面からアップロードしたメディアファイルを入れる先としてS3のバケットを作る。

copilot storage init -n wordpress_db -t Aurora -w wordpress --engine MySQL --initial-db my_wordpress
copilot storage init -n wordpress-media -t S3 -w wordpress

参考: AWSリソースを追加する

copilot deploy

copilot svc deploy --name wordpress --env staging

やりましたね。

これでALBでロードバランスされたアクセスすると・・・無事データベースに接続できないという画面が出ました。成功です。

Aurora Serverlessへの接続

Auroraへ接続するためのwp-config.phpを作成して再度デプロイする。

Dockerfileの修正

作成したwp-config.phpをコンテナ内に設置するため、Dockerfileを修正。コピー元のパスは適宜変更しても大丈夫。

FROM wordpress:5.9.3

COPY env/production/config/wp-config.php /var/www/html/wp-config.php

wp-config.php

Copilot CLIでAuroraを作ると環境変数にJSONでDBの接続情報が入るようになるので、それをデコードして使うというわけ。
「WORDPRESSDB_SECRET」の「WORDPRESSDB」のところはAuroraを作るときに指定したnameに依存する。

<?php
$db_secret = json_decode(getenv("WORDPRESSDB_SECRET"), true);
define('DB_NAME', $db_secret["dbname"]);
define('DB_USER', $db_secret["username"]);
define('DB_PASSWORD', $db_secret["password"]);
define('DB_HOST', $db_secret["host"]);
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');

define('AUTH_KEY',         '2d56acd5da8a7dfb856fc20a4ec0d5a24925153a');
define('SECURE_AUTH_KEY',  'ced4dd7568bc9dbbeff0c8d7b0ea18a5b1ad6a2b');
define('LOGGED_IN_KEY',    '18a88aa15b6a180cc3f71145316554828afe50b2');
define('NONCE_KEY',        'ca94dd4fcd5981592847cf140217175021c5869d');
define('AUTH_SALT',        '481f5164e5ab20f7807806bf5b79cc70a2928a3e');
define('SECURE_AUTH_SALT', '155f484d55dd4bcad9d27083a00120c4654c82ba');
define('LOGGED_IN_SALT',   '3fa8e249e9b9171b193fd0d0341515b879e547dc');
define('NONCE_SALT',       '1181b696d3d74882d325c9758ffefebdd29f6fd9');

$table_prefix  = 'wp_';

define('WP_DEBUG', true);

if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}

define ('WPLANG', 'ja');

if ( !defined('ABSPATH') )
    define('ABSPATH', dirname(__FILE__) . '/');

require_once(ABSPATH . 'wp-settings.php');

WP_DEBUGは運用が安定してきたらfalseにするが今はtrue。
HTTP_X_FORWARDED_PROTOあたりはCloudFrontのための設定。

再度デプロイ

copilot svc deploy --name wordpress --env staging

なんだかエラーが出てデプロイが完了できませんね。

WordPressを設置した直後はインストールが終わってないので、インストール画面に302リダイレクトしてしまう。よってALBのヘルスチェックに失敗するのが原因。

DBには接続できるようになったということなので、確実に前進はしている。あわてるな。

WordPressのインストール

ALBのヘルスチェック設定を変更して再々度デプロイ。

ALBのヘルスチェック設定の変更

ヘルスチェックをパスするようにAWSコンソールから設定を変更する。

EC2 > Target groups > xxxxx-Targe-xxxxxxxxxxx > Health checks

Success codesを「200」から「200,302」に。すごくやっつけ。

再々度デプロイ

copilot svc deploy --name wordpress --env staging

毎度毎度かなり時間がかかるのでお茶でも飲んで待つ。

前回の失敗デプロイをCtrl+Cで中断した場合はタイムアウト(?)するまで再度のデプロイはできない。
散歩にでも出掛けて待つか、AWSコンソールでCloudFormationを開いて動いてるスタックの更新をキャンセルするべし。

WordPressのインストール

デプロイが完了したら今度こそWordPressのインストール画面にアクセスできる。
サクッとインストールを完了してしまいましょう。

これでデフォルトのテーマで文章を書いていくだけのブログとしてなら一応動くようになった。

ALBのヘルスチェック設定を戻す

Success codesを「200」に戻しておく。そのままにしとくと何があるか分からないので。

さて残りのタスクはどうなるか

それは次回で。

  • プラグインのインストール
  • テーマのインストール
  • 既存のWordPressのDBをインポート
  • S3でメディアファイルを配信
  • ログをCloudWatch Logsに流す
  • 本番環境の構築

参考

AWS Copilotで本番環境をコンテナ化する

備考

Aurora Serverlessのバージョン何かなと気になって調べた。copilot svc execしてコンテナの中に入って、そこからMySQLに接続して調べると。

Server version: 5.7.12 MySQL Community Server

MySQL [shinsho_plus]> select aurora_version(), @@aurora_version;
+------------------+------------------+
| aurora_version() | @@aurora_version |
+------------------+------------------+
| 2.08.3           | 2.08.3           |
+------------------+------------------+
1 row in set (0.003 sec)

Serverless v2にするとAurora MySQL 3.02.0 (compatible with MySQL 8.0.23) しか選択できないので、今現在のCopilot CLIで作られるAuroraはServerless v1ってことだな。

本番運用に入る前にcopilot storage initで作ったDBを使うか、それともTerraformや手動などで別途作ったものを使うか、それは費用面もあわせて考えておいた方がよさげだ。