​ 有Docker以前,我们部署一个软件或者服务,需要考虑不同平台的兼容性,需要考虑是不是和服务器已经安装的软件冲突。自从Docker的出现,在很大程度上解决了这些问题。只要软件提供了对应的Docker镜像,我们只需要简简单单一条命令,就可以迅速的安装和使用这个软件,而且也不用考虑平台的差异性以及和其它软件是否会有冲突。甚至,由于Docker的轻量性,我们可以在一台服务器上同时部署多个软件对外提供服务,只需要小心每个容器映射主机的端口不要冲突就行了。

​ 但是我们也知道,HTTP默认的端口是80,HTTPS默认的端口是443,如果我们想让用户不需要指定端口,只通过不同的域名就可以访问到我们同一个服务器上不同Docker容器运行的服务,就需要在宿主机上安装一个Nginx服务,通过nginx的反向代理来将不同的域名反向代理到不同的服务上。

​ 此外,如果我们想使用HTTPS,则必须拥有域名对应的证书。现在最流行的免费证书是Letsencrypt,虽然说证书的有效期只有三个月,但是可以借助Letsencrypt提供的Certbot工具来实现快到期自动续签,不需要担心证书会失效。

​ Docker的一个强大之处是其及其丰富的生态系统,不仅有各种各样的服务镜像,还有一些有趣实用的工具镜像。今天介绍的就是其中的nginx-proxy镜像和acme-companion镜像,这两个镜像配合使用,可以实现自动反向代理容器中运行的服务和自动签发证书,可以说是十分的方便和实用了。

启动nginx-proxy

nginx-proxy容器的作用通过设置反向代理,将别的容器中的服务通过80端口或者443端口暴露在公网上。nginx-proxy容器会监视别的容器的启动,一旦发现别的容器里有VIRTUAL_HOST(指定了Host)和VIRTUAL_PORT(指定了容器中服务监听的端口)这两个环境变量,会自动对其添加相应的反向代理配置中。

首先需要启动nginx-proxy容器。在启动之前,先创建/data/nginx目录。也可以修改这个目录为自己需要的目录。创建好目录以后,就可以输入下面命令来启动nginx-proxy了。

1
2
3
4
5
6
7
8
9
sudo docker run --detach \
--name nginx-proxy \
--publish 80:80 \
--publish 443:443 \
--volume /data/nginx/certs:/etc/nginx/certs \
--volume /data/nginx/vhost:/etc/nginx/vhost.d \
--volume /data/nginx/html:/usr/share/nginx/html \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
nginxproxy/nginx-proxy

启动以后,如果我们看到下面的文字,说明IPv4 forwarding是禁用状态,需要先把IPv4 forwarding打开,否则,就无法从公网上访问我们的docker容器内的服务了

1
IPv4 forwarding is disabled. Networking will not work

打开也非常简单,只需要修改/etc/sysctl.conf文件中的net.ipv4.ip_forward,将其值设置为1,然后用下面命令重启网络

1
systemctl restart network

重启网络之后,为了保险起见,最好用sudo docker rm -f nginx-proxy删除掉容器以后,重新启动该容器。

容器启动成功后,可以看到其已经正常运行了

我们可以运行一个服务来简单测试一下nginx-proxy是否像预期那样的工作。

首先,我将grafana.lixf.ink解析到了这台服务器的IP上。对此步有疑问的请参考这里

然后,使用下面命令运行一个grafana。从命令中可以看出,并没有将容器的然和端口映射到宿主机的端口上,只是额外添加了两个环境变量VIRTUAL_HOST和VIRTUAL_PORT来告诉nginx-proxy想使用的域名是grafana.lixf.ink,容器中服务的端口是3000。

1
2
3
4
5
sudo docker run --detach \
--name grafana \
--env "VIRTUAL_HOST=grafana.lixf.ink" \
--env "VIRTUAL_PORT=3000" \
grafana/grafana

容器启动成功后,在浏览器中访问http://grafana.lixf.in即可访问到grafana。

启动acme-companion

acme-companion容器的作用是自动为域名签发证书,而且还会在证书快到有效期之前自动续发新的证书,从而保证证书永不过期。和nginx-proxy容器一样,acme-companion容器会监视别的容器的启动,一旦发现别的容器里有LETSENCRYPT_HOST(指定了要签发证书的域名)和LETSENCRYPT_EMAIL(指定了邮箱)这两个环境变量,就会为指定的域名签发对应的证书。签发了证书之后,acme-companion容器会自动修改nginx-proxy中相应的配置,使HTTPS和证书生效。

要注意的是,和nginx-proxy不同的是,nginx-proxy不一定非要在公网环境下才能使用,而acme-companion是必须在公网环境中才能使用的。一方面原因是letsencrypt服务器需要通过访问域名来验证域名所有权,另一个更直接的原因是acme-companion无法访问到letsencrypt服务器,就更不用说签发证书了。

好了,啰嗦了这么多,其实运行命令只有下面一条

1
2
3
4
5
6
7
sudo docker run --detach \
--name nginx-proxy-acme \
--volumes-from nginx-proxy \
--volume /var/run/docker.sock:/var/run/docker.sock:ro \
--volume acme:/etc/acme.sh \
--env "DEFAULT_EMAIL=326256365@qq.com" \
nginxproxy/acme-companion

这时候执行docker ps,可以看到我们nginx-proxy和acme-companion在运行了

我们可以运行一个服务来简单测试一下acme-companion是否像预期那样的工作。这里我同时启动grafana和wordpress来进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
docker run --detach \
--name grafana \
--env "VIRTUAL_HOST=grafana.lixf.ink" \
--env "VIRTUAL_PORT=3000" \
--env "LETSENCRYPT_HOST=grafana.lixf.ink" \
grafana/grafana

docker run --detach \
--name wordpress \
--env "VIRTUAL_HOST=blog.lixf.ink" \
--env "VIRTUAL_PORT=80" \
--env "LETSENCRYPT_HOST=blog.lixf.ink" \
wordpress

等待两个容器启动成功后,我们可以分别在浏览器中访问grafana.lixf.ink和blog.lixf.ink,可以看到,连个地址都是HTTPS协议了。