- A+
一:ansible剧本
1:简介
一系列ansible命令的集合,使用yaml语言进行编写的,从上往下的执行,支持很多的特性,比如,将某个命令的状态作为变量给其他的任务执行,变量,循环,判断,错误纠正,可以是一个playbook或者是多个playbook执行
2:yaml基本语法
1、yaml约束
- 编写yaml的时候,不能使用tab键,只能使用空格(vim版本有关系,vim会自动将tab转化为4个空格键)
- 大小写严格区别,大写是大写,小写是小写;大写的变量和小写的变量是不一样的
- 使用缩进来表示一个层级关系
- 使用空格来表示层级关系,空格的数量没有限制,使用#表示注释
2、yaml数据类型
- 纯量:类似于变量的值,最小的单位。无法进行切割
- 数组(序列、列表):一组有次序的值,每一个 ‘-’ 就是一个列表
- 对象(键值对)字典、哈希、映射:key=value
3:playbook
1、简单的案例
[root@server mnt]# cat file1.yaml - name: touch file1 hosts: all tasks: - name: file: path: /opt/file1 state: touch
2、输出的信息解读
[root@server mnt]# ansible-playbook file1.yaml ##这个是playbook的名字 PLAY [touch file1] ************************************************************* #这个是playbook第一个任务,默认的任务。用于收集远程主机的各种信息,Ip等 TASK [Gathering Facts] ********************************************************* ok: [client] #工作任务 TASK ******************************************************************** changed: [client] #这个就是执行命令后的总结 PLAY RECAP ********************************************************************* client : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 #rc为0的话代表这个执行成功,不为0的话,代表这执行失败了
可以使用-v查看详细的信息,但是最多是4个v
3、playbook执行之前的检查
在执行之前使用命令检查一下
#检查语法的问题(正常的情况下) [root@server mnt]# ansible-playbook file1.yaml --syntax-check playbook: file1.yaml #错误的情况 [root@server mnt]# ansible-playbook file1.yaml --syntax-check ERROR! A malformed block was encountered while loading tasks: {'-name': {'file': {'path': '/opt/file1', 'state': 'touch'}}} should be a list or None but is <class 'ansible.parsing.yaml.objects.AnsibleMapping'> The error appears to be in '/mnt/file1.yaml': line 1, column 3, but may be elsewhere in the file depending on the exact syntax problem. The offending line appears to be: - name: touch file1 ^ here #还有一个就是语法正确但是输出有问题的情况下 使用-C就是模拟执行的,但不是真正的执行 [root@server mnt]# ansible-playbook file1.yaml -C PLAY [touch file1] ************************************************************************ TASK [Gathering Facts] ******************************************************************** ok: [client] TASK ******************************************************************************* ok: [client] PLAY RECAP ******************************************************************************** client : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4、多个剧本
很多个剧本写在了一起,有很多的任务
[root@server mnt]# cat file1.yaml - name: touch file1 hosts: client tasks: - name: file: path: /opt/file1 state: touch - name: touch file2 hosts: server tasks: - name: file: path: /opt/file2 state: touch
5、playbook结构上
1)主机或者主机组
定义play的名字,远程操作的主机,用户,提权等相关的配置
2)变量
定义变量,然后输出
[root@server mnt]# cat var.yaml - name: var hosts: client vars: RHCE: rhel9 tasks: - name: debug: var: RHCE
3)任务列表
就是tasks里面的
4)handler(特殊的任务)
通过监听某个task或者几个task,然后这个tasks执行成功后,并且状态是chaged,所有的任务执行成功后,最后再来执行
就是要先触发再来执行
[root@server mnt]# cat var.yaml - name: var hosts: client tasks: - name: file1 file: path: /mnt/file1 state: touch notify: get_status handlers: - name: get_status file: path: /mnt/file2 state: touch #要使用notify这个参数,来进行监听,类似与键值对的
必须是chaged才执行,并且其他的任务也没有出现错误,才行,
6、剧本出现了错误
现象:
[root@server mnt]# cat var.yaml - name: var hosts: client tasks: - name: file1 shell: ls /opt/qwqwqwq - name: file2 file: path: /mnt/file2 state: touch #后面的命令不会执行
总结:
1、就是运行剧本到一半的时候,出现了错误,会立刻的停止(对于当前任务的主机二样),不会影响到其他任务的主机(正常的运行),下次执行的话,就是从错误的地方开始执行
2、按照主机划分,执行任务
3、具有幂等性,就是一次和多次执行的结果都一样
如果期望值和执行的结果一致的话,则任务就是执行成功
如果任务执行的前后,内容上的修改(文件的时间戳也算),那么再次执行的任务的话,就会发生覆盖
解决的方法
1)命令模块报错
使用||,配置/usr/bin/true,让rc的返回值为0
[root@server mnt]# cat var.yaml - name: var hosts: client tasks: - name: file1 shell: ls /opt/qwqwqwq || /usr/bin/true - name: file4 file: path: /mnt/file4 state: touch
2)其他模块报错
使用ignore_errors: true跳过错误的任务,继续执行后面的任务
[root@server mnt]# cat var.yaml - name: var hosts: client tasks: - name: file1 shell: ls /opt/qwqwqwq ignore_errors: true - name: file3 file: path: /mnt/file3 state: touch 执行后,会报错,但是执行了后面的创建file3的操作
3)handler方法
使用handler来规避错误,如果有任务错误的话,handler也不会执行,所有的任务执行完成后,才会执行handler的task任务,强制去执行handler任务,加上一个字段force_handlers:yes即可
#中间有错误,依然执行 [root@server mnt]# cat var.yaml - name: var hosts: client force_handlers: yes tasks: - name: touch file10 file: path: /opt/file10 state: touch notify: get_status - name: chakan shell: ls /opt/121212 handlers: - name: get_status file: path: /opt/file11 state: touch #如果监听的是错误了 那就无法执行了
二:ansibel变量的定义和引用
1:为什么需要变量
ansible管理很多个主机的时候,就是他们的主机名或者顺序不一样什么的,就需要使用变量来进行一个统一的管理,或者端口什么的都可以使用变量来进行定义
1、变量有数字,字母,下划线,组成,
2、变量名可以是字母,不能以数字开头,严格区分大小写
3、在自定义变量的时候,不要以ansible开头,因为系统中有ansible开头的变量
案例:
[root@server mnt]# cat v1.yml - name: use vars hosts: client vars: name_rhel: rhel9 tasks: - shell: touch "/opt/{{name_rhel}}" #使用vars来定义变量,然后使用{{}}来引用变量
4、调试变量的方式
需要使用debug模块来进行调试,有2个参数来进行调试的,一个是var,另外一个是msg,2个不能一起使用
可以显示出变量的内容,但是其他的模块在执行剧本后,看不到返回的消息
msg:使用{{变量名}},来进行输出内容
var:name_rhel,age_rhel可以输出多个变量名
#专门打印变量的内容,无法打印信息 [root@server mnt]# cat v1.yml - name: use vars hosts: client vars: name_rhel: rhel9 tasks: - name: debug debug: var: name_rhel #mes可以打印内容,也可以打印信息 [root@server mnt]# cat v1.yml - name: use vars hosts: client vars: name_rhel: rhel9 tasks: - name: debug debug: msg: "this is a {{name_rhel}}"
2:主机清单中定义变量
内置变量:就是这些变量ansible都自定义好了的
ansible_become类似的,这种主机清单的优先级比配置文件的优先级高
1、定义主机变量
[root@server ansible]# cat hosts client webserver=nginx #使用ad-hoc调用debug模块,打印变量 client | SUCCESS => { "webserver": "nginx" }
2、定义主机组变量
#使用[主机组:vars]这样的方式来进行定义 [root@server ansible]# cat hosts client webserver=nginx [servers] client [servers:vars] rhelname=rhce 当然,如果主机组的变量和主机发生了冲突的话,以主机的优先级高为主 [root@server ansible]# cat hosts client rhelname=nginx [servers] client [servers:vars] rhelname=rhce client | SUCCESS => { "rhelname": "nginx" }
3、通过主机和主机组的目录文件定义变量
里面定义的都是键值对的方式来定义的,注意格式的规范
与hosts文件在同一个路径下创建2个目录,然后主机名为命名的文件即可
主机的目录hosts_vars,以主机名为命名的文件,只有该主机能够引用变量
mkdir /etc/ansible/host_vars [root@server host_vars]# cat client name: rhel9 引用变量: [root@server host_vars]# ansible client -m shell -a 'echo "{{name}}"' client | CHANGED | rc=0 >> rhel9
主机组的目录为group_vars,只有该主机组能引用变量
mkdir /etc/ansible/group_vars [root@server group_vars]# cat servers age: 100 #引用变量 [root@server group_vars]# ansible servers -m shell -a 'echo "{{age}}"' client | CHANGED | rc=0 >> 100
2、剧本中定义变量
1:vars来定义
就是在task任务之前定义即可,可以定义多次变量
就是定义了这个变量之后,然后可以不用重复的写这个内容,直接调用这个变量即可,代码就省略了很多,但是呢,这个变量是已经写死了的
#Vars定义,输出出来 [root@server mnt]# cat v1.yaml - name: use vars hosts: client vars: rname: rhel9 rage: 80 tasks: - name: use shell shell: echo "{{rname}} {{rage}}" > /opt/file111 #使用debug模块来进行引用变量 使用var来引用变量 [root@server mnt]# cat v1.yaml - name: use vars hosts: client vars: rname: rhel9 rage: rrr tasks: - name: use debug debug: var: rname,rage #使用msg来引用变量 [root@server mnt]# cat v1.yaml - name: use vars hosts: client vars: rname: rhel9 rage: rrr tasks: - name: use debug debug: msg: this is "{{rname}} {{rage}}n"
2:vars_files来定义
引入外部的变量文件,外部的变量文件的内容是字典的形式,不能使用列表的形式(-)
使用vars_files这个参数
引用的话使用 键.键的方式来引用变量即可
文件的写法 第一种写法 [root@server mnt]# cat file1.yaml user: name: zhangshan age: 18 sex: boy 引用变量 #使用{{user.name}}这中键的方式来获取值 [root@server mnt]# cat file1.yaml user: name: zhangshan age: 18 sex: boy [root@server mnt]# cat v1.yaml - name: use vars hosts: client vars_files: - /mnt/file1.yaml tasks: - name: shell shell: echo "{{user.name}}" >> /opt/file111 #第二种写法,就是一个大的字典 [root@server mnt]# cat file2.yaml users: job: name: aaa age: 90 joe: name: bbb age: 80 #输出 [root@server mnt]# cat v1.yaml - name: use vars hosts: client vars_files: - /mnt/file2.yaml tasks: - name: shell shell: echo "{{users.joe.name}}" >> /opt/file111
3:注册变量
就是将一个任务的执行结果注册为一个变量
使用关键字register去得到任务的执行结果
#就是如果使用剧本来执行任务的话,就是不显示详细的信息,可以使用注册变量来让其显示详细的信息,并且也可以按照指定的变量来进行输出 [root@server mnt]# cat v1.yaml - name: use vars hosts: client tasks: - name: shell shell: ls /etc/passwd register: get_status - debug: var: get_status #执行这个剧本 ok: [client] => { "get_status": { "changed": true, "cmd": "ls /etc/passwd", "delta": "0:00:00.002357", "end": "2024-03-26 16:43:34.086058", "failed": false, "rc": 0, "start": "2024-03-26 16:43:34.083701", "stderr": "", "stderr_lines": [], "stdout": "/etc/passwd", "stdout_lines": [ "/etc/passwd" ] } } 可以指定要的变量 [root@server mnt]# cat v1.yaml - name: use vars hosts: client tasks: - name: shell shell: ls /etc/passwd register: get_status - debug: var: get_status.rc #一般应用的场景就是需要收集被控节点的信息,并且将其保存到主控节点上面 #使用这个变量来对其进行操作 [root@server mnt]# cat v1.yaml - name: use vars hosts: client tasks: - name: shell shell: ls /etc/passwd register: get_status - copy: content: "{{get_status.rc}}" dest: /opt/file222
案例:就是收集被控节点的信息,然后拷贝到主控节点上面去
先使用copy模块,将信息存放到被控节点上面,在使用fetch模块,将信息存放到主控节点上面
4:命令模式来定义变量
使用临时定义的变量,非常的有用
[root@server mnt]# ansible --help|grep EXTRA_VARS [-e EXTRA_VARS] [--vault-id VAULT_IDS] -e EXTRA_VARS, --extra-vars EXTRA_VARS #使用剧本的方式,临时定义变量 [root@server mnt]# cat v1.yaml - name: use vars hosts: client tasks: - name: file file: path: "{{path}}" state: touch #如果有多个变量的话,就使用双引号加上逗号 [root@server mnt]# ansible-playbook v1.yaml -e "path=/opt/eeee" #使用ad-hoc来定义变量 [root@server mnt]# ansible client -m debug -a 'var=rhel_name' -e rhel_name=90 client | SUCCESS => { "rhel_name": "90" }
5:fact变量
事实变量,就是收集被控节点的主机的信息,然后定义一个变量,通过setup模块可以收集被控节点的主机信息,然后通过setup模块中中的facts参数,专门用来进行收集主机信息,通过这种方式收集到的信息称为facts变量(事实变量)
ansible_facts变量中有很多的信息,主机名、网卡设备、ip地址、磁盘和磁盘空间、文件系统bios版本,架构等
[root@controller ansible]# ansible node1 -m setup| head -n 10 node1 | SUCCESS => { "ansible_facts": { "ansible_all_ipv4_addresses": [ "172.25.250.20" ], "ansible_all_ipv6_addresses": [ "fe80::20c:29ff:fea3:688c" ], "ansible_apparmor": { "status": "disabled" 很多的信息会被输出来
执行playbook的时候,默认会收集被控节点的主机信息(gather facts)
1、可以使用filter进行过滤
注意的就是只能过滤出ansible_facts的下一层级的变量,如果有多个层级的话,不能进行收集
1)通过指定的方式进行收集
使用的变量名就是精确的
[root@controller ansible]# ansible node1 -m setup -a "filter=ansible_all_ipv4_addresses" node1 | SUCCESS => { "ansible_facts": { "ansible_all_ipv4_addresses": [ "172.25.250.20" ], "discovered_interpreter_python": "/usr/bin/python" }, "changed": false }
2)通过通配符来收集信息
[root@controller ansible]# ansible node1 -m setup -a "filter=ansible_*addresses" node1 | SUCCESS => { "ansible_facts": { "ansible_all_ipv4_addresses": [ "172.25.250.20" ], "ansible_all_ipv6_addresses": [ "fe80::20c:29ff:fea3:688c" ], "discovered_interpreter_python": "/usr/bin/python" }, "changed": false }
2、导出facts变量到文件,引用facts变量
就是将变量文件导出来,然后存放到被控节点上面去
直接的引用fact变量,因为的话,就是在执行剧本的时候,会默认的收集这些信息,直接输出想要的信息即可
#导出facts变量 2钟的方式进行导出 //保存到主控节点上面鹅,但是都是一行,不容易的看 [root@controller nod1-setup.txt]# ansible node1 -m setup --tree /opt/nod1-setup.txt 第二种方式直接使用重定向的,直接重定向到一个文件里面去即可 [root@controller opt]# ansible node1 -m setup > /opt/node1setup.txt 内容不在同一行上,可以使用搜索 #引用变量 [root@controller opt]# cat facts.yml - name: facts hosts: node1 tasks: - debug: var: ansible_all_ipv4_addresses 直接使用debug模块来进行引用
3、禁用facts变量的收集
默认是开启的收集,禁用收集
[root@controller opt]# cat facts.yml - name: facts hosts: node1 gather_facts: no tasks: - debug: var: ansible_all_ipv4_addresses #再次执行这个剧本的时候,就会报错,说没有定义这个变量 ok: [node1] => { "ansible_all_ipv4_addresses": "VARIABLE IS NOT DEFINED!" }
使用模块,还是能够收集信息,但是这种情况不常见
[root@controller opt]# cat facts.yml - name: facts hosts: node1 gather_facts: no tasks: - setup: - debug: var: ansible_all_ipv4_addresses #执行剧本 ok: [node1] => { "ansible_all_ipv4_addresses": [ "172.25.250.20" ] }
4、自定义的facts事实变量
让每一个主机都有自己的facts变量,每一个facts变量都是存放到/etc/ansible/facts.d目录下
定义的格式有要求的:ini或者yaml,文件的后缀必须是.fact结尾(ini格式的话就是网卡的配置文件的格式)
自定义facts事实变量
#创建一个自定义变量的文件,后缀为fact结尾的 [root@controller mnt]# cat userinfo.fact [user] name = zhangsan age = 18 sex = boy #创建yaml文件 #并将变量文件文件拷贝过去 [root@controller mnt]# cat userinfo.yaml - name: user fact hosts: node1 tasks: - name: create dir file: path: /etc/ansible/facts.d state: directory - name: copy copy: src: /mnt/userinfo.fact dest: /etc/ansible/facts.d #引用变量自定义变量文件 #自定义变量文件使用ansible_local [root@controller mnt]# ansible node1 -m setup -a "filter=ansible_local" node1 | SUCCESS => { "ansible_facts": { "ansible_local": { "userinfo": { "user": { "age": "18", "name": "zhangsan", "sex": "boy" } } }, "discovered_interpreter_python": "/usr/bin/python" }, "changed": false }
6:set_fact模块
可以生成一个变量,获取不同主机的版本,然后可以使用shell模块,来将这个变量的值保存到本地的主机上面去
将rhel和版本拼接在一起,然后赋值给另外的一个变量,直接引用这个fact事实变量,然后进行拼接
使用setup模块,来进行拼接,赋值给一个变量,使用debug模块来进行输出 [root@controller mnt]# cat set.yaml - name: setfact hosts: node1 tasks: - set_fact: get_version: "{{ ansible_distribution }}--{{ansible_distribution_version}}" - debug: var: get_version ok: [node1] => { "get_version": "RedHat--9.0" }
7:lookup变量
都还是就需要set_fact这个模块,来生成一个变量,来使用lookup这个参数,为这个变量赋值
在某些的情况下,需要引用外部的内容来作为内容,可以将主控节点的公钥作为变量,然后传输到被控节上面去
lookup变量能够从文件,命令,变量中作为变量的值
1、从文件中赋值变量
格式: get_passwd:"{{loopup('file','/etc/passwd')}}" 就是将主控节点这个值赋给了get_passwd这个变量
案例:
#将一个/etc/passwd这个文件拷贝到被控节点上面 #这个就是将这个文件里面的内容都拷贝到这个被控节点的文件上面去了 [root@controller mnt]# cat look.yml - name: look hosts: node1 tasks: - set_fact: fff: "{{lookup('file','/etc/passwd')}}" - name: copy copy: content: "{{fff}}" dest: /opt/passwd
2、从命令中赋值给变量
格式:get_passwd:"{{lookup('pipe','date +%T')}}"
就是将命令的结果作为变量的值
#天剑一个用户,并且密码是redhat #使用这个来进行一个密码的加密,然后赋值给pd这个变量名,最后使用user模块,来引用这个变量 [root@controller mnt]# cat user.yml - name: create user hosts: node1 tasks: - name: passwd set_fact: pd: "{{lookup('pipe','openssl passwd -6 redhat')}}" - name: created user user: name: q1000 password: "{{pd}}"
3、从变量中赋值
就是从环境变量中进行赋值
#直接引用这个环境变量 #最后使用debug模块来进行输出 [root@controller mnt]# cat env.yml - name: env hosts: node1 tasks: - name: env set_fact: get_env: "{{lookup('env','HOME')}}" - name: debug debug: msg: "{{get_env}}" ok: [node1] => { "msg": "/root" }
8:魔法变量
就是内置的变量, 内置变量有特殊含义的就被称为魔法变量
fact变量只有运行的主机才能够调用,就是只想要所有的被控节点都调用node1的主机的主机名
1、hostvars
获取指定主机的变量信息,可以使用主机名或者主机ip地址(主机清单和- hosts中指定都是ip地址才可以使用ip地址),让所有的主机都获取主机node1的主机名,说到底还是获取的是facts变量的内容,setup模块中的
案例:
#在很多的主机名中指定输出一个主机名即可 [root@controller mnt]# cat magic.yaml - name: magic hosts: node1 tasks: - debug: msg: "{{hostvars['node1'].ansible_default_ipv4.address}}"
2、inventory_hostname
列出当前运行的任务的主机(常常和when判断使用)
when是一个判断语句,判断是不是当前的主机名,如果不是则不执行任务,是的话,就执行任务
#当前的主机名是node1的话就执行 - name: magic hosts: node1 tasks: - debug: var: ansible_hostname when: inventory_hostname == 'node1' ok: [node1] => { "ansible_hostname": "node1" }
3、groups
groups列出当前主机清单中的所有的主机组,groups.all列出所有的主机(常用于循环)
groups.web列出web主机组的主机
案例:
#列出主机清单中的所有的主机组和主机 #groups就能列出所有的 - name: magic hosts: node1 tasks: - debug: var: groups #列出所有的主机 - name: magic hosts: node1 tasks: - debug: var: groups.all #列出web中的主机 - name: magic hosts: node1 tasks: - debug: var: groups.web
总结:
1、错误的补救的方法
ignore_errors,忽略这个错误,然后继续执行下一个任务
handlers这个强制的使用,通过监听的方式
2、变量
vars和vars_files,命令模式来定义变量这些都是只能定义一些普通的变量
facts就是收集被控节点的主机的信息
register注册变量就是收集命令的执行结果返回的数据,可以指定结果的输出
set_fact变量:就是可以生成一个变量,
可以根据fact变量直接调用,然后赋值
lookup直接赋值,就是直接使用一个值
自定义赋值
魔法变量的话:也是内置的变量,只不过有特殊含义的变量,可以输出指定的主机名,指定的主机组,以及和when常常使用的能输出当前的是哪一个主机组