用户使用 Ansible 批量更新服务器 SSL证书是一种高效、可重复的自动化运维方式。下面将从准备证书、设计 Playbook、安全处理私钥、服务重启等方面详细说明,并提供一个完整的示例。
1. 准备工作
证书文件:将新的证书文件(`.crt`)、私钥文件(`.key`)以及中间证书(如果有)存放在 Ansible 控制节点上(例如 `files/ssl/` 目录下)。
主机清单:明确需要更新证书的目标主机分组,例如 `webservers`。
服务信息:知道目标主机上负责 HTTPS 的服务(Nginx、Apache、HAProxy 等)及其证书路径和重启命令。
2. Playbook 设计思路
一个典型的更新 SSL 证书的 Playbook 包含以下步骤:
1. 确保证书目录存在:如果目标路径不存在则创建。
2. 分发证书和私钥:使用 `copy` 或 `template` 模块将文件推送到指定位置。
3. 设置正确的权限:私钥文件权限应设为 `0600`,证书文件通常为 `0644`。
4. 通知服务重启:当证书文件发生变化时,触发 handler 来重新加载或重启服务。
5. (可选)验证证书:可使用 `shell` 或 `command` 模块检查证书是否生效。
3. 安全处理私钥
私钥是敏感信息,不要明文提交到版本库。
使用 **Ansible Vault** 加密私钥文件或整个变量文件。
也可以在 Playbook 中通过 `ansible-vault` 加密后的文件直接分发,或者将私钥内容定义为变量,通过 `copy` 的 `content` 参数写入。
4. 示例 Playbook
以下示例假设目标主机为 Nginx,证书路径为 `/etc/nginx/ssl/`,证书文件名为 `example.com.crt`,私钥为 `example.com.key`。
yaml
name: 批量更新 SSL 证书
hosts: webservers
become: yes
vars:
cert_src_dir: "files/ssl/"
cert_dest_dir: "/etc/nginx/ssl/"
cert_name: "example.com"
service_name: "nginx"
tasks:
- name: 创建证书存放目录
file:
path: "{{ cert_dest_dir }}"
state: directory
owner: root
group: root
mode: 0755
name: 分发证书文件
copy:
src: "{{ cert_src_dir }}/{{ cert_name }}.crt"
dest: "{{ cert_dest_dir }}/{{ cert_name }}.crt"
owner: root
group: root
mode: 0644
notify: reload nginx
name: 分发私钥文件(使用 Vault 加密)
copy:
src: "{{ cert_src_dir }}/{{ cert_name }}.key.vault"
dest: "{{ cert_dest_dir }}/{{ cert_name }}.key"
owner: root
group: root
mode: 0600
notify: reload nginx
name: 分发中间证书(如果有)
copy:
src: "{{ cert_src_dir }}/ca-bundle.crt"
dest: "{{ cert_dest_dir }}/ca-bundle.crt"
owner: root
group: root
mode: 0644
notify: reload nginx
handlers:
- name: reload nginx
systemd:
name: "{{ service_name }}"
state: reloaded
# 对于非 systemd 系统,可使用 service 模块
说明:
私钥文件被命名为 `.key.vault`,实际是一个用 `ansible-vault` 加密的普通文件。Playbook 执行时需提供 Vault 密码(`--ask-vault-pass`)。
使用 `notify` 机制,仅当文件真正发生变化时才触发服务重载,避免不必要的重启。
如果证书路径或服务名在不同主机上不同,可以使用 `group_vars` 或 `host_vars` 来定义变量。
5. 批量处理多个站点/不同证书
如果一台服务器上运行多个 HTTPS 站点,每个站点使用不同证书,可以通过循环或变量数组来处理。
示例:使用 `with_items` 循环分发多个证书。
yaml
vars:
sites:
- domain: example.com
crt_file: example.com.crt
key_file: example.com.key
- domain: test.com
crt_file: test.com.crt
key_file: test.com.key
tasks:
name: 分发证书文件
copy:
src: "{{ cert_src_dir }}/{{ item.crt_file }}"
dest: "{{ cert_dest_dir }}/{{ item.crt_file }}"
mode: 0644
loop: "{{ sites }}"
notify: reload nginx
name: 分发私钥文件
copy:
src: "{{ cert_src_dir }}/{{ item.key_file }}.vault"
dest: "{{ cert_dest_dir }}/{{ item.key_file }}"
mode: 0600
loop: "{{ sites }}"
notify: reload nginx
6. 更高级的考虑
6.1 使用 template 生成配置
如果证书路径需要写入配置文件(如 Nginx 的 `server` 块),可以使用 `template` 模块动态生成配置文件,并通知服务重载。
6.2 检测证书是否真的需要更新
可以在分发前检查目标主机上现有证书的 `md5sum` 或 `stat` 属性,只有当不同时才替换,减少不必要的服务重载。但通常 `copy` 模块本身已具备幂等性(通过文件校验)。
6.3 滚动更新(避免服务中断)
如果服务集群支持负载均衡,可以分批更新,确保服务不中断。使用 `serial` 关键字控制并发:
yaml
name: 更新 SSL 证书
hosts: webservers
serial: 1 # 每次只更新一台
6.4 验证证书
更新后可以增加一个验证任务,例如使用 `openssl` 检查证书有效期:
yaml
- name: 验证证书有效期
shell: "openssl x509 -enddate -noout -in {{ cert_dest_dir }}/{{ cert_name }}.crt"
register: cert_enddate
changed_when: false
- name: 显示证书过期时间
debug:
msg: "{{ cert_enddate.stdout }}"
6.5 使用 acme.sh 或 Let's Encrypt 自动获取
如果证书来自 Let's Encrypt,可以结合 Ansible 调用 `acme_certificate` 模块自动申请和更新证书,但前提是控制节点或目标主机上需要配置好 ACME 环境。
7. 安全性补充
使用 `ansible-vault` 加密所有包含私钥的文件或变量。
确保 Playbook 中不包含明文密码或私钥。
为私钥设置严格的权限(`0600`),仅 root 可读。
考虑在目标主机上设置 SELinux 上下文,如果使用 SELinux,可能需要 `seboolean` 或 `sefcontext` 调整。
8. 执行与测试
测试模式:先使用 `--check` 模式查看变更,避免误操作。
逐步执行:可以先用 `--limit` 限制在一台主机上执行,验证无误后再全量更新。
备份:在分发新证书前,使用 `backup: yes` 备份旧证书:
yaml
copy:
src: ...
dest: ...
backup: yes
通过 Ansible Playbook 批量更新服务器SSL证书,可以确保一致性、可追溯性和自动化程度。关键点包括:妥善保管私钥、使用 handler 触发服务重载、利用幂等性避免不必要的重启,以及结合滚动更新策略减少影响。根据实际环境调整证书路径、服务名称和中间证书配置即可。