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
可讀的文本。