作者:Leonardo Giordani
翻译:老齐
与本文相关书籍推荐:《跟老齐学Python:Django实战》
4 Web server 4.1 基本原理 我们给Web server的一般标签是:用于执行任务的软件,nginx和Apache是两个常用的web server,这两个开源项目目前在市场上处于领先地位,它们使用不同的技术方法,都实现了我们在上一节中讨论的所有特性(以及更多特性)。
4.2 实施 为了测试nginx,又要避免与操作系统中其他软件包冲突,我们可以使用Docker。Docker对于模拟多机环境很有用,对于实际的生产环境,也能选择Docker(例如,AWS ECS与Docker容器配合使用)。
即将运行的基本配置非常简单,一个容器将包含Flask代码并使用Gunicorn运行框架,而另一个容器将运行nginx。Gunicorn将在内部端口8000上提供HTTP,这个端口不会被Docker公开,因此无法从浏览器访问。但是nignx将公开端口80,这是传统的HTTP端口。
在文件wsgi.py
的同一目录中,创建一个Dockerfile
1 2 3 4 5 6 7 8 FROM python:3.6 ADD app /app ADD wsgi.py / WORKDIR . RUN pip install flask gunicorn EXPOSE 8000
从Python Docker开始,添加app
目录和wsgi.py
文件,并安装Gunicorn,然后在同一目录中名为nginx.conf
的文件中为nginx创建一个配置
1 2 3 4 5 6 7 8 server { listen 80; server_name localhost; location / { proxy_pass http://application:8000/; } }
这样就定义了一个服务器,它监听端口80,并将以/
开头的所有URL连接到端口8000上名为application
的服务器,该服务器是运行Gunicorn的容器。
最后,创建一个描述容器配置的文件docker compose.yml
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 version: "3.7" services: application: build: context: . dockerfile: Dockerfile command: gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi expose: - 8000 nginx: image: nginx volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - 8080:80 depends_on: - application
如你所见,我们在nginx配置文件中提到的名称application
不是一个魔法字符串,而是我们在Docker Compose配置中分配给Gunicorn容器的名称。
要创建这个基础设施,我们需要通过pip install Docker Compose
在我们的虚拟环境中安装Docker Compose。我还用项目名创建了一个名为.env
的文件。
1 COMPOSE_PROJECT_NAME=service
此时,你可以使用Docker Compose up -d
运行Docker Compose。
1 2 3 4 $ docker-compose up -d Creating network "service_default" with the default driver Creating service_application_1 ... done Creating service_nginx_1 ... done
如果一切正常,打开浏览器并访问localhost
应该会显示Flask提供的HTML页面。
通过docker compose
日志,我们可以检查服务正在做什么。我们可以在名为application
的服务日志中识别Gunicorn的输出。
1 2 3 4 5 6 7 8 $ docker-compose logs application Attaching to service_application_1 application_1 | [2020-02-14 08:35:42 +0000] [1] [INFO] Starting gunicorn 20.0.4 application_1 | [2020-02-14 08:35:42 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) application_1 | [2020-02-14 08:35:42 +0000] [1] [INFO] Using worker: sync application_1 | [2020-02-14 08:35:42 +0000] [8] [INFO] Booting worker with pid: 8 application_1 | [2020-02-14 08:35:42 +0000] [9] [INFO] Booting worker with pid: 9 application_1 | [2020-02-14 08:35:42 +0000] [10] [INFO] Booting worker with pid: 10
现在我们最感兴趣的是名为nginx
的服务,所以我们使用docker compose logs -f nginx
实时跟踪日志。刷新你用浏览器访问的localhost
页面,容器应该输出如下内容:
1 2 3 $ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | 192.168.192.1 - - [14/Feb/2020:08:42:20 +0000] "GET / HTTP/1.1" 200 13 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0" "-"
这是nginx的标准日志格式。它显示客户机的IP地址(192.168.192.1
)、连接时间戳、HTTP请求和响应状态代码(200),以及客户端的其他信息。
现在让我们增加服务的数量,以查看负载平衡机制的作用。为此,我们首先需要更改nginx的日志格式,以显示对请求做出响应的机器的IP地址。更改“nginx.conf”文件,添加 log_format
和 access_log
选项。
1 2 3 4 5 6 7 8 9 10 11 12 log_format upstreamlog '[$time_local] $host to: $upstream_addr: $request $status'; server { listen 80; server_name localhost; location / { proxy_pass http://application:8000; } access_log /var/log/nginx/access.log upstreamlog; }
变量$upstream_addr
是nginx代理的服务器的IP地址。现在运行docker compose down
停止所有容器,然后通过docker compose up -d --scale application=3
重新启动。
1 2 3 4 5 6 7 8 9 10 11 12 $ docker-compose down Stopping service_nginx_1 ... done Stopping service_application_1 ... done Removing service_nginx_1 ... done Removing service_application_1 ... done Removing network service_default $ docker-compose up -d --scale application=3 Creating network "service_default" with the default driver Creating service_application_1 ... done Creating service_application_2 ... done Creating service_application_3 ... done Creating service_nginx_1 ... done
如你所见,Docker Compose为application
启动了3个容器,如果你打开日期,可以看到如下内容。
1 2 3 $ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | [14/Feb/2020:09:00:16 +0000] localhost to: 192.168.240.4:8000: GET / HTTP/1.1 200
你可以在这里找到to:192.168.240.4:8000
,这是其中一个应用所在容器的IP地址。如果你现在多次访问该页面,应该会注意到上游地址的更改,例如:
1 2 3 4 5 6 7 $ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | [14/Feb/2020:09:00:16 +0000] localhost to: 192.168.240.4:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.3:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.4:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:09:00:17 +0000] localhost to: 192.168.240.2:8000: GET / HTTP/1.1 200
这表明nginx正在执行负载平衡,但说实话,这是通过Docker的DNS进行的,而不是通过web服务器执行的显式操作。我们可以通过访问nginx容器并运行dig application
来验证这一点(你需要运行apt update
和apt install dnsutils
来安装dig
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 root@99c2f348140e:/# dig application ; <<>> DiG 9.11.5-P4-5.1-Debian <<>> application ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7221 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;application. IN A ;; ANSWER SECTION: application. 600 IN A 192.168.240.2 application. 600 IN A 192.168.240.4 application. 600 IN A 192.168.240.3 ;; Query time: 1 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Fri Feb 14 09:57:24 UTC 2020 ;; MSG SIZE rcvd: 110
要查看nginx执行的负载平衡,我们可以显式地定义两个服务并为它们分配不同的权重。运行docker compose down
并将nginx配置更改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 upstream app { server application1:8000 weight=3; server application2:8000; } log_format upstreamlog '[$time_local] $host to: $upstream_addr: $request $status'; server { listen 80; server_name localhost; location / { proxy_pass http://app; } access_log /var/log/nginx/access.log upstreamlog; }
我们在这里定义了一个upstream
结构。它列出了两种不同的服务:application1
和application2
。其中第一种服务的权重为3。这意味着:每4个请求中,有3个请求将被路由到第一种服务,1个被路由到第二种服务。现在nginx不仅仅依赖于DNS,而是有意识地在两种不同的服务之间进行选择。
我们在Docker Compose配置文件中相应地定义服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 version: "3" services: application1: build: context: . dockerfile: Dockerfile command: gunicorn --workers 6 --bind 0.0.0.0:8000 wsgi expose: - 8000 application2: build: context: . dockerfile: Dockerfile command: gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi expose: - 8000 nginx: image: nginx volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - 80:80 depends_on: - application1 - application2
我基本上重复了application
的定义,但是第一种服务现在运行6个工作线,只是为了显示两者之间可能的区别。现在运行docker-compose up -d
和 docker-compose logs -f nginx
。如果多次刷新浏览器上的页面,你将看到如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 $ docker-compose logs -f nginx Attaching to service_nginx_1 nginx_1 | [14/Feb/2020:11:03:25 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:25 +0000] localhost to: 172.18.0.2:8000: GET /favicon.ico HTTP/1.1 404 nginx_1 | [14/Feb/2020:11:03:30 +0000] localhost to: 172.18.0.3:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:31 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:32 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:33 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:33 +0000] localhost to: 172.18.0.3:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:34 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:34 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:35 +0000] localhost to: 172.18.0.2:8000: GET / HTTP/1.1 200 nginx_1 | [14/Feb/2020:11:03:35 +0000] localhost to: 172.18.0.3:8000: GET / HTTP/1.1 200
你可以清楚地看到172.18.0.2(application1)
和172.18.0.3(application2)
之间的负载平衡。
我不会在这里展示反向代理或HTTPS的例子,以免这篇文章过长,你可以在下一节中找到有关这类内容的资源。
4.3 参考资料 这些资源提供了关于本节讨论的主题的更详细的信息。
Docker Compose official documentation
nginx documentation: in particular the sections about log_format and upstream directives
How to configure logging in nginx
How to configure load balancing in nginx
Setting up an HTTPS Server with nginx and how to created self-signed certificates
How to create a reverse proxy with nginx, the documentation of the location directive and some insights on the location choosing algorithms (one of the most complex parts of nginx)
The source code of this example is available here
4.4问题 现在,我们可以说任务完成了。我们在多线程Web框架前面加了一个用于生产的Web服务器,我们可以专注于编写Python代码,而不是处理HTTP头信息。
使用Web服务器允许我们扩展基础设施,只需在其后面添加新实例,而不会中断服务。HTTP并发服务器运行框架的多个实例,框架本身使HTTP抽象化,将其映射到我们的高级语言。
云基础设施 在互联网的早期,公司都要有自己的服务器,而系统管理员则直接在光秃秃的操作系统上运行所有东西,不用说,这是复杂、昂贵和容易失败的。
现在“云”是一个好东西,很多网站都部署到云上,而且也有很多组件供我们使用。
(完毕)
阅读链接 :
剖析Web技术栈(一)
剖析Web技术栈(二)
剖析Web技术栈(三)
原文链接:https://www.thedigitalcatonline.com/blog/2020/02/16/dissecting-a-web-stack/
搜索技术问答的公众号:老齐教室
在公众号中回复:老齐 ,可查看所有文章、书籍、课程。