Elementorのメモリ消費をSwapfileで対策する

 

LightsailのWordPress@BitnamiインスタンスでElementorを入れて固定ページを組んでいるとメモリ消費が著しく、いつの間にかElementor編集画面を開くだけで数百MBとか、編集画面を保存するとGB単位でメモリを消費するようになりインスタンスがハングアップするようになった。


当初、最も小さいインスタンスで十分だったが、メモリだけきつくてだましだましで10USDコースまで引き上げたがまだまだ足りない。この調子ではいくらスケールアップしても安心できない。




料金 - Amazon Lightsail | AWS



Elementorがメモリ食いというのは検索すると情報はあるものの、セーフモードとか再起動とか根本的な対策を探し当てることができず、自力でも何に食っているのか分からずお茶を濁したはなし。


試してみたこと




メモリを食いそうな要因についてダイエットを試したがいずれも芯を食ってない。


  • 画像のサイズ縮小、圧縮、パララックス解除 → 効果小
  • 投稿一覧ウィジェットを削除 → 効果小
  • js実装削除 → 効果なし
  • 未使用のプラグインをお掃除 → 効果なし

結論




メモリがスパイクするのはElementor編集している時だけなので、編集時のスパイクをSwapfileで凌ぐ。
通常のページ閲覧は物理メモリで十分賄えているのでパフォーマンスに影響はない。


# Swap領域がない状態
free
               total        used        free      shared  buff/cache   available
Mem:         1994048     1204604      625100       56884      164344      603616
Swap:              0           0           0

# 2GBのswapfileを作成して0埋め
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048

# パーミッション設定
sudo chmod 600 /swapfile

# スワップ領域として設定
sudo mkswap /swapfile

# スワップファイルを有効化(マウント)する
sudo swapon /swapfile

# 起動時にswapfileを自動的にマウント
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# swapfileとして認識された
free
               total        used        free      shared  buff/cache   available
Mem:         1994048     1204604      625100       56884      164344      603616
Swap:        2097148      214272     1882876

AmazonLinux1(CentOS6世代)×fluentd GemでCloudWatchにログを転送する

AmazonLinux1(CentOS6世代) のlatesttd-agent3(embedded ruby2.4) において、CloudWatch連携プラグインfluent-plugin-cloudwatch-logsのgem依存関係がruby>=2.5を必要とするようになったため、システムのrubyランタイムを使ってfluentdgem直でCloudWatchにサーバーログを送信するためのロードマップ。


ruby環境

rbenvで2.5.1を入れてglobalをスイッチ


# rbenvバイナリをgithubからクローン
git clone https://github.com/sstephenson/rbenv.git /opt/rbenv

# ログインプロフィールにrbenvの環境設定
echo 'export RBENV_ROOT="/opt/rbenv"' > /etc/profile.d/rbenv.sh
echo 'export PATH="${RBENV_ROOT}/bin:${PATH}"' >> /etc/profile.d/rbenv.sh
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh

# rbenvのpluginsにruby-buildをクローン
git clone https://github.com/sstephenson/ruby-build.git /opt/rbenv/plugins/ruby-build

# ログインプロフィールをローディング
source /etc/profile.d/rbenv.sh

# ruby 2.5.1をインストールする
rbenv install -v 2.5.1
rbenv rehash

# インストールバイナリ確認
rbenv versions

* system
2.5.1 (set by /opt/rbenv/version)

# システムバージョンを2.5.1に変更
rbenv global 2.5.1

# システムバージョンを確認
rbenv versions
  system
* 2.5.1 (set by /opt/rbenv/version)


fluentdパッケージをインストール


一応rbenv付きで環境を縛る


rbenv exec gem install fluentd

# yajl-rubyがrequire ruby >= 2.6.0
ERROR:  Error installing fluentd:
        The last version of yajl-ruby (~> 1.0) to support your Ruby & RubyGems was 1.4.1. Try installing it with `gem install yajl-ruby -v 1.4.1` and then running the current command again
        yajl-ruby requires Ruby version >= 2.6.0. The current ruby version is 2.5.0.

# yajl-rubyがrequire ruby >= 2.Xなv1.4.1に落としてインストール
rbenv exec gem install yajl-ruby -v 1.4.1

rbenv exec gem install fluentd

# bundlerがrequire ruby >= 3.0.0
ERROR:  Error installing fluentd:
        The last version of bundler (>= 0) to support your Ruby & RubyGems was 2.3.27. Try installing it with `gem install bundler -v 2.3.27` and then running the current command again
        bundler requires Ruby version >= 3.0.0. The current ruby version is 2.5.0.

# bundlerをrequire ruby >= 2.Xなv2.3.27に落としてインストール
rbenv exec gem install bundler -v 2.3.27

# 成功
rbenv exec gem install fluentd

fluentdプラグインをインストール

fluent-plugin-cloudwatch-logsプラグイン


CloudWatchに送信するためにfluent-plugin-cloudwatch-logsプラグインをインストールする


# OK
rbenv exec gem install fluent-plugin-cloudwatch-logs

fluent-plugin-record-reformerプラグイン


ログにホストのメタ情報を添加するためにfluent-plugin-record-reformerプラグインをインストールする


# OK
rbenv exec gem install fluent-plugin-record-reformer

fluentdのセットアップ

fluent.conf


setupコマンドでconfを作成する


fluentd --setup /etc/fluentd
> Installed /etc/fluentd/fluent.conf.

fluentdの制御


ネイティブ制御


fluentdのフロント実行コマンド


fluentd -c /path/to/fluent.conf -vv &

終了は直接プロセスを落とす


pkill fluentd

init.d制御


