FrankenPHPを使ってみた

Published:

Updated:

FrankenPHPとは

FrankenPHPとはGo言語で書かれた、新しいPHPサーバーです。

主な特徴は、

  1. Caddy上に構築されたPHP8.2以降のサーバー
  2. シングルバイナリ
  3. ハイパフォーマンス
  4. HTTP/2およびHTTP/3のサポート
  5. Early Hintsのサポート
  6. Mercureを利用したWebSocket
  7. Let’s EncryptまたはZeroSSLを利用した証明書の自動更新

また、Laravel OctaneによりLaravelは完全にサポートされています。

FrankenPHPを使う

PHPサーバーを構築する際、一般的には次のような構成になると思います。

  • Apache + mod_php
  • Nginx + FPM

FrankenPHPの場合は、FrankenPHPのみで動作します。

Dockerを利用する場合は、次のコマンドで実行できます。

docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp dunglas/frankenphp

1つのコンテナだけで済むので、Amazon ECSなどとの相性はいいです。

WordPressをFrankenPHPで動かしてみる

WordPressを動かす場合、次のようなDockerfileとcompose.ymlになります。

Dockerfile

FROM dunglas/frankenphp:php8.3
RUN docker-php-ext-install pdo_mysql mysqli
COPY wordpress /app/public

compose.yml

services:
  php:
    build: .
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
  mysql:
    image: 'mysql/mysql-server:8.0'
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: 'mysql'
      MYSQL_USER: 'mysql'
      MYSQL_PASSWORD: 'password'

パフォーマンス比較

DockerでFrankenPHP、FPM + Nginx、Apacheの環境に対して、WordPressの表示測定しました。 測定条件はNginxのFastCGIまわりの設定を除き、サーバーもWordPressもデフォルトの設定で、siegeを用い記事の表示を60秒間、同接100と200ので測定しました。

実行環境

FrankenPHPの環境

