最近升级了下家里的 LB,通过 gitlab 将 LB 的配置做了版本管理,并且通过 docker-compose 实现 LB 的快速部署。但是家里的网络做了内外网的区分,为了实现内网 https 访问,我需要在内网的 LB 上配置一套 SSL 证书(公网的部分直接在 CDN 上配置了 let's encrypt 的免费证书,一年一换)

为了避免麻烦,对于内网的 https 证书希望做到以下两点:

  • 到期自动续

  • 泛域名

查找了一番,有两个工具比较符合:Certbotacme.sh,都是通过 ACME protocol 去自动获取免费证书,但是需要自动获取泛域名证书的话,还需要能够自动在 DNS Provider 处更新 DNS TXT Record。由于我的域名是在 DNSPod 购买的,因此需要能够支持在 DNSPod 上自动更新 TXT 记录。Certbot 没有对应的官方插件,但是有第三方好心人写的插件能够实现该功能,如 certbot-dns-dnspod;而 acme.sh 是国人写的工具,官方支持 Aliyun 和 DNSPod,思来想去,还是打算使用 acme.sh

Docker Compose Configuration

根据 官方文档 先将 Docker Compose 配置好,从文档里可以看出,acme.sh 容器会以 daemon 的形式一直运行,后续的申请证书、部署、续签等操作都需要 docker compose exec 单独去操作。Dockers Compose 配置如下:

version: "3.9"
services:
  nginx:
    restart: always
    container_name: nginx
    image: nginx:latest
    ports:
      - 80:80
      - 443:443
    labels:
      - sh.acme.autoload.domain=${DOMAIN_NAME}
    volumes:
      - $PWD/conf.d:/etc/nginx/conf.d:ro
      - $PWD/nginx.conf:/etc/nginx/nginx.conf:ro
      - $PWD/log/:/var/log/nginx/:rw
    environment:
      - TZ=Asia/Shanghai
  
  acme:
    image: neilpang/acme.sh
    container_name: acme.sh
    command: daemon
    volumes:
      - $PWD/acmeout:/acme.sh
      - /var/run/docker.sock:/var/run/docker.sock
    dns:
      - 8.8.8.8
    environment:
      - DEPLOY_DOCKER_CONTAINER_LABEL=sh.acme.autoload.domain=${DOMAIN_NAME}
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE=/etc/nginx/ssl/key.pem
      - DEPLOY_DOCKER_CONTAINER_CERT_FILE=/etc/nginx/ssl/cert.pem
      - DEPLOY_DOCKER_CONTAINER_CA_FILE=/etc/nginx/ssl/ca.pem
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE=/etc/nginx/ssl/full.pem
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"
      - DP_Id=${DP_Id}
      - DP_Key=${DP_Key}

另外还需要添加一个 .env 文件用来存放如下变量:

  • ${DOMAIN_NAME}:用于申请证书的泛域名

  • ${DP_Id}:DNSPod API Key ID

  • ${DP_Key}:DNSPod API Key

申请证书

acme.sh 默认的 ssl 服务器是 ZeroSSL.com,根据 文档 里提到的,在申请证书之前,需要先注册账户

docker compose exec acme --register-account -m <your email address>

注册完成后就可以正式申请证书了

docker compose exec acme --issue --dns dns_dp -d ${DOMAIN_NAME}

等待证书申请完成后,可以看到 acmeout 目录下会出现一个 ${DOMAIN_NAME} 命名的目录,该目录下就是我们申请到的证书。接着运行命令将证书部署到 nginx 的目录下(该目录则是在 docker compose 中通过 environment 传参给 acme 容器的)

docker compose exec acme --deploy -d ${DOMAIN_NAME}  --deploy-hook docker

理论上到这里我们的证书申请和部署就已经结束了。

修锅

当然我在申请证书的时候,遇到了些问题,比如在等待证书签售的过程中,一直在报错

Order status is processing, lets sleep and retry

等待的超时时间是 15s,连续 retry 了多次之后肯定是有问题的,参考了 网络上其他的人建议,将默认的 CA Server 从 ZeroSSL.com 更换成了 Let's Encrypt

docker compose exec acme --set-default-ca --server letsencrypt

续签证书

acme.sh 不用我们手动去执行 renew 的命令来续签到期的证书,正如之前所说,acme 的容器是以 daemon 状态运行的,因此他会定时的去检测我们的证书到期时间,在到期之前会自动进行 renew 操作。可以在 acmeout/${DOMAIN_NAME}/${DOAMIN_NAME}.conf 中看到,有一项 Le_NextRenewTimeStr 配置,该配置了记录了 acme.sh 下一次续签证书的时间。