Ansible 免密登录与主机清单配置
Ansible 通过 SSH 协议免密登录远程主机
安装好 ansible 后,第一件事当然是连接上远程主机。
1. 批量扫描主机指纹
ansible 使用 ssh 协议登录远程主机进行操作,我想用过 ssh user@host
命令的都知道,首次登录远程主机时都会有如下提示:
ryan@RYAN-MI-DESKTOP:~$ ssh user@github.com
The authenticity of host 'github.com (13.250.177.223)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,13.250.177.223' (RSA) to the list of known hosts.
这里会要求你输入 yes 将主机指纹保存到本地,是一种安全措施,防止在遇到 DNS 污染或 IP 冲突等异常情况时,目标主机被冒名顶替。
Ansible 默认情况下也会使用这个指纹对主机进行验证,因此我们希望能够快速地扫描出所有主机的指纹,这里使用 ssh-keyscan 命令:
echo "
192.168.58.131
192.168.58.132
192.168.58.133
" > my-hosts
ssh-keyscan -f my-hosts >> ~/.ssh/known_hosts
# 或者添加 -H 参数,只保存主机 IP/域名的 hash 值,更安全
ssh-keyscan -H -f my-hosts >> ~/.ssh/known_hosts
2. 设置免密登录
大量的远程主机都使用同一个密码提供 ssh 远程登录是很不安全的,一般都建议所有主机都只开启私钥登录,禁用密码登录。相关配置在主机的 /etc/ssh/sshd_config
中,详细配置方法请自行搜索。
2.1 虚拟机免密登录的快捷设置方法
- 如果你使用的 ProxmoxVE 等支持 cloud-init 的虚拟化系统创建虚拟机,可以直接通过 cloud-init 在虚拟机创建时设定私钥。
(进一步地,可以考虑使用 terraform/pulumi 直接自动化创建与配置虚拟机,相当方便)
- 如果你的虚拟化系统(比如 VMware)不支持 cloud-init,可以考虑制作一个基础的 ova 镜像,把运维账号(如 ops)和通用的公钥打包在这个 ova 中,后续有需要再更换成更安全的密钥对。
2.2 为支持密码登录的远程主机批量设置免密登录
而对于已经存在的、支持密码登录的一批远程主机,免密登录的设置流程如下:
首先你需要在本地通过 ssh-keygen
命令生成好公私钥,默认使用 rsa 算法。
下一步是通过 ansible 来批量添加 ssh 公钥到所有主机上:
编写一个名叫 add-sshkey.yml
的 playbook(playbook 将在后面详细介绍):
---
- hosts: all # 使用 inventory 中的所有主机
gather_facts: false
remote_user: root # 使用这个账号登录远程主机
tasks:
- name: install ssh key
authorized_key: # 查看该 module 的文档:`ansible-doc authorized_key`
user: root # 给远程主机上的这个用户添加公钥。建议不要直接使用 root 账号(可以用 ops)
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" # 也可以使用 url,这样公钥可以直接放 nginx 上挂着,更方便。
state: present
然后用如下命令运行这个 playbook,输入密码,就能在所有远程主机上添加好 ssh 公钥:
# --inventory 指定主机清单,就用我们之前进行 ssh-keyscan 时用的那个文件就行
# --ask-pass 可以让我们交互式地输入主机密码(所有主机的密码必须相同)
ansible-playbook --inventory my-hosts --ask-pass ssh-addkey.yml
ansible 使用
/etc/ansible/hosts
文件作为它的默认 inventory 主机清单,但是我比较追求「基础设施即代码」,
为了将这个 hosts 文档和相关代码/文档放在一起,提交到 git 仓库保存,我推荐每次都使用-i [hosts_file]
的方式指定 inventory 主机清单。
如果各主机的 ssh 端口、密码等参数不一致,就需要在 my-hosts
中设定更详细的参数,详见 Ansible Docs - intro_inventory
3. 开始愉快地玩耍
OK,现在免密登录就配好了,可以愉快地用 Ansible 玩耍了。
简单地进行下测试:
# ansible [pattern] -m [module] -u [remote user] -a "[module options]"
# -u root # 使用 root 账户登录远程主机,这个对应前面 playbook 中的 remote_user
# all # [pattern],all 表示选中 my-hosts 中的所有主机
# -m [module] # 指定使用的 ansible 模块,默认使用 `command`,即在远程主机上执行 -a 参数中的命令
# -a "ls -al" # 指定 module 的参数,这里是提供给 `command` 模块的参数。
ansible -u root -i my-hosts all -a "ls -al"
# 或者使用 ansible-console 交互式执行命令,更适合愉快地游玩hhh
ansible-console -i my-hosts all -u root
4. inventory 主机清单
前面给出的 inventory 只是非常简单的 IP 地址列表。
可如果我们不同的主机有不同的 ssh 端口号、ssh 密钥,或者不同的用户等等,那这样一个简单的 IP 列表就不够用了。
为了解决这个问题,我们需要使用更高级的 inventory 语法,以对主机进行分类,对不同类别的主机配置不同的参数。
ini 格式的配置举例如下:
# 给服务器分组,组名只能用 [a-zA-Z0-9_]
[databases]
# 指定一个数字范围
192.168.1.1[01:50]
[k8s_cluster]
# 指定一个字母表范围
worker[01:30].k8s.local
worker-[a:h].k8s.local
# k8s-cluster 组的公用参数
[k8s_cluster:vars]
ntp_server=ntp.svc.local
proxy=proxy.svc.local
[app]
# 给服务器指定别名(git),通过关键字参数指定其他参数
git ansible_host=git.svc.local ansible_port=225 ansible_ssh_private_key_file=<path/to/git-server-ssh>
# 使用指定的账号密码(危险!)
tester ansible_host=tester.svc.local ansible_user=root ansible_password=xxx
另外也可以使用 yaml 格式配置 inventory 主机清单,上面的 ini 配置写成 yaml 格式是这样的:
---
databases:
hosts: 192.168.1.1[01:50]
k8s_cluster:
hosts: # 没有别名的服务器
worker[01:30].k8s.local:
worker-[a:h].k8s.local:
vars: # 公共的参数
ntp_server: ntp.svc.local
proxy: proxy.svc.local
app:
hosts:
git: # 服务器别名
ansible_host: git.svc.local # 如果未定义这个,默认以「别名」为 host。(在这里就是 git)
ansible_port: 225
ansible_ssh_private_key_file: <path/to/git-server-ssh>
tester:
ansible_host: tester.svc.local
ansible_user: root
ansible_password: xxx # 危险!尽量不要写明文密码
写好配置后,可以通过如下命令验证并查看你的配置:
ansible-inventory -i xxx.yml --list --yaml
该命令会提示出你错误的配置,并且打印出最终得到的 yaml 配置内容。
批量扫描主机指纹
验证通过后,就可以通过 ansible
/ansible-playbook
/ansible-console
愉快地玩耍了么?很遗憾的是——不行。
我们在前面使用不带任何参数的文档作为 inventory 时,因为 ssh-keyscan
也能解析它,所以我们很方便地就完成了主机指纹的批量扫描。
但是现在我们的 inventory 变得很复杂了,ssh-keyscan
解析不了它了,该如何去批量扫描主机指纹呢?难道几十上百台服务器的指纹,我必须得手动一个个去添加?!
答案是可以批量加,最简单有效的方法,是使用如下命令:
# 使用环境变量 ANSIBLE_HOST_KEY_CHECKING 临时关闭主机指纹检查
ANSIBLE_HOST_KEY_CHECKING=false ansible -i inventory.yaml all -m ping
经测试,不论登录成功与否,ping
模块都会自动将所有主机的指纹添加到 known_hosts 中。
但是在 ping 的文档里没有讲到这个功能,这算是未定义行为。
其他方法:
- 网上有很多文档会教你修改
/etc/ansible/ansible.cfg
以关闭指纹的验证,但是这是很危险的操作!你可能会连接到了黑客伪造的主机! - 参考中有个问答,里面有人提供了一个 playbook 批量添加指纹,但是该方法不支持「主机别名」!
- 该 playbook 会将别名当作 host 解析,根本不理会
ansible_host
参数。 - 另外测试发现它用到了 ansbile 的 local 连接,而这种用法在 wsl1(ubuntu) 上无法使用,会报权限错误。。
- 该 playbook 会将别名当作 host 解析,根本不理会
- 自己写个小脚本读取
ansible-inventory -i xxx.yml --list
输出的 json,将它转换成ssh-keyscan
可读的文本。