摘要: 本文介绍如何使用 Nginx-proxy 和 Let’s Encrypt 依托 Docker 和 Docker Compose 部署 Nexus Repository。并且 Nexus Repository 支持 Docker 的 Connector。通过本文可以快速部署一台 Nexus Private Repository,并且提供如下支持:SSL注册及更新,Docker Connector支持等。
Introduction
阅读本文前,您需要了解 Docker, Docker Compose 以及 Nginx 的基本知识。
传统部署方法:如果熟悉服务器部署,我们一般的部署方式,首先运行服务,然后将服务端口暴露出来,通过端口访问测试服务,然后安装 nginx 并配置 nginx reverse proxy 将指定的域名或路径 proxy 到指定的端口。如果需要 SSL 支持,需要自行使用 cerbot 来注册 let‘s encrypt 证书,然后在nginx 中配置。同时 3 个月后需要手动 renew 证书。
我可以看到整个过程比较繁琐,而且很难使用自动化脚本执行。下面介绍一下新的思路:基于 Docker 以及 nginx-proxy 部署方法。
首先所有的服务全部由 Docker 部署,这样不需要安装,只需要关注配置。其次我们使用 nginx-proxy 来实现 reverse proxy 自动化配置,然后我们使用 docker-letsencrypt-nginx-proxy-companion 实现 Let‘s Encrypt,最后我们使用 Docker Compose 来编排管理所有服务。
具体架构如下:
Preparation
- 一台安装有 Docker 和 Docker Compose 的 VPS,其中 80 和 443 需要允许访问
- 一个域名,并且域名 DNS 解析到 VPS 的 IP
假设我们的域名为: mengxin.science
VPS IP 为: 192.168.1.111
我们DNS增加如下记录:
- 根域名: @ 192.168.1.111
- test子域名:test 192.168.1.111
- nexus子域名:nexus 192.168.1.111
Nginx-proxy & Let’s Encrypt
在 VPS 上,首先我们需要通过 Docker Compose 部署环境,已经有人分享了 compose 的编排配置:docker-compose-letsencrypt-nginx-proxy-companion。所以根据其 README 可以快速的部署一套环境:
- docker-gen: 负责生成配置文件
- nginx-proxy: 负责 Nginx 反向代理
- letsencrypt-nginx-proxy-companion:负责注册证书
docker-gen
It can be used to generate various kinds of files for:
- Centralized logging - fluentd, logstash or other centralized logging tools that tail the containers JSON log file or files within the container.
- Log Rotation - logrotate files to rotate container JSON log files
- Reverse Proxy Configs - nginx, haproxy, etc. reverse proxy configs to route requests from the host to containers
- Service Discovery - Scripts (python, bash, etc..) to register containers within etcd, hipache, etc..
nginx-proxy
这里实际上并没有使用 nginx-proxy docker 镜像,而是使用的 nginx 的 template。
letsencrypt-nginx-proxy-companion
Nexus Compose
有了这个基础环境,我们部署一个应用就非常简单,这里我们部署一个 nexus repository 服务器,这个例子相对较为复杂,所以通过这个例子我们可以在现有基础上进行自定义。
需求
- 首先我们需要部署一个nexus 服务器,域名为 nexus.mengxin.science,内部端口默认 8081
- 我们需要支持 docker 的连接,内部 connector 端口设置为 8082
Docker Compose 编排
1 | version: "3" |
这里有几个关键点:
外部网络设置
如果我们希望这个应用使用我们的环境部署,就需要加入到其网络中,所以我们使用外部网络指定配置来配置网络为环境网络,其名称这里是默认,如果你修改了网络名,这里也要相对应的更改。具体配置参考: https://docs.docker.com/compose/networking/#use-a-pre-existing-network
1 | networks: |
VIRTUAL_HOST & VIRTUAL_PORT
1 | - VIRTUAL_HOST=nexus.mengxin.science |
这两个配置是使用 nginx 自动 proxy 应用的关键,通过这两个配置,我们会发现先一旦应用启动后, nginx-web 的配置文件会更新为:
1 | # nexus.mengxin.science |
这里会增加一个 upstream nexus.mengxin.science
然后将 location /
proxy 到 http://nexus.mengxin.science;
。 这里需要了解一下基本的 Nginx 知识。实际就是实现,当我们访问 nexus.mengxin.science
,nginx 首先找到: server
配置为 server_name nexus.mengxin.science;
的配置项 (这里我们先忽略 SSL),然后根据配置将请求 proxy 到指定的 8081 端口,从而将请求送达 nexus repository service。
VIRTUAL_PORT
默认是 80, 如果不设置,默认 proxy 到 80,在 nginx 中也不需要显示的配置。
LETSENCRYPT_HOST & LETSENCRYPT_EMAIL
1 | - LETSENCRYPT_HOST=nexus.mengxin.science |
这两个配置使用用来获取 SSL 证书,并且生成 nginx 的 SSL 相关配置。我们可以在上面的生成的 nginx 的配置中看到,80 端口的访问直接重定向到 443 端口了。443 端口配置了证书等信息。
自定义 VHOST NGINX 配置
我们需求中有一条是支持 docker,但是在 nexus repository 中,docker 有独立的端口,所以目前的配置不足以完成 docker 请求的 proxy。所以我们需要自定义。
我们可以参考 nginx-proxy 的文档 per-virtual_host
部分: https://github.com/jwilder/nginx-proxy#per-virtual_host
原理很简单,因为在自动生成的 ngxin 配置中有一句配置:
1 | include /etc/nginx/vhost.d/nexus.mengxin.science; |
如果我们观察 nginx-data
目录 (这是我们配置环境的时候将 ngxin 的配置映射出来的目录,里面包含所有的 nginx 的配置),其中有一个 vhost.d 的子目录,这个子目录中默认只有一个 default
的文件,如果我们保持这个目录为 default,上面的配置就会是:
1 | include /etc/nginx/vhost.d/default; |
所以我们想自定义 nginx 配置有两个途径
- 修改 default,所有的没有单独配置 vhost 的 server 配置都会更改
- 添加一个和 VHOST 名字相同的文件在 vhost.d 中,这样生成的 ngxin 的配置文件就是默认 include 这个文件,这样我们就实现了为每个服务单独配置。
这里我们只需要对 nexus.mengxin.science
进行配置,所以我们添加 nexus.mengxin.science
到 vhost.d
文件中。具体配置如下:
1 | ## Start of configuration add by letsencrypt container |
这个配置是参考了一篇文章:
Setting up a Docker Private Registry with Authentication Using Nexus and Nginx
配置思路就是如果访问来自 docker (使用 agent 判断),那么服务就 proxy 到 nexus docker connector 的端口 (8082),否则就到服务端口 8081。另外为了访问和测试 docker v2 api,我们把 v2 路径也 proxy 到 8082,当然如果有其他的服务使用 v2 路径,这个配置需要修改,目前没有在 nexus 中发现这个路径。
其中 nexus3
是我们在 前面 docker-compose 配置中配置的 service 名称,在 Docker 同一个网络中,可以用来访问指定服务,原理就像我们在内网中每台电脑都有一个主机名,可以用来代替 IP 访问。
这里还需要注意 Nginx 配置路径的问题,因为顶层的配置已经包含了 /
路径,所以不能再使用想用的配置了。我们需要简单的了解一下 nginx 配置路径的方法和优先级。
最后附一张 Nexus Docker Connector 的配置,我们只需要 http 端口即可
Ref: Nginx
location [ = | ~ | ~* | ^~ ] uri { … }
location @name { … }
语法规则很简单,一个location关键字,后面跟着可选的修饰符,后面是要匹配的字符,花括号中是要执行的操作。
修饰符
- = 表示精确匹配。只有请求的url路径与后面的字符串完全相等时,才会命中。
- ~ 表示该规则是使用正则定义的,区分大小写。
- ~* 表示该规则是使用正则定义的,不区分大小写。
- ^~ 表示如果该符号后面的字符是最佳匹配,采用该规则,不再进行后续的查找。
匹配过程
对请求的url序列化。例如,对%xx等字符进行解码,去除url中多个相连的/,解析url中的.,..等。这一步是匹配的前置工作。
location有两种表示形式,一种是使用前缀字符,一种是使用正则。如果是正则的话,前面有~或~*修饰符。
具体的匹配过程如下:
首先先检查使用前缀字符定义的location,选择最长匹配的项并记录下来。
如果找到了精确匹配的location,也就是使用了=修饰符的location,结束查找,使用它的配置。
然后按顺序查找使用正则定义的location,如果匹配则停止查找,使用它定义的配置。
如果没有匹配的正则location,则使用前面记录的最长匹配前缀字符location。
基于以上的匹配过程,我们可以得到以下两点启示:
使用正则定义的location在配置文件中出现的顺序很重要。因为找到第一个匹配的正则后,查找就停止了,后面定义的正则就是再匹配也没有机会了。
使用精确匹配可以提高查找的速度。例如经常请求/的话,可以使用=来定义location。
示例
接下来我们以一个例子来具体说明一下匹配过程。
假如我们有下面的一段配置文件:
1 | location = / { |
请求/精准匹配A,不再往下查找。
请求/index.html匹配B。首先查找匹配的前缀字符,找到最长匹配是配置B,接着又按照顺序查找匹配的正则。结果没有找到,因此使用先前标记的最长匹配,即配置B。
请求/user/index.html匹配C。首先找到最长匹配C,由于后面没有匹配的正则,所以使用最长匹配C。
请求/user/1.jpg匹配E。首先进行前缀字符的查找,找到最长匹配项C,继续进行正则查找,找到匹配项E。因此使用E。
请求/images/1.jpg匹配D。首先进行前缀字符的查找,找到最长匹配D。但是,特殊的是它使用了^~修饰符,不再进行接下来的正则的匹配查找,因此使用D。这里,如果没有前面的修饰符,其实最终的匹配是E。大家可以想一想为什么。
请求/documents/about.html匹配B。因为B表示任何以/开头的URL都匹配。在上面的配置中,只有B能满足,所以匹配B。
location @name的用法
@用来定义一个命名location。主要用于内部重定向,不能用来处理正常的请求。其用法如下:1
2
3
4
5
6
7location / {
try_files $uri $uri/ @custom
}
location @custom {
# ...do something
}
上例中,当尝试访问url找不到对应的文件就重定向到我们自定义的命名location(此处为custom)。
值得注意的是,命名location中不能再嵌套其它的命名location。
URL尾部的/需不需要
关于URL尾部的/有三点也需要说明一下。第一点与location配置有关,其他两点无关。
location中的字符有没有/都没有影响。也就是说/user/和/user是一样的。
总结
location的配置有两种形式,前缀字符和正则。查找匹配的时候,先查找前缀字符,选择最长匹配项,再查找正则。正则的优先级高于前缀字符。
正则的查找是按照在配置文件中的顺序进行的。因此正则的顺序很重要,建议越精细的放的越靠前。
使用=精准匹配可以加快查找的顺序,如果根域名经常被访问的话建议使用=。