编写和运行 Playbook
环境
[yuxb@controller web 09:45:02]$ cat ansible.cfg
[defaults]
inventory = ./inventory
remote_user = yuxb
#ask_pass = True
#module_name = command
#private_key_file = /opt/id_rsa
#host_key_checking = False
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_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 ~/.vimrc
set ai ts=2 number
set cursorcolumn cursorline
Playbook 编写
playbook.yaml 内容
[yuxb@controller web 09:59:32]$ cat playbook.yml
---
- name: play 1
hosts: node1
tasks:
- name: user jack
user:
name: jack
uid: 1088
state: present
- name: play 2
hosts: node2
tasks:
- name: user tom
user:
name: tom
uid: 1088
state: 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 server
hosts: node1
tasks:
- name: install the latest version of Apache
yum:
name: httpd
state: latest
- name: enable and start Apache
service:
name: httpd
state: started
enabled: yes
- name: enable and start Firewalld
service:
name: firewalld
state: started
enabled: yes
- name: set firewalld for http
firewalld:
service: http
permanent: yes
immediate: yes
state: enabled
- name: prepare index.html
copy:
content: "hello world from node1\n"
dest: /var/www/html/index.html
- name: check web server
hosts: node2
tasks:
- name: check web site
uri:
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.cloud
hello 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 play
hosts: node1
vars:
user: joe
home: /home/joe
tasks:
- name: add user {{ user }}
user:
name: "{{ user }}"
home: "{{ home }}"
state: present
- name: debug user
debug:
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 play
hosts: node1
vars_files:
- vars/user1.yaml
tasks:
- name: add user {{ user }}
user:
name: "{{ user }}"
home: "{{ home }}"
state: present
- name: debug user
debug:
msg: >
username is {{ user}}
home is {{ home }}
[yuxb@controller web 11:48:28]$ mkdir vars
[yuxb@controller web 11:48:37]$ vim vars/user1.yaml
user: user1
home: /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 server
hosts: nodes
tasks:
- name: install the latest version of Apache
yum:
name: httpd
state: latest
- name: enable and start Apache
service:
name: httpd
state: started
enabled: yes
- name: enable and start Firewalld
service:
name: firewalld
state: started
enabled: yes
- name: set firewalld for http
firewalld:
service: http
permanent: yes
immediate: yes
state: enabled
# 这里做了修改 {{node}}
- name: prepare index.html
copy:
content: "hello world from {{node}}\n"
dest: /var/www/html/index.html
- name: check web server
hosts: node2
tasks:
- name: check web site
uri:
url: http://node1.yuxb.cloud
...
[yuxb@controller web 13:41:16]$ cat inventory
controller
# 这里指定了node1和2的node 所以输出也会是指定的
[nodes]
node1 node=Node1
node2 node=Node2
node3
node4
[nodes:vars]
node=Node
[yuxb@controller web 13:42:14]$ ansible nodes -m debug -a var=node
node3 | 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
node3
node4
# 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.yml
node: 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
│ └── 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 |
主机连接特殊变量
[yuxb@controller web 14:38:12]$ cat inventory
controller
[nodes]
web1
node2
node3
node4
[yuxb@controller web 14:38:15]$ ansible web1 -a id -e ansible_host=node1
web1 | 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 play
hosts: node1
vars:
users:
yuxb:
user_name: yuxb
home_path: /home/yuxb
laowang:
user_name: laowang
home_path: /home/laowang
tasks:
- name: add user {{users.yuxb.user_name}}
user:
name: '{{users.yuxb.user_name}}'
home: "{{users.yuxb.home_path}}"
- name: debug laowang
debug:
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 result
hosts: node1
tasks:
- name: Install the package
yum:
name: httpd
state: installed
register: 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]
node3
node4
[yuxb@controller web 15:24:23]$ vim deploy_web.yml
# 上面改成nodes,下面改成inventory_hostname
---
- name: deploy web server
hosts: nodes
tasks:
......
- name: prepare index.html
copy:
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://node1
hello world from node1
[yuxb@controller web 15:26:19]$ curl http://node2
hello world from node2
[yuxb@controller web 15:26:22]$ curl http://node3
hello world from node3
[yuxb@controller web 15:26:23]$ curl http://node4
hello world from node4
查看 Ansible 的主机组信息
# node1 所属主机组
[yuxb@controller web 15:43:10]$ ansible node1 -m debug -a var=group_names
node1 | SUCCESS => {
"group_names": [
"nodes",
"webs"
]
}
# 清单中所有主机组,以及主机组中主机
[yuxb@controller web 15:43:36]$ 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主机组......
[yuxb@controller web 15:43:48]$ ansible node1 -m debug -a var=groups.all
node1 | SUCCESS => {
"groups.all": [
"controller",
"node1",
"node2",
"node3",
"node4"
]
}
[yuxb@controller web 15:43:58]$ ansible node1 -m debug -a var=groups.nodes
node1 | SUCCESS => {
"groups.nodes": [
"node1",
"node2",
"node3",
"node4"
]
}
[yuxb@controller web 15:44:03]$ ansible node1 -m debug -a var=groups.dbs
node1 | 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 -l
277
# 查看指定主机的组信息
[yuxb@controller web 15:51:45]$ ansible node1 -m debug -a var=hostvars.node2.group_names
node1 | SUCCESS => {
"hostvars.node2.group_names": [
"nodes",
"webs"
]
}
# 查看指定主机的 Ansible 版本
[yuxb@controller web 15:52:04]$ 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"
}
}
管理 SECRETS
安全地管理敏感信息
# 创建 Vault 文件
# 目的:确保文件中存储的敏感信息是加密状态,防止明文泄露。
[yuxb@controller web 16:05:41]$ ansible-vault create user.yaml
New 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: yuxb
login_password: 123
login_host: 10.1.8.11
login_type: ssh
[yuxb@controller web 16:08:38]$ cat user.yaml
$ANSIBLE_VAULT;1.1;AES256
31633735336335633830643838326162633239386431363432313162633333326664656462623862
6661313937363233663064303361646365623563613166370a346138316439363761663563356136
34303337613338633036336434353639306631633230393966343635346439636161346433373633
6236636564623739330a383064633933623530656638623035316564633232306234373263633833
30396436653164396231636436383664643865396232343334636532353135336435343966663939
36326539353237346538653531376432373461316531393266636531623832363463393232323539
35343736313934303331376638313433613235333237313237306364623731633164336431643135
36393131663163613631663362613966663430346533353431653066313463386636303036303764
3236
# 解密 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: yuxb
login_password: 123
login_host: 10.1.8.11
login_type: ssh
# 加密 Vault 文件
# 将明文文件重新加密。
# 目的:恢复文件的安全状态。
[yuxb@controller web 16:09:42]$ ansible-vault encrypt user.yaml
New Vault password:
Confirm New Vault password:
Encryption successful
# 编辑 Vault 文件
# 在加密状态下直接修改文件内容。
# 目的:修改敏感信息而不暴露明文。
[yuxb@controller web 16:10:53]$ ansible-vault edit user.yaml
Vault 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: yuxb
login_password: 123123
login_host: 10.1.8.11
login_type: ssh
[yuxb@controller web 16:13:51]$ ansible-vault rekey user.yaml
Vault 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-vault
New 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: yuxb
login_password: 123123
login_host: 10.1.8.11
login_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 server
hosts: node1
tasks:
- name: install mariadb-server
yum:
name:
- mariadb-server
- python3-PyMySQL
state: present
- name: enable and start mariadb
service:
name: mariadb
enabled: yes
state: 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.yml
Encryption successful
[yuxb@controller web 16:46:31]$ ansible-vault view host_vars/node1/vaults.yml
user: yuxb
password: 123
host: '%'
priv: '*.*:ALL'
[yuxb@controller web 16:46:43]$ cat ansible.cfg
[defaults]
inventory = ./inventory
remote_user = yuxb
vault_password_file = ./password-for-vault
#ask_pass = True
#module_name = command
#private_key_file = /opt/id_rsa
#host_key_checking = False
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
[yuxb@controller web 16:46:50]$ cat password-for-vault
123
Ansible Playbook
在 node1
上自动化部署和配置 MariaDB 数据库服务
[yuxb@controller web 17:22:01]$ cat deploy_mariadb.yml
---
# 安装 MariaDB 及依赖
- name: config mariadb server
hosts: node1
tasks:
- name: install mariadb-server
yum:
name:
- mariadb-server
- python2-PyMySQL
state: present
# 启动并设置开机自启
- name: enable and start mariadb
service:
name: mariadb
enabled: yes
state: started
# 设置 root 密码
- name: set password for root
shell: mysqladmin password {{ password }}
# 删除匿名用户 anonymous@完整主机名
# ansible_fqdn 是 Ansible fact,表示主机的完整域名
# localhost 是本地主机的匿名用户。
- name: delete user anonymous@{{ ansible_fqdn }}
mysql_user:
login_host: localhost
login_port: 3306
login_user: root
login_password: "{{ password }}"
name: ""
host: "{{ ansible_fqdn }}"
state: absent
# 删除匿名用户 anonymous@localhost
- name: delete user anonymous@localhost
mysql_user:
login_host: localhost
login_port: 3306
login_user: root
login_password: "{{ password }}"
name: ""
host: "localhost"
state: absent
#删除测试数据库
# 作用:删除默认的 test 数据库(MariaDB 默认自带,用于测试,生产环境通常删除)。
- name: delete database test
mysql_db:
login_host: localhost
login_port: 3306
login_user: root
login_password: "{{ password }}"
name: test
state: absent
# 创建新用户
# {{ host }} → 用户允许访问的主机(如 % 或 localhost)
# {{ priv }} → 权限(如 *.*:ALL 表示所有库全权限)
- name: create user {{ user }}
mysql_user:
login_host: localhost
login_port: 3306
login_user: root
login_password: "{{ password }}"
name: "{{ user }}"
password: "{{ password }}"
host: "{{ host }}"
priv: "{{ priv }}"
state: present
# 创建新库
# 作用:创建一个新的数据库,名字由 {{ db_name }} 变量决定。
- name: create database db_name
mysql_db:
login_host: localhost
login_port: 3306
login_user: root
login_password: "{{ password }}"
name: "{{ db_name }}"
state: present
...
总结
这个 playbook 的功能就是:
-
安装 MariaDB 服务及依赖
-
启动并开机自启 MariaDB
-
设置 root 密码
-
删除匿名用户和默认测试数据库
-
创建自定义用户
-
创建自定义数据库
可以理解为 一个典型的 MariaDB 初始配置和安全加固的自动化流程。