有时我们需要在nginx反向代理后面为后端服务器提供服务。例如,当我们想为Djnago或Flask应用程序提供服务时。在这个例子中,我想展示用nginx来做这件事是多么容易。
我们将从一个假的Flask应用程序开始。
from flask import Flask
from datetime import datetime
app = Flask(__name__)
@app.get("/")
def home():
now = datetime.now()
return f'Hello {now}'
我们的想法是使用nginx的反向代理来服务该应用程序。我们可以这样配置nginx来完成。
upstream loadbalancer {
server backend:5000;
}
server {
server_tokens off;
client_max_body_size 20M;
location / {
proxy_pass http://loadbalancer;
}
}
最后,我们可以创建docker-compose.yml文件。我们只需要设置副本,反向代理就会发挥其作用。
version: '3.6'
services:
nginx:
image: nginx:production
ports:
- "8080:80"
backend:
image: flask:production
deploy:
replicas: 3
command: gunicorn -w 1 app:app -b 0.0.0.0:5000
正如我们所看到的,我们有3个副本在nginx的反向代理后面。也许这对我们来说已经足够了,但也许我们需要区分不同的副本,比如说在日志中。
(venv) ➜ docker stack services loadbalancer
ID NAME MODE REPLICAS IMAGE PORTS
u5snhg9tysr0 loadbalancer_backend replicated 3/3 flask:production
4w0bf8msdiq6 loadbalancer_nginx replicated 1/1 nginx:production *:8080->80/tcp
我对我们的Flask应用程序做了一点改动。
import logging
from datetime import datetime
import socket
import os
from logging.handlers import TimedRotatingFileHandler
from flask import Flask
handlers = [
logging.StreamHandler()
]
if os.getenv('ENVIRONMENT') == 'production':
slot = os.getenv('SLOT')
log_path = f"./logs/log{os.getenv('SLOT')}.log"
file_handler = TimedRotatingFileHandler(log_path, backupCount=2)
file_handler.setLevel(logging.INFO)
handlers.append(file_handler)
logging.basicConfig(
format=f'%(asctime)s ({socket.gethostname()}) [%(levelname)s] %(message)s',
level='INFO',
handlers=handlers,
datefmt='%d/%m/%Y %X'),
logger = logging.getLogger(__name__)
app = Flask(__name__)
@app.get("/")
def home():
now = datetime.now()
logger.info(f"home {now}")
return f'Hello {now} from {socket.gethostname()}. Slot: {os.getenv("SLOT")}'
当然还有我们的docker-compose.yml文件。
version: '3.6'
services:
nginx:
image: nginx:production
ports:
- "8080:80"
backend:
image: flask:production
hostname: "backend.{{.Task.Slot}}"
environment:
SLOT: "{{.Task.Slot}}"
ENVIRONMENT: production
volumes:
- log:/src/logs
deploy:
replicas: 3
command: gunicorn -c gunicorn.conf.py -w 1 app:app -b 0.0.0.0:5000
volumes:
log:
name: 'log-{{.Task.Slot}}'
现在我们用槽号改变了后端服务的主机名(而不是默认的主机名)。我们还向后端服务传递了一个SLOT环境变量来区分副本,如果我们需要这样做的话。也许你会问自己,我们到底为什么要这样做?答案很简单。处理遗留代码很困难,有时我们需要做一些非常奇怪的事情。
这个例子的源代码在我的github上