FROM dunglas/frankenphp:php8.3
ADD https://ja.wordpress.org/latest-ja.zip .
RUN apt-get update && \
    apt-get install -y unzip && \
    rm -rf /var/lib/apt/lists/*  && \
    docker-php-ext-install pdo_mysql mysqli && \
    unzip latest-ja.zip && \
    mv wordpress/* /app/public/ && \
    rm -r wordpress latest-ja.zip
services:
  php:
    build: .
    ports:
      - "80:80"
    environment:
      SERVER_NAME: ':80'

  mysql:
    image: 'mysql/mysql-server:8.0'
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: 'mysql'
      MYSQL_USER: 'mysql'
      MYSQL_PASSWORD: 'password'
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
    volumes:
      - 'mysql-volume:/var/lib/mysql'

volumes:
  mysql-volume:
    driver: local

php-fpm + Nginxの環境

FROM php:8.3-fpm
ADD https://ja.wordpress.org/latest-ja.zip .
RUN apt-get update && \
    apt-get install -y unzip && \
    rm -rf /var/lib/apt/lists/*  && \
    docker-php-ext-install pdo_mysql mysqli && \
    unzip latest-ja.zip && \
    mv wordpress/* /var/www/html/ && \
    rm -r wordpress latest-ja.zip
FROM nginx:1.27
COPY nginx.conf /etc/nginx/conf.d/default.conf
ADD https://ja.wordpress.org/latest-ja.zip .
RUN apt-get update && \
    apt-get install -y unzip && \
    rm -rf /var/lib/apt/lists/*  && \
    unzip latest-ja.zip && \
    mkdir -p /var/www/html/ && \
    mv wordpress/* /var/www/html/ && \
    rm -r wordpress latest-ja.zip
server {
    listen 80;
    server_name localhost;
    root /var/www/html;

    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
services:
  php:
    build:
      context: .
      dockerfile: fpm.Dockerfile

  web:
    build:
      context: .
      dockerfile: nginx.Dockerfile
    ports:
      - "80:80"

  mysql:
    image: 'mysql/mysql-server:8.0'
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: 'mysql'
      MYSQL_USER: 'mysql'
      MYSQL_PASSWORD: 'password'
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
    volumes:
      - 'mysql-volume:/var/lib/mysql'

volumes:
  mysql-volume:
    driver: local

Apacheの環境

FROM php:8.3-apache
ADD https://ja.wordpress.org/latest-ja.zip .
RUN apt-get update && \
    apt-get install -y unzip && \
    rm -rf /var/lib/apt/lists/*  && \
    docker-php-ext-install pdo_mysql mysqli && \
    unzip latest-ja.zip && \
    mv wordpress/* /var/www/html/ && \
    rm -r wordpress latest-ja.zip
services:
  php:
    build: .
    ports:
      - "80:80"

  mysql:
    image: 'mysql/mysql-server:8.0'
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: 'mysql'
      MYSQL_USER: 'mysql'
      MYSQL_PASSWORD: 'password'
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
    restart: always
    volumes:
      - 'mysql-volume:/var/lib/mysql'

volumes:
  mysql-volume:
    driver: local

結果

FrankenPHPFPMApacheFrankenPHPFPMApache
同接100100100200200200
Transactions28000 hits23366 hits21972 hits27807 hits23344 hits6144 hits
Availability100.00%100.00%100.00%100.00%100.00%84.64%
Elapsed time60.21 secs60.98 secs60.19 secs60.94 secs60.98 secs33.76 secs
Data transferred275.79 MB306.99 MB84.93 MB273.92 MB306.72 MB26.34 MB
Response time0.21 secs0.26 secs0.27 secs0.42 secs0.50 secs1.01 secs
Transaction rate465.04 trans/sec383.17 trans/sec365.04 trans/sec456.30 trans/sec382.81 trans/sec181.99 trans/sec
Throughput4.58 MB/sec5.03 MB/sec1.41 MB/sec4.49 MB/sec5.03 MB/sec0.78 MB/sec
Concurrency99.0398.4598.57193.72192.61183.57
Successful transactions28014233742198227822233566144
Failed transactions000001115
Longest transaction2.272.8919.784.345.9023.74
Shortest transaction0.000.000.000.000.000.00

vs. FPM

パフォーマンス面ではやや劣るものの、ほぼ同じようなスコアになりました。

一方構築面ではFPMコンテナのほか、Nginxなどのコンテナが必要となり、常に2つのコンテナを動かすことになります。 同じコンテナ内に収める方法もありますが、1プロセス1コンテナのベストプラクティスに反します。 また、NginxにFastCGIの設定を書いたファイルが必要になるなど、動かすまでに少し手間がかかります。

vs. Apache

パフォーマンス面では、FrankenPHPや、FPMと比べると大きく劣る結果になりました。 とくに同接200では唯一、正常応答が100%になっていません。

一方構築面ではFrankenPHP同様、Apache自身がPHPを実行するためコンテナは1つになります。 またとくに設定しなくても、ドキュメントルートにPHPファイルを置くだけで実行できます。

FrankenPHPの問題

一見、FrankenPHPは便利そうに見えますが、 既知の問題もあります。

2024年10月時点では、PHP拡張機能のimapとnewrelicがサポートされていません。また、ext-opensslとparallelは不具合が報告されています。

PHP拡張機能以外では、get_browser()関数のパフォーマンス低下などがあります。

いずれの場合も回避策はありますが、ほかと比べると安定性や互換性の面がやや気になるところです。

また、FrankenPHPは共有メモリを利用して高速化しています。 状況によってはメモリーリークなどが発生します。

おわりに

FrankenPHPは従来のApache、FPMとは異なるアプローチでPHP用のWebサーバーです。 サクッとPHPサーバーを構築できパフォーマンスもよく、使いやすいと思います。 安定性や互換性によほどシビアな要求がなければ、十分に実戦投入できると思います。