/etc/init.d/fluentdに下記のinit.dスクリプトを作成する


  • コンフィグファイル: /etc/fluentd/fluent.conf
  • ログファイル: /var/log/fluentd/fluentd.log

#!/bin/sh
# chkconfig: 2345 99 01
# description: Fluentd
### BEGIN INIT INFO
# Provides:          fluentd
# Required-Start:    $local_fs $remote_fs
# Required-Stop:     $local_fs $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start daemon at boot time
# Description:       Enable service provided by daemon.
### END INIT INFO
# ログ出力のためroot
USER=root
# rbenv×ruby×fluentdのバイナリ
DAEMON=/opt/rbenv/shims/fluentd

DAEMON_ARGS="-c /etc/fluentd/fluent.conf -o /var/log/fluentd/fluentd.log --daemon /var/run/fluentd.pid"
PID=/var/run/fluentd.pid
case "$1" in
 start)
       echo -n "Starting Fluentd: "
       su - $USER -c "$DAEMON $DAEMON_ARGS"
       echo "done"
       ;;
 stop)
       echo -n "Stopping Fluentd: "
       kill `cat $PID`
       echo "done"
       ;;
 restart)
       $0 stop
       $0 start
       ;;
 *)
       echo "Usage: $0 {start|stop|restart}"
       exit 1
esac
exit 0

制御方法


# init.dスクリプトに実行権限あてる
chmod +x /etc/init.d/fluentd
# 起動
/etc/init.d/fluentd start
# 終了
/etc/init.d/fluentd stop
# 再起動
/etc/init.d/fluentd restart

自動起動の設定


chkconfig --add fluentd
chkconfig fluentd on

chkconfig | grep fluentd
> fluentd         0:off   1:off   2:on    3:on    4:on    5:on    6:off

設定


/etc/fluentd/fluent.conf


apacheのカスタムアクセスログの構成例 )
※formatディレクティブの検証はフォーマットデバッガでテストできる。


