【运维进阶】管理变量和事实
管理变量和事实
实验环境
[lth@controller ~ 19:45:31]$ mkdir web && cd web[lth@controller web 19:46:21]$ cat > ansible.cfg <<'EOF'
[defaults]
remote_user = lth
inventory = ./inventory[privilege_escalation]
become = True
become_user = root
become_method = sudo
become_ask_pass = False
EOF[lth@controller web 19:46:27]$ cat > inventory <<'EOF'
controller
node1
node2
node3
node4
EOF
管理 VARIABLES
变量简介
Ansible 中的变量就像一个 “容器”,用来存储各种信息(比如文件名、服务名、密码、路径等),方便在 Playbook 中重复使用,避免重复写死内容。
举个例子:
如果你在 Playbook 里多次用到数据库密码 123456
,可以定义一个变量 db_password: "123456"
,之后全用 {{ db_password }}
代替。这样要改密码时,只改一处就行,不用到处找。变量的核心作用:简化修改、统一管理、让 Playbook 更灵活。
变量命名规则
- 只能包含字母、数字和下划线(如包含空格、点、$符号都为非法变量名)
- 只能以字母开头
变量范围和优先级
ansible项目文件中多个位置支持定义变量,主要包含三个基本范围:
- Global scope:从命令行或 Ansible 配置设置的变量。(
-e "var=value"
)- 最 “强势”,直接在执行 playbook 时指定,会覆盖其他地方的同名变量。
例:ansible-playbook site.yml -e "user=admin"
- 最 “强势”,直接在执行 playbook 时指定,会覆盖其他地方的同名变量。
- Play scope:在play和相关结构中设置的变量。
- Host scope:由清单、事实(fact)收集或注册的任务,在主机组和个别主机上设置的变量。
优先级从高到低顺序:Global -> Play -> Host。
在多个级别上定义了相同名称的变量,则采用优先级别最高的变量。
记住一个原则:“离执行点越近的变量,优先级越高”。比如命令行变量直接作用于本次执行,所以优先级最高;而内置变量是基础,优先级最低。
Global scope
通过选项-e传递给ansible或者ansible-playbook命令。
[lth@controller web 19:48:17]$ ansible node1 -m debug -a "msg={{ package }}" -e "package=httpd"
node1 | SUCCESS => {"msg": "httpd"
}[lth@controller web 19:48:36]$ ansible node1 -m yum -a "name={{ package }} state=present" -e "package=httpd"
node1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"msg": "Nothing to do","rc": 0,"results": ["Installed: httpd"]
}
Play scope
vars 声明
[lth@controller web 19:50:18]$ vim playbook.yml
[lth@controller web 19:50:49]$ 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 }}...
验证:
[lth@controller web 19:54: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中。
示例:
[lth@controller web 19:56:03]$ 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 }}
[lth@controller web 20:48:28]$ mkdir vars
[lth@controller web 20:48:37]$ vim vars/user1.yaml
user: user1
home: /home/user1
Host scope
主机变量应用于主机和主机组。主机变量优先级高于主机组变量。
主机清单中定义
较旧的做法是直接在清单文件中定义。不建议采用,但仍可能会遇到。
# Ansible Playbook 动态内容生成实验
# 用 inventory 变量(node)控制每个节点上生成的网页内容,最后再 curl 验证
[lth@controller web 20:49:21]$ 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.xiexin.cloud
...[lth@controller web 13:41:16]$ cat inventory
controller
# 这里指定了node1和2的node 所以输出也会是指定的
[nodes]
node1 node=Node1
node2 node=Node2
node3
node4[nodes:vars]
node=Node[lth@controller web 20:49:37]$ ansible nodes -m debug -a var=node
node3 | SUCCESS => {"node": "Node"
}
node1 | SUCCESS => {"node": "Node1"
}
node2 | SUCCESS => {"node": "Node2"
}
node4 | SUCCESS => {"node": "Node"
}# 验证
[lth@controller web 20:50:01]$ ansible-playbook deploy_web.yml
[lth@controller web 20:50:08]$ curl http://node3/
hello world from Node
[lth@controller web 20:50:14]$ curl http://node4/
hello world from Node
[lth@controller web 20:50:21]$ curl http://node1/
hello world from Node1
[lth@controller web 20:50:35]$ curl http://node2/
hello world from Node2
目录分层结构定义
在项目目录中创建如下目录:
-
group_vars,定义主机组变量。目录中文件名可以直接使用
主机组名
或者主机组名.yaml
。 -
host_vars,定义主机变量。目录中文件名可以直接使用
主机名
或者主机名.yaml
。 -
host_vars 优先级更高,group_vars 是永远也覆盖不了 host_vars 的。
示例1:
[lth@controller web 21:04:21]$ cat inventory
controller[nodes]
node1
node2
node3
node4# group_vars/nodes.yml 定义了整个 nodes 组的默认变量
[lth@controller web 21:04:26]$ mkdir group_vars host_vars
[lth@controller web 21:04:33]$ cat group_vars/nodes.yml
node: NODES
# 这意味着,如果某个节点没有更高优先级的同名变量,就会使用 "NODES"# 这些会覆盖 group_vars 里的 node: NODES,因为 主机变量优先级高于分组变量。
[lth@controller web 21:04:37]$ echo "node: NODE1"> host_vars/node1.yml
[lth@controller web 21:04:43]$ echo "node: NODE2"> host_vars/node2.yml
[lth@controller web 21:04:48]$ ansible-playbook deploy_web.yml # 验证
[lth@controller web 21:04:53]$ curl http://node1/
hello world from NODE1
[lth@controller web 21:05:01]$ curl http://node2/
hello world from NODE2
[lth@controller web 21:05:21]$ curl http://node3/
hello world from NODES
[lth@controller web 21:05:21]$ curl http://node4/
hello world from NODES
Ansible 变量优先级对比图
inventory├── controller├── [nodes]│ ├── node1│ ├── node2│ ├── node3│ └── node4
group_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 |
数组变量
数组变量就是用来存放一组数据的变量。
打个比方:如果普通变量是一个 “小盒子”(只能装一个东西),那数组变量就是一个 “抽屉柜”,里面有多个格子,每个格子可以放一个数据,而且这些数据通常是同一类的(比如全是数字、全是名字)。
总之,数组变量就是为了方便批量管理一组相关数据而设计的变量。
示例:
user1_first_name: Bob
user1_last_name: Jones
user1_home_dir: /users/bjones
user2_first_name: Anne
user2_last_name: Cook
user2_home_dir: /users/acook
改写如下:
users:bjones:first_name: Boblast_name: Joneshome_dir: /users/bjonesacook:first_name: Annelast_name: Cookhome_dir: /users/acook
数组变量引用方式一:
# Returns 'Bob'
users.bjones.first_name
# Returns '/users/acook'
users.acook.home_dir
数组变量引用方式二:
# Returns 'Bob'
users['bjones']['first_name']
# Returns '/users/acook'
users['acook']['home_dir']
引用方式总结:
- 如果使用方法一**.分隔符**引用的关键字与python的功能函数同名,例如discard、copy、add,那么就会出现问题。方法二[‘’]引用方式可以避免这种错误。
- 尽管两种方法都可以使用,为了减少排故难度,Ansible中统一使用其中一种方法。
演示:
[lth@controller web 21:06:43]$ vim playbook.yml
[lth@controller web 21:06:45]$ cat playbook.yml
---
- name: test vars statement in playhosts: node1vars:users:lth:user_name: lthhome_path: /home/lthlaobao:user_name: laobaohome_path: /home/laobaotasks:- name: add user {{users.lth.user_name}}user:name: '{{users.lth.user_name}}'home: "{{users.lth.home_path}}"- name: debug laobaodebug:msg: >username is {{users['laobao']['user_name']}}home_path is {{users['laobao']['home_path']}}
...# 验证
[lth@controller web 21:10:21]$ ansible-playbook playbook.yml PLAY [test vars statement in play] ******************************************************************TASK [Gathering Facts] ******************************************************************************
ok: [node1]TASK [add user lth] ********************************************************************************
ok: [node1]TASK [debug laobao] ********************************************************************************
ok: [node1] => {"msg": "username is laobao home_path is /home/laobao\n"
}PLAY RECAP ******************************************************************************************
node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
register 语句
**register 语句捕获任务输出。**输出保存在一个临时变量中,稍后在playbook中可用于调试用途或者达成其他目的。
# 测试并美化 Ansible 注册变量 register 的输出
[lth@controller web 21:16:21]$ vim playbook.yml
[lth@controller web 21:16:29]$ 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 输出 格式化显示,更易读
[lth@controller web 21:16:45]$ 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 库
[lth@controller web 21:18:24]$ sudo yum install -y yajl
[lth@controller web 21:19:45]$ 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 变量)
魔术变量是 Ansible 自动生成的变量,用它们能获取到特定被控主机的相关信息。
最常用四个 Magic 变量:
-
inventory_hostname,包含清单中配置的当前受管主机的主机名称。这可能因为各种原因而与FACTS报告的主机名称不同。
-
group_names,列出当前受管主机所属的所有主机组。
-
groups,列出清单中的所有组,以及组中含有的主机。
-
hostvars,包含所有受管主机的变量,可用于获取另一台受管主机的变量的值。如果还没有为受管主机收集FACTS,则它不会包含该主机的 FACTS。
例如:
hostvars.controller.group_names
示例:
假设当前inventory清单内容为:
controller[webs]
node1
node2[dbs]
node3
node4
[lth@controller web 21:24:15]$ 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
.....[lth@controller web 21:24:34]$ ansible-playbook deploy_web.yml# 测试
[lth@controller web 21:30:01]$ curl http://node1
hello world from node1
[lth@controller web 21:30:14]$ curl http://node2
hello world from node2
[lth@controller web 21:30:21]$ curl http://node3
hello world from node3
[lth@controller web 21:30:25]$ curl http://node4
hello world from node4
查看 Ansible 的主机组信息
# node1 所属主机组
[lth@controller web 21:30:36]$ ansible node1 -m debug -a var=group_names
node1 | SUCCESS => {"group_names": ["nodes", "webs"]
}# 清单中所有主机组,以及主机组中主机
[lth@controller web 21:30:48]$ ansible node1 -m debug -a var=groups
node1 | 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主机组......
[lth@controller web 21:33:32]$ ansible node1 -m debug -a var=groups.all
node1 | SUCCESS => {"groups.all": ["controller", "node1", "node2", "node3", "node4"]
}
[lth@controller web 21:33:41]$ ansible node1 -m debug -a var=groups.nodes
node1 | SUCCESS => {"groups.nodes": ["node1", "node2", "node3", "node4"]
}
[lth@controller web 21:33:55]$ ansible node1 -m debug -a var=groups.dbs
node1 | SUCCESS => {"groups.dbs": ["node3", "node4"]
}
查看 Ansible 的 hostvars
变量
# 查询所有主机变量信息
[lth@controller web 21:36:07]$ ansible node1 -m debug -a var=hostvars# 统计行数
[lth@controller web 21:36:15]$ ansible node1 -m debug -a var=hostvars | wc -l
277# 查看指定主机的组信息
[lth@controller web 21:36:19]$ ansible node1 -m debug -a var=hostvars.node2.group_names
node1 | SUCCESS => {"hostvars.node2.group_names": ["nodes", "webs"]
}# 查看指定主机的 Ansible 版本
[lth@controller web 21:36:24]$ ansible node1 -m debug -a var=hostvars.node2.ansible_version
node1 | SUCCESS => {"hostvars.node2.ansible_version": {"full": "2.9.27", "major": 2, "minor": 9, "revision": 27, "string": "2.9.27"}
}
这些魔术变量用起来特别方便,就像给 Ansible 准备了几个现成的 “信息小助手”:
-
想知道现在操作的机器在清单里叫啥名?用inventory_hostname一查就知道,比如能直接告诉你 “这是 node1”;
-
想弄清楚这台机器属于哪些主机组?group_names会直接列出来,比如告诉你 “它在 webs 组里”;
-
想了解整个清单里有哪些组、每个组都包含哪些机器?groups能把所有组和组里的主机都列得明明白白;
-
要是想看看其他机器的变量信息?hostvars就能帮你获取到,比如能查到 controller 主机的分组情况。
有了这几个变量,不管是写 Playbook 还是输命令,都能轻松拿到各种主机信息,配置操作的时候就能更有针对性,不用费劲自己去找了。