使用nginx反向代理来服务docker swarm副本的方法

459 阅读2分钟

有时我们需要在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