Files
computer-playbook/group_vars/all/04_webserver.yml
kevinveenbirkenbachandClaude Opus 5.5 f52da1c875 fix(svc-prx-openresty): size worker_connections independently of pids_limit
The shared proxy's nginx.conf derived worker_connections from the
openresty container's pids_limit. pids_limit caps processes and threads,
worker_connections caps simultaneous sockets per worker (client and
upstream), so with pids_limit 256 and one worker the whole host's proxy
held about 256 sockets, roughly 128 proxied requests in flight across
every vhost. Raising pids_limit to buy connections would loosen the
fork-bomb guard.

svc-prx-openresty now declares services.openresty.worker_connections
(4096 per worker, the common nginx production range, enough for several
chunk-heavy apps such as OpenWebUI at ~150-170 requests per page load).
WEBSERVER_PROXY_WORKER_CONNECTIONS reads it through lookup('config'),
without a default, so a missing key fails loudly. pids_limit stays 256.

WEBSERVER_PROXY_WORKER_RLIMIT_NOFILE is derived as 2x worker_connections
(8192), one descriptor for each client and upstream socket of a proxied
request, and nginx.conf renders it as worker_rlimit_nofile. The openresty
container pins its nofile ulimit (soft and hard) to the same value:
workers call setrlimit as root without CAP_SYS_RESOURCE, so they can only
lower the hard limit, and the inherited hard limit depends on the host
daemon (Docker >= 25 defaults to 1024:524288, DiD nests whatever the
outer container has). An explicit ulimit makes the rlimit succeed on
every host instead of logging a setrlimit alert.

The unit test pins worker_connections 4096, worker_rlimit_nofile 8192 and
the container ulimit, and fails when worker_connections follows
pids_limit again.

Live proof (compose deploy of web-svc-html, both plays failed=0): the
deployed nginx.conf renders worker_rlimit_nofile 8192 and
worker_connections 4096; openresty's master, worker and cache manager
run with "Max open files 8192 8192"; the openresty log carries no
setrlimit or "worker_connections are not enough" line.

Closes #1300

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 03:00:03 +01:00

50 lines
2.1 KiB
YAML

---
# Webserver Configuration
# Effective CPUs (float) across proxy and the current app
WEBSERVER_CPUS_EFFECTIVE: >-
{{
[
(lookup('applications') | resource_filter('svc-prx-openresty', 'cpus', service_name | default(''), RESOURCE_CPUS)) | float,
(lookup('applications') | resource_filter(application_id, 'cpus', service_name | default(''), RESOURCE_CPUS)) | float
] | min
}}
WEBSERVER_WORKER_PROCESSES: >-
{{
1 if (WEBSERVER_CPUS_EFFECTIVE | float) < 1
else (WEBSERVER_CPUS_EFFECTIVE | float | int)
}}
# worker_connections from pids_limit (use the smaller one), with correct key/defaults
WEBSERVER_WORKER_CONNECTIONS: >-
{{
[
(lookup('applications') | resource_filter('svc-prx-openresty', 'pids_limit', service_name | default(''), RESOURCE_PIDS_LIMIT)) | int,
(lookup('applications') | resource_filter(application_id, 'pids_limit', service_name | default(''), RESOURCE_PIDS_LIMIT)) | int
] | min
}}
# Not WEBSERVER_WORKER_*: their application_id is whichever role loaded the shared proxy first, whose limits then cap every vhost.
WEBSERVER_PROXY_WORKER_PROCESSES: >-
{{
[
(lookup('applications') | resource_filter('svc-prx-openresty', 'cpus', '', RESOURCE_CPUS)) | float | int,
1
] | max
}}
WEBSERVER_PROXY_WORKER_CONNECTIONS: "{{ lookup('config', 'svc-prx-openresty', 'services.openresty.worker_connections') | int }}"
WEBSERVER_PROXY_WORKER_RLIMIT_NOFILE: "{{ (WEBSERVER_PROXY_WORKER_CONNECTIONS | int) * 2 }}"
# Response security headers of every vhost; an app overrides any key under `security_headers` in its meta/server.yml.
WEBSERVER_SECURITY_HEADERS:
nosniff: false # X-Content-Type-Options: nosniff
referrer_policy: "" # Referrer-Policy, sent only when the upstream sends none; "" sends none
permissions_policy: "" # Permissions-Policy on HTML documents; "" sends none
hsts:
max_age: 31536000 # Strict-Transport-Security max-age in seconds
include_subdomains: false # adds includeSubDomains, which covers every subdomain of the vhost
preload: false # adds preload; requires include_subdomains and a max_age of one year