SREのkmsn17です。前回は[ラズパイ](https://cuebic.hatenablog.com/entry/raspberrypi) について書きました。今回ですが、WordPress自動同期ツールについて記載したいと思います。
WordPressとは!?
ブログ、ホームページなどのサイトの作成のために使うContents Management Systemです。CMSと言われるものです。
なぜ、作ったの?
キュービックでは、100メディア弱ありますがそれらは全てWordPressでできています。検証も合わせると100台を超えると思います。ビジネスサイド側が、本番で書いた記事を検証にも書き起こす作業をしており、そういった作業コストを削減するために作りました。また、削減した時間を別の作業や思考作業にも使ってもらいたい思いもあります。Web上で、ビジネスサイド側が好きなときに操作していつでも同期ができるものです。
システムについて
PHPで書いてます。Web上で表示するPHPファイル、バックグラウンド側で動くPHPファイルに分けてます。こちらですが、 実際に同期を実施するプログラムはbashで行っています。セキュリティ上、web画面からbashが動けるようにしてはいけないので分けてます。こちらのプログラムはWeb上に表示して、同期するメディアを選択する内容になっています。
フロント側で動くプログラム
1 <!DOCTYPE html> 2 <html lang="ja"> 3 <head> 4 <meta charset="utf-8"> 5 <title>同期画面</title> 6 <style> 7 .message { 8 background-color: #f9e8d0; 9 padding: 10px; 10 border-radius: 5px; 11 text-align: center; 12 font-size: 18px; 13 font-weight: bold; 14 color: blue; 15 } 16 .button:hover { 17 background-color: #ffd700; 18 color: white; 19 } 20 </style> 21 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anony mous"> 22 </head> 23 <body> 24 <?php 25 $action = isset($_GET['action']) ? $_GET['action'] : ''; 26 ?> 27 <div class="message">同期するメディアのジャンルを選んでください</div> 28 29 <?php if ($action === 'hoge-test-dev' || $action === 'dev-test-dev'): ?> 30 <?php if ($action === 'hoge-test-dev'): ?> 31 <div>【本番】<a href="https://hoge-test.dev.kmsn.work/">https://hoge-test.dev.kmsn.work/</a> →【検証】<a href="https://kmsn-dev.dev.kmsn.work">https://kmsn-dev.dev.kmsn.work</a> に同期しますか?</div> 32 <?php elseif ($action === 'dev-test-dev'): ?> 33 <div>【本番】<a href="https://dev-test.dev.kmsn.work/">https://dev-test.dev.kmsn.work/</a> →【検証】<a href="https://dev-kmsn.dev.kmsn.work">https://dev-kmsn.dev.kmsn.work</a> に同 期しますか?</div> 34 <?php endif; ?> 35 36 37 <form method="post" action="SyncTool.php"> 38 <input type="hidden" name="action" value="<?php echo $action; ?>"> 39 <button class="btn btn-primary" type="submit" name="syncButton">同期する</button> 40 </form> 41 42 43 <button onclick="history.back()" class="btn btn-primary">戻る</button> 44 45 46 47 48 <?php elseif ($action === 'hoge-dev'): ?> 49 <a href="?action=hoge-test-dev" class="btn btn-primary">hoge-test-dev</a> 50 <a href="?action=dev-test-dev" class="btn btn-primary">dev-test-dev</a> 51 <button onclick="history.back()" class="btn btn-primary">戻る</button> 52 53 <?php elseif ($action === 'cuebic-dev'): ?> 54 <a href="?action=hskn" class="btn btn-primary">hskn</a> 55 <a href="?action=mrrn" class="btn btn-primary">mrrn</a> 56 <button onclick="history.back()" class="btn btn-primary">戻る</button> 57 58 59 <?php elseif ($action === 'hskn'): ?> 60 <div>【本番】<a href="https://hskn-media.stg.kmsn.work/">https://hskn-media.stg.kmsn.work/ </a> →【検証】<a href="https://kmhoge-media.stg.kmsn.work/">https://kmhoge-media.stg.kmsn.work</a> に同期する?</div> 61 <form method="post" action="SyncTool.php"> 62 <input type="hidden" name="action" value="hskn"> 63 <button class="btn btn-primary" type="submit" name="syncButton">同期する</button> 64 </form> 65 66 <button onclick="history.back()" class="btn btn-primary">戻る</button> 67 68 69 <?php elseif ($action === 'mrrn'): ?> 70 <div>【本番】<a href="https://mrrn-media.stg.kmsn.work/">https://mrrn-media.stg.kmsn.work/ </a> →【検証】<a href="https://kmhoge-media.stg.kmsn.work/">https://kmhoge-media.stg.kmsn.work</a> に同期する?</div> 71 <form method="post" action="SyncTool.php"> 72 <input type="hidden" name="action" value="mrrn"> 73 <button class="btn btn-primary" type="submit" name="syncButton">同期する</button> 74 </form> 75 76 <button onclick="history.back()" class="btn btn-primary">戻る</button> 77 78 79 <?php else: ?> 80 <a href="?action=hoge-dev" class="btn btn-primary">hoge-devメディア</a> 81 <a href="?action=cuebic-dev" class="btn btn-primary">cuebic-devメディア</a> 82 83 <?php endif; ?> 84 85 86 </body> 87 </html>
バックグラウンド側で動くプログラムについて
SSMを使ってコマンドを動かすようにしてます。多重同期防止のためDynamoDBを使っています。同期するメディアを選択して、同期ボタンを押したあとDynamoDB上に選択したメディアが値として設定されて、その間多重同期の防止が動きます。メディア毎に多重同期の防止が動けるようにしたので、別のメディアは動きます。
1 <!DOCTYPE html> 2 <html lang="ja"> 3 <head> 4 <meta charset="utf-8"> 5 <title>CB WordPress Sync</title> 6 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anony mous"> 7 </head> 8 <body> 9 <?php 10 // SlackのAPIを使用して通知を送信する関数 11 function sendSlackNotification($accessToken, $channel, $message) { 12 $url = 'https://slack.com/api/chat.postMessage'; 13 14 $headers = [ 15 'Authorization: Bearer ' . $accessToken, 16 'Content-Type: application/json', 17 ]; 18 19 $payload = json_encode([ 20 'channel' => $channel, 21 'text' => $message, 22 ]); 23 24 $ch = curl_init(); 25 curl_setopt($ch, CURLOPT_URL, $url); 26 curl_setopt($ch, CURLOPT_POST, true); 27 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 28 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 29 curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); 30 31 $result = curl_exec($ch); 32 curl_close($ch); 33 34 return $result; 35 } 36 37 require 'vendor/autoload.php'; // AWS SDK for PHPのautoloadファイルを読み込む 38 39 use Aws\Ssm\SsmClient; // SSM(Systems Manager)クライアントを使用する 40 use Aws\DynamoDb\DynamoDbClient; // DynamoDBクライアントを使用する 41 42 // AWS SSMクライアントを初期化 43 $client = new SsmClient([ 44 'version' => 'latest', 45 'region' => '', // AWSリージョンを指定 46 ]); 47 48 // AWS DynamoDBクライアントを初期化 49 $dynamoDbClient = new DynamoDbClient([ 50 'version' => 'latest', 51 'region' => '', // AWSリージョンを指定 52 ]); 53 54 // POSTリクエストで送信された'action'の値を受け取る 55 $action = isset($_POST['action']) ? $_POST['action'] : ''; 56 57 // DynamoDBに同期中フラグをセット 58 $tableName = 'hoge-dev'; // DynamoDBのテーブル名を設定 59 $lockKey = $action; // ロックのキーを設定 60 61 try{ 62 63 $item= $dynamoDbClient->getItem([ 64 'TableName' => $tableName, 65 'Key' => [ 66 'LockTable' => ['S' => $lockKey] 67 ] 68 ]); 69 70 if (isset($item['Item']) ) { // Itemが存在する場合 71 echo '<div class="message">前の人が同期を実施中です。完了するまでslackの通知を見てお待ちください 。トップ画面に戻るには、<a href="https://hoge-test.dev.kmsn.work/hogehoge/HogeMedia.php"> https://hoge-test.dev.kmsn.work/hogehoge/HogeMedia.php</a>にアクセスしてください</div>'; 72 }else{ // Itemが存在しない場合 73 // DynamoDBにItemを追加して同期中であることを示す。 74 $dynamoDbClient->putItem([ 75 'TableName' => $tableName, 76 'Item' => [ 77 'LockTable' => ['S' => $lockKey] 78 ], 79 ]); 80 81 // ディレクトリの中のファイルをスキャン 82 $directoryPath = "/XXXXX/XXXXX/HogeMedia/profiles/" ; 83 $files = array_diff(scandir($directoryPath), array('..', '.')); // 現在と親のディレクトリを除外 84 85 // $mediaMap の連想配列を動的に作成 86 $mediaMap = []; 87 foreach ($files as $file) { 88 if (pathinfo($file, PATHINFO_EXTENSION) == 'yml') { 89 $filenameWithoutExt = pathinfo($file, PATHINFO_FILENAME); 90 $mediaMap[$filenameWithoutExt] = $filenameWithoutExt; 91 } 92 } 93 94 95 // 連想配列から該当のアクションのymlファイル名を取得 96 if (array_key_exists($action, $mediaMap)) { $media = $mediaMap[$action]; 98 $command = "bash /xxxxx/HogeMedia/sync/hoge.sh /xxxxx/HogeMedia/profiles/${hoge}.yml"; 99 $commands = [$command]; 100 } else { 101 echo "不正なアクションが指定されました。"; 102 exit; 103 } 104 105 106 // SSMコマンドを実行 107 $result = $client->sendCommand([ 108 'DocumentName' => 'AWS-RunShellScript', // 実行するドキュメントの名前 109 'Parameters' => [ 110 'commands' => $commands 111 ], 112 'Targets' => [ 113 [ 114 'Key' => 'InstanceIds', 115 'Values' => ['XXXXXXXXXXXXXXXX'] // コマンドを実行するインスタンスのIDを指定 116 ] 117 ], 118 'MaxConcurrency' => '2', // 1度に同時に実行するインスタンス数の上限を設定 119 'MaxErrors' => '0', // 同時実行中に許容されるエラー数の上限を設定 120 ]); 121 122 // 同期中メッセージを表示 123 echo '<div class="message" style="background-color: Bisque; color: black; font-size: 50px;">同期 中です。トップ画面に戻るには、<a href="https://hoge-test.dev.kmsn.work/hogehoge/HogeMedia.php">https://hoge-test.dev.kmsn.work/hogehoge/HogeMedia.php</a>にアクセスしてください</div>'; 124 125 // コマンドの実行結果を取得 126 $commandId = $result['Command']['CommandId']; 127 128 // Slackに通知を送信 129 $slackAccessToken = ''; // SlackのAPIトークンを設定 130 $slackChannel = ''; // 送信先のSlackチャンネルを設定(# から始まる) 131 132 $slackMessage = "'$media'から同期中です。今しばらくお待ちください"; // 通知するメッセージを設定 133 134 // Slack通知を送信 135 $result = sendSlackNotification($slackAccessToken, $slackChannel, $slackMessage); 136 } 137 138 }catch(Exception $e){ 139 echo $e; 140 } 141 ?> 142 </body> 143 </html>
DynamoDBの画面です。選択されたメディア名が、LockTable上に値として設定される箇所です。
bashの中身
WordPressの差分同期のコマンドをbashで表示してます。ymlファイルを読み込んで、引数を元に各メディアの同期を実施しています。同期完了後、slackで通知して、最後にDynamoDBのItem値を削除しています。
1 #!/bin/bash 2 3 set -e -o pipefail 4 5 echo -e "\e[1;36;40m設定確認##########################################\e [m" 6 echo -e "\e[1;36;40m【src_media】$(yq -r .src_media $1)\e[m" 7 echo -e "\e[1;36;40m【dest_media】$(yq -r .dest_media $1)\e[m" 8 echo -e "\e[1;36;40m【src_media_url】$(yq -r .src_media_url $1)\e[m" 9 echo -e "\e[1;36;40m【dest_media_url】$(yq -r .dest_media_url $1)\e[m" 10 echo -e "\e[1;36;40m設定確認##########################################\e [m" 11 12 source /xxxx/xxxx/.bash_profile 13 BACKUP_MD_NAME=$(yq -r .src_media $1) 14 DEST_MD_NAME=$(yq -r .dest_media $1) 15 BACKUP_MD_NAME_URL=$(yq -r .src_media_url $1) 16 DEST_MD_NAME_URL=$(yq -r .dest_media_url $1) 17 WORK_DIR=/xxxxx/xxxxx/xxxxx/xxxxx/xxxxx/xxxxx/xxxxx/xxxxx 18 WORDPRESS_DIR=/xxxxx/xxxxx/xxxxx/xxxxx 19 20 echo \"${WORK_DIR}/${BACKUP_MD_NAME}\" | xargs -I {{}} sh -c 'sudo mkdir {{}} && sudo chown www-data:www-data {{}}' 21 sudo -u www-data wp db export ${WORK_DIR}/${BACKUP_MD_NAME}/dump.sql --path=${WORDPRESS_DIR}/${BACKUP_MD_NAME}/DocumentRoot --set-gtid-purged=OFF 22 sudo -u www-data rsync -av --delete --exclude=/.htaccess ${WORDPRESS_DIR}/${BACKUP_MD_NAME}/DocumentRoot/ user名@移行先のサーバのIPアドレス:${WORDPRESS_DIR}/${DEST_MD_NAME}/DocumentRoot/ 23 sudo -u www-data wp db import ${WORK_DIR}/${BACKUP_MD_NAME}/dump.sql user名@移行先サーバのIPアドレス:${WORDPRESS_DIR}/${DEST_MD_NAME}/DocumentRoot/ 24 sudo rm -rf ${WORK_DIR}/${BACKUP_MD_NAME} 25 26 27 # Slackに通知を送信する関数 28 function send_slack_notification { 29 local slack_token="" 30 local channel="#" 31 local message="$1" 32 33 curl -X POST -H "Authorization: Bearer $slack_token" -H 'Content-type: application/json' --data "{ 34 \"channel\": \"$channel\", 35 \"text\": \"$message\" 36 }" "https://slack.com/api/chat.postMessage" 37 } 38 39 # 通知メッセージを作成 40 notification_message="$BACKUP_MD_NAME_URL から $DEST_MD_NAME_URL への同期が完了しました。反映するまで10分程お待ちください" 41 42 # Slackに通知を送信 43 send_slack_notification "$notification_message" 44 45 # dynamoDBのItemを削除 46 aws dynamodb delete-item --table-name hoge-dev --key '{"LockTable": {" S": "'$BACKUP_MD_NAME'"}}' --region awsリージョン名 47
ymlファイルの中身
ymlファイルの中身です
src_media: hoge-test-dev dest_media: kmsn-dev-dev src_media_url: https://hoge-test.dev.kmsn.work/ dest_media_url: https://kmsn-dev.dev.kmsn.work/
総括
現在ですが、システムテストをしていまして、まだ社内では公開してないです。今回初めて、セキュリティやオペレーションミスを防ぐことを考えてプログラムを書きまして色々と勉強になりました。当初はif文で〜の場合の条件式を書きまくってハードコードになりました。次は、0からWebアプリケーションを作れるようにしていきたいです。
お知らせ!!
キュービックでは、一緒に働いてくれる仲間を募集しています。興味がある方はカジュアル面談Webエンジニアから応募をお願いします。ご応募お待ちしております。 herp.careers