使用 Nginx 和 docker-compose 设置反向代理
Nginx 是一款出色的软件,它允许您轻松地将应用程序包装在反向代理中,然后可以处理与服务器相关的方面,例如 SSL 和缓存,对其背后的应用程序完全透明。
这是从我的个人网站转发的帖子。
介绍
Web 应用程序的某些方面,例如 SSL 加密、请求缓存和服务发现,可以在应用程序本身之外进行管理。像 Nginx 这样的反向代理可以处理其中的许多任务,因此我们作为开发人员无需在软件中考虑这些。
此外,有些软件由于缺乏适当的安全措施,不宜通过互联网访问。许多数据库都是如此。一般来说,不将不必要的内部服务公开是一种良好做法。
所有这些都可以通过 docker-compose 和 Nginx 实现。
docker-compose
docker-compose是一个简洁的小工具,可以让你定义一系列需要同时启动的 docker 容器,以及它们启动时的配置。这些配置包括导出的端口、容器所属的网络、映射到容器的卷、环境变量以及所有其他可以通过命令配置的内容docker run
。
在本节中,我将简要解释如何配置本文中使用的 docker-compose 功能。更多详细信息,请参阅文档。
主入口点是一个docker-compose.yml
文件。它配置了需要一起启动的容器的各个方面。
以下是一个例子docker-compose.yml
:
version: '3'
services:
nginx:
image: nginx:latest
container_name: production_nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- 80:80
- 443:443
ismydependencysafe:
image: ismydependencysafe:latest
container_name: production_ismydependencysafe
expose:
- "80"
如您所见,指定了两个镜像。
第一个nginx
镜像名为production_nginx
。它指定了一个卷,用于替换默认的 Nginx 配置文件。此外,它还定义了主机端口 80 和 443 到容器端口 80 和 443 的映射。
第二个镜像是我自己创建的,它暴露了 80 端口。与ports
配置的不同之处在于,它们不会发布到主机。这就是为什么它也可以指定 80 端口,尽管nginx
之前已经指定了。
本文还使用了一些其他配置选项,特别是网络、卷和环境变量。
网络
通过网络,可以指定哪些容器可以相互通信。它们被指定为新的根配置条目,并包含在容器配置中。
version: '3'
services:
nginx:
...
networks:
- my-network-name
ismydependencysafe:
...
networks:
- my-network-name
networks:
my-network-name:
在根对象中networks
,定义了网络my-network-name
。每个容器都通过将它添加到列表中来分配到该网络network
。
如果未指定网络,则所有容器都位于同一个网络中,该网络是默认创建的。因此,如果仅使用一个网络,则无需指定任何网络。
网络的一个便捷特性是,同一个网络中的容器可以通过名称相互引用。在上面的例子中,urlhttp://ismydependencysafe
将解析为 container ismydependencysafe
。
卷
卷定义了 Docker 容器的持久存储。如果应用程序在未定义卷的地方写入数据,容器停止时该数据将会丢失。
卷有两种类型。一种是将文件或目录映射到容器内部;另一种是仅将文件或目录持久化(称为卷),但不使其在文件系统上可访问(当然它们在某个地方,但这是 Docker 实现特定的,不应干预)。
第一种类型是将特定文件或目录映射到容器中的卷,我们已经在上面的示例中见过。这里再次介绍一下,其中还有一个附加卷,它以相同的方式指定目录:
version: '3'
services:
nginx:
image: nginx:latest
container_name: production_nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- /etc/letsencrypt/:/etc/letsencrypt/
...
命名卷的指定方式与网络类似,作为单独的根配置条目并直接在容器配置上。
version: '3'
services:
nginx:
...
volumes:
- "certificates:/etc/letsencrypt/"
...
volumes:
certificates:
...
环境变量
Docker 还可以为容器中的应用程序指定环境变量。在 Compose 配置中,有多种方法可以做到这一点:要么指定包含它们的文件,要么直接在 中声明它们docker-compose.yml
。
version: '3'
services:
nginx:
...
env_file:
- ./common.env
environment:
- ENV=development
- APPLICATION_URL=http://ismydependencysafe
...
如您所见,这两种方法可以同时使用。但请注意,设置的变量environment
会覆盖从文件加载的变量。
环境文件必须具有以下格式VAR=VAL
,每行一个变量。
ENV=production
APPLICATION_URL=http://ismydependencysafe
命令行界面
启动和停止容器的命令非常简单。
要启动,请使用docker-compose up -d
。
指定-d
它应在后台启动。如果没有它,容器将在命令行关闭时停止。
停止使用docker-compose down
。
这两个命令都会在当前目录中查找docker-compose.yml
文件。如果文件位于其他目录,请使用 指定-f path/to/docker-compose.yml
。
现在 docker-compose 的基础知识已经清楚了,让我们继续讨论 Nginx。
Nginx
Nginx 是一款功能丰富的 Web 服务器,其中包括本文所介绍的反向代理功能。
它配置了一个nginx.conf
。默认情况下,它会在 中查找/etc/nginx/nginx.conf
,但当然也可以指定其他文件。
作为反向代理,它可以透明地处理 Web 应用程序的两个非常重要的方面:加密和缓存。但在详细介绍之前,让我们先看看反向代理功能本身是如何配置的:
http {
server {
server_name your.server.url;
location /yourService1 {
proxy_pass http://localhost:80;
rewrite ^/yourService1(.*)$ $1 break;
}
location /yourService2 {
proxy_pass http://localhost:5000;
rewrite ^/yourService1(.*)$ $1 break;
}
}
server {
server_name another.server.url;
location /yourService1 {
proxy_pass http://localhost:80;
rewrite ^/yourService1(.*)$ $1 break;
}
location /yourService3 {
proxy_pass http://localhost:5001;
rewrite ^/yourService1(.*)$ $1 break;
}
}
}
Nginx 配置以contexts为单位,contexts 定义了它们处理的流量类型。context http
(显然)处理 http 流量。其他 contexts 包括mail
和stream
。
该server
配置指定了一个虚拟服务器,每个虚拟服务器可以拥有自己的规则。该server_name
指令定义了虚拟服务器响应哪些 URL 或 IP 地址。
该location
配置定义了传入流量的路由目标。根据 URL,请求可以传递到某个服务。在上面的配置中,路由的开头指定了服务。proxy_pass
设置新的 URL,并rewrite
重写 URL 以使其适合该服务。在本例中,yourService{x}
将从 URL 中删除 。
这是一个概述,后面的部分将解释如何配置缓存和 SSL。
有关更多详细信息,请查看文档。
现在我们已经了解了各个部分,让我们开始将它们组合在一起。
在 Docker 中将 Nginx 设置为反向代理
对于基本设置,只需要 3 件事:
1)将主机端口映射到容器端口
2)将配置文件映射到默认 Nginx 配置文件/etc/nginx/nginx.conf
3)Nginx 配置
在 docker-compose 文件中,可以使用配置条目完成端口映射ports
,正如我们上面看到的。
...
ports:
- 80:80
- 443:443
...
Nginx 配置的映射是通过卷完成的,我们之前也见过:
...
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
...
docker-compse.yml
Nginx 配置被认为与( )位于同一目录中./nginx.conf
,但当然可以位于任何位置。
缓存配置
在设置中添加缓存非常简单,只需更改 Nginx 配置即可。
在http
上下文中添加一条proxy_cache_path
指令,定义缓存内容的本地文件系统路径以及内存区域的名称和大小。
但请记住,该路径位于容器内部,而不是主机的文件系统上。
http {
...
proxy_cache_path /data/nginx/cache keys_zone=one:10m;
}
在应缓存响应的上下文中,添加server
指定内存区域的指令。location
proxy_cache
...
server {
proxy_cache one;
...
使用默认缓存配置定义缓存就足够了。还有很多其他指令可以更详细地指定要缓存哪些响应。有关这些指令的更多详细信息,请参阅文档。
使用 SSL 保护 HTTP 流量
至此,服务器设置已完成。docker-compose 启动所有容器,Nginx 容器充当服务的反向代理。只剩下一件事需要设置,正如这个网站解释得非常清楚的那样,那就是加密。
要安装 certbot(从 Let's Encrypt 获取证书的客户端),请按照安装说明进行操作。
使用 certbot 生成 SSL 证书
certbot 提供多种获取 SSL 证书的方法。其中包括适用于 Apache 和 Nginx 等主流 Web 服务器的插件,以及使用独立 Web 服务器验证域名的方法,当然还有手动获取证书的方法。
我们将使用该standalone
插件。它会启动一个单独的 Web 服务器来处理证书验证,这意味着 80 或 443 端口必须可用。为了使其正常工作,必须关闭 Nginx Web 服务器,因为它绑定了这两个端口,并且 certbot 服务器需要能够接受至少一个端口上的入站连接。
要创建证书,请执行
certbot --standalone -d your.server.url
-d
并按照说明操作。您还可以通过添加更多参数(例如-d your.server1.url
-d your.server2.url
)来一次为多个 URL 创建证书。
自动更新证书
Let's Encrypt CA 颁发的证书是短期证书,有效期仅为 90 天。因此,自动续订过程至关重要。幸好,certbot 使用命令 轻松实现了这一点certbot renew
。它会检查所有已安装的证书,并续订那些将在 30 天内过期的证书。
续订时将使用与最初获取证书时相同的插件。在我们的例子中,就是这个standalone
插件。
验证流程相同,因此续订时 80 或 443 端口也必须处于空闲状态。certbot
提供了预处理和后处理钩子,用于在续订期间停止和启动 Web 服务器,以释放端口。
这些钩子仅在证书需要续订时执行,因此不会造成不必要的服务停机。
由于我们使用的是docker-compose
,因此整个命令如下所示:
certbot renew --pre-hook "docker-compose -f path/to/docker-compose.yml down" --post-hook "docker-compose -f path/to/docker-compose.yml up -d"
要完成自动化,只需将上一个命令添加为 cronjob。
使用 打开 cron 文件crontab -e
。
在其中添加一行
@daily certbot renew --pre-hook "docker-compose -f path/to/docker-compose.yml down" --post-hook "docker-compose -f path/to/docker-compose.yml up -d"
就是这样。现在续订命令每天都会执行,您不必再担心证书的到期日期了。
在 Nginx Docker 容器中使用证书
到目前为止,证书已经请求并存储在服务器上,但我们尚未使用它们。为此,我们必须
1)使证书可用于 Nginx 容器
2)更改配置以使用它们
要使证书可用于 Nginx 容器,只需将整个letsencrypt
目录指定为其上的卷。
...
nginx:
image: nginx:latest
container_name: production_nginx
volumes:
- /etc/letsencrypt/:/etc/letsencrypt/
...
调整配置并使其更安全需要更多工作。
默认情况下,虚拟服务器监听端口 80,但使用 SSL 时,它还应该监听端口 443。这需要通过两个listen
指令来指定。
此外,还必须定义证书。这可以通过ssl_certificate
和ssl_certificate_key
指令来完成。
...
server {
...
listen 80;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/your.server.url/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.server.url/privkey.pem;
}
...
这些小改动足以配置 nginx 的 SSL 连接。
它使用 Nginx 的默认 SSL 设置,虽然还算可以,但还可以改进。
提高 Nginx 配置的安全性
在本节开头我应该提到,如果您使用最新版本的 nginx,其默认 SSL 设置是安全的。无需定义协议、密码和其他参数。
话虽如此,我们仍然可以使用一些 SSL 指令来进一步提升安全性。
但请记住,设置这些指令后,您有责任自行更新它们。Nginx 对默认配置设置的更改不会对您造成影响,因为您只是覆盖了它们。
首先,设置
ssl_protocols TLSv1.1 TLSv1.2;
这将禁用所有 SSL 协议和 TLSv1.0,因为它们被认为是不安全的(TLSv1.0、SSLv3、SSLv2)。在撰写本文时(2018 年 7 月),TLSv1.1 和 TLSv1.2 被认为是安全的,但没有人能保证它们将来不会被破坏。
接下来,设置
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DHE+AES128:!ADH:!AECDH:!MD5;
密码定义了加密的方式。这些值是从这篇文章中复制过来的,因为我不是这方面的专家。
这些是最重要的设置。为了进一步提高安全性,请遵循以下文章:
您可以使用 SSL Labs 提供的优秀网站检查您的 SSL 配置的安全性。
包起来
在本文中,我们介绍了如何设置 docker-compose、如何使用其网络和卷功能、如何设置环境变量、如何使用 Nginx 作为反向代理,以及缓存和 SSL 安全。托管项目所需的一切。
请记住,这不是一个非常专业的设置,任何重要的服务都需要更复杂的设置,但对于小型项目或副项目来说,这是完全没问题的。
修正案
以下是生成的nginx.conf
文件docker-compose.yml
。它们包含应用程序的占位符名称、URL 和路径。
docker-compose.yml
version: '3'
services:
nginx:
image: nginx:latest
container_name: production_nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./nginx/error.log:/etc/nginx/error_log.log
- ./nginx/cache/:/etc/nginx/cache
- /etc/letsencrypt/:/etc/letsencrypt/
ports:
- 80:80
- 443:443
your_app_1:
image: your_app_1_image:latest
container_name: your_app_1
expose:
- "80"
your_app_2:
image: your_app_2_image:latest
container_name: your_app_2
expose:
- "80"
your_app_3:
image: your_app_3_image:latest
container_name: your_app_3
expose:
- "80"
nginx.conf
events {
}
http {
error_log /etc/nginx/error_log.log warn;
client_max_body_size 20m;
proxy_cache_path /etc/nginx/cache keys_zone=one:500m max_size=1000m;
server {
server_name server1.your.domain;
location /your_app_1 {
proxy_pass http://your_app_1:80;
rewrite ^/your_app_1(.*)$ $1 break;
}
location /your_app_2 {
proxy_pass http://your_app_2:80;
rewrite ^/your_app_2(.*)$ $1 break;
}
}
server {
server_name server2.your.domain;
proxy_cache one;
proxy_cache_key $request_method$request_uri;
proxy_cache_min_uses 1;
proxy_cache_methods GET;
proxy_cache_valid 200 1y;
location / {
proxy_pass http://your_app_3:80;
rewrite ^/your_app_3(.*)$ $1 break;
}
listen 80;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/server2.your.domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/server2.your.domain/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
}
}
在Twitter上关注我,了解我的更多想法、文章、项目和工作。
鏂囩珷鏉ユ簮锛�https://dev.to/domysee/setting-up-a-reverse-proxy-with-nginx-and-docker-compose-29jg