Ansible 实操笔记:Playbook 与变量管理
编写和运行 Playbook
环境
[yuxb@controller web 09:45:02]$ cat ansible.cfg [defaults]inventory = ./inventoryremote_user = yuxb#ask_pass = True#module_name = command#private_key_file = /opt/id_rsa#host_key_checking = False[privilege_escalation]become=Truebecome_method=sudobecome_user=rootbecome_ask_pass=False[yuxb@controller web 09:45:38]$ cat inventory [controllers]controller[nodes]node[1:4]
Playbook 介绍
Vim 编辑器设置
[yuxb@controller web 10:00:05]$ cat ~/.vimrcset ai ts=2 numberset cursorcolumn cursorline
Playbook 编写
playbook.yaml 内容
[yuxb@controller web 09:59:32]$ cat playbook.yml ---- name: play 1hosts: node1tasks:- name: user jackuser:name: jackuid: 1088state: present- name: play 2hosts: node2tasks:- name: user tomuser:name: tomuid: 1088state: present...# 执行文件[yuxb@controller web 10:08:25]$ ansible-playbook playbook.yml PLAY [play 1] ***************************************************************************************TASK [Gathering Facts] ******************************************************************************ok: [node1]TASK [user jack] ************************************************************************************changed: [node1]PLAY [play 2] ***************************************************************************************TASK [Gathering Facts] ******************************************************************************ok: [node2]TASK [user tom] *************************************************************************************changed: [node2]PLAY RECAP ******************************************************************************************node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0
编写playbook,部署httpd, 设置防火墙放行,并测试站点可用性。
[yuxb@controller web 10:51:57]$ cat deploy_web.yml ---- name: deploy web serverhosts: node1tasks:- name: install the latest version of Apacheyum:name: httpdstate: latest- name: enable and start Apacheservice:name: httpdstate: startedenabled: yes- name: enable and start Firewalldservice:name: firewalldstate: startedenabled: yes- name: set firewalld for httpfirewalld:service: httppermanent: yesimmediate: yesstate: enabled- name: prepare index.htmlcopy:content: "hello world from node1\n"dest: /var/www/html/index.html- name: check web serverhosts: node2tasks:- name: check web siteuri:url: http://node1.yuxb.cloud...# 检测[yuxb@controller web 10:53:36]$ ansible-playbook deploy_web.yml PLAY [deploy web server] ****************************************************************************TASK [Gathering Facts] ******************************************************************************ok: [node1]TASK [install the latest version of Apache] *********************************************************ok: [node1]TASK [enable and start Apache] **********************************************************************changed: [node1]TASK [enable and start Firewalld] *******************************************************************changed: [node1]TASK [set firewalld for http] ***********************************************************************changed: [node1]TASK [prepare index.html] ***************************************************************************changed: [node1]PLAY [check web server] *****************************************************************************TASK [Gathering Facts] ******************************************************************************ok: [node2]TASK [check web site] *******************************************************************************ok: [node2]PLAY RECAP ******************************************************************************************node1 : ok=6 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [yuxb@controller web 10:54:03]$ curl http://node1.yuxb.cloudhello world from node1
管理变量和事实
实验环境
和上面一样
管理 VARIABLES
变量命名规则
只能包含字母、数字和下划线(如包含空格、点、$符号都为非法变量名)
只能以字母开头
变量范围和优先级
nsible项目文件中多个位置支持定义变量,主要包含三个基本范围:
Global scope:从命令行或 Ansible 配置设置的变量。
Play scope:在play和相关结构中设置的变量。
Host scope:由清单、事实(fact)收集或注册的任务,在主机组和个别主机上设置的变量。
优先级从高到低顺序:Global -> Play -> Host。
在多个级别上定义了相同名称的变量,则采用优先级别最高的变量。
Global scope
通过选项-e传递给ansible或者ansible-playbook命令。
[yuxb@controller web 11:21:27]$ ansible node1 -m debug -a "msg={{package}}" -e "package=httpd"node1 | SUCCESS => {"msg": "httpd"}[yuxb@controller web 11:34:12]$ ansible node1 -m yum -a "name={{package}} state=present" -e "package=httpd"node1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "", "rc": 0, "results": ["httpd-2.4.6-99.el7.centos.1.x86_64 providing httpd is already installed"]}
Play scope
vars 声明
[yuxb@controller web 11:38:17]$ vim playbook.yml [yuxb@controller web 11:43:29]$ cat playbook.yml ---- name: test vars statement in playhosts: node1vars:user: joehome: /home/joetasks:- name: add user {{ user }}user:name: "{{ user }}"home: "{{ home }}"state: present- name: debug userdebug:msg: |username is {{ user }}home is {{ home }}...
# 验证[yuxb@controller web 11:44:11]$ ansible-playbook playbook.yml PLAY [test vars statement in play] ******************************************************************TASK [Gathering Facts] ******************************************************************************ok: [node1]TASK [add user joe] *********************************************************************************changed: [node1]TASK [debug user] ***********************************************************************************ok: [node1] => {"msg": "username is joe\nhome is /home/joe\n"}PLAY RECAP ******************************************************************************************node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vars_files 声明
如果变量比较多,我么可以使用变量文件进行分类,然后分列别应用到playbook中。
[yuxb@controller web 11:46:34]$ vim playbook.yml---- name: test vars statement in playhosts: node1vars_files:- vars/user1.yamltasks:- name: add user {{ user }}user:name: "{{ user }}"home: "{{ home }}"state: present- name: debug userdebug:msg: >username is {{ user}}home is {{ home }}[yuxb@controller web 11:48:28]$ mkdir vars[yuxb@controller web 11:48:37]$ vim vars/user1.yamluser: user1home: /home/user1[yuxb@controller web 11:50:34]$ ansible-playbook playbook.yml
Host scope
主机变量应用于主机和主机组。主机变量优先级高于主机组变量。
主机清单中定义
示例:
# Ansible Playbook 动态内容生成实验# 用 inventory 变量(node)控制每个节点上生成的网页内容,最后再 curl 验证[yuxb@controller web 13:48:40]$ cat deploy_web.yml ---- name: deploy web serverhosts: nodestasks:- name: install the latest version of Apacheyum:name: httpdstate: latest- name: enable and start Apacheservice:name: httpdstate: startedenabled: yes- name: enable and start Firewalldservice:name: firewalldstate: startedenabled: yes- name: set firewalld for httpfirewalld:service: httppermanent: yesimmediate: yesstate: enabled# 这里做了修改 {{node}}- name: prepare index.htmlcopy:content: "hello world from {{node}}\n"dest: /var/www/html/index.html- name: check web serverhosts: node2tasks:- name: check web siteuri:url: http://node1.yuxb.cloud...[yuxb@controller web 13:41:16]$ cat inventory controller# 这里指定了node1和2的node 所以输出也会是指定的[nodes]node1 node=Node1 node2 node=Node2node3node4[nodes:vars]node=Node[yuxb@controller web 13:42:14]$ ansible nodes -m debug -a var=nodenode3 | SUCCESS => {"node": "Node"}node1 | SUCCESS => {"node": "Node1"}node2 | SUCCESS => {"node": "Node2"}node4 | SUCCESS => {"node": "Node"}# 验证[yuxb@controller web 13:47:38]$ ansible-playbook deploy_web.yml[yuxb@controller web 13:48:53]$ curl http://node3/hello world from Node[yuxb@controller web 13:48:56]$ curl http://node4/hello world from Node[yuxb@controller web 13:49:31]$ curl http://node1/hello world from Node1[yuxb@controller web 13:49:34]$ curl http://node2/hello world from Node2
实验:验证 Ansible 变量覆盖规则
host_vars 优先级更高
group_vars 是永远也覆盖不了 host_vars 的
`group_vars` 就像公司发的统一工装(默认穿)`host_vars` 就像你自己带的衣服(想穿就穿,肯定优先)你带了自己的衣服,就不会穿公司发的那件了。
# 清空变量[yuxb@controller web 14:04:51]$ cat inventory controller[nodes]node1 node2 node3node4# group_vars/nodes.yml 定义了整个 nodes 组的默认变量[yuxb@controller web 14:04:54]$ mkdir group_vars host_vars[yuxb@controller web 14:10:04]$ cat group_vars/nodes.ymlnode: NODES# 这意味着,如果某个节点没有更高优先级的同名变量,就会使用 "NODES"# 这些会覆盖 group_vars 里的 node: NODES,因为 主机变量优先级高于分组变量。[yuxb@controller web 14:07:20]$ echo "node: NODE1"> host_vars/node1.yml[yuxb@controller web 14:07:49]$ echo "node: NODE2"> host_vars/node2.yml[yuxb@controller web 14:07:56]$ ansible-playbook deploy_web.yml # 验证[yuxb@controller web 14:10:20]$ curl http://node1/hello world from NODE1[yuxb@controller web 14:10:23]$ curl http://node2/hello world from NODE2[yuxb@controller web 14:10:25]$ curl http://node3/hello world from NODES[yuxb@controller web 14:10:27]$ curl http://node4/hello world from NODES
Ansible 变量优先级对比图
inventory├── controller├── [nodes]│ ├── node1│ ├── node2│ ├── node3│ └── node4group_vars└── nodes.yml → node: "NODES" (分组变量)host_vars├── node1.yml → node: "NODE1" (主机变量)└── node2.yml → node: "NODE2" (主机变量)
变量优先级(从低到高):
group_vars (分组默认值) node: "NODES"host_vars (特定主机覆盖) node1/node2 各有自己的值
最终生效值表:
主机 | group_vars 默认值 | host_vars 覆盖值 | 最终生效值 |
---|---|---|---|
node1 | NODES | NODE1 | NODE1 |
node2 | NODES | NODE2 | NODE2 |
node3 | NODES | 无 | NODES |
node4 | NODES | 无 | NODES |
主机连接特殊变量
[yuxb@controller web 14:38:12]$ cat inventory controller[nodes]web1 node2 node3node4[yuxb@controller web 14:38:15]$ ansible web1 -a id -e ansible_host=node1web1 | CHANGED | rc=0 >>uid=0(root) gid=0(root) 组=0(root)
数组变量
[yuxb@controller web 14:39:43]$ vim playbook.yml [yuxb@controller web 15:00:45]$ cat playbook.yml ---- name: test vars statement in playhosts: node1vars:users:yuxb:user_name: yuxbhome_path: /home/yuxblaowang:user_name: laowanghome_path: /home/laowangtasks:- name: add user {{users.yuxb.user_name}}user:name: '{{users.yuxb.user_name}}'home: "{{users.yuxb.home_path}}"- name: debug laowangdebug:msg: >username is {{users['laowang']['user_name']}}home_path is {{users['laowang']['home_path']}}...# 验证[yuxb@controller web 15:02:38]$ ansible-playbook playbook.yml PLAY [test vars statement in play] ******************************************************************TASK [Gathering Facts] ******************************************************************************ok: [node1]TASK [add user yuxb] ********************************************************************************ok: [node1]TASK [debug laowang] ********************************************************************************ok: [node1] => {"msg": "username is laowang home_path is /home/laowang\n"}PLAY RECAP ******************************************************************************************node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
register 语句
# 测试并美化 Ansible 注册变量 register 的输出[yuxb@controller web 15:02:41]$ vim playbook.yml [yuxb@controller web 15:09:00]$ cat playbook.yml ---- name: Installs a package and prints the resulthosts: node1tasks:- name: Install the packageyum:name: httpdstate: installedregister: install_result- debug: var: install_result...# 把注册变量的 JSON 输出 格式化显示,更易读[yuxb@controller web 15:09:37]$ echo '{"changed": false, "msg": "", "rc": 0, "results": ["httpd-2.4.6-99.el7.centos.1.x86_64 providing httpd is already installed"]}' | json_reformat-bash: json_reformat: 未找到命令# 安装 yajl 库[yuxb@controller web 15:14:44]$ sudo yum install -y yajl[yuxb@controller web 15:15:43]$ echo '{"changed": false, "msg": "", "rc": 0, "results": ["httpd-2.4.6-99.el7.centos.1.x86_64 providing httpd is already installed"]}' | json_reformat{"changed": false,"msg": "","rc": 0,"results": ["httpd-2.4.6-99.el7.centos.1.x86_64 providing httpd is already installed"]}# 目的是查看 register 变量的内容,并格式化输出方便阅读。
MAGIC 变量
# 清单内容[yuxb@controller web 15:15:47]$ vim inventory [yuxb@controller web 15:19:59]$ cat inventory controller[webs]node1 node2[dbs]node3node4[yuxb@controller web 15:24:23]$ vim deploy_web.yml# 上面改成nodes,下面改成inventory_hostname---- name: deploy web serverhosts: nodestasks:......- name: prepare index.htmlcopy:content: "hello world from {{inventory_hostname}}\n"dest: /var/www/html/index.html.....[yuxb@controller web 15:25:49]$ ansible-playbook deploy_web.yml# 测试[yuxb@controller web 15:26:11]$ curl http://node1hello world from node1[yuxb@controller web 15:26:19]$ curl http://node2hello world from node2[yuxb@controller web 15:26:22]$ curl http://node3hello world from node3[yuxb@controller web 15:26:23]$ curl http://node4hello world from node4
查看 Ansible 的主机组信息
# node1 所属主机组[yuxb@controller web 15:43:10]$ ansible node1 -m debug -a var=group_namesnode1 | SUCCESS => {"group_names": ["nodes", "webs"]}# 清单中所有主机组,以及主机组中主机[yuxb@controller web 15:43:36]$ ansible node1 -m debug -a var=groupsnode1 | SUCCESS => {"groups": {"all": ["controller", "node1", "node2", "node3", "node4"], "dbs": ["node3", "node4"], "nodes": ["node1", "node2", "node3", "node4"], "ungrouped": ["controller"], "webs": ["node1", "node2"]}}# 特定主机组中主机,例如:.all,.nodes,.dbs,代表all主机组......[yuxb@controller web 15:43:48]$ ansible node1 -m debug -a var=groups.allnode1 | SUCCESS => {"groups.all": ["controller", "node1", "node2", "node3", "node4"]}[yuxb@controller web 15:43:58]$ ansible node1 -m debug -a var=groups.nodesnode1 | SUCCESS => {"groups.nodes": ["node1", "node2", "node3", "node4"]}[yuxb@controller web 15:44:03]$ ansible node1 -m debug -a var=groups.dbsnode1 | SUCCESS => {"groups.dbs": ["node3", "node4"]}
查看 Ansible 的 hostvars
变量
# 查询所有主机变量信息[yuxb@controller web 15:44:08]$ ansible node1 -m debug -a var=hostvars# 统计行数[yuxb@controller web 15:51:37]$ ansible node1 -m debug -a var=hostvars | wc -l277# 查看指定主机的组信息[yuxb@controller web 15:51:45]$ ansible node1 -m debug -a var=hostvars.node2.group_namesnode1 | SUCCESS => {"hostvars.node2.group_names": ["nodes", "webs"]}# 查看指定主机的 Ansible 版本[yuxb@controller web 15:52:04]$ ansible node1 -m debug -a var=hostvars.node2.ansible_versionnode1 | SUCCESS => {"hostvars.node2.ansible_version": {"full": "2.9.27", "major": 2, "minor": 9, "revision": 27, "string": "2.9.27"}}
管理 SECRETS
安全地管理敏感信息
# 创建 Vault 文件# 目的:确保文件中存储的敏感信息是加密状态,防止明文泄露。[yuxb@controller web 16:05:41]$ ansible-vault create user.yamlNew Vault password: Confirm New Vault password: # 查看 Vault 内容# 临时解密查看内容,但文件本身仍然加密。# 目的:在不破坏加密的情况下查看敏感信息。[yuxb@controller web 16:08:04]$ ansible-vault view user.yaml Vault password: Value passwd: '123'login_name: yuxblogin_password: 123login_host: 10.1.8.11login_type: ssh[yuxb@controller web 16:08:38]$ cat user.yaml $ANSIBLE_VAULT;1.1;AES256316337353363356338306438383261626332393864313634323131626333333266646564626238626661313937363233663064303361646365623563613166370a346138316439363761663563356136343033376133386330363364343536393066316332303939663436353464396361613464333736336236636564623739330a383064633933623530656638623035316564633232306234373263633833303964366531643962316364363836646438653962323433346365323531353364353439666639393632653935323734653865353137643237346131653139326663653162383236346339323232353935343736313934303331376638313433613235333237313237306364623731633164336431643135363931316631636136316633626139666634303465333534316530663134633866363030363037643236# 解密 Vault 文件# 将加密文件解密成明文文件。# 目的:可以直接编辑或查看明文内容。[yuxb@controller web 16:09:08]$ ansible-vault decrypt user.yaml Vault password: Decryption successful[yuxb@controller web 16:09:29]$ cat user.yaml Value passwd: '123'login_name: yuxblogin_password: 123login_host: 10.1.8.11login_type: ssh# 加密 Vault 文件# 将明文文件重新加密。# 目的:恢复文件的安全状态。[yuxb@controller web 16:09:42]$ ansible-vault encrypt user.yamlNew Vault password: Confirm New Vault password: Encryption successful# 编辑 Vault 文件# 在加密状态下直接修改文件内容。# 目的:修改敏感信息而不暴露明文。[yuxb@controller web 16:10:53]$ ansible-vault edit user.yamlVault password: # 使用密码文件访问 Vault# 通过存储的密码文件免输密码访问 Vault。# 目的:自动化任务时避免手动输入密码。[yuxb@controller web 16:12:26]$ echo 123 > password-for-vault[yuxb@controller web 16:12:53]$ ansible-vault view user.yaml --vault-password-file password-for-vault login_name: yuxblogin_password: 123123login_host: 10.1.8.11login_type: ssh[yuxb@controller web 16:13:51]$ ansible-vault rekey user.yamlVault password: New Vault password: Confirm New Vault password: Rekey successful# 重置 Vault 密码(rekey)# 修改 Vault 文件的加密密码。# 目的:定期更新密码,提高安全性,同时不改变文件内容。[yuxb@controller web 16:14:43]$ ansible-vault rekey user.yaml --vault-password-file password-for-vaultNew Vault password: Confirm New Vault password: Rekey successful[yuxb@controller web 16:15:41]$ echo 123 > password-for-vault[yuxb@controller web 16:15:46]$ ansible-vault view user.yaml --vault-password-file password-for-vaultlogin_name: yuxblogin_password: 123123login_host: 10.1.8.11login_type: ssh
综合案例
# ansible vault 综合实验[yuxb@controller web 16:37:50]$ vim deploy_mariadb.yml[yuxb@controller web 16:49:51]$ cat deploy_mariadb.yml ---- name: config mariadb serverhosts: node1tasks:- name: install mariadb-serveryum:name: - mariadb-server- python3-PyMySQLstate: present- name: enable and start mariadbservice:name: mariadbenabled: yesstate: started- name: config user {{ user }}mysql_user:name: "{{ user }}"password: "{{ password }}"host: "{{ host }}"priv: "{{ priv }}"state: present[yuxb@controller web 16:40:37]$ ls host_vars/node1.yml node2.yml[yuxb@controller web 16:40:46]$ mkdir host_vars/node1[yuxb@controller web 16:42:42]$ mv host_vars/node1.yml host_vars/node1/vars.yml[yuxb@controller web 16:42:55]$ vim host_vars/node1/vaults.yml[yuxb@controller web 16:43:50]$ vim inventory [yuxb@controller web 16:44:28]$ grep vault_pa /etc/ansible/ansible.cfg #vault_password_file = /path/to/vault_password_file[yuxb@controller web 16:44:56]$ vim ansible.cfg [yuxb@controller web 16:45:41]$ ls password-for-vault password-for-vault[yuxb@controller web 16:45:50]$ ansible-vault encrypt host_vars/node1/vaults.ymlEncryption successful[yuxb@controller web 16:46:31]$ ansible-vault view host_vars/node1/vaults.ymluser: yuxbpassword: 123host: '%'priv: '*.*:ALL'[yuxb@controller web 16:46:43]$ cat ansible.cfg [defaults]inventory = ./inventoryremote_user = yuxbvault_password_file = ./password-for-vault#ask_pass = True#module_name = command#private_key_file = /opt/id_rsa#host_key_checking = False[privilege_escalation]become=Truebecome_method=sudobecome_user=rootbecome_ask_pass=False[yuxb@controller web 16:46:50]$ cat password-for-vault123
Ansible Playbook
在 node1
上自动化部署和配置 MariaDB 数据库服务
[yuxb@controller web 17:22:01]$ cat deploy_mariadb.yml ---# 安装 MariaDB 及依赖- name: config mariadb serverhosts: node1tasks:- name: install mariadb-serveryum:name: - mariadb-server- python2-PyMySQLstate: present# 启动并设置开机自启- name: enable and start mariadbservice:name: mariadbenabled: yesstate: started# 设置 root 密码- name: set password for rootshell: mysqladmin password {{ password }}# 删除匿名用户 anonymous@完整主机名# ansible_fqdn 是 Ansible fact,表示主机的完整域名# localhost 是本地主机的匿名用户。- name: delete user anonymous@{{ ansible_fqdn }}mysql_user:login_host: localhostlogin_port: 3306login_user: rootlogin_password: "{{ password }}"name: ""host: "{{ ansible_fqdn }}" state: absent# 删除匿名用户 anonymous@localhost- name: delete user anonymous@localhostmysql_user:login_host: localhostlogin_port: 3306login_user: rootlogin_password: "{{ password }}"name: ""host: "localhost"state: absent#删除测试数据库# 作用:删除默认的 test 数据库(MariaDB 默认自带,用于测试,生产环境通常删除)。- name: delete database testmysql_db:login_host: localhostlogin_port: 3306login_user: rootlogin_password: "{{ password }}"name: teststate: absent# 创建新用户# {{ host }} → 用户允许访问的主机(如 % 或 localhost)# {{ priv }} → 权限(如 *.*:ALL 表示所有库全权限)- name: create user {{ user }}mysql_user:login_host: localhostlogin_port: 3306login_user: rootlogin_password: "{{ password }}"name: "{{ user }}"password: "{{ password }}"host: "{{ host }}"priv: "{{ priv }}"state: present# 创建新库# 作用:创建一个新的数据库,名字由 {{ db_name }} 变量决定。- name: create database db_namemysql_db:login_host: localhostlogin_port: 3306login_user: rootlogin_password: "{{ password }}"name: "{{ db_name }}"state: present...
总结
这个 playbook 的功能就是:
安装 MariaDB 服务及依赖
启动并开机自启 MariaDB
设置 root 密码
删除匿名用户和默认测试数据库
创建自定义用户
创建自定义数据库
可以理解为 一个典型的 MariaDB 初始配置和安全加固的自动化流程。