如何通过Puppet将主节点的Let's Encrypt证书部署至备节点?
嘿,这个场景我之前在搭建高可用HAProxy集群的时候正好碰到过,给你分享几个实践下来靠谱的解决方案:
方案1:用Puppet导出/导入资源同步证书
这是我最常用的方式,利用Puppet的**导出资源(Exported Resources)**特性,让主节点把证书文件导出,备节点自动导入:
主节点配置
在主节点的Puppet代码里,把生成好的证书文件导出,加上唯一标签方便备节点识别:
# 导出Let's Encrypt生成的证书文件 @@file { '/etc/haproxy/certs/your-domain.pem': content => file('/etc/letsencrypt/live/your-domain/fullchain.pem'), owner => 'haproxy', group => 'haproxy', mode => '0600', tag => 'haproxy_cluster_cert', # 确保证书生成后才导出 require => Exec['certbot_obtain_cert'], } # 导出私钥文件 @@file { '/etc/haproxy/certs/your-domain.key': content => file('/etc/letsencrypt/live/your-domain/privkey.pem'), owner => 'haproxy', group => 'haproxy', mode => '0600', tag => 'haproxy_cluster_cert', require => Exec['certbot_obtain_cert'], }
备节点配置
备节点只需要导入带有指定标签的资源即可,Puppet会自动从主节点拉取最新的证书内容:
# 导入主节点导出的证书和私钥 File <<| tag == 'haproxy_cluster_cert' |>> # 确保HAProxy在证书更新后重载 service { 'haproxy': ensure => running, enable => true, subscribe => File['/etc/haproxy/certs/your-domain.pem', '/etc/haproxy/certs/your-domain.key'], }
注意:要确保主备节点在同一个Puppet环境,且Puppet Server允许导出资源的同步(默认是开启的,但如果有环境隔离需要检查配置)。
方案2:基于共享存储的文件同步
如果你的主备节点已经挂载了共享存储(比如NFS、GlusterFS),可以直接把Let's Encrypt的证书目录挂载到共享存储上,然后主备节点的HAProxy都引用这个共享路径的证书:
# 主备节点都配置共享存储挂载 mount { '/etc/letsencrypt/live': ensure => mounted, device => 'nfs-server:/path/to/shared/certs', fstype => 'nfs', options => 'rw,sync', require => Package['nfs-common'], } # HAProxy配置引用共享存储里的证书 haproxy::listen { 'https_frontend': # 其他配置... ssl_cert => '/etc/letsencrypt/live/your-domain/fullchain.pem', ssl_key => '/etc/letsencrypt/live/your-domain/privkey.pem', require => Mount['/etc/letsencrypt/live'], }
这种方式的好处是证书只需要在主节点生成一次,备节点自动获取最新版本,不需要额外的Puppet同步逻辑。但要确保共享存储的高可用性,避免成为单点故障。
方案3:备节点独立申请证书(去中心化方案)
其实Let's Encrypt允许同一个域名在多个节点申请独立的证书,只要每个节点都能完成ACME挑战。你已经配置了HAProxy把/.well-known/acme-challenge请求转发到本地端口,那备节点也可以运行Certbot申请证书:
# 备节点同样配置Certbot申请证书 exec { 'certbot_obtain_cert': command => 'certbot certonly --webroot -w /var/www/acme -d your-domain --agree-tos --email your-email@example.com --non-interactive', creates => '/etc/letsencrypt/live/your-domain/fullchain.pem', require => [Package['certbot'], File['/var/www/acme']], } # 同样配置HAProxy引用本地证书 haproxy::listen { 'https_frontend': # 其他配置... ssl_cert => '/etc/letsencrypt/live/your-domain/fullchain.pem', ssl_key => '/etc/letsencrypt/live/your-domain/privkey.pem', require => Exec['certbot_obtain_cert'], }
如果担心HTTP验证在备节点无法通过(比如VIP不在备节点上),可以改用DNS-01挑战,只需要你能控制域名的DNS解析,添加TXT记录完成验证,这种方式更适合多节点集群场景,不需要依赖VIP的网络访问。
通用注意事项
- 不管用哪种方案,都要确保证书和私钥的权限是
0600,且属于haproxy用户/组,避免HAProxy无法读取或者权限泄露。 - 证书更新后(比如自动续期),一定要让HAProxy重载配置,所以要在Puppet里通过
subscribe或者notify关联HAProxy服务和证书文件。 - 如果用导出资源的方案,要注意Puppet的事实缓存问题,主节点证书更新后,备节点可能需要等待下一次Puppet运行才能同步,你可以手动触发备节点的Puppet run来加速同步。
内容的提问来源于stack exchange,提问作者watain

