Azure App Service on Linux(PHP 8.0)で.htaccessが使えなかった

2021.09.04

いわさです。

App Service on LinuxでランタイムにPHP8.0を選択しサイトを構築しました。
既定のドキュメントを変更するために、.htaccessに以下の記述を行いアップロードしました。

.htaccess

DirectoryIndex hoge.html

しかし、ルートURLでアクセスすると、デフォルトで配置されているhostingstart.htmlが表示されてしまいました。

App Service on Windowsでは既定のドキュメントに関する設定項目がありますが、Linuxにはありません。
今回のように既定のドキュメントを変更したいなどなったときはドキュメントでは.htaccessを利用するように案内されています。

App Service の既定の PHP イメージでは Apache が使用されていて

しかし、.htaccessの設定が効いてない感じがします。
どういうことでしょうか。

実はApp Service on Linuxではランタイムによってミドルウェアを含むイメージ部分が異なっており、PHPの場合だと8.0とそれ未満では大きく構成が違っていました。
PHPのバージョンはApp Serviceの構築時に指定、またはアプリ構成から変更が可能です。

PHP 7.4

先程と同様に、FTPで変更対象のhtmlファイルと.htaccessをアップロードします。

今度は期待どおり、既定のドキュメントが変わりました。

少しイメージの中身を見てみましょう。
AzureポータルメニューにSSHの機能がありますので、Webブラウザでリモートアクセスします。

  _____
  /  _  \ __________ _________   ____
 /  /_\  \___   /  |  \_  __ \_/ __ \
/    |    \/    /|  |  /|  | \/\  ___/
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/
A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
PHP quickstart: https://aka.ms/php-qs
PHP version : 7.4.16
Note: Any data outside '/home' is not persisted
root@589cec522b63:/home# ps -x
  PID TTY      STAT   TIME COMMAND
    1 ?        SNs    0:00 /bin/sh /opt/startup/startup.sh
   25 ?        SNs    0:00 /usr/sbin/sshd
   31 ?        SN     0:00 apache2 -DFOREGROUND
   48 ?        SNs    0:00 sshd: root@pts/0
   50 pts/0    SNs    0:00 -bash
   53 pts/0    RN+    0:00 ps -x

実行プロセスを確認してみると、Apacheが起動されていますね。
こちらについてはドキュメントのとおり.htaccessを使えば良さそうです。

PHP 8.0

解析

まずは同じようにSSHアクセスし、実行プロセスを確認してみます。

  _____
  /  _  \ __________ _________   ____
 /  /_\  \___   /  |  \_  __ \_/ __ \
/    |    \/    /|  |  /|  | \/\  ___/
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/
A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
PHP quickstart: https://aka.ms/php-qs
PHP version : 8.0.3
Note: Any data outside '/home' is not persisted
root@49257a730b5f:/home# ps
  PID TTY          TIME CMD
   46 pts/0    00:00:00 bash
   49 pts/0    00:00:00 ps
root@49257a730b5f:/home# ps -x
  PID TTY      STAT   TIME COMMAND
    1 ?        SNs    0:00 /bin/bash /bin/init_container.sh
   20 ?        SNs    0:00 /usr/sbin/sshd
   26 ?        SN     0:00 /bin/sh /opt/startup/startup.sh
   39 ?        SNs    0:00 nginx: master process /usr/sbin/nginx
   40 ?        SNs    0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
   44 ?        SNs    0:00 sshd: root@pts/0
   46 pts/0    SNs    0:00 -bash
   50 pts/0    RN+    0:00 ps -x

Apacheが実行されていないですね。
だから.htaccessが有効にならなかったのですね。
どうやらこちらは、nginx + php-fpm構成のようです。

では、nginx.confを確認してみましょう。

root@c24acaa2abe6:/etc/nginx# cat /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 10068;
        multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log off;
        error_log /dev/stderr;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}


#mail {
#       # See sample authentication script at:
#       # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#       # auth_http localhost/auth.php;
#       # pop3_capabilities "TOP" "USER";
#       # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
#       server {
#               listen     localhost:110;
#               protocol   pop3;
#               proxy      on;
#       }
#
#       server {
#               listen     localhost:143;
#               protocol   imap;
#               proxy      on;
#       }
#}

nginxではnginx.confは直接修正せず、サイトごとの設定をsites-enabled配下に設定してincludeすることが多いです。
sites-availableに設定ファイルを配置し、sites-enabledには設定ファイルへのシンボリックリンクを配置しています。

デフォルトの設定ファイル配下は以下のようになっています。

root@49257a730b5f:/home# cat /etc/nginx/sites-available/default
server {
    #proxy_cache cache;
        #proxy_cache_valid 200 1s;
    listen 8080;
    listen [::]:8080;
    root /home/site/wwwroot;
    index  index.php index.html index.htm;
    server_name  example.com www.example.com;

    location / {
        index  index.php index.html index.htm hostingstart.html;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /html/;
    }

    # Add locations of phpmyadmin here.

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
        fastcgi_pass 127.0.0.1:9000;
        include fastcgi_params;
        fastcgi_param HTTP_PROXY "";
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param QUERY_STRING $query_string;
        fastcgi_intercept_errors on;
        fastcgi_connect_timeout         300;
        fastcgi_send_timeout           3600;
        fastcgi_read_timeout           3600;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
        fastcgi_temp_file_write_size 256k;
    }
}