<source>
  @type tail
  format /^(?<xforwarded>[^ ]*) (?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")? "(?<sess>[^\"]*)"$/
  time_format %d/%b/%Y:%H:%M:%S %z
  path /usr/local/httpd-2.4/logs/idt:443/access.log
  pos_file /var/log/fluentd/log.httpd.access.idt.pos
  # メタ情報を添加するために.reformタグをつける
  tag this.tagname.reform
</source>

# メタ情報を添加するために.reformタグをスクリーニング
<match **.reform>

  # record-reformerプラグイン
  @type record_reformer
  renew_record false
  enable_ruby true

  # 添加後、スクリーニング再入で本来のmatchアクションディレクティブに補足させるためにreformタグをクレンジングする
  tag ${(tag_parts - ["reform"]).join(".")}

  # ホスト名とタイムスタンプを添加する
  <record>
    hostname ${hostname}
    servertime ${time.iso8601(3)}
  </record>
</match>

# アクションディレクティブ
<match this.tagname.**>
  # cloudwatch-logsプラグイン
  @type cloudwatch_logs

  region ap-northeast-1
  log_group_name path.to.loggroup

  # ロググループ内に{タグ名}で自動的にログストリームを作成する
  auto_create_stream true
  use_tag_as_stream true
</match>


sourceディレクティブに入力されるapacheアクセスログ


# ログ
XXX.XXX.XXX.XXX YYY.YYY.YYY.YYY - - [23/Mar/2024:05:45:00 +0900] "GET /path/to/page HTTP/1.1" 200 138792 "https://host.example.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36" "abcdefg"

フォーマット後のjson


{
    "xforwarded": "XXX.XXX.XXX.XXX",
    "host": "YYY.YYY.YYY.YYY",
    "user": "-",
    "method": "GET",
    "path": "/path/to/page",
    "code": "200",
    "size": "687368",
    "referer": "https://host.example.com/",
    "agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
    "sess": "abcdefg",
	# record_reformerで追加したメタ情報
    "hostname": "fluent-host",
    "servertime": "2024-03-23T05:45:03.000+09:00"
}


AWS


EC2/IAM


EC2のロールにCloudWatchLogsFullAccessポリシーを追加する


CloudWatch


ロググループpath.to.loggroupを作成しておく
※ログストリームはauto_create_streamオプションで自動的に作成してくれる

paged.jsで作るPagedMediaな帳票文書




帳票、契約書などのビジネス文書をhtmlでプレビューして印刷するまでのロードマップ。

Paged Media


Paged MediaとはCSS3のページ化メディア= 版物の用紙サイズ、ふち、改ページなどのスタイルを指定するためのど真ん中の仕様セット。

ページ化メディアとかページ組版とかPaged Mediaとか、いかにも専門チックな用語が登場するが、ページ概念のあるスタイルセットという理解でOK。


しかしながら、いま現在もメジャーブラウザの対応状況はまちまちだそうで、ブラウザ互換性という意味で安定していないようだ。


ライブプレビュー


それでもPagedMediaで印刷をスタイリング出来るのは結構だが、(印刷ダイアログではなく)スクリーンでも精度の高いライブプレビューを再現したいというもの。
残念ながらPagedMediaはスクリーンのそれとは異なるため、スクリーンのスタイルは別途作り込む必要がある。


単ページの帳票なら印刷ダイアログっぽく用紙サイズのページ要素のコンテナを作ってCSSオンリーでスタイリング出来るが、
可変ページ概念が出てくるとページ要素の増減、ページを横断する段落/テーブル要素の分割&リビルドとか、画像は改ページして、、気の遠くなる調整実装が待っている。


paged.js


そこで、上記の気の遠くなるページ化メディア調整をしてくれるpaged.jsというオープンソースのポリフィルがある。
スクリーンでPagedMediaオリエントにDOMを再構築しれるので、スクリーンを印刷に合わせるのではなくて、印刷にスクリーンを再現させることができる。


使用するPaged Media仕様は@pageディレクティブの用紙サイズsize/マージンmargin/ふち印字@bottom-center/改ページ制御break-insideあたり


実装

スクリプト


次のCDNスクリプトを読み込むと自動的にpaged.jsのポリフィルが走る。
Getting Started with Paged.js —


<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>

2024.3.18現在 CDNで公開中のステーブルバージョンv0.4.3


/**
 * @license Paged.js v0.4.3 | MIT | https://gitlab.coko.foundation/pagedjs/pagedjs
 */

文書を組む


paged.jsは@media printクエリを展開する

paged.jsはPagedMediaを完全擬態しようとするので@media printクエリを展開する。
逆に@media screenクエリはデフォルトで無視するため、スクリーンにのみ適用したいスタイルはlinkタグやstyleタグにmedia=“screen”指定することで適用してくれる。


<!-- linkはmedia=“screen”を入れる -->
<link rel="stylesheet" type="text/css" href="screen.css" media="screen">

<!-- styleタグにmedia="screen" -->
<style media="screen">  
  p {  
    color: red;  
  }  
</style>

じつは上記仕様に至るに破壊的な変更があり、v0.4.0以前はスクリーン向けには本来の@media screenクエリを展開していたが、スクリーンも完全なPagedMediaオリエントへと変遷したようだ。以前v0.2を利用していて、今回現最新版のv0.4.3を試したところ、プレビュー専用のスタイルが言うことを聞かないので頭を悩ませた。

バージョン 0.4.1 以降、Pages.js は @media screenのメディア クエリ内にある CSS の解析を停止します。

Paged Break, the long overdue update —


キャンバス

以下のスタイルを当てると用紙サイズのキャンバスができるので、あとは画面ノリで組むだけ。



<!-- スクリーンにのみあてるスタイル -->
<style>
	@page{
		/* A4・横向きでサイズ指定 */
    	size: A4 landscape;
		/* ふちを20mmとる */
    	margin: 20mm;
		/* 各ページのフッター中央に { 現在ページ } / { 総ページ } を印字 */
    	@bottom-center{
        	content: counter(page) '/' counter(pages);
    	}
	}
</style>

<!-- スクリーンにのみあてるスタイル media="screen" -->
<style media="screen">
body{
	/* 背景色を指定してプレビューぽくする */
    background: #666;
}

/* ポリフィルがあてるページクラスとコンテナクラスをプレビュー用にスタイリングする */

/* コンテナクラス は縦並びでセンタリングする*/
.pagedjs_pages {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    width: calc(var(--pagedjs-width));
    margin: 0 auto;
}
/* ポリフィルがあてるページシートクラス */
.pagedjs_page {
    margin: 5mm;
    border: 1px solid #333;
    background: #fff;
}
</style>

スクリーンに帳票を組んだ結果


印刷プレビュー
スクリーンをそのまま再現できている




シートから溢れた要素を適宜改ページしてくれる


表の改ページ精度問題


シートから溢れた文字列とかブロック要素は問題なく改ページしてくれるが、TableGridは下図のようにちょん切れたり(widthもありえない数字)、不可視領域に吹っ飛んだり動作不安定でまともに使えず、公式フォーラムでもこの手のイシューがちらほらあるが猛者たちが調整で頑張っている。


Paged.JS and CSS Flex / Grid
Paged.js TABLE breaks · Issue #149 · pagedjs/pagedjs
Chrome: Missing row content on page break within table (v0.4.3 vs v0.5.0-beta.0) (#431) · イシュー · pagedjs / pagedjs · GitLab



表に関しては下記のようなFlexテーブルで代替する。それでもちょん切れる場合は行要素に改ページ禁止break-inside: avoid;を当てるとうまくいく。


<style>
    .flex-column{ display: flex; flex-flow: column;}
    .flex-row{ display: flex; flex-flow: row;}
    .flex-cell {
        flex-grow: 1; padding: 2pt 6pt 2pt 6pt;
        border: 1px solid #000;
    }

    /* 隣接するセル間のボーダーを重複させない */
    .flex-row .flex-cell:not(:first-child) {
        border-left: none;
    }
    .flex-row:not(:first-child) .flex-cell {
        border-top: none;
    }
    .flex-row.head .flex-cell {
        text-align: center;
    }
    .flex-cell:nth-of-type(1){flex-basis: 30%;}
    .flex-cell:nth-of-type(2){flex-basis: 20%;}
    .flex-cell:nth-of-type(3){flex-basis: 10%;}
    .flex-cell:nth-of-type(4){flex-basis: 10%;}
    .flex-cell:nth-of-type(5){flex-basis: 30%;}

    /* 項目と表途中の改ページ禁止 */
    .no-break{
        break-inside: avoid;
    }

</style>

<div class="flex-column">
    <div class="flex-row head">
        <div class="flex-cell">品名</div>
        <div class="flex-cell">規格</div>
        <div class="flex-cell">単価</div>
        <div class="flex-cell">金額</div>
        <div class="flex-cell">摘要</div>
    </div>

    <div class="flex-row">
        <div class="flex-cell">1</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">2</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">3</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">4</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">5</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row no-break">
        <div class="flex-cell" style="flex-basis: 100%; height: 36mm;">
            【備考欄】
        </div>
    </div>
</div>


ALBのOIDCフロールールセットでPOSTリクエストのデータが空になる


ALBのルールセットでOIDCフローを組んでいるWebアプリで起こったはなし。


UAのフォームPOSTデータが稀に空っぽでバックエンドに到達することがある。
クライアント環境/Webサーバー/アプリケーション/セッション層=自分の保守範囲で考えうる仮説と検証を繰り返してもどうしても穴がないため、フロント=>バックエンド間=aws/idp層の調査を開始する。


ALB


問題発生時間帯のALBのアクセスログをAthenaでトレースするとめちゃめちゃど真ん中2に気になるアクセスログ/oauth2/idpresponse?code=...があった。
/oauth2/idpresponseといえば、ALBがOIDCフローのIDプロバイダからユーザの認証結果を受け取るリダイレクトのコールバックエンドポイントだ。



紐解くと、

  • 1では、本来のPOSTのリクエストはバックエンド前段でALB層でUAがIDプロバイダに302リダイレクトされていることがわかる。
  • 2では、IDプロバイダでユーザ検証が完了してALBにリダイレクトしてきたので、ALBが本来リクエストに302リダイレクト復帰させようとしている。
  • 問題は3で、UAから本来POSTメソッドのリクエストがGETメソッドでリクエストされたので、バックエンドが送信元画面に302リダイレクトで差し戻している。
  • 結果、4で、UAが差し戻しで元画面にリクエストしている。ユーザは2と、3が見えずこの間1秒未満なので、何かわからんが元画面に戻った?という状況

なお、1から2の間は、IDプロバイダの認証有効期限とか、ましてALBがUAに食わせる認証結果の収納クッキーの有効期限切れとか起こっているわけでもなく、定期的にIDプロバイダに送ってユーザ検証をしてくれていて、ログ全体でこの検証フローがたびたび発生していて、POSTリクエストに当たったときに問題として顕在化したようだ。


Httpメソッドとデータも維持して復帰してくださいよ、、と思うが、aws公式で、自分で対策してくれとレポートが出ているためしょうがない。


: Application Load Balancer は 301 と 302 のリダイレクトのみをサポートします。これらのリダイレクトにより、クライアントは後続のリクエストで HTTP メソッドを POST から GET に変更できます。307 リダイレクトが必要な場合は、リダイレクトはターゲットアプリケーションを経由する必要があります。

Application Load Balancer を使用して、ドメインを別のドメインにリダイレクトする


対策


私のユースケースではOIDCフローは追加認証要素でありアプリ層には別途、認証と、一般的な検証&ログインリダイレクト機構を担保しているので、
ホスト全体に適応していたALBのOIDCルールを認証ロケーションに狭めることで、IDプロバイダへの検証割り込みを回避することで対策とした。

MVCフレームワークでRequest_URLとRequire envでアクセス制御するときの注意点


apacheconfまたは.htaccessでRequest_URIに基づいてSetEnvIfで環境変数でフラグを立てて、Requireでホワイトリスト判定しているのに拒否される場合の対策。


通常



SetEnvIf Request_URI {ロケーション} Requireでロケーションで判定するシンプルな例


# http://host/path/to/location でリクエスト

# ロケーションにマッチして環境変数をフラグする
SetEnvIf Request_URI /path/to/location allowed_location

# Requireでフラグを判定して許可
Require env allowed_location

MVCフレームワーク下におけるリダイレクト影響



Webフレームワーク+apache環境ではだいたいmod_rewriteを使った次のコードスニペットが公開されている。


RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L]
# => http://host/index.php?_url=/path/to/location にインターナルリダイレクトする

原因


上記インターナルリダイレクトでconfの再評価が入るとロケーションが一致しなくなる。


# http://host/index.php?_url=/path/to/location でインターナルリダイレクト

# ロケーションにマッチしないので環境変数をフラグしない
SetEnvIf Request_URI /path/to/location allowed_location

# Requireでフラグを判定して拒否
Require env allowed_location

対策


リダイレクト後のロケーションもフラグメントするのではさすがに不毛だわ


SetEnvIf Request_URI /path/to/location allowed_location
# リダイレクト後のロケーションもフラグメント
SetEnvIf Request_URI /index.php?_url=/path/to/location allowed_location

# Requireでフラグを判定して拒否
Require env allowed_location

結論


mod_rewriteによるリダイレクトでは、前回評価で定義した環境変数がREDIRECT_プレフィックス付きでREDIRECT_{環境変数}のように引き継がれるので、
RequireREDIRECT_{環境変数}を追加して、本来のユーザリクエストに基づく評価結果をもって通過させる。


# リダイレクト再入ではマッチしない
SetEnvIf Request_URI /path/to/location allowed_location

# が、REDIRECT_allowed_locationを評価して許可
Require env allowed_location REDIRECT_allowed_location

phalconをv3からv4にアップグレードする


php:7.2-fpm-alpine3.12コンテナイメージが、vscode-v1.77以降のDevContainersでvscode-serverの起動エラーでコンテナに接続出来なくなったので、alpine-v3.16以降の
コンテナイメージへのアップグレードの必要に迫られた。

VsCode v1.77以降のDevContainers拡張でalpine v3.12コンテナに接続できない


php公式イメージで要件を満たすもっとも世代の近いイメージphp:7.4-fpm-alpine3.16php-v7.4に対応するphalconのバージョン要件がv4以降のため、phalconをv3からv4にメジャーアップデートする際の対応録。


イメージビルド編


ベースイメージの変更




# FROM php:7.2-fpm-alpine
FROM php:7.4-fpm-alpine3.16

oniguruma



docker-php-ext-installツールでmbstringをインストールしようとすると依存ライブラリのonigurumaがないといってビルドに失敗する。


が、php:7.4-fpm-alpine3.16にはそもそもoniguruma非依存のmbstringがセットアップ済みのため、追加インストールなど不要だったのでシンプルにdocker-php-ext-installからmbstringを削除して解決。


FROM php:7.4-fpm-alpine3.16
RUN docker-php-ext-install pdo_mysql mysqli mbstring < これ

#5 15.29 configure: error: Package requirements (oniguruma) were not met:
#5 15.29 
#5 15.29 Package 'oniguruma', required by 'virtual:world', not found
#5 15.29 
#5 15.29 Consider adjusting the PKG_CONFIG_PATH environment variable if you
#5 15.29 installed software in a non-standard prefix.
#5 15.29 
#5 15.29 Alternatively, you may set the environment variables ONIG_CFLAGS
#5 15.29 and ONIG_LIBS to avoid the need to call pkg-config.
#5 15.29 See the pkg-config man page for more details.

備忘録として


こちらさまによるとphp-v7.4らへんのmbstringビルドではonigurumaという荒々しい名の正規表現ライブラリに依存するようになったらしく、libonig-devというパッケージに収納されているとのこと。apkではこちらさまのとおりoniguruma-devというパッケージ名で配布されていた。oniguruma-devのインストール追加でイメージビルドは成功した。


# RUN docker-php-ext-install pdo_mysql mysqli mbstring
# docker-php-ext-installからmbstringを削除してapkでoniguruma-devをインストールする
RUN docker-php-ext-install pdo_mysql mysqli mbstring
apk add --no-cache oniguruma-dev < これ

phalcon



phalconのソースをバージョンを4.0系のlatestバージョンv4.0.6に変更


RUN cd /usr/local \
# phalconのビルドソースを3系から4系に変更
# && git clone https://github.com/phalcon/cphalcon.git -b v3.4.3 \
&& git clone https://github.com/phalcon/cphalcon.git -b v4.0.6 \
&& cd cphalcon/build \
&& sh install \
&& echo "extension=phalcon.so" > /usr/local/etc/php/conf.d/phalcon.ini \

psr




コンテナを起動してhttpアクセスするとphpワーニングでUncaught Error: Class 'Phalcon\Config' not found
え?メジャーアップデートでnamespaceがっつり変わった?と焦るも、公式見てもPhalcon\Configは健在だ。


ビルド失敗している?と思ってphpのロードモジュールを確認すると、psrモジュールがローディングできてない?からphalconモジュールがローディングできていないようだ。


php -m | grep phalcon
PHP Warning:  Cannot load module 'phalcon' because required module 'psr' is not loaded in Unknown on line 0

phalcon-v4からpsrに対応するためライブラリ依存するようだ。
Fatal error: Uncaught Error: Class 'Phalcon\Config' not found
Php can not load module phalcon
Installation - Phalcon Documentation


Dockerfileにpecl-psrのインストールコマンドを追加して、phalconモジュールのローディングの前にローディングする
Phalconをdocker imageに詰め込みたいとき


# xdebugのインストール
RUN pecl install psr \
&& docker-php-ext-enable psr \
&& echo "extension=psr.so" >> /usr/local/etc/php/conf.d/phalcon.ini \
&& echo "extension=phalcon.so" >> /usr/local/etc/php/conf.d/phalcon.ini \

xdebugバージョン

xdebug3.2.0でphp7がサポート対象外になったのでDockerfileを直した



xdebug-v3.2.0以降、php-v7がサポート対象外になったため、xdebugはバージョン指定でアーカイブDLする


[ 5/17] RUN pecl install xdebug && docker-php-ext-enable xdebug:
> #9 5.238 pecl/xdebug requires PHP (version >= 8.0.0, version <= 8.2.99), installed version is 7.3.29
> #9 5.238 No valid packages found
> #9 5.238 install failed

peclではxdebug-{バージョン}でアーカイブがディストリビュートされている


RUN pecl install xdebug-3.1.6 && docker-php-ext-enable xdebug

アプリケーション編

How to upgrade - Phalcon Documentation


Class 'Phalcon\Session\Adapter\Files' not found




サーバーセッションのファイルアダプタクラスはv4でPhalcon\Session\Adapter\Streamに置き換えられた
Phalcon 4.0 setting up Session


# => Class 'Phalcon\Session\Adapter\Files' not found
$di->setShared('session',function(){
    $session = new \Phalcon\Session\Adapter\Files();
    $session->start();
    return $session;
});

// 保存先`savePath`を指定する
$di->setShared('session',function(){
    $session = new Phalcon\Session\Manager();
    $files = new Phalcon\Session\Adapter\Stream( [
        'savePath' => '/tmp',
    ]);
    $session->setAdapter($files)->start();
    return $session;
});

Fatal error: Uncaught Error: Call to undefined method Phalcon\Logger\Adapter\Stream::log()




v3ではPhalcon\Logger\Adapter\Streamが出力log()と出力先のストリームアダプタを担っていたが、v4から出力はPhalcon\Loggerに分離されてPhalcon\Logger\Adapter\Streamを利用する形に変更となった。ログのインターフェースはpsr-3準拠。
ということでアダプタがlog()など知らんと言っている。


Fatal error: Uncaught Error: Call to undefined method Phalcon\Logger\Adapter\Stream::log() - Discussion - Phalcon Framework
Phalcon logger - Phalcon Documentation
PSR-3: Logger Interface 読了 #PHP - Qiita


$formatter = new Phalcon\Logger\Formatter\Line("[%date%] %message%");
$formatter->setDateFormat('Y/m/d H:i:s');

$adapter = new Phalcon\Logger\Adapter\Stream("php://stdout");
$adapter->setFormatter($formatter);

$logger = new Phalcon\Logger(
	// ロガーに名前をつけられるがマニュアルに書いてないのでとりあえず任意名
	// public function __construct( string $name, array $adapters = [] );
    'hoge', 
    [
		// 任意名のラベル名。Loggerからラベルに対してアダプタを選択したりできる。
        'stdout' => $adapter,
    ]
);

$logger->info('this is log message');


$application->handle(); triggers a "Wrong number of parameters"




フレームワークのエントリポイントapplication#handle()ではリクエストURIをとるようになった
$application->handle(); triggers a "Wrong number of parameters" error after last upgrade - Discussion - Phalcon Framework


// Handle the request
$application = new Phalcon\Mvc\Application();
# $application->handle(); triggers a "Wrong number of parameters"
$response = $application->handle();
$response->send();

// Handle the request
$application = new Phalcon\Mvc\Application();
$request = new Phalcon\Http\Request();
$response = $application->handle($request->getURI());
$response->send();

Phalcon\Mvc\Router#handle()も同じく


// triggers a "Wrong number of parameters"
$router->handle();

$request = new Phalcon\Http\Request();
$router->handle($request->getURI());

Class 'Phalcon\Mvc\Url' not found




Phalcon\Mvc\Urlはv4でPhalcon\Urlになった。そしてv5ではまさかのPhalcon\Mvc\Urlになった


VoltEngine#setOptionsのキーが変わった




compiledプレフィックスがなくなった
How to disable volt cache - Discussion - Phalcon Framework


$volt = new Phalcon\Mvc\View\Engine\Volt()
$volt->setOptions(
    [
        'compiledPath'      => BASE_PATH . $config->application->cacheDir,
        'compiledExtension' => '.compiled',
        'compiledSeparator' => '_',
        'stat' => true,
        'compiledAlways' => true
    ]
);

$volt = new Phalcon\Mvc\View\Engine\Volt()
$volt->setOptions(
    [
        'path'      => BASE_PATH . $config->application->cacheDir,
        'extension' => '.compiled',
        'separator' => '_',
        'stat' => true,
        'always' => true
    ]
);

Too few arguments to function {closure}(), 2 passed …




DIに渡すクロージャに設定していた引数がエラーになったので、キャプチャで渡す。変更になったのか元々バグっていてpsrで顕在化したのかな。
Task Too few arguments to function {closure}(), 2 passed and exactly 4 expected · Issue #4576 · swoole/swoole-src


// $di->setShared('voltService', function ($view, $di) use ( $config ){
$di->setShared('voltService', function () use ($config){

		$di = Phalcon\DI::getDefault();
        $volt = new Volt($di->get('view'), $di);
        $volt->setOptions(
            [
                'path'      => BASE_PATH . $config->application->cacheDir,
                'extension' => '.compiled',
                'separator' => '_',
                'stat' => true,
                'always' => true
            ]
        );
        return $volt;
    }
);

Fatal error: Cannot override final method Phalcon\Mvc\Model::getSource()




Model::getSource()はオーバーライド不可となったのでイニシャライザにModel::setSource('テーブル名')で指定する
Fatal error: Cannot override final method Phalcon\Mvc\Model::getSource() - Discussion - Phalcon Framework


public function initialize()
{       
    $this->setSchema("phalcon_demo-app");       
    $this->setSource("users");        
}

Fatal error: Uncaught Error: Class 'Phalcon\Validation\Validator' not found




カスタムバリデーションの実装で問題が発生


  • Phalcon\Validation\ValidatorはアブストラクトクラスPhalcon\Validation\Validatorに置き換えられた
  • PHP7型宣言の厳格化でオーバーライドに戻り値の宣言が必要

// phalcon v4でのカスタムバリデーションクラス実装
class CustomEmailValidator extends \Phalcon\Validation\AbstractValidator implements \Phalcon\Validation\ValidatorInterface
{
    public function validate(Validation $validation, $attribute): bool { ... }
}

Fatal error: Declaration of { 関数宣言 } must be compatible with { 関数宣言 }: string




PHP7型宣言の厳格化でオーバーライドに戻り値の宣言が必要


Phalcon\Model::findFirst to return null instead of false if no record was found




Phalcon\Model::findFirstで結果が無い場合に戻り値falsenullになった
変更箇所が多岐にわたるので、モデルクラスでfindFirstをオーバーライドしてnullの場合はfalseを返却して後方互換性を維持する


Model::saveのホワイトリストが効かない




The save() method no longer accepts parameters to set data. You can use assign instead.


// フィールドnullになる
$model->save($assign_key_val, $whitelist_key);

// assignに変更
$model->assign($assign_key_val);
$model->save();

VsCode v1.77以降のDevContainers拡張でalpine v3.12コンテナに接続できない


vscode - v1.77以降のalpine - v3.12のコンテナイメージ(php:7.2-fpm-alpine)で、DevContainers拡張でコンテナに接続できなくなった。
ログを見ると何やらsymbol not foundエラーが出ていて、コンテナ内のVsCodeServerの起動に失敗しているためVsCodeから通信ができないようだ。


なお、コンテナ単体は起動する。


[5733 ms] Container server: Error relocating /root/.vscode-server/bin/b3e4e68a0bc097f0ae7907b217c1119af9e03435/node: _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7reserveEv: symbol not found
Error relocating /root/.vscode-server/bin/b3e4e68a0bc097f0ae7907b217c1119af9e03435/node: _ZSt28__throw_bad_array_new_lengthv: symbol not found
[5752 ms] Error: stream ended with:0 but wanted:9
        at c (/Users/fullhouse/.vscode/extensions/ms-vscode-remote.remote-containers-0.292.0/dist/extension/extension.js:24:101670)
        at /Users/fullhouse/.vscode/extensions/ms-vscode-remote.remote-containers-0.292.0/dist/extension/extension.js:24:101851
        at s (/Users/fullhouse/.vscode/extensions/ms-vscode-remote.remote-containers-0.292.0/dist/extension/extension.js:27:5371)
        at Socket.<anonymous> (/Users/fullhouse/.vscode/extensions/ms-vscode-remote.remote-containers-0.292.0/dist/extension/extension.js:27:5541)
        at Socket.emit (node:events:525:35)
        at endReadableNT (node:internal/streams/readable:1358:12)
        at process.processTicksAndRejections (node:internal/process/task_queues:83:21)

原因




spdlogと他のネイティブモジュールは、microsoft/vscode-linux-build-agent@007bc8f以降のnode:16.9.1-alpineでビルドされており、libstdc++ v6.0.30が付属しているのに対し、ruby:2.7-alpine3.13にはlibstdc++ v6.0.28しか含まれていません。また、node.jsバイナリのアップストリームalpineイメージは、libstdc++ v6.0.30 https://github.com/nodejs/docker-node/tree/bd6c00f07c5a3b311ce0e346ab69df6b9ef8d08f/16/alpine3.17 でビルドされています。したがって、nodejs/node#41058で述べられているように、ユーザがコンテナ内のlibstdc++バージョンをアップグレードするしか解決策はないと思います。

1.77 you can no longer have any extensions on a remote container as of 10295b5 · Issue #178748 · microsoft/vscode · GitHub


VsCodeリポジトリのフォーラムによるとvscode-v1.77以降でvscode-server(nodejs)の依存ライブラリspdlogと他のネイティブモジュールのビルド環境のアップデートによって、これらが依存するlibstdc++ライブラリがv6.0.30に引き上げられたようだが、古いalpineイメージにおいてはv6.0.28がインストールされるため互換が失われた。的なことのようだ。

php:7.2-fpm-alpineコンテナでlibstdc++のバージョンを確認すると、確かにlibstdc++.so.6.0.28がインストールされていた。


cat /etc/os-release 

NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.12.3
PRETTY_NAME="Alpine Linux v3.12"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"

ls -lav /usr/lib | grep libstdc++.so
lrwxrwxrwx    1 root     root            19 Oct 19  2022 libstdc++.so -> libstdc++.so.6.0.28
lrwxrwxrwx    1 root     root            19 Oct 19  2022 libstdc++.so.6 -> libstdc++.so.6.0.28
-rwxr-xr-x    1 root     root       1657328 May 18  2020 libstdc++.so.6.0.28 < これ


# apk infoで確認すると-9.3.0-r2なのでバージョン満たしているかと思いきや
# これはGCCバージョンで、libstdc++.soのバージョンニングではない
apk info libstdc++
libstdc++-9.3.0-r2 description:
GNU C++ standard runtime library

libstdc++-9.3.0-r2 webpage:
https://gcc.gnu.org

libstdc++-9.3.0-r2 installed size:
1724416

対策



応急処置


libstdc++のアップデートによる影響が未知過ぎるため、まずはVsCodeのダウングレードで凌ぐ。vscideの公式アーカイブから直前のvscode - v1.76バイナリを取得して置き換えたら復帰した。VsCodeが自動更新される度にまた戻してやる必要があるが、今後奇跡的にパッチが発明されることを願って旧バージョンで凌ぐことにした。


ためだ


これこれ1年くらい粘って旧バージョンで凌いだが、いよいよ機能拡張やらバージョン互換が切れ出して辛くなってきたので対応する。


alpineイメージを移行する


The issue here is from nodejs binary nodejs/node#41058, @hamad-9 is it possible for you update to >=alpine-3.15 as base image ?


Thank you, it's finally worked. I updated the base image to python:3.9-alpine3.18.

Container Build Failed - An error occurred setting up the container. · Issue #8450 · microsoft/vscode-remote-release · GitHub


こちらの公式フォーラムによるとalpine - v3.15が互換要件のalpineバージョンとのこと。


要件を満たしつつ、php:7.2-fpm-alpineから最も近いイメージDocker | php:7.3-fpm-alpine3.15を試したが変化がなかった。
もうひとつアップストリームのphp:7.4-fpm-alpine3.16だとvscode-serverの起動に無事成功して、DevContainer拡張で接続することが出来た。


なお、libstdc++の場合はv6.0.29がインストールされていた。v6.0.29 ~ v6.0.30間の互換性があるようだ。


cat /etc/os-release 
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.16.3
PRETTY_NAME="Alpine Linux v3.16"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"

ls -lav /usr/lib | grep libstdc++.so
lrwxrwxrwx    1 root     root            19 Jan 25 09:11 libstdc++.so -> libstdc++.so.6.0.29
lrwxrwxrwx    1 root     root            19 Jan 25 09:11 libstdc++.so.6 -> libstdc++.so.6.0.29
-rwxr-xr-x    1 root     root       1952104 Apr 11  2022 libstdc++.so.6.0.29

CloudfrontでBloggerのサーチコンソールインデックス登録エラーを対策する


サーチコンソールでインデックスをリクエストしたBloggerページがのきなみリダイレクト要因で登録エラーとなっているではないか。




この記事はCloudfrontを導入して対策するロードマップです。

原因


Bloggerはモバイルユーザーエージェントに対してはGETクエリm=1をつけてモバイル最適ページにリダイレクトさせるようだ。


そしてインデックス登録したURLにスマートフォン用 Googlebotがやってきてリダイレクトエラーに引っかかっるようだ。


Google はほとんどのサイトについて、主としてコンテンツのモバイル バージョンをインデックスに登録します。そのため、Googlebot のクロール リクエストの大部分はモバイル クローラーを使用して行われ、一部がデスクトップ クローラーを使用して行われます。

Googlebot とは | Google 検索セントラル  |  ドキュメント  |  Google for Developers


対策


結論


Cloudfrontのマネージドポリシーで解決した。



m=1付きでインデックスをリクエストする作戦

だめパターン



インスタントな対策としてはm=1付きのurlでインデックスリクエストする手があるが、ページのカノニカルURLとしてはm=1がついていないので下記のループが待っているため今後も仕様変更によって踊らされる可能性が高く恒久対策にはならない。


  1. いつかパソコン用 Googlebotがやってくる
  2. m=1無しでインデックスを更新する
  3. いつかスマートフォン用 Googlebotがやってくる
  4. リダイレクトエラーでインデックス解除する
  5. わい) m=1付きでリクエストする
  6. スマートフォン用 Googlebotがやってきてインデックス登録する → 1に戻る

CDNをかます作戦

本題



CDNを中継してスマホと思しきユーザーエージェントの場合、はなからm=1クエリを付記してコンテンツサーバー(Blogger)にリクエストしてリダイレクトを抑止する。


CDNを選定する


Cloudflare ※だめパターン

  • 検索すると真っ先にこの手のソリューションとして引っかかってくる🔎 blogger m=1 cdn
  • 無料プランの範囲でユーザエージェント判定と、urlのトランスフォームルールが組むことができる最有力なCDN

無料プランの限界


さっそくアカウントを作ってトライすると、


  • CloudflareはディストリドメインのDNSホストゾーンを預ける必要がある
  • いまのホストゾーンRoute53でレコードを組みまくっているし、突然知ったCloudflareを信用してないので渡せない、渡したくない
  • だったらブログホストのサブドメインを切り出してcloudflareのネームサーバーに委任しよう
    → Freeプランはルートドメイン(ネイキッドドメイン)しか登録させてもらえない

ということでCloudflareは却下。`Cloudflareをそもそも知っていたとしたら強力なソリューションであったので今後に活かしたい。


CloudFront

  • この手のソリューションとしてCloudfrontが検索で出て来ないが、lambda@EDGEでユーザーエージェントを判定してオリジンへのクエリコントロールを実装できる。
  • 常時無料利用枠が付帯する。

常時無料利用枠に含まれます

  • 1 か月あたり 1 TB のインターネットへのデータ転送
  • 1 か月あたり 10,000,000 件の HTTP または HTTPS リクエスト
  • 1 か月あたり 200 万件の CloudFront 関数呼び出し
  • 1 か月あたり 2,000,000 回の CloudFront KeyValueStore の読み取り
  • 無料の SSL 証明書
  • 制限なし、すべての機能が利用可能

料金 - Amazon CloudFront | AWS


ということで、CDNはCloudfrontで決定した。


Cloudfrontの設定


変更前


カスタムドメインのCNAMEをBloggerのカスタムドメイン用ホストghs.google.comに指定する


変更後


カスタムドメインのCNAMEをCloudfrontディストリビューションにして、オリジンにBloggerのカスタムドメイン用のホストghs.google.comを指定する


ロードマップ

  1. Cloudfrontディストリビューションを作成する
  2. 代替ドメインにBloggerのカスタムドメインを設定する
  3. ACMでバージニアリージョンのSSL証明書を発行してディストリビューションに紐づける
  4. オリジンにBloggerのカスタムドメイン用のホストを設定する
  5. キャッシュポリシーをノーキャッシュで設定
  6. オリジンリクエストポリシーにHostヘッダを指定する
  7. カスタムドメインのCNAMEをCloudfrontディストリビューションに変更する

以上でサーチコンソールのインデックス登録が成功した。



作業中の問題と対策

オリジンにBloggerのカスタムドメイン用のホストを指定する



カスタムドメインのCNAMEをCloudfrontディストリビューションに変更するため、ということはオリジンに何を指定するのか要領を得ず、デフォルトの {登録ホスト}.blogspot.comを指定したときに起こった問題。


まずカスタムドメイン設定した状態でデフォルトの{登録ホスト}.blogspot.comホストにリクエストするとhttps://{カスタムドメイン}に移動しました。的なページに誘導される。


ので、カスタムドメイン設定を解除してオリジンに {登録ホスト}.blogspot.comを指定すると直アクセスは繋がるが、ページに含まれるアセットやリンクのホストには{登録ホスト}.blogspot.com/~が適用されるため、リンクを踏むと{登録ホスト}.blogspot.comに移動しているという悲しい事態となった。


結論としては、Bloggerは元のカスタムドメインを設定したままで良くて、オリジンにはghs.google.comを指定する。


オリジンリクエストポリシーにHostヘッダを指定する



502 ERROR The request could not be satisfied.エラー画面で接続できない問題。


Cloudfrontはビューワーのリクエストヘッダをオリジンリクエストポリシーで明示的に指定しない限りオリジンに渡さない。


よって、オリジンリクエストポリシーが未設定だとHostヘッダを渡さないのでghs.google.comからカスタムドメインへの連携が出来ず
https://ghs.google.comでは証明書エラーだし、http://ghs.google.comは404だし、結果502 ERROR The request could not be satisfied.となる。


結論としては、オリジンリクエストポリシーにHostヘッダを設定することでBloggerサイトに疎通可能となる。


逆にUserAgentヘッダも指定しない限りは渡されないので、結果的にモバイルページリダイレクトは発生しなくなった。
そして、少なくとも私がBlogger使っているテーマで通常ページとモバイルページ(?m=1)をソース差分を確認した結果、何も差分がなかったのでlambda@EDGEの対応も不必要となった。


カスタムドメインのCNAMEをCloudfrontディストリビューションに変更する



Route53のAエイリアスレコードでCloudfrontのディストリビューシュンを指定しようとしたらサジェストに出てこない。


CNAMEレコードのFQDNに合致する代替ドメインをCloudfrontディストリビューションに設定したらサジェストに出てきた。
そりゃそうか良く出来ているなという感じ。