告别手动续签:使用 Ansible 与 acme.sh 实现多台 Nginx 服务器 SSL 证书自动部署
在当今的互联网环境中,HTTPS 已是标配。对于运维工程师而言,管理一个庞大的 Nginx 集群证书是一项艰巨的任务。当服务器数量增长到数十台甚至上百台时,手动更新和部署 SSL 证书不仅是一场噩梦,更是潜在的故障点:操作繁琐、效率低下、极易出错,任何一次遗忘都可能导致线上服务因证书过期而中断。
为了彻底解决这一痛点,我们需要一套企业级 SSL 证书自动化管理方案。本文将以资深运维的视角,详细阐述如何结合使用 acme.sh
和 Ansible
,打造一个从证书自动续签到分发的全自动化系统。acme.sh
负责与 Let's Encrypt 交互,实现证书的自动申请与续签;而 Ansible
则作为强大的自动化部署工具,负责将更新后的证书精准、安全地分发到多台机器上,并平滑地重载 Nginx 服务。
这套方案的核心优势在于:
- 完全自动化:实现从证书续签到部署的端到端自动化,无需人工干预。
- 幂等性保障:利用 Ansible 的幂等性,确保只有在证书更新时才触发服务重载,避免无效操作。
- 高可扩展性:轻松应对服务器节点的增减,只需在清单文件中做简单修改即可统一管理。
- 安全性与健壮性:通过精细的文件权限控制和配置预检查,最大限度避免因配置错误导致的服务中断。
核心流程概览:证书自动续签与部署
在我们深入细节之前,先通过一个流程图来理解这套自动化系统的运作逻辑,它清晰地展示了 acme.sh 如何通过 renew-hook 钩子触发 Ansible Playbook。
graph TD; subgraph "Master 节点 (Ansible 控制端)" A[acme.sh Cron 每日检查] --> B{证书是否即将过期?}; B -- 否 --> C[结束]; B -- 是 --> D[acme.sh 自动续签证书]; D --> E[调用 renew_hook.sh 钩子脚本]; E --> F[执行 Ansible Playbook 进行证书部署]; end subgraph "所有被控节点 (Nginx 服务器)" G[接收新证书文件] --> H[接收新私钥文件]; H --> I[执行 Nginx 配置预检查 nginx -t]; I --> J{检查是否成功?}; J -- 是 --> K[平滑重载 Nginx 服务]; J -- 否 --> L[任务失败并告警]; end F --> G; classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#333,font-family:Arial,font-size:14px; classDef special fill:#d4edda,stroke:#155724,stroke-width:2px,color:#155724,font-family:Arial,font-size:14px; class A,B,C,D,E,F special; class G,H,I,J,K,L default; style B stroke-dasharray: 5 5; style J stroke-dasharray: 5 5;
第一步:为 Ansible 自动化部署准备环境
假设我们有一个 master
节点(作为 Ansible 控制端)和两个 worker
节点(运行 Nginx)。
master
: 192.168.1.10worker1
: 192.168.1.11worker2
: 192.168.1.12
1. 建立 SSH 免密登录
这是 Ansible 能够统一管理多台服务器的基础。我们需要在 master
节点上生成 SSH 密钥,并将公钥分发到包括自身在内的所有被控节点。
- 在
master
节点上,生成 SSH 密钥对:ssh-keygen -t rsa -b 4096 -C "ansible_control_key" # 一路回车,确保私钥没有密码,以便 Ansible 能自动使用
- 使用
ssh-copy-id
命令将公钥分发到所有节点:# 假设我们将以 root 用户身份进行管理 ssh-copy-id root@master ssh-copy-id root@worker1 ssh-copy-id root@worker2
- 验证:在
master
节点上,逐一测试 SSH 连接,确保无需密码即可登录。ssh root@master "hostname" ssh root@worker1 "hostname" ssh root@worker2 "hostname"
2. 安装 Ansible 和 acme.sh
- 在
master
节点上安装 Ansible,它是我们自动化部署任务的执行者。# 以 CentOS/RHEL 为例 sudo yum install epel-release -y sudo yum install ansible -y # 以 Debian/Ubuntu 为例 sudo apt update sudo apt install software-properties-common -y sudo add-apt-repository --yes --update ppa:ansible/ansible sudo apt install ansible -y
- 在
master
节点上安装acme.sh
,它是证书申请和续签的核心工具。curl https://get.acme.sh | sh # 安装后,重新登录或执行 source ~/.bashrc 使 acme.sh 命令生效 acme.sh --set-default-ca --server letsencrypt
第二步:项目结构搭建
一个清晰的项目结构是可维护性的开端。在 master
节点上,创建我们的 autossl-ansible
项目目录。
mkdir ~/autossl-ansible
cd ~/autossl-ansible
我们将采用以下目录结构:
autossl-ansible/
├── inventory.ini # Ansible 主机清单,定义了我们要管理谁
├── ansible.cfg # Ansible 配置文件,优化 Ansible 行为
├── playbook-deploy-certs.yml # Ansible Playbook,自动化任务的核心
└── scripts/
└── renew_hook.sh # acme.sh 续签成功后调用的钩子脚本```
### **第三步:编写 Ansible Playbook 实现证书自动分发**
现在,我们来逐一创建和填充这些文件。
#### **1. `inventory.ini` (主机清单)**
这个文件告诉 Ansible 要对哪些服务器执行**证书自动分发**任务。
```ini
[nginx_servers]
master ansible_host=192.168.1.10
worker1 ansible_host=192.168.1.11
worker2 ansible_host=192.168.1.12
[all:vars]
# 定义全局变量
ansible_user=root
# 自定义变量,用于 Playbook
domain_name=your-domain.com
master_cert_path=/root/.acme.sh/{{ domain_name }}_ecc
worker_cert_base_path=/etc/nginx/ssl
细节解析: [nginx_servers]
是一个主机组,方便我们对所有 Nginx 服务器批量操作。_ecc
后缀表示我们使用 ECDSA 证书,请在首次申请证书后务必确认此路径是否正确。
2. ansible.cfg
(Ansible 配置文件)
这是一个推荐的配置文件,用于定义 Ansible 的行为。
[defaults]
inventory = inventory.ini
remote_tmp = /tmp/.ansible-${USER}
host_key_checking = False
[privilege_escalation]
become = False
3. playbook-deploy-certs.yml
(核心 Playbook)
这是定义如何部署 SSL 证书到 Nginx 服务器的核心文件,包含了所有自动化逻辑。
---
- name: Deploy SSL Certificate to Nginx Servers
hosts: nginx_servers
gather_facts: no
vars:
worker_cert_path: "{{ worker_cert_base_path }}/{{ domain_name }}"
cert_file_path: "{{ worker_cert_path }}/fullchain.cer"
key_file_path: "{{ worker_cert_path }}/{{ domain_name }}.key"
tasks:
- name: Ensure target certificate directory exists
ansible.builtin.file:
path: "{{ worker_cert_path }}"
state: directory
owner: root
group: root
mode: '0755'
- name: Copy new certificate fullchain
ansible.builtin.copy:
src: "{{ master_cert_path }}/fullchain.cer"
dest: "{{ cert_file_path }}"
owner: root
group: root
mode: '0644'
notify: Reload Nginx
- name: Copy new certificate private key
ansible.builtin.copy:
src: "{{ master_cert_path }}/{{ domain_name }}.key"
dest: "{{ key_file_path }}"
owner: root
group: root
mode: '0600' # 私钥权限必须严格控制
notify: Reload Nginx
handlers:
- name: Test Nginx configuration before reloading
listen: Reload Nginx
ansible.builtin.command: nginx -t
changed_when: false
register: nginx_test_result
- name: Reload Nginx service if config is OK
listen: Reload Nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
when: nginx_test_result.rc == 0
- name: Fail playbook if Nginx config is bad
listen: Reload Nginx
ansible.builtin.fail:
msg: "Nginx configuration test failed! Output: {{ nginx_test_result.stderr }}"
when: nginx_test_result.rc != 0
原理与细节深度解析:
- 幂等性部署:
ansible.builtin.copy
模块是幂等的,这是实现自动化 SSL 证书部署的关键。只有当新旧证书内容不同时,它才会执行复制并触发后续的 Nginx 重载,避免了不必要的操作。 - Handlers 与 Notify:
notify: Reload Nginx
机制确保了只有在证书文件真正被更新后,才会执行 Nginx 的重载逻辑,这是保证服务稳定性的重要一环。 - 安全性与健壮性: 在重载 Nginx 前执行
nginx -t
预检查,是自动化流程中的“安全阀”,能有效防止因证书文件损坏或配置错误导致的 Nginx 服务中断。
4. scripts/renew_hook.sh
(acme.sh 钩子脚本)
这个 Shell 脚本是连接 acme.sh
和 Ansible
的桥梁,是实现续签后自动部署的核心。
#!/bin/bash
PROJECT_DIR="/root/autossl-ansible"
cd "$PROJECT_DIR" || exit 1
echo "=================================================="
echo "SSL Certificate renewed, starting Ansible playbook to deploy certs..."
echo "Timestamp: $(date)"
# 执行 Ansible Playbook,将新证书分发到所有 Nginx 服务器
ansible-playbook -i inventory.ini playbook-deploy-certs.yml >> /var/log/autossl-ansible.log 2>&1
echo "Ansible playbook for deploying certs finished."
echo "=================================================="
- 重要: 记得给这个脚本添加执行权限:
chmod +x scripts/renew_hook.sh
第四步:首次证书申请与部署
1. 在 master
节点申请证书
对于多服务器 Nginx 集群,DNS 验证方式是最佳选择,因为它天然支持通配符证书,且无需在 Web 服务器上做任何改动。
以 Cloudflare 为例 (dns_cf
):
# 在 master 节点执行
export CF_Key="YOUR_CLOUDFLARE_GLOBAL_API_KEY"
export CF_Email="your_cloudflare_email@example.com"
acme.sh --issue --dns dns_cf -d your-domain.com -d *.your-domain.com -k ec-256
2. 关联钩子脚本,打通自动续签与部署
证书申请成功后,我们需要告诉 acme.sh
,在未来每次证书自动续签成功后,都要调用我们的部署脚本。
# 在 master 节点执行
acme.sh --install-cert -d your-domain.com --ecc \
--renew-hook "/root/autossl-ansible/scripts/renew_hook.sh"
注意: 这里的 --install-cert
命令只是用来注册 --renew-hook
这个续签后的动作。真正的安装部署由我们的 Ansible Playbook 负责。
3. 手动触发首次 Ansible 证书部署
现在证书已生成,让我们手动运行一次 Ansible Playbook,完成所有服务器的首次部署。
# 在 master 节点的 ~/autossl-ansible 目录下执行
ansible-playbook playbook-deploy-certs.yml
- 此时,在所有 Nginx 节点上配置好证书路径,并访问
https://your-domain.com
,应该可以看到新的 SSL 证书已经生效。
第五步:验证全自动 SSL 证书生命周期管理
至此,一套完整的从证书续签到部署的自动化流水线已经建立!
- 自动续签:
acme.sh
的 cron 任务会每天检查,在证书到期前自动续签。 - 自动部署: 续签成功后,
renew_hook.sh
脚本被自动调用。 - 触发 Ansible: 钩子脚本运行
ansible-playbook
,将最新的证书推送到所有nginx_servers
并平滑重载服务。
如何模拟测试?
您可以强制续签一次证书来完整地测试整个自动化流程,验证避免手动更新 SSL 证书的效果:
# 在 master 节点执行
acme.sh --renew -d your-domain.com --force --ecc
观察屏幕输出,您会看到 acme.sh
续签成功后,Ansible Playbook 被自动触发并完成所有服务器的更新。这证明了我们的自动化系统工作正常,您已成功告别繁琐的手动证书管理。