ここに、デフォルトドキュメントの記述がありますね。
試しに修正してみましょう。

hoge.htmlを追加し、nginx -s reloadでnginxを再起動します。

反映されましたね。 めでたしめでたし。

しかし、喜ぶのはまだ早い。
App Serviceを再起動してみましょう。

戻ってしまいました。
先程の設定ファイルを確認してみると追加した設定が消えています。

App Service起動時に毎回仮想環境を作り直しているようですね。

対応方法の検討

実行プロセスをもう一度見てみると、初期化にあたっていくつかスクリプトを実行しています。
init_container.shが実行されたのちに、startup.shが実行されています。

どうにかして初期化処理でnginx.confに割り込めそうか、それぞれのスクリプトの中身を少し見てみましょう。

root@49257a730b5f:/home# cat /bin/init_container.sh
#!/bin/bash
cat >/etc/motd <<EOL
  _____
  /  _  \ __________ _________   ____
 /  /_\  \\___   /  |  \_  __ \_/ __ \
/    |    \/    /|  |  /|  | \/\  ___/
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/
A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
PHP quickstart: https://aka.ms/php-qs
PHP version : `php -v | head -n 1 | cut -d ' ' -f 2`
Note: Any data outside '/home' is not persisted
EOL
cat /etc/motd

# Get environment variables to show up in SSH session
eval $(printenv | sed -n "s/^\([^=]\+\)=\(.*\)$/export \1=\2/p" | sed 's/"/\\\"/g' | sed '/=/s//="/' | sed 's/$/"/' >> /etc/profile)

# starting sshd process
sed -i "s/SSH_PORT/$SSH_PORT/g" /etc/ssh/sshd_config
/usr/sbin/sshd

appPath="/home/site/wwwroot"
runFromPath="/tmp/webapp"
startupCommandPath="/opt/startup/startup.sh"
userStartupCommand="$@"
if [ -z "$userStartupCommand" ]
then
  userStartupCommand="php-fpm;";
else
  userStartupCommand="$userStartupCommand; php-fpm;"
fi

oryxArgs="create-script -appPath $appPath -output $startupCommandPath \
    -bindPort $PORT -startupCommand '$userStartupCommand'"

echo "Running oryx $oryxArgs"
eval oryx $oryxArgs
$startupCommandPath

スタートアップコマンドを使って、スクリプトを組み立てていますね。

root@49257a730b5f:/home# cat /opt/startup/startup.sh
#!/bin/sh
# Enter the source directory to make sure the script runs where the user expects
cd /home/site/wwwroot
export NGINX_PORT=8080

if [  -n "$PHP_ORIGIN" ] && [ "$PHP_ORIGIN" = "php-fpm" ]; then
   export NGINX_DOCUMENT_ROOT='/home/site/wwwroot'
   service nginx start
else
   export APACHE_DOCUMENT_ROOT='/home/site/wwwroot'
fi

php-fpm;

なるほど。
ここのスクリプトnginxやphp-fpmを起動しつつ、間でスタートアップコマンドを実行していますね。
このタイミングで設定ファイルを変更しnginxを再起動できれば動くかもしれないです。

ちなみに、$PHP_ORIGINによってApacheの設定も利用できるようです。
ここで確認出来たのはドキュメントルートの変数のみですが、他にも影響があるのかもしれないです。
本日はこちらについては踏み込んでないですが、後日もう少し調べてみたいと思います。もしかしたら他のアプローチがあるのかもしれない。

今回はnginxの設定を更新する方向で進めてみましょう。

対応方法

site-enabled用の設定ファイルを作成し、FTPで永続ストレージのルートディレクトリにでもアップロードし、スタートアップコマンドで、nginx.confにincludeされるようにしてみましょう。
ここの更新アプローチは色々方法が考えられると思いますが、今回は上述のようにFTPで設定ファイルを永続エリアにアップロードしておき、シンボリックリンクの向き先を変更しました。
割り込み用のスクリプトを用意しても良いと思いますが、今回はそのままコマンドを書きました。

App Serviceのアプリ構成の全般設定からスタートアップコマンドを指定することが出来ます。

ln -nfs /home/site/wwwroot/default /etc/nginx/sites-enabled/default; nginx -s reload

そして、App Service構築後 or 再起動後の、スタートアップスクリプトを確認してみましょう。

root@807491349dce:/home# cat /opt/startup/startup.sh
#!/bin/sh
# Enter the source directory to make sure the script runs where the user expects
cd /home/site/wwwroot
export NGINX_PORT=8080

if [  -n "$PHP_ORIGIN" ] && [ "$PHP_ORIGIN" = "php-fpm" ]; then
   export NGINX_DOCUMENT_ROOT='/home/site/wwwroot'
   service nginx start
else
   export APACHE_DOCUMENT_ROOT='/home/site/wwwroot'
fi

ln -nfs /home/site/wwwroot/default /etc/nginx/sites-enabled/default; nginx -s reload; php-fpm;

良さそうですね。
ブラウザでも確認してみましょう。

設定が反映されていることを確認出来ました。

まとめ

今回は既定のドキュメントを変更しましたが、nginxのパラメータ設定はもちろん、スタートアップコマンドを活用してやるとランタイムコンテナの既定動作を色々とカスタマイズ出来そうですね。
ランタイムごとにどういう設定変更方法を取ればよいかは、実行プロセスからどういう構成を取られているのか、その構成とするとどう変更すれば良さそうか、を考えてやると良さそうです。