LoginSignup
3
4

More than 1 year has passed since last update.

WordPressを静的サイト化してslackからDeployできるようにしてみた

Last updated at Posted at 2022-06-25

はじめに

WordPressでサイトを公開していて、以下のような課題に悩まされた経験はありませんか?

  • 脆弱性を狙った攻撃の対象になる セキュリティ の課題
  • 負荷対策が必要となる パフォーマンスとスケーラビリティ の課題
  • DBが落ちて データベース接続確立エラー となったり EC2のリタイアメント等の必須メンテナンスの発生による 運用 の課題
  • サーバ費用等の コスト の課題

このような課題をシンプルに解決するため、コーポレートサイトのリニューアルを機に、WordPressを静的サイト化してみました。

全体像

undefined__5__png.png

この構成がもたらす課題解決

  • セキュリティ
    • OriginがEC2上のWordPressではなくS3上の静的HTMLとなる為、攻撃可能領域がほとんど無くなります
  • パフォーマンスとスケーラビリティ
    • CDN(CloudFront)を噛ませる事で、スケーラビリティとパフォーマンスの課題を解決します
  • 運用
    • slack経由で簡単にEC2の起動/停止とデプロイが可能となります
      • デザイナーやバックオフィス系の方々(広報/人事等)が、ご自身でサーバ起動停止やデプロイが出来るようになりました
    • WordPress本体(EC2)が公開サイトから切り離されている為、いつでも好きなタイミングでメンテナンスが可能となります
  • コスト
    • 常時発生するコストはCloudFrontとS3のみとなり大幅なコスト削減が可能となります

