:::: MENU ::::

WordPressからS3+CloudFront構成に変更したときの全作業メモ

Pocket

VPSを使う機会も減ってきてWordPressだけのために契約しているのもあれなんで、当ブログもS3+CloudFront構成にしました。

静的化プラグイン入れてS3に上げてCloudFrontからS3にendpoint指定するだけだろ?と思ってたんですが、やってみると意外と他にも作業が多かったので、また同じことをやるときのために手順を残しておきます。

方針

  • VPSは解約する前提(なにも既存リソースは残さない)
  • WordPressはlocalhostにDockerで立ち上げる
  • URLは維持する(サブディレクトリ構成のWordPress)
  • サイト内検索は使われていないので削除
  • ドメインはすでにRoute 53管理なのでそのまま

WordPress本体の移行

たいした作業ではないので、やったことを箇条書き。

VPCのWordPressの作業

  1. VPSのWordPressを最新版にアップグレード
  2. WP-DBManagerでバックアップを取得
  3. wp-content/(plugins|theme|upload|backup-db) を固めてローカルに持ってくる

DockerのWordPressの作業

  1. bitnami/bitnami-docker-wordpressでWordPressを立ち上げる
  2. /etc/hostsを書き換えてlocalhostという名前以外のものを新しく作る
    記事中にlocalhostドメインがあると、それも置換されてしまうので、WordPressのURLを別の名前にする
127.0.0.1   localhost
127.0.0.1   yourlocalhost
  1. http://yourlocalhost/wp-admin/ に遷移しWordPressにログイン
  2. ホストのマウントポイントにある wp-content にVPCから持ってきたデータを展開
  3. WordPressの管理画面に行きVPCのWordPressと同じプラグインを有効化する
  4. WP-DBManagerでバックアップしたデータをリストアする

リストア前にプラグインを有効化しておかないと、カラム数が合わないなどでエラーになります。

AWS上の作業

IAM

  1. IAMから静的化ページアップロード用のRoleとPolicyを作成
  2. IAMからコンソール利用ユーザーを作成し上記Roleをアタッチ
  3. アクセスキーをゲット

