PHP-FPM integration released
Refer to PHP-FPM.md for the most current documentation.
PHP-FPM is a high performance PHP daemon built on FastCGI and introduced in apnscp 3.1. On apnscp platforms PHP-FPM demonstrates a 2-3x higher throughput than mod_php ("ISAPI"), which integrates into Apache as a module. In PHP-FPM, a request is sent over a UNIX domain socket to a dedicated worker pool for processing. In ISAPI, PHP requests are handled by a separate VM integrated into Apache that must scaffold and tear down at the end of each request. ISAPI is ideal in low-memory environments but loses relevance outside this niche scenario.
PHP-FPM offers several advantages over an ISAPI integration:
Advantages
Resource enforcement
PHP-FPM pools run under the group ID of each account, which affords simple cgroup treatment to each process. ISAPI runs in a threaded environment that is incompatible with cgroup v1 (cgroup v2 supports threaded accounting at the cost of immense complexity). Every request that comes through may be governed by CPU, memory, block IO, and network IO limits. Likewise every request may be accounted towards an account's cumulative usage.
Jailing
Pools run within the synthetic root of each account ensuring isolation between accounts. ISAPI uses a variety of mechanisms to impede arbitrary access (SECURITY.md) that may provide a loose deterrent. PHP-FPM requests are jailed using systemd namespaces, a powerful OS feature that is part of userland management (PID 1).
To limit snooping or the potential of socket remaps, sockets are stored outside the synthetic root in a general runtime directory that inhibits access beyond directory access using conventional discretionary access controls.
Path cache
Running each account in a jail obviates the requirement for open_basedir restrictions. open_basedir restrictions disable realpath caching (bug #52312) to stymie symlink attacks. Enabling realpath caches improves throughput by caching filesystem metadata and making assumptions about the properties of the file.
User customization
As each pool runs independent of other accounts, end-users may tailor the PHP pool to their needs, such as changing the process manager or PHP settings without relying on .htaccess directives that are always re-evaluated on each request. Users cannot tune critical values such as the pool user, chroot, socket path (without grave consequence) or other protected values as these are extracted to the systemd service definition.
cgroup enforcement is strongly encouraged to prevent abuse. Set cgroup,memory
as a minimum to ensure that a user cannot define a static pool that could spawn an egregious number of workers to cause an out-of-memory condition.
RewriteBase fixupSCRIPT_FILENAME
is rewritten before being passed to proxy. Because of this, PHP scripts on a subdomain or addon domain no longer require RewriteBase /
to be set greatly reducing confusion on migrating to an apnscp platform.
Multi-tenancy PENDING
Accounts may spawn multiple PHP-FPM pools each with different users. For example, it would be possible to create a PHP pool for production and staging in which the production environment adheres to the principles of Fortification and the staging environment is owned entirely by the developer account; both operate under different UIDs.
cgroup enforcement is strongly encouraged to prevent abuse. Set cgroup,memory
as a minimum to ensure that a user cannot define a static pool that could spawn an egregious number of workers to cause an out-of-memory condition.
Flexible ownership
Similarly, each pool may operate under a different UID, preferably a system user without machine access, to provide further separation between accounts and ensure zero overlap when discretionary access controls are applied. Setting the pool to the account owner facilitates easy management without the need for Fortification but also negates any benefits of audit trails should an account get hacked from an insecure WordPress plugin or any PHP application.
To place a FPM pool under the account user for ease of convenience,
EditDomain -c apache,webuser=adminuser -D domain.com
# To switch back to system user
EditDomain -c apache,webuser=apache -D domain.com
Disadvantages
Memory requirements
Each pool requires a minimum of 40 MB. In real-world situations each additional worker may require an additional 16-24 MB memory in typical usage scenarios. Workers that spawn from the pool manager follow copy-on-write semantics idiosyncratic of a fork() syscall, meaning the address space for PHP is shared when a worker spawns. Only the PHP scripts loaded within the worker are allocated additional memory.
To help ameliorate memory constraints in high density environments, PHP-FPM uses the ondemand process manager to automatically sleep idle processes after a set time (1 minute). This can be overrode by changing the PHP-FPM configuration template. ondemand exhibits a very low latency when used in conjunction with OPCache (enabled by default) to spin up additional workers.
Configuring
Switching an account over is a breeze! Flip the apache,jail
setting to enable jailing:
EditDomain -c apache,jail=1 -D domain.com
To set the default going forward, either make the adjustment in a plan via ./artisan opcenter:plan
or set the default FPM behavior, cpcmd config:set apnscp.config httpd use_fpm true
. All new accounts created will use PHP-FPM by default.
To perform an en-masse edit:
cd /home/virtual
for i in site[0-9]* ; do
EditDomain -c apache,jail=1 $i
done
And for the overachieving variety:
yum install -y jq
cpcmd -o json admin:collect null '[apache.jail:0]' | jq 'keys[]' | tr -d '"' | while read -r SITE ; do
echo "Editing $(get_config "$SITE" siteinfo domain)"
EditDomain -c apache,jail=1 "$SITE"
done
There will be an elision delay configured in [httpd] => reload_delay designed to allow multiple HTTP reload calls to merge into a single call to prevent a denial of service attack. By default, this is 2 minutes.
Grouped management
Worker throughput may be examined via systemd. FPM workers are watchdog-aware, which means they automatically report health back to systemd within a deadline window to improve reliability, recovering as needed. Worker metrics may be examined via systemctl status
,
systemctl status php-fpm-site1-domain.com
PHP-FPM workers are grouped <site>-<marker> . By default the marker is the primary domain on the account. site is the immutable siteXX designator of the domain.
systemctl status php-fpm-site1-domain.com
#########################
# Sample response follows
#########################
● php-fpm-site1-domain.com.service - PHP worker for site1 - domain.com
Loaded: loaded (/usr/local/apnscp/resources/templates/apache/php/fpm-service.blade.php; disabled; vendor preset: disabled)
Active: active (running) since Fri 2019-08-30 17:35:01 EDT; 1min 25s ago
Process: 17905 ExecStartPost=/bin/sh -c for i in /sys/fs/cgroup/*/site1/tasks ; do echo $MAINPID > $i ; done (code=exited, status=0/SUCCESS)
Main PID: 17898 (php-fpm)
Status: "Processes active: 0, idle: 1, Requests: 3, slow: 0, Traffic: 0.0667req/sec"
Tasks: 1
Memory: 26.0M
CGroup: /system.slice/php-fpm-site1-domain.com.service
├─17898 php-fpm: master process (/etc/php-fpm.d/sites/domain.com.conf)
└─17906 php-fpm: pool domain.com
apnscp manages pool groups, restarting as needed after the elision window expires. To restart or suspend the pool for a site, use the php-fpm-siteXX
service wrapper.
# Suspend all pools allocated to site1
# Note: socket activation will start the worker on demand!
systemctl stop php-fpm-site1
# Restart all PHP-FPM pools, for example configuration updated
systemctl restart php-fpm-site1
Permanent suspension may be achieved by disabling the corresponding socket,
systemctl mask php-fpm-site1-*.socket
systemctl stop php-fpm-site1-*
However this is seldom useful as suspending the account achieves a similar result:
SuspendDomain site1
Resource enforcement
All cgroup
service directives apply to PHP-FPM workers, including blkio IO throttling. To set a 2 MB/s write throttle on all PHP-FPM tasks use blkio,writebw
or throttle IOPS use the "iops" equivalent, blkio,writeiops
:
EditDomain -c cgroup,writebw=2 domain.com
# Apply the min of blkio,writ.ebw/blkio,writeiops
# Both are equivalent assuming 4 KB blocks
EditDomain -c cgroup,writebw=2 -c blkio,writeiops=512 domain.com
Memory ceilings likewise may be set via cgroup,memory
.
# Set ceiling of 512 MB for all processes
EditDomain -c cgroup,memory=512 domain.com
IO and CPU weighting may be set via ioweight and cpuweight respectively. ioweight requires usage of the CFQ/BFQ IO elevators.
# Default weight is 100
# Halve IO priority, double CPU priority
EditDomain -c cgroup,ioweight=50 -c cgroup,cpuweight=200 domain.com