docs.pyserve.org/guides/process-orchestration.html
Илья Глазунов 00119ce463
All checks were successful
Deploy to Production / deploy (push) Successful in 5s
Refactor documentation for reverse proxy and routing guides
2025-12-08 01:05:52 +03:00

358 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Process Orchestration - pyserve</title>
<link rel="stylesheet" href="../style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
</head>
<body>
<div id="container">
<div id="header">
<h1>pyserve</h1>
<div class="tagline">async http server</div>
</div>
<div class="breadcrumb">
<a href="../index.html">Home</a> / <a href="index.html">Guides</a> / Process Orchestration
</div>
<div id="content">
<h2>Process Orchestration</h2>
<p>Process Orchestration is pyserve's flagship feature for running multiple Python web
applications with full process isolation. Each application runs in its own subprocess
with independent lifecycle, health monitoring, and automatic restart on failure.</p>
<h3>Overview</h3>
<p>Unlike <a href="asgi-mount.html">ASGI Mounting</a> which runs apps in-process,
Process Orchestration provides:</p>
<ul class="indent">
<li><strong>Process Isolation</strong> — Each app runs in a separate Python process</li>
<li><strong>Health Monitoring</strong> — Automatic health checks with configurable intervals</li>
<li><strong>Auto-restart</strong> — Failed processes restart with exponential backoff</li>
<li><strong>Multi-worker Support</strong> — Configure multiple uvicorn workers per app</li>
<li><strong>Dynamic Port Allocation</strong> — Automatic port assignment (9000-9999)</li>
<li><strong>WSGI Support</strong> — Flask/Django apps via automatic wrapping</li>
<li><strong>Request Tracing</strong> — X-Request-ID propagation through proxied requests</li>
</ul>
<h3>Architecture</h3>
<pre><code class="language-bash">
PyServe Gateway (:8000)
┌────────────────┼────────────────┐
▼ ▼ ▼
FastAPI Flask Starlette
:9001 :9002 :9003
/api/* /admin/* /ws/*
</code></pre>
<p>PyServe acts as a gateway, routing requests to the appropriate subprocess based on URL path.</p>
<h3>Basic Configuration</h3>
<pre><code class="language-yaml">server:
host: 0.0.0.0
port: 8000
extensions:
- type: process_orchestration
config:
apps:
- name: api
path: /api
app_path: myapp.api:app
- name: admin
path: /admin
app_path: myapp.admin:app</code></pre>
<h3>App Configuration Options</h3>
<dl>
<dt>name</dt>
<dd>Unique identifier for the application (required)</dd>
<dt>path</dt>
<dd>URL path prefix for routing requests (required)</dd>
<dt>app_path</dt>
<dd>Python import path. Format: <code>module:attribute</code> (required)</dd>
<dt>app_type</dt>
<dd>Application type: <code>asgi</code> or <code>wsgi</code>. Default: <code>asgi</code></dd>
<dt>workers</dt>
<dd>Number of uvicorn workers. Default: <code>1</code></dd>
<dt>port</dt>
<dd>Fixed port number. Default: auto-allocated from port_range</dd>
<dt>factory</dt>
<dd>If <code>true</code>, app_path points to a factory function. Default: <code>false</code></dd>
<dt>env</dt>
<dd>Environment variables to pass to the subprocess</dd>
<dt>module_path</dt>
<dd>Path to add to <code>sys.path</code> for module resolution</dd>
</dl>
<h3>Health Check Options</h3>
<dl>
<dt>health_check_enabled</dt>
<dd>Enable health monitoring. Default: <code>true</code></dd>
<dt>health_check_path</dt>
<dd>Endpoint to check for health. Default: <code>/health</code></dd>
<dt>health_check_interval</dt>
<dd>Interval between health checks in seconds. Default: <code>10.0</code></dd>
<dt>health_check_timeout</dt>
<dd>Timeout for health check requests. Default: <code>5.0</code></dd>
<dt>health_check_retries</dt>
<dd>Failed checks before restart. Default: <code>3</code></dd>
</dl>
<h3>Restart Options</h3>
<dl>
<dt>max_restart_count</dt>
<dd>Maximum restart attempts before giving up. Default: <code>5</code></dd>
<dt>restart_delay</dt>
<dd>Initial delay between restarts in seconds. Default: <code>1.0</code></dd>
<dt>shutdown_timeout</dt>
<dd>Timeout for graceful shutdown. Default: <code>30.0</code></dd>
</dl>
<h3>Global Configuration</h3>
<pre><code class="language-yaml">extensions:
- type: process_orchestration
config:
port_range: [9000, 9999]
health_check_enabled: true
proxy_timeout: 60.0
logging:
httpx_level: warning
proxy_logs: true
health_check_logs: false
apps:
# ...</code></pre>
<dl>
<dt>port_range</dt>
<dd>Range for dynamic port allocation. Default: <code>[9000, 9999]</code></dd>
<dt>proxy_timeout</dt>
<dd>Timeout for proxied requests in seconds. Default: <code>60.0</code></dd>
<dt>logging.httpx_level</dt>
<dd>Log level for HTTP client (debug/info/warning/error). Default: <code>warning</code></dd>
<dt>logging.proxy_logs</dt>
<dd>Log proxied requests with latency. Default: <code>true</code></dd>
<dt>logging.health_check_logs</dt>
<dd>Log health check results. Default: <code>false</code></dd>
</dl>
<h3>FastAPI Example</h3>
<pre><code class="language-python"># myapp/api.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/users")
async def get_users():
return [{"id": 1, "name": "Alice"}]</code></pre>
<pre><code class="language-yaml">extensions:
- type: process_orchestration
config:
apps:
- name: api
path: /api
app_path: myapp.api:app
workers: 4
health_check_path: /health</code></pre>
<p>Requests to <code>/api/users</code> are proxied to the FastAPI process as <code>/users</code>.</p>
<h3>Flask Example (WSGI)</h3>
<pre><code class="language-python"># myapp/admin.py
from flask import Flask
app = Flask(__name__)
@app.route("/health")
def health():
return {"status": "ok"}
@app.route("/dashboard")
def dashboard():
return {"page": "dashboard"}</code></pre>
<pre><code class="language-yaml">extensions:
- type: process_orchestration
config:
apps:
- name: admin
path: /admin
app_path: myapp.admin:app
app_type: wsgi
workers: 2</code></pre>
<div class="note">
<strong>Note:</strong> WSGI support requires <code>a2wsgi</code> package:
<code>pip install a2wsgi</code>
</div>
<h3>Factory Pattern</h3>
<pre><code class="language-python"># myapp/api.py
from fastapi import FastAPI
def create_app(debug: bool = False) -> FastAPI:
app = FastAPI(debug=debug)
@app.get("/health")
async def health():
return {"status": "ok", "debug": debug}
return app</code></pre>
<pre><code class="language-bash">apps:
- name: api
path: /api
app_path: myapp.api:create_app
factory: true</code></pre>
<h3>Environment Variables</h3>
<p>Pass environment variables to subprocesses:</p>
<pre><code class="language-bash">apps:
- name: api
path: /api
app_path: myapp.api:app
env:
DATABASE_URL: "postgresql://localhost/mydb"
REDIS_URL: "redis://localhost:6379"
DEBUG: "false"</code></pre>
<h3>Multiple Applications</h3>
<pre><code class="language-yaml">extensions:
- type: process_orchestration
config:
port_range: [9000, 9999]
apps:
# FastAPI REST API
- name: api
path: /api
app_path: apps.api:app
workers: 4
# Flask Admin Panel
- name: admin
path: /admin
app_path: apps.admin:app
app_type: wsgi
workers: 2
# Starlette WebSocket Handler
- name: websocket
path: /ws
app_path: apps.websocket:app
workers: 1</code></pre>
<h3>Request Tracing</h3>
<p>PyServe automatically generates and propagates <code>X-Request-ID</code> headers:</p>
<ul class="indent">
<li>If a request has <code>X-Request-ID</code>, it's preserved</li>
<li>Otherwise, a UUID is generated</li>
<li>The ID is passed to subprocesses and included in response headers</li>
<li>All logs include the request ID for tracing</li>
</ul>
<h3>Process Orchestration vs ASGI Mount</h3>
<table class="dirindex">
<tr>
<th>Feature</th>
<th>Process Orchestration</th>
<th>ASGI Mount</th>
</tr>
<tr>
<td>Isolation</td>
<td>Full process isolation</td>
<td>Shared process</td>
</tr>
<tr>
<td>Memory</td>
<td>Separate per app</td>
<td>Shared</td>
</tr>
<tr>
<td>Crash Impact</td>
<td>Only that app restarts</td>
<td>All apps affected</td>
</tr>
<tr>
<td>Health Checks</td>
<td>Yes, with auto-restart</td>
<td>No</td>
</tr>
<tr>
<td>Multi-worker</td>
<td>Yes, per app</td>
<td>No</td>
</tr>
<tr>
<td>Latency</td>
<td>HTTP proxy overhead</td>
<td>In-process (faster)</td>
</tr>
<tr>
<td>Use Case</td>
<td>Production, isolation needed</td>
<td>Development, simple setups</td>
</tr>
</table>
<div class="note">
<strong>When to use Process Orchestration:</strong>
<ul class="indent">
<li>Running multiple apps that shouldn't affect each other</li>
<li>Need automatic restart on failure</li>
<li>Different resource requirements per app</li>
<li>Production deployments</li>
</ul>
<strong>When to use ASGI Mount:</strong>
<ul class="indent">
<li>Development and testing</li>
<li>Simple setups with trusted apps</li>
<li>Minimal latency requirements</li>
</ul>
</div>
<div class="note">
<strong>See Also:</strong>
<ul class="indent">
<li><a href="asgi-mount.html">ASGI Mounting Guide</a> — In-process app mounting</li>
<li><a href="../reference/extensions.html">Extensions Reference</a> — All extension types</li>
<li><a href="configuration.html">Configuration Guide</a> — Full configuration reference</li>
</ul>
</div>
</div>
<div id="footer">
<p>pyserve &copy; 2024-2025 | MIT License</p>
</div>
</div>
</body>
</html>