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 虚拟机免密登录的快捷设置方法

  1. 如果你使用的 ProxmoxVE 等支持 cloud-init 的虚拟化系统创建虚拟机,可以直接通过 cloud-init 在虚拟机创建时设定私钥。
    (进一步地,可以考虑使用 terraform/pulumi 直接自动化创建与配置虚拟机,相当方便)
  1. 如果你的虚拟化系统(比如 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 的文档里没有讲到这个功能,这算是未定义行为。

其他方法:

  1. 网上有很多文档会教你修改 /etc/ansible/ansible.cfg 以关闭指纹的验证,但是这是很危险的操作!你可能会连接到了黑客伪造的主机!
  2. 参考中有个问答,里面有人提供了一个 playbook 批量添加指纹,但是该方法不支持「主机别名」
    • 该 playbook 会将别名当作 host 解析,根本不理会 ansible_host 参数。
    • 另外测试发现它用到了 ansbile 的 local 连接,而这种用法在 wsl1(ubuntu) 上无法使用,会报权限错误。。
  3. 自己写个小脚本读取 ansible-inventory -i xxx.yml --list 输出的 json,将它转换成 ssh-keyscan 可读的文本。

参考

posted @ 2020-05-12 18:50  於清樂  阅读(3831)  评论(0编辑  收藏  举报