S3

  1. WordPressの静的ページを保存するバケットを作成
  2. バケットのプロパティからStatic website hostingを有効化
  3. バケットポリシーに以下を設定する
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-wp-static-bucket/*",
            "Condition": {
                "StringEquals": {
                    "aws:UserAgent": "Amazon CloudFront"
                }
            }
        }
    ]
}

今回はドメイン直下ではなくサブディレクトリの下にアップロードするので、ドメイン直下にアクセスされた場合ブログのトップに飛ばす設定を入れます。
(以前から特に直下に置くコンテンツもないのでブログに飛ばしています)

  1. バケットの直下に適当にindex.htmlを作成
  2. index.htmlのメタデータにWebsite-Redirect-Locationを設定
Key: Website-Redirect-Location
Value: https://14code.com/blog/

Route 53 (1/2)

ドメイン移管もしたかったらこちらを参照

Certificate Manager

  1. バージニア北部(us-east-1)に移動
    (CloudFrontから使う場合はus-east-1で証明書を作る必要があります)
  2. 証明書のリクエスト
  3. 証明書を発行したドメインを記入(複数可能)
  4. 確認方法はDNSを選択
  5. 「Route53でのレコード作成」を押してレコード作成
  6. 長くても10分もすれば発行される

SSL/TLS 証明書により保護するサイトの完全修飾ドメイン名 (www.example.com など) を入力します。同じドメイン内の複数のサイトを保護するには、アスタリスク (*) を使用して、ワイルドカード証明書をリクエストします。たとえば *.example.com とすると、www.example.com、site.example.com、images.example.com が保護されます。

こう書いてあるので *.14code.com だけで最初に証明書を作ったのだが、なぜか 14code.com も www.14code.com も証明してくれなかった(設定の仕方が悪いのだろうか)
しかたないので必要なドメインをすべて書いて対応した。ワイルドカード証明書ではなくマルチドメイン証明書として使っている。

CloudFront

  1. CloudFrontからCreate Distributionを選択
  2. WebのGet Startを選択
  3. 下記のように設定項目を入力
  4. Create Distributionを押下して作成
Origin Domain Name: 
    S3のStatic website hostingのエンドポイントを入力
Viewer Protocol Policy: 
    Redirect HTTP to HTTPS
Compress Objects Automatically: 
    Yes
Price Class: 
    Use Only US, Canada, Europe and Asia
Alternate Domain Names (CNAMEs): 
    www.14code.com
    14code.com
SSL Certificate: 
    *.14code.com

Origin Domain Nameを選択するとS3のバケットを選択するプルダウンが出てくるが、これは無視。
Static website hostingのエンドポイントを入力しないと、サブディレクトリのインデックスドキュメントを読んでくれなくなる。

SSL Certificateに作った証明書が選択肢から出てこないことがあった。
一旦Default CloudFront Certificate (*.cloudfront.net) を選んであとから変更しにいったら選択できた。作りたての証明書だと、すぐに出てこないのかもしれない。

Route 53(2/2)

  1. CloudFrontで指定したAlternate Domain Namesに合致するAレコードを選択
  2. AliasをYesにしてCloudFrontのドメインを入力

WordPressプラグインの修正

以下、どうしてもプラグインの修正が必要となったものがありました。

プラグインを修正するのは正直愚策です。
プラグイン更新時にこの変更はなくなってしまうので。

サブディレクトリ対応

静的化プラグインはみんなS3の直下にアップロードする仕組みを取っているようです。
しかしそれだとサブディレクトリで切ったWordPressを実現できませんでした。
(リダイレクトを駆使すればできる?)

雑な対応ですがS3にアップロードするときの処理に手を加えて、アップロードパスにサブディレクトリを追加しました。

StaticHtmlOutput.php

function UploadDirectory($S3, $Bucket, $dir, $siteroot) {
    $files = scandir($dir);
    foreach($files as $item){
        if($item != '.' && $item != '..'){
            if(is_dir($dir.'/'.$item)) {
                UploadDirectory($S3, $Bucket, $dir.'/'.$item, $siteroot);
            } else if(is_file($dir.'/'.$item)) {
                $clean_dir = str_replace($siteroot, '', $dir.'/'.$item);
                // 設定したいサブディレクトリをS3アップロードパスに追加
                $targetPath = 'blog/'.$clean_dir;
                $f = file_get_contents($dir.'/'.$item);

                if($targetPath == '/index.html') {
                }

                UploadObject($S3, $Bucket, $targetPath, $f, 'public-read', GuessMimeType($item));
            } 
        }
    }
}

全部終わったあとでCloudFrontのBehaviorからPath Patternの設定を増やせば、これやらなくてよかったことに気づいた。。。

sitemap.xml静的化対応

サイトマップ生成にはGoogle XML Sitemaps を使っています。
そのままだと静的化対象にならないので、Additional URLsに入れて静的化を行おうと思います。
ただAddional URLsにたくさんURLを書くのが嫌なので、ちょっと手を加えます。

Google sitemap XMLはindex, misc, posts, category, tags, archives とそれぞれURLを分けてsitemapを作ってくれますが、postsは月ごとに出力されます(sitemap-pt-post-2018-04.xml)。
1つのサイトマップファイルのサイズ制限を考慮した結果なんでしょうが、うちのは大きなブログでもなくので手を加えて年で1つのXMLにしてしまいます。

変更差分がちょっと多いのでgistに上げておきました。
monthに関わる部分を削除して、 sitemap-pt-post-2018-00.xml と月に「00」を指定すると年単位で表示されるように変更しました。

プラグインを修正後、Addional URLsに対象のsitemapのURLを入力して静的化の対象とします。

http://yourlocalhost/sitemap.xml
http://yourlocalhost/sitemap-misc.xml
http://yourlocalhost/sitemap-tax-post_tag.xml
http://yourlocalhost/sitemap-tax-category.xml
http://yourlocalhost/sitemap-pt-post-2018-00.xml
http://yourlocalhost/sitemap-pt-post-2017-00.xml
http://yourlocalhost/sitemap-pt-post-2016-00.xml

RSSのドメイン変換不備対応

AdditionalUrlsにfeedのURLを入れても出力されたデータはBase URLを置換してくれなかった。
Sitemapは同じ方法でできるのに……あとで調べる。

WordPressテーマを修正

静的化あまり関係ないですけど、使ってないものを掃除しました。

Mixed Contents

いくつかURL変換ができていない部分があったので、直リンしていしたり削除したりして修正。

コメント関連削除

コメントはすべてDisqusに変更して、何件コメントありなどの表示系を削除。

検索ウィジェット削除

検索機能はなくしたので削除。

不要なjsの削除

WordPress謹製のemojiを使うjsがURL置換できていなかったが、使わないので削除。

functions.php

remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_action('wp_print_styles', 'print_emoji_styles' );
remove_action('admin_print_styles', 'print_emoji_styles');

カテゴリーとタグページにDescriptionがついていなかったので対応

いつからかAll in one SEOがカテゴリとタグページのDescriptionを作ってくれなくなったので、自前で置き換えて対応

functions.php

add_filter( 'aioseop_description', 'custom_aioseop_description' );
function custom_aioseop_description( $description ) {
  $blog_name = get_bloginfo('name');
  $blog_desc = get_bloginfo('description');
  if ( is_category() ) {
    $cat_name = single_cat_title("", false);
    $description = $cat_name.' に関する記事一覧です。'.$blog_name.' は'.$blog_desc;
  } else if ( is_tag() ) {
    $tag_name = single_tag_title("", false);
    $description = $tag_name.' タグが付いた記事一覧です。'.$blog_name.' は'.$blog_desc;
  }
  return $description;
}

パーマリンクの変更

CloudFront+S3構成では「/」止めにしていないURLは「/」止めに302リダイレクトされます。
実利用的には問題ないですが、なんか気持ち悪いので修正します。

パーマリンクの設定から「/」止めに変更します。

/%year%%monthnum%%day%_%post_id%/

カテゴリーやタグのほうは設定しなくても、こちらを設定したら同じく「/」止めにしてくれました。

静的ページ出力 + S3アップロード

ようやく準備が整ったので静的出力します。

  1. WP Static HTML Outputプラグインをインストール
  2. Base Urlに最終的に公開したいドメインとパスを追加
  3. Additional Urlsに追加で静的化したいURLを追加
  4. Transfer files via S3にチェックを入れる
    (初めて出力するときはこの設定を飛ばしてローカルで確認したほうが早く済みます)
  5. IAMのアクセスキーとバケット名、CloudFrontのDistribution IDを入力
  6. Save current optionsを押下して保存
  7. Start static site exportを押下して静的化開始
  8. (稀に静的化対象外とされてしまう添付ファイルとかあるので、それは手動でアップロードする)

静的化プラグインはいくつかありますが、うちの環境ではWP Static HTML Outputが合っていました。
StaticPressはデフォルトで必要のないものもS3に上げることが多かったのでやめました。
Simply Staticでも機能面ではよかったんですが、先にWP Static HTML Output使っていたのでこちらを使っています。

StaticPressは日本語のURLを持ったページをしっかり日本語ファイルで吐いてくれるが嬉しいんですが、別の他のプラグインでもURLエンコードして出力してくれるので特に困りませんでした。どうせ上げた後にS3直に触ってなにかすることもしませんし。

動作確認

キャッシュ削除

  1. CloudFrontのDistributionを選択
  2. Invalidationsタグへ移動
  3. Create Invalidationを押下
  4. Object Pathsに「/*」を指定してInvalidate
  5. StatusがCompletedになるまで待つ

キャッシュは結構残っているので、プラグインやテーマを変更した時はすべてのパスのキャッシュを消した方がいい。

サイトの確認

  • ドメイン直下にアクセスして /blog にリダイレクトされるか確認
  • /blog にアクセスしてディベロッパーツールで404やコンソールエラーが出ていないことを確認
  • 一覧ページや詳細ページへのリンクが正常か確認
  • 画像などの添付ファイルが正しく表示されているか確認
  • sitemap.xmlのドメインが正しく置換されているか確認
  • GA等の集計ツールが正しく動作しているか確認
  • (あとで)SearchConsoleにエラーが出ていないか確認

さいごに

WordPressのプラグイン改修など、AWSや静的化とは直接的には関係のないところで詰まった時間のほうが長かったです。
しかし一度構築してしまえば、格安でサイトを公開し続けられるメリットは大きい。
特に証明書を気にしなくていいのはありがたい。

静的化するときに全件出力ではなく差分出力してくれるプラグインはないのだろうか。
記事数が少ないならいいけれど、記事数が多いと出力するまで時間がかかって辛い。
S3にアップロード後に特定パスのCloudFrontキャッシュを削除するのは、Lambdaを使えばいけそうなので、これはあとで作ってみよう。

Pocket