使用 certbot 配置 docker nginx https
环境:Docker-1.13.1 ,nginx-1.15.12,certbot
给站点上 HTTPS 这件事,单独在物理机上配 nginx 并不难,教程一搜一大把。但当 nginx 跑在 docker 容器里时,证书文件要靠 volumes 挂载进容器,中间就多了一层文件系统的间接性。恰好 certbot 生成的证书文件又是软链接,这两件事撞在一起,就成了一个不查文档很难想明白的坑。这篇把当时的排查过程记下来。
由于现在没有https会被标识不安全网站,我就打算配置一下,去申请免费的Let's Encrypt证书,毕竟也不是很麻烦,但万万没想到给 docker nginx 容器中配置 ssl 证书竟然暗藏玄机。由于我是白手起家,docker文档从未看过,在此处被坑了一波,以后有机会还是有必要慢慢的看一下官方文档。
证书是怎么来的
先说明一下我的情况,利用cartbot申请了免费的域名ssl证书之后,得到的
fullchain.pem,privkey.pem文件是个软链接文件。
这不是偶然,而是 certbot 的固定设计:真实的证书文件存放在 /etc/letsencrypt/archive/<域名>/ 下,每次续期都会生成一组新文件;而 /etc/letsencrypt/live/<域名>/ 下的 fullchain.pem、privkey.pem 只是指向 archive 里最新一版的软链接。这样 nginx 配置里可以固定写 live 路径,续期后不用改配置。在物理机上这套机制很省心,但放到容器里就出问题了。
挂载进容器
再用docker volumes 把ssl配置文件夹挂载到 nginx 容器内部,顺便配置nginx.conf(注意nginx容器的nginx.conf配置文件有点差异 nginx.conf会引入/etc/nginx/conf.d/下的*.conf文件,这个小细节需要注意,还有别忘记映射容器443端口)
站点配置大致长这样:
# 放在 /etc/nginx/conf.d/ 下,会被主配置的 include 引入
server {
listen 443 ssl;
server_name example.com;
# 这里写的是容器内的路径,必须和挂载后的实际位置对得上
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
坑在哪里
这里就有个坑了。由于证书文件是个软链接文件,挂载进入容器之后是无法使用的。
nginx就会报错,错误信息为 无法找到该证书文件。但你进入容器又会发现证书文件确实在容器中,但是!此证书文件是无法读取的,跟权限无关,完全是因为docker volumes 本身只能映射硬链接信息。而软链接是无法映射的,留在容器中的只是一个软链接的空壳。
排查这类问题有个简单办法:进容器里 ls -l 看一眼,如果证书文件显示为指向某个路径的链接,再 cat 一下试试能不能读。读不出来,基本就是链接指向的目标在容器里不存在。
为什么会这样
其实也不难理解 因为软链接只是一个快捷方式,如果volumes 中有宿主机的快捷方式还可以访问宿主机硬链接那才有问题了。因为软链接可以在/home卷下,而软链接对应的硬链接文件其实在/usr卷下。但我配置的volumes 只映射了/home卷 是不能去访问/usr卷的数据的,这里必须要准确映射到对应的软文件,不能是软文件的目录。也不能是直接挂载软文件。
换个角度说:软链接本身只存了一条目标路径,docker 把链接原样放进容器后,容器会按这条路径在自己的文件系统里找目标。目标路径没被一起挂载进来,这条链接就成了死链。这也是容器隔离的应有之义——如果挂载一个快捷方式就能顺着它读到宿主机上任意位置的文件,那隔离就形同虚设了。
挂载路径指向完整的证书软文件路径,才可以生效 。docker中的nginx启动时才能根据软文件对应的文件地址获取到证书文件。
也就是说,关键在于让软链接指向的目标路径在容器内真实存在。对 certbot 来说,live 和 archive 两个目录要一起进容器,链接才解析得开:
# 启动容器时把整个 letsencrypt 目录挂进去,
# live 下的软链接和 archive 下的真实文件都在,链接才能正常解析
docker run -d \
-p 80:80 -p 443:443 \
-v /etc/letsencrypt:/etc/letsencrypt:ro \
-v /data/nginx/conf.d:/etc/nginx/conf.d \
nginx
踩坑与注意
-
nginx 官方镜像的主配置只做全局设置,站点配置靠 include
/etc/nginx/conf.d/*.conf,自己的 server 块要放对位置,否则改了也不生效。 -
443 端口容易忘。容器内 nginx 监听了 443,宿主机没映射,浏览器照样连不上,而 nginx 日志里看不出任何异常。
-
Let's Encrypt 证书有效期只有 90 天,续期后 archive 目录下会多出新一版文件,live 下的软链接随之更新。如果当初图省事把解析后的真实文件路径写死在配置里,续期后路径就对不上了——这也是更推荐挂载整个目录、配置里引用 live 路径的原因。续期后记得让容器内的 nginx 重新加载配置。
-
容器里看到文件"存在"不代表能读。对着软链接
ls是看不出问题的,cat一下或者直接看 nginx 的报错更直接。
小结
这个坑的本质是两个各自合理的设计撞在了一起:certbot 用软链接把"固定路径"和"会变的证书文件"解耦,docker volumes 又只忠实地搬运链接本身、不追踪链接目标。理解了软链接"只存路径"这一点,问题就不神秘了——把链接和它指向的目标一起挂进容器,让路径在容器内闭环,证书自然就能读到。也算是用一次踩坑补上了没看 docker 文档的课。
评论 / COMMENTS