Самодостаточность через экспорт сервисов посредством привязки портов
Экспортируйте сервисы посредством привязки портов
Port Binding — седьмой фактор 12-Factor App. Принцип гласит:
Приложение должно быть самодостаточным и экспортировать свои сервисы через привязку к порту, а не полагаться на внешний веб-сервер.
# ❌ Неправильно: приложение зависит от внешнего веб-сервера
# app.wsgi для Apache mod_wsgi
# Требуется конфигурация Apache/Nginx для запуска
# ✅ Правильно: приложение само запускает веб-сервер
from flask import Flask
app = Flask(__name__)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) # Привязка к портуТрадиционный подход (до 12-Factor):
┌─────────────────┐ ┌──────────────┐ ┌─────────────┐
│ Nginx/Apache │────▶│ mod_wsgi/ │────▶│ Приложение │
│ (веб-сервер) │ │ PHP-FPM │ │ (код) │
└─────────────────┘ └──────────────┘ └─────────────┘
Проблемы:
┌─────────────────────────────────┐
│ Приложение + веб-сервер │
│ (Gunicorn, uWSGI, Tornado) │
│ Привязка к порту 5000 │
└─────────────────────────────────┘
│
▼
Порт 5000
Преимущества:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
if __name__ == '__main__':
# ❌ Только для разработки!
app.run(host='0.0.0.0', port=5000, debug=True)# Запуск
python app.py
# Доступ
curl http://localhost:5000Важно: Встроенный сервер Flask не предназначен для production!
# Установка
pip install gunicorn# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
# Не нужно указывать host/port в коде!
# Gunicorn управляет запуском# Запуск с Gunicorn
gunicorn --bind 0.0.0.0:5000 app:app
# С указанием воркеров
gunicorn --bind 0.0.0.0:5000 --workers 4 app:app
# С указанием типа воркера
gunicorn --bind 0.0.0.0:5000 --workers 4 --worker-class sync app:app# Установка
pip install uvicorn[standard]# app.py (FastAPI или Starlette)
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def hello():
return {'message': 'Hello, World!'}# Запуск с Uvicorn
uvicorn app:app --host 0.0.0.0 --port 5000
# С reload для разработки
uvicorn app:app --host 0.0.0.0 --port 5000 --reload
# С несколькими воркерами
uvicorn app:app --host 0.0.0.0 --port 5000 --workers 4# Gunicorn как процесс-менеджер, Uvicorn как воркер
gunicorn app:app \
--bind 0.0.0.0:5000 \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorkerimport os
from flask import Flask
app = Flask(__name__)
# Чтение порта из переменной окружения
port = int(os.environ.get('PORT', 5000))
host = os.environ.get('HOST', '0.0.0.0')
if __name__ == '__main__':
app.run(host=host, port=port)# Запуск с разным портом
PORT=8080 python app.py
# В Docker
docker run -e PORT=8080 -p 8080:8080 myapp# gunicorn_config.py
import multiprocessing
# Привязка к порту
bind = '0.0.0.0:5000'
# Количество воркеров
workers = multiprocessing.cpu_count() * 2 + 1
# Тип воркера
worker_class = 'sync'
# Таймаут
timeout = 30
# Логирование
accesslog = '-' # stdout
errorlog = '-' # stderr
loglevel = 'info'# Запуск с конфигом
gunicorn --config gunicorn_config.py app:appFROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir gunicorn flask
COPY . .
# Приложение экспортирует порт 5000
EXPOSE 5000
# Gunicorn привязывается к 0.0.0.0:5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]version: '3.8'
services:
web:
build: .
ports:
- "5000:5000" # Хост:Контейнер
environment:
- PORT=5000
restart: unless-stopped
# Несколько экземпляров для масштабирования
web-replica:
build: .
ports:
- "5001:5000" # Разные порты на хосте
environment:
- PORT=5000
depends_on:
- web# Запуск
docker-compose up
# Масштабирование
docker-compose up --scale web=3# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: web
image: myapp:latest
ports:
- containerPort: 5000 # Порт внутри контейнера
env:
- name: PORT
value: "5000"# service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80 # Порт сервиса
targetPort: 5000 # Порт контейнера
type: ClusterIP # Внутренний сервис# ingress.yaml (внешний доступ)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80# Привязка ко всем IPv4 интерфейсам
gunicorn --bind 0.0.0.0:5000 app:app
# Привязка к конкретному IP
gunicorn --bind 192.168.1.100:5000 app:app
# Привязка к localhost (только локальный доступ)
gunicorn --bind 127.0.0.1:5000 app:app# Привязка ко всем IPv6 интерфейсам
gunicorn --bind [::]:5000 app:app
# Привязка к localhost IPv6
gunicorn --bind [::1]:5000 app:app# Привязка к Unix-сокету
gunicorn --bind unix:/var/run/myapp.sock app:app
# С указанием прав
gunicorn --bind unix:/var/run/myapp.sock --umask 007 app:app# Docker Compose с Unix-сокетом
services:
web:
image: myapp
volumes:
- /var/run/myapp:/var/run
command: gunicorn --bind unix:/var/run/myapp.sock app:app
nginx:
image: nginx
volumes:
- /var/run/myapp:/var/run
# Nginx подключается к сокету┌─────────────────┐
│ Приложение │
│ Порт 5000 │
└────────┬────────┘
│
▼
Интернет
Использование: Development, внутренние сервисы
┌─────────────┐ ┌─────────────────┐
│ Nginx │────▶│ Приложение │
│ Порт 80 │ │ Порт 5000 │
└─────────────┘ └─────────────────┘
Использование: Production с SSL, кэшированием
┌─────────────┐
│ Load │
│ Balancer │
│ Порт 443 │
└──────┬──────┘
│
┌───┴───┬───┬───┐
▼ ▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│ App │ │ App │ │ App │
│:5000│ │:5000│ │:5000│
└─────┘ └─────┘ └─────┘
Использование: Масштабирование, высокая доступность
# ❌ Неправильно: порт зашит в коде
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000) # Только localhost!Проблема: Невозможно запустить в Docker с другим портом.
# ❌ Неправильно: недоступно извне контейнера
app.run(host='127.0.0.1', port=5000)Проблема: В Docker контейнер изолирован, 127.0.0.1 недоступен снаружи.
Решение:
# ✅ Правильно
app.run(host='0.0.0.0', port=5000)# ❌ Неправильно: требуется Apache с mod_wsgi
<VirtualHost *:80>
WSGIDaemonProcess myapp python-path=/app
WSGIProcessGroup myapp
WSGIScriptAlias / /app/wsgi.py
</VirtualHost>Проблема: Приложение не самодостаточно, требует настройки Apache.
# ❌ Конфликт портов
gunicorn --bind 0.0.0.0:5000 app1:app
gunicorn --bind 0.0.0.0:5000 app2:app # Ошибка: порт занят!Решение: Разные порты или Unix-сокеты.
# Linux
ss -tlnp | grep 5000
netstat -tlnp | grep 5000
# macOS
lsof -i :5000
# В Docker
docker exec <container> ss -tlnp# Локальная проверка
curl http://localhost:5000/health
# Проверка из контейнера
docker exec <container> curl http://localhost:5000/health
# Проверка с хоста
curl http://$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container>):5000/healthfrom flask import Flask, jsonify
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify({'status': 'healthy'}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)gunicorn app:app \
--bind 0.0.0.0:5000 \
--workers 4 \
--worker-class sync \
--worker-connections 1000 \
--timeout 30 \
--keep-alive 5 \
--access-logfile - \
--error-logfile - \
--loglevel info \
--pid /var/run/gunicorn.pid \
--graceful-timeout 30# /etc/nginx/nginx.conf
upstream myapp {
server 127.0.0.1:5000;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
server {
listen 80;
server_name myapp.example.com;
location / {
proxy_pass http://myapp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /health {
access_log off;
return 200 'healthy';
add_header Content-Type text/plain;
}
}# /etc/apache2/sites-available/myapp.conf
<VirtualHost *:80>
ServerName myapp.example.com
WSGIDaemonProcess myapp \
python-path=/var/www/myapp \
processes=2 \
threads=15
WSGIProcessGroup myapp
WSGIScriptAlias / /var/www/myapp/wsgi.py
<Directory /var/www/myapp>
Require all granted
</Directory>
</VirtualHost>Проблемы:
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from self-contained app!'# Dockerfile
FROM python:3.12
WORKDIR /app
COPY requirements.txt .
RUN pip install gunicorn flask
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
restart: unless-stopped# Запуск одной командой
docker-compose up
# Масштабирование
docker-compose up --scale web=5Результат:
Ключевой вывод: Приложение должно быть самодостаточным и экспортировать свои сервисы через привязку к порту. Это обеспечивает переносимость, упрощает развёртывание и масштабирование в облачных средах.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.