この構成のポイント

  • WordPress(EC2)
    • slack経由でEC2の起動/停止とデプロイを指示
      • EC2はサイト更新時にのみ起動すればよい
      • WordPress内のコンテンツを更新しても閲覧サイトへは反映されない(静的HTML出力→S3デプロイ後に閲覧可能となる
    • 信頼する接続元IP(管理者のOffice, VPN等)からのみアクセス可能となるようSecurityGroupでIP制限を行う
    • EC2の停止忘れ防止の為、夜間自動的に停止する処理を導入
  • 閲覧用サイト(CloudFront+S3)
    • キャッシュ期間は長めでOK(デプロイ時にキャッシュ削除(Invalidation)を実施
    • S3+CloudFrontの構成で静的Webサイトを構築した場合、サブディレクトリからデフォルトのルートオブジェクトを返さないという問題をCloudFront Functionsで解決
  • AWS Chatbot
    • slack→Lambda→SSM(Run Command)経由でEC2上のデプロイスクリプトを実行
    • EC2の起動停止はslack→AWS Chatbotからaws cli経由で直接実行
  • 余談
    • EC2に直接S3をマウントする方法(goofys, s3fs等)も検討しましたが、動作が不安定だったため不採用としました。

構築手順

1. WordPress(EC2)の構築

EC2上に、WordPressを構築しWebサイトを作成します

  • EC2上にWordpressをインストールします。詳細な手順は割愛しますが下記の記事が参考になります

  • WordPressを使ってWebコンテンツを作成します

2. 閲覧用サイト(CloudFront+S3)の構築

閲覧用サイトを構築します

  • CloudFront+S3で静的Webサイトを構築します。詳細な手順は割愛しますが下記の記事が参考になります

3. デプロイ処理の構築

EC2上の静的HTMLをS3に同期し、CloudFrontのキャッシュ削除(Invalidation)を行う処理を作ります

  • WordPressに静的化プラグインを導入します
    • Simply Static等のプラグインを導入し、WordPress上のWebコンテンツを静的HTMLとしてローカルに出力できる事を確認します

  • デプロイに必要な権限を付与したEC2用のIAM Roleを作成し、EC2(WordPress)にアタッチします
IAM Role
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateInvalidation",
                "cloudfront:GetDistribution",
                "cloudfront:GetDistributionConfig",
                "cloudfront:GetInvalidation",
                "cloudfront:ListDistributions",
                "cloudfront:ListInvalidations",
                "cloudfront:ListStreamingDistributions"
            ],
            "Resource": [
                "arn:aws:cloudfront::123456789012:distribution/ディストリビューションID"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::s3-bucket-name/*",
                "arn:aws:s3:::s3-bucket-name"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "s3:ListAllMyBuckets",
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetEncryptionConfiguration"
            ],
            "Resource": "*"
        }
    ]
}
  • デプロイ用スクリプトを作成し、動作確認します
shellの実装例(/usr/local/bin/s3_sync.sh)
#!/bin/sh
#set -e

DISTRIBUTION_ID=ディストリビューションID
ORIGIN_PATH=静的HTML出力先DIRのパス
DISTINATION_PATH=s3://s3-bucket-name/

INVALIDATION_PATHS="/*"

echo "----- s3 sync ----"
set -x
aws s3 sync ${ORIGIN_PATH} ${DISTINATION_PATH} --delete

set +x
echo ""
echo "----- create invalidation ----"

set -x
aws cloudfront create-invalidation --distribution-id "$DISTRIBUTION_ID" \
  --paths "$INVALIDATION_PATHS"

4. Lambdaの設定

LambdaからSSM経由でEC2上のデプロイ用スクリプトを実行させます

  • EC2(WordPress)に割り当てたIAM RoleにSSM関連の許可ポリシーを追加でアタッチします
    IAM_Management_Console.png
  • Lambda用のIAM Roleを新規作成し、以下のポリシーをアタッチします
    IAM_Management_Console.png
  • slackのweb hook用URLを発行しておきます(手順は割愛
  • Lambda関数を作成します
    • 一から作成 を選択し、上で作成したIAM Roleを実行ロールとして割り当て関数を作成します
      • (詳細設定はチェック不要)
  • コードを実装します
Python3.9の実装例(lambda_function.py)
import boto3
import logging
import requests
import time
import json

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ec2 = boto3.client('ec2')
ssm = boto3.client('ssm')

def lambda_handler(event, context):
    try:
        ## 固定値(インスタンス再作成時は更新すること)
        instanceId = "i-*****************"
        r = ssm.send_command(
            InstanceIds = [instanceId],
            DocumentName = "AWS-RunShellScript",
            Parameters = {
                "commands": [
                    "sh /usr/local/bin/s3_sync.sh"
                ],
                "executionTimeout": ["3600"]
            },
        )
        command_id = r['Command']['CommandId']
        
        ## Command結果待ちループ
        while True:
            time.sleep(3)

            res = ssm.list_command_invocations(
              CommandId = command_id,
              Details = True
            )

            invocations = res['CommandInvocations']
            if len(invocations) <= 0:
                continue
            
            status = invocations[0]['Status']
            if status in {'Success','Failed'}:
                break

        ## 結果格納
        dict = {
            'Success':{
                'color':'good',
                'pretext':'Result:成功 :white_check_mark: :thumbsup:'},
            'Failed':{
                'color':'danger',
                'pretext':'Result:失敗 :warning: :thumbsdown:'}
        }
        response = invocations[0]['CommandPlugins'][0]['Output']
        logger.info('## 実行結果 Status:[' + status + ']\r' + response)

        ## slackにPost
        WEB_HOOK_URL = "https://hooks.slack.com/services/XXXXXXX/YYYYYYY/abcdefg1234567890"
        requests.post(WEB_HOOK_URL, headers={'Content-Type': 'application/json'}, data=json.dumps({
            'text': dict[status]['pretext'] + '```' + response + '```'
        }))
    except Exception as e:
        logger.error(e)
        raise e
  • コードをzipで固めてLambda関数にデプロイします

  • Lambda関数のテスト実行
    • LambdaからEC2上のデプロイ処理スクリプトが実行され、結果がCloudWatch Logsから確認できればOKです
      CloudWatch_Management_Console.png

5. AWS Chatbotの設定

AWS Chatbotをslackと連携し、slackからLambdaを実行したりEC2を起動停止したり出来るようにします

  • AWS Chatbot とSlackを連携します
    • 下記サイトを参考に設定を行います(Lambda関数は既に作成済みのため Lambda関数作成 の章は飛ばしてOK

  • slackから@aws lambda invoke --function-name Lambda関数名 --region ap-northeast-1 のようにしてLambdaを実行 (途中 [Run] commandボタンをクリックする必要がある)し、下記のような実行結果が表示されればOKです
    Slack___a_corpsite-ops___vega-c_png.png

6. その他、細かい設定

  • slackからEC2を起動停止できるようにする
    • Chatbot用IAM Roleに以下のようなインラインポリシーを追加(WordPressのインスタンスIDのみ起動/停止/確認を許可)し、 slackから @aws ec2 {start|stop}-instances --instance-ids i-*************** --region ap-northeast-1 のようにして実行
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "arn:aws:ec2:*:*:instance/i-***************"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DescribeInstanceStatus"
            ],
            "Resource": "*"
        }
    ]
}
  • S3+CloudFrontの構成で静的Webサイトを構築した場合に、サブディレクトリからデフォルトのルートオブジェクトを返さないという問題の解決
    • 過去に別記事としてまとめましたので参考までに

以上!

あとがき

いかがでしたでしょうか?

リプレース前は、WordPressの前にCloudFrontやWAFを置いて負荷耐性やセキュリティ対策をしていたのですが、EC2のメンテナンスがやり辛い点、たまにプロセスが落ちたり固まってしまう問題等、運用面で面倒な課題がありましたが、現在の構成にすることで様々な課題が解決され、低コストで楽に運用ができるようになりました。

同じような課題に悩まされている方の参考になれば幸いです。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4