Ansible临时命令ad-hoc

ansible中有两种模式,分别是ad-hoc模式和playbook模式
​
ad-hoc简而言之,就是"临时命令"
https://docs.ansible.com/ansible/latest/user _guide/intro adhoc.html
临时命令非常适合您很少重复的任务。例如,如果您想在圣诞节假期关闭实验室中的所有机器。
​
Ansible ad-hoc 命令使用/usr/bin/ansible命令行工具在一个或多个托管节点上自动执行单个任务。

Ansible palybook介绍

Ansible Playbooks 提供了一个可重复、可重用、简单的配置管理和多机部署系统,非常适合部署复杂的应用程序。
如果您需要多次使用Ansible执行任务,请编写剧本并将其置于源代码控制之下。

1.Playbooks是Ansible的配置,部署和编排语言。它们可以描述您希望远程系统执行的策略,或一般IT流程中的一组步骤。

如果说ansible 模块 是你车间里的工具,那么playbooks 是你的说明书/使用手册,并且资源清单上的主机是你的原材料。

在基本层面上,剧本可以用于管理远程主机的配置与部署,在更高的一层来说,它们可以对涉及滚动更新的多层发布任务进行排序,并且可以将操作委派给其他主机,同时与监视服务器和负载平衡器进行交互。

官网介绍的说playbooks 篇章有很多的内容,他们不建议我们一下子学完,重在积累,以及在使用的过程中来学习,建议不错。嘿嘿

在ansible 上使用Playbooks是一种完全不同于ad-hoc的任务执行模式,并且特别强大。简单地说,playbooks是一个非常简单的配置管理和多机器部署系统的基础,以及非常适合部署复杂应用程序的系统。

Playbooks可以对任务进行编排,就像我们要安装一个程序,写个安装shell脚本一样,在哪一步复制配置文件,最后一步启动服务。虽然/usr/bin/ansible 可以运行一些临时任务,但是针对复杂的配置,并且可以将配置标准化,这个时候就需要Playbooks了。

2.Playbooks Language example

Playbooks 语言是以YAML 格式表示,并且有最小的语法,有意的说明它不是一个编程或者脚本语言,它是一种写配置文件的语言。简洁可读性高。

YAML 语法

在线json转换yaml

https://tooltt.com/json2yaml/

https://www.bejson.com/json/json2yaml/#google_vignette

yaml转json

http://www.esjson.com/jsontoyaml.html

yaml特点

  1. 严格的缩进(空格数)表示层级关系(一般敲2个空格表示一个层级关系)

  2. 不要使用tab键

  3. 冒号 : 后面一定得有空格

  4. 短横线 - 后面一定得有空格

  5. 剧本文件名必须是yaml或者yml,程序可以读取,以及vim提供颜色高亮

安装nginx的示例对比

ad-hoc命令模式

ansible web -m yum -a "name=nginx state=absent"
ansible web -m shell -a "rpm -qa nginx warn=false"

palybook模式

  • 语法的对齐,不得多一个少一个空格

  • 输入法保证英文

写法一:变量风格
[root@master-61 /opt]#cat install_nginx.yml
#install nginx yaml ,by yangge
- hosts: web
  tasks:
    - name: Install nginx Package
      yum: name=nginx state=present
    - name: Start Nginx Package
      systemd: name=nginx state=started
    - name: Copy Nginx.conf
      copy: src=./nginx.conf dest=/etc/nginx/nginx.conf mode=0664
  
写法二:字典风格
[root@master-61 /opt]#cat install_nginx.yml  
---
- name: install nginx yaml ,by yangge
  hosts: nfs
  tasks: 
  - name: 01 安装nginx
    yum: 
      name: nginx
      state: installed
  - name: 02 启动nginx
    systemd:
      name: nginx
      state: started
  - name: 03 复制Nginx.conf
    copy:
      src: ./nginx.conf
      dest: /etc/nginx/nginx.conf 
      mode: 0664
      
      
2.可以去验证yaml语法是否正确
ansible-playbook  -C  install_nginx.yml 

解释如上的playbook代码,按行解释

1.表示注释信息,可以用#,也可以用--- 三个短横线
2.定义playbook管理的目标主机,all表示所有的主机,也可以写 `主机组名`
3.定义playbok所有的任务集合信息,比如该文件,定义了3个任务,安装nginx,启动nginx,拷贝nginx配置文件
4.定义了任务的名词,自定义的帮助信息
5.定义任务的具体操作,比如这里用yum模块实现nginx的安装
6.注释信息
7.第六、第七两行作用是使用copy模块,把本地当前的nginx.conf配置文件,分发给其他所有客户端机器,且授权

playbook组成规范

  • hosts: 需要执行的机器

  • tasks: 需要执行的任务

  • name: 任务名称

剧本就像演员演戏,导演提供的文字资料,因此剧本重要的就是定义演员的信息,演员的任务而Ansible的剧本也是由最基本的两个部分组成

  • hosls定义剧本管理的主机信息(演员有哪些)

  • tasks定义被管理的主机需要执行的任务动作(演员需要做什么事)

剧本的hosts部分

定义剧本管理主机信息有一个重要的前提,就是被管理的主机,必须在Ansible主机清单文件中定义 也就是默认的 /etc/ansible/hosts,否则剧本无法直接管理对应主机。 定义剧本的hosts部分,可以有如下多种方式,常见的有

# 方式一:定义所管理的主机IP地址
- hosts: 192.168.178.111
  tasks: 
    动作...
​
# 方式二:定义所管理主机的名字
- hosts: backup01
  tasks:
    动作...
​
# 方式三:定义管理主机
- hosts: 192.168.178.111, rsync01
  tasks:
    动作...
​
# 方式四:管理所有主机
- hosts: all
  tasks:
    动作...

剧本的tasks部分

具体模块的参数,如何在yaml中填写,语法有2个

  • 字典形式定义任务

  • 变量形式定义task任务

字典风格的模块参数

[root@master-61 /opt]#cat install_nginx.yml 
---
- name: 这是一个安装nginx的剧本
  hosts: 172.16.1.7,172.16.1.8,nfs
  tasks: 
  - name: 01 安装nginx
    yum: 
      name: nginx
      state: installed
  - name: 02 启动nginx
    systemd:
      name: nginx
      state: started

变量风格的模块参数

[root@master-61 /opt]#cat vars_install_nginx.yml 
---
- name: 这是一个安装nginx的剧本
  hosts: 172.16.1.7,172.16.1.8,nfs
  tasks: 
  - name: 01 安装nginx
    yum: name=nginx state=installed 
  - name: 02 启动nginx
    systemd: name=nginx state=started
  - name: 03 设置nginx开机自启
    systemd: name=nginx enabled=yes

yaml支持的数据类型

yaml这个语法中,只有三个数据类型

  • 字典类型,特点就是 key : value形式

  • 列表形式,特点是 通过 短横线定义

  • 纯变量形式

数据类型
YAML 支持以下几种数据类型:
​
对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
​
数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
​
纯量(scalars):单个的、不可再分的值
​
我们都以python的数据类型来称呼yaml中支持的数据类型

字典类型

key : value 类型 字典键值对使用冒号结构表示 key: value,冒号后面要加一个空格。 支持字典嵌套

列表类型

- 短横线开头,表示构成一个列表

你写一个 - 短横线,就是定义了一个新列表

image-20240923164012024

在python中列表形式为 [1,2,3,'a','b','李四'],非常强大
​
shell也支持列表(数组),表示形式为,功能比较单一
[root@yuchao-tx-server ~]#students=("张三" "李四" "王五")
[root@yuchao-tx-server ~]#set|grep students
students=([0]="张三" [1]="李四" [2]="王五")
[root@yuchao-tx-server ~]#echo ${students[0]}
张三
[root@yuchao-tx-server ~]#echo ${students[1]}
李四
[root@yuchao-tx-server ~]#echo ${students[2]}
王五

纯变量类型

表示单纯的变量,支持数据类型有

  • 字符串

  • 布尔值

  • 整数

  • 浮点数

  • Null

  • 时间

  • 日期

实例

1.一个短横线

[
  {
    "老师": "马超",
    "男同学": ["张三 李四 王五"],
    "女同学": ["小红"]
  }
]

转变为yaml格式

- 老师: 马超
  男同学:
    - 张三 李四 王五
  女同学:
    - 小红

2.多个短横线

[
  {
    "老师": "马超",
    "男同学": ["张三","李四","王五"],
    "女同学": ["小红"]
  }
]

转变为yaml格式

- 老师: 马超
  男同学:
    - 张三
    - 李四
    - 王五
  女同学:
    - 小红

关于yaml的短横线定义列表

短横线用于定义列表
​
一个短横线,同一个缩进下的元素,表示是一个整体,大字符串
​
- xxx
  ooo
  ddd
  ccc
  
其实处理的数据是 "xxx ooo ddd ccc"
​
多个短横线,同一个缩进下的元素,表示一个列表中的多个元素
- xxx
- ooo
- ddd
- ccc
​
"xxx", "ooo","ddd","ccc"
识别为,同一个列表下的多个元素

JSON

Json的特点

JSON(JavaScript Object Notation,Js 对象标记))是一种轻量级的数据交换格式; 完全独立于编程语言的文本格式来存储和表示数据; 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。 数据传输是我们在敲代码时,经常遇到的一个场景,前后端交互。 给数据一个统一的格式有利于我们编写和解析数据。

json语法

JSON 是一种数据格式。它本身是一串字符申,只是它有固定格式的字符串,符合这个数据格式要求的字符串,我们称之为 JSON。

JSON 可以和任意编程语言交互,C、golang、java、python等,再转变为程语言特有的数据类型;

JSON 键值对数据结构如上图,以 “{”开始,以 ”}”结束。中间包裹的为 Key :Value 的数据结构。

在线格式化json

json语法规则

JSON 语法是 JavaScript 对象表示语法的子集。

  • 数据在名称/值对中

  • 数据由逗号分隔

  • 大括号 { } 保存字典

  • 中括号 [ ] 保存列表

{
    "name": "歌手",
    "女歌手": ["邓紫棋,徐佳莹,杨乃文"],
    "男歌手": [
        "周杰伦",
        "陈奕迅",
        "薛之谦"
    ]
}

json数据类型

键值对(字典)

JSON 数据的书写格式是:
key : value
​
{
    "name": "吴彦祖",
    "age": 18,
    "tel": null
}

json值

json的值也就是value、可以是如下的数据类型

JSON 值可以是:

  • 数字(整数或浮点数)

  • 字符串(在双引号 " " 中)

  • 逻辑值(true 或 false)

  • 数组(在中括号 [ ] 中)

  • 对象(在大括号 { } 中)

  • null

数字类型

{
"age": 18
}

键值类型

{
    "name": "吴彦祖",
    "hoboy": {
        "study": "linux",
        "play": [
            "movie",
            "music"
        ]
    }
}

列表

列表就是包裹了多个数据,json支持复杂的数据嵌套,通过这样的层级关系,可以很轻松的提取数据

{
    "name": "吴彦祖",
    "hoboy": {
        "study": "linux",
        "play": ["movie,music"],
        "friends": [
            {
                "name": "樵夫",
                "age": 30
            },
            {
                "name": "张三丰",
                "age": 300
            }
        ]
    }
}

布尔值

json的布尔值可以是真假值

{
    "name": "新垣结衣",
    "你老婆": false,
    "我老婆": true
}

空值

{
    "name": "新垣结衣",
    "联系方式": null
}

jq命令

jq工具Linux系统默认没有安装需要手动安装一下

yum install jq -y
2.简单json数据提取
​
[root@master-61 ~]#echo   '{"name":"德玛西亚","price":6888}'   | jq  
​
提取名字的值,价格
#必须使用jq命令的过滤器,如下的语法
语法是 通过 . 提取,比如 jq '.name,.price'
​
[root@master-61 ~]#echo   '{"name":"德玛西亚","price":6888}'   | jq  '.name'
"德玛西亚"
[root@master-61 ~]#echo   '{"name":"德玛西亚","price":6888}'   | jq  '.price'
6888
​
[root@master-61 ~]#echo   '{"name":"德玛西亚","price":6888}'   | jq  '.price,.name'
6888
"德玛西亚"
​
3.数据再多一点
[root@master-61 ~]#echo   '{"name":"德玛西亚","price":6888,"hero_logo":"https://www.tukuppt.com/muban/zanyjwnk.html"}'   | jq '.price,.hero_logo,.name'
6888
"https://www.tukuppt.com/muban/zanyjwnk.html"
"德玛西亚"

串行执行(jq提供的管道符)

也就是字典套字典。 列表套字典;一层层嵌套,一层层拆,提取数据。

[root@master-61 ~]#echo '{"students":[{"name":"吴彦祖","age":35},{"name":"狗蛋","female":true},{"name":"金三胖","sddress":"朝鲜"}]}' > test.json
 
[root@master-61 ~]#cat test.json |jq
{
  "students": [
    {
      "name": "吴彦祖",
      "age": 35
    },
    {
      "name": "狗蛋",
      "female": true
    },
    {
      "name": "金三胖",
      "sddress": "朝鲜"
    }
  ]
}
​
#先拆第一层,提取studetns的值
[root@master-61 ~]#cat test.json |jq '.students'
 [
  {
    "name": "吴彦祖",
    "age": 35
  },
  {
    "name": "狗蛋",
    "female": true
  },
  {
    "name": "金三胖",
    "sddress": "朝鲜"
  }
]
​
#再拆第二层,拿到列表中的全部值
[root@master-61 ~]#cat test.json |jq '.students |.[]'
 {
  "name": "吴彦祖",
  "age": 35
}
{
  "name": "狗蛋",
  "female": true
}
{
  "name": "金三胖",
  "sddress": "朝鲜"
}
​
#提取狗蛋和金三胖的值
[root@master-61 ~]#cat test.json |jq '.students |.[1:3]'
 [
  {
    "name": "狗蛋",
    "female": true
  },
  {
    "name": "金三胖",
    "sddress": "朝鲜"
  }
​
#提取第二个列表中的值
[root@master-61 ~]#cat test.json |jq '.students |.[1] |.name,.female'
"狗蛋"
true
​
#提取所有name的值
[root@master-61 ~]#cat test.json |jq '.students |.[] |.name'
"吴彦祖"
"狗蛋"
"金三胖"
 
#提取female的值
[root@master-61 ~]#cat test.json |jq '.students |.[1] |.female'
true

练习JSON转YAML

{
  "0024": {
    "老师": "杨",
    "学生们": [
      {
        "张三": [
          {
            "年龄": "23",
            "地址": "重庆"
          }
        ],
        "李四": [
          {
            "年龄": "24",
            "地址": "上海"
          }
        ],
        "王五": {
          "年龄": "25",
          "地址": "北京"
        }
      }
    ]
  }
}
"0024":
  "老师": "杨"
  "学生们":
    - "张三":
      - "年龄": "23"
        "地址": "重庆"
      "李四": 
       - "年龄": "24"
         "地址": "上海"
      "王五": 
         "年龄": "25"
         "地址": "北京"

Palybook剧本高级特性篇

循环

在写 playbook 的时候发现了很多 task 都要重复引用某个相同的模块,比如一次启动10个服务,或者一次拷贝10个文件,如果按照传统的写法最少要写10次,这样会显得 playbook 很臃肿。

如果使用循环的方式来编写 playbook ,这样可以减少重复编写 task 带来的臃肿。

https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html?highlight=loop
关于循环的标准用法
早期ansible教程中,关于循环关键字是with_item
ansible自2.5版本后,通过loop关键字提供循环功能
​
[root@master-61 /script]#ansible --version
ansible 2.9.27
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]

创建多个系统用户

需求,在nfs机器组中创建5个用户test1~5,且均设置密码yangge666

比较low的写法,看着都头疼,但是没办法,语法就是这样

(添加、或是删除用户,区别在于state=presentstate=absent

执行剧本
[root@master-61 ~]#ansible-palybook create_user.yml
创建用户present
[root@master-61 ~]# cat create_user.yml
---
- name: create user test1~5
  hosts: nfs
  tasks:
    - name: create test1
      user: 
        name: test1
        state: present
    - name: create test2
      user: 
        name: test2
        state: present
    - name: create test3
      user: 
        name: test3
        state: present
    - name: create tes4
      user: 
        name: test4
        state: present
    - name: create test5
      user: 
        name: test5 
        state: present
        
删除用户(把present替换为absent就行)
[root@master-61 ~]# sed -i s#present#absent#g create_user.yml

循环创建用户

循环创建用户并设置密码yangge666
​
--- 
- name: create test1~5
  hosts: nfs
  tasks:
    - name: create test1~5
      user:
        name: "{{ item }}"
        state: present
      loop:
        - test1
        - test2
        - test3
        - test4
        - test5
    - name: set password
      shell: echo 'yangge666' |passwd --stdin "{{item}}"
      loop:
        - test1
        - test2
        - test3
        - test4
        - test5

循环删除用户

--- 
- name: create test1~5
  hosts: nfs
  tasks:
    - name: create test1~5
      user:
        name: "{{ item }}"
        state: absent
      loop:
        - test1
        - test2
        - test3
        - test4
        - test5

通过vars变量定义循环变量

上面会发现已然有重复的变量,还可以再简化

  • 通过vars关键字定义用户列表,变量一般定义在任务开始之前

  • 通过item关键字提取loop中每次循环的数据

循环创建用户且设置密码

---
- name: create user
  hosts: nfs
  vars:
     users_list:
      - test1
      - test2
      - test3
  tasks:
    - name: create user
      user:
        name: "{{ item }}"
        state: present
      loop: "{{ users_list }}"
​
    - name: set password
      shell: echo 'yuchao666' | passwd --stdin "{{ item }}"
      loop: "{{ users_list }}"

循环处理字典数据

创建用户以及用户id号

循环字典数据如下,字典就是key:value这样的数据

字典用法,主要是根据key、获取value

---
- name: create user
  hosts: nfs
  tasks: 
  - name: create user and uid
    user:
        name: "{{ item.user }}"
        uid: "{{ item.uid }}"
​
    loop:
    - {user: 'test1', uid: '2000'}
    - {user: 'test2', uid: '2001'}
    - {user: 'test3', uid: '2002'}
    - {user: 'test4', uid: '2003'}

写法2:通过vars定义字典数据

  • vars定义字典数据

  • loop提供循环功能,通过item变量提取循环数据

---
- name: create user
  hosts: nfs
  vars:
    users_list:
    - {user: 'test1', uid: '2000'}
    - {user: 'test2', uid: '2001'}
    - {user: 'test3', uid: '2002'}
    - {user: 'test4', uid: '2003'}
  tasks:
  - name: create user and uid
    user:
      name: "{{ item.user}}"
      uid: "{{ item.uid }}"
    loop: "{{ users_list }}"

循环安装多个软件(yum基础环境安装)

在咱们期中综合架构开篇时,需要大家系统初始化,这个初始化步骤也是需要你做成ansible脚本的。
比如如下大量的基础软件,如何安装?
​
yum install -y tree wget bash-completion bash-completion-extras lrzsz net-tools sysstat iotop iftop htop unzip telnet ntpdate lsof

必然不能挨个的执行yum模块去安装,那得累死,循环写法如下

- name: www.haloyang.top
  hosts: nfs
  remote_user: root
  tasks:
    - name: install basic packages
      yum:
        name: "{{ item }}"
        state: installed
      loop:
        - tree
        - wget 
        - bash-completion 
        - bash-completion-extras 
        - lrzsz 
        - net-tools 
        - sysstat 
        - iotop 
        - iftop
        - htop
        - unzip
        - telnet 
        - ntpdate
        - lsof

写法2:通过vars定义变量

- name: www.haloyang.top
  hosts: nfs
  remote_user: root
  vars:
    basic_packages:
        - tree
        - wget 
        - bash-completion 
        - bash-completion-extras 
        - lrzsz 
        - net-tools 
        - sysstat 
        - iotop 
        - iftop
        - htop
        - unzip
        - telnet 
        - ntpdate
        - lsof
  tasks:
    - name: install basic packages
      yum:
        name: "{{ item }}"
        state: installed
      loop: "{{ basic_packages }}"

rsync文件夹场景

比如部署nfs、rsync、nginx的综合剧本;
1.要安装多个软件
2.创建多个目录
3.复制多个目录
4.每个文件的权限都不一样

循环风格1:单行模式

比如rsync创建备份目录,有多个目录需要创建,普通的写法出现了诸多重复语句

- name: create data dir
  file: path=/data state=directory owner=www group=www
​
- name: create backup dir
  file: path=/backup state=directory owner=www group=www

循环风格2:缩进模式

上述创建备份目录的剧本语法,也可以用如下的缩进模式写,但是依然很多重复语句

- name: create data dir
  file:
    path: /data
    state: directory
    owner: www
    group: www
​
- name: create backup dir
  file:
    path: /backup
    state: directory
    owner: www
    group: www

改造为循环语句,使用yaml的缩进语法

- name: create data,backup dir
  file:
    path: "{{ item }}"
    state: directory
    owner: www
    group: www
  loop:
    - /data
    - /backup

循环风格3:混合语法

  • 等号赋值语法

  • 缩进语法

- name: create data backup 
  file: path="{{ item }}" state=directory owner=www group=www
  loop:
    - /data
    - /backup

循环风格4:循环结合字典取值

比如rsync服务部署,需要创建多个文件夹,以及对应的权限设置

- name: www.haloyang.top
  hosts: backup
  tasks:
  - name: create_data
    file:
      path: "{{ item.file_path }}"
      state: directory
      owner: www
      group: www
      mode: "{{ item.mode }}"
    loop:
      - { file_path: '/data' ,mode: '755' }
      - { file_path: '/backup' ,mode: '755' }

vars变量定义

官方文档

在ansible中使用变量,能让我们的工作变得更加灵活,在ansible中,变量的使用方式有很多种,我们慢慢聊。

先说说怎样定义变量,变量名应该由字母、数字、下划线组成,变量名需要以字母开头,ansible内置的关键字不能作为变量名。

变量我们已经在循环知识中使用过了,主要通过vars关键字定义变量名、以及对应的变量值。

应用场景

1.通过自定义变量,可以在多个tasks中,通过变量名调用,取值
2.ansible在启动时,默认收集了客户端机器大量的静态属性(变量),可以提取该客户端的机器信息。

vars自定义变量

定义多个文件夹变量,创建rsync的数据目录,配置文件

- hosts: backup
  vars:
    data_path: /data
    dest_path: /etc
    config_path: /etc/rsync.passwd
  tasks:
    - name: 01 mkdir data dir
      file:
        path: "{{ data_path }}" 
        state: directory
​
    - name: 02 copy  config file
      copy:
        src: "{{ config_path }}"
        dest: "{{ dest_path }}"

ansible内置变量

完整的ansible内置变量手册

通过setup模块可以看到所有的内置变量,提取变量的值需要遵循python的数据类型语法,如列表还是字典取值

[root@master-61 ~]#ansible web -m ansible.builtin.setup |wc -l
ansible默认提供了一个模块,setup模块,
​
master-61在通过ssh远程连接,操作目标机器的时候
ansible会默认收集这个机器的所有信息,放入到一个setup模块中
这个机器的 主机名,ip地址,mac地址,磁盘数量,是否是虚拟化,cpu核数
所有的这些静态数据

获取主机静态属性(ip地址)

内置获取ip的变量
ansible_all_ipv4_addresses #适用于多ip
ansible_default_ipv4.address #适用单ip

剧本

[root@master-61 ~]#cat get_ip.yaml 
- hosts: web
  tasks:
  - name: 01 get ip address
    debug: msg="该web组机器,ip是  {{ ansible_all_ipv4_addresses }}" 
  - name: 02 get hostname
    debug: msg="该web组,主机名是 {{ ansible_hostname }}"
  - name: 03 单ip
    debug: msg="{{ansible_default_ipv4.address }}"
  - name: 04 eth0 ip地址是
    debug: msg="{{ansible_facts.eth0.ipv4.address}}"
  - name: 05 eth1 ip地址是
    debug: msg="{{ansible_facts.eth1.ipv4.address}}"

主机清单文件中也用到了变量

1.主机清单文件中定义变量

[root@master-61 ~]#tail -20  /etc/ansible/hosts 
​
[all:vars]
ansible_port=22999
#ansible_user=root
#ansible_password=123123
​
​
[web:vars]
nginx_version='1.19'
​
[web]
172.16.1.7 port=22999 
172.16.1.8 port=22999
172.16.1.9 port=22999
​
[nfs]
172.16.1.31
​
[backup]
172.16.1.41

2.只要操作该主机组,即可使用该变量

- hosts: web
  tasks:
  - name: 01 get nginx port
    debug: msg="nginx port is {{port}}"
  - name: 02 get nginx version
    debug: msg="nginx version is {{nginx_version}}"

执行

[root@master-61 ~]#ansible-playbook get_nginx.yaml

loop循环中引用变量

- name: www.haloyang.top
  hosts: backup
  vars:
    my_files:
      - { file_path: '/data' ,mode: '755' }
      - { file_path: '/backup' ,mode: '755' }
​
  tasks:
  - name: create_data
    file:
      path: "{{ item.file_path }}"
      state: directory
      owner: www
      group: www
      mode: "{{ item.mode }}"
    loop:
      "{{ my_files }}"

register注册变量

register模块的用法

ansible中的返回值提供了哪些参数

ansible的模块在运行之后,其实都会返回一些"返回值",只是默认情况下,这些"返回值"并不会显示而已

我们可以把这些返回值写入到某个变量中,这样我们就能够通过引用对应的变量从而获取到这些返回值了

这种将模块的返回值写入到变量中的方法被称为"注册变量"

那么怎样将返回值注册到变量中呢?我们来看一个playbook示例。

使用场景

调试,回显命令的执行结果
把状态保存为变量,其他task再继续调用

用内置变量获取IP地址写入文件,并且显示文件内容

剧本

- hosts: nfs
  tasks:
  - name: echo ip address
    shell: "echo {{ ansible_default_ipv4.address }} >> /tmp/ip.log"
​
  - name: cat ip.log
    shell: "cat /tmp/ip.log"
    register: about_ip_log
​
  - name: debug about_ip_log
    debug: 
      msg: "{{ about_ip_log.stdout_lines }}"
  1. 执行了个命令,默认有返回值

  2. 通过register提取该返回值,写入到变量

  3. 通过debug模块,msg参数 ,打印了这个变量的信息

执行结果

[root@master-61 /scripts]#ansible-playbook register_test.yml 
PLAY [nfs] ******************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [172.16.1.31]
TASK [echo ip address] ******************************************************************
changed: [172.16.1.31]
TASK [cat ip.log] ***********************************************************************
changed: [172.16.1.31]
TASK [debug about_ip_log] ***************************************************************
ok: [172.16.1.31] => {
    "msg": [
        "10.0.0.31"
    ]
}
PLAY RECAP ******************************************************************************
172.16.1.31                : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
​
[root@nfs-31 ~]#cat /tmp/ip.log 
10.0.0.31

注册多个变量

同时记录且显示客户端的ip信息、主机名信息

结合循环知识,打印多个命令的结果

提前配置好nfs-31机器配置

[root@nfs-31 ~]#yum install nfs-utils rcpbind -y
[root@nfs-31 ~]#mkdir /data
[root@nfs-31 ~]#cat /etc/exports
/data *{rw}
[root@nfs-31 ~]#systemctl start nfs
[root@nfs-31 ~]#showmount -e 172.16.1.31
Export list for 172.16.1.31:
/data *{rw}

剧本

- name: www.haloyang.top
  hosts: nfs
  tasks:
  - name: 01 get ip
    shell: "echo {{ ansible_default_ipv4.address }} > /tmp/ip.log"
​
  - name: 02 get hostname
    shell: "echo {{ ansible_hostname }} > /tmp/hostname.log"
​
  - name: 03 echo hostname
    shell: "cat /tmp/hostname.log"
    register: hostname_log
​
  - name: 04 echo ip
    shell: "cat /tmp/ip.log"
    register: ip_log
​
  - name: 05 show mount info
    shell: "showmount -e 172.16.1.31"
    register: showmount_log
​
  - debug:
      msg: "{{item}}"
​
    loop:
      - "{{ showmount_log.stdout_lines}}"
      - "{{ ip_log.stdout_lines}}"
      - "{{ hostname_log.stdout_lines}}"

执行结果

[root@master-61 /scripts]#ansible-playbook register_test2.yml 
​
PLAY [yang.cn] **************************************************************************
​
TASK [Gathering Facts] ******************************************************************
ok: [172.16.1.31]
​
TASK [01 get ip] ************************************************************************
changed: [172.16.1.31]
​
TASK [02 get hostname] ******************************************************************
changed: [172.16.1.31]
​
TASK [03 echo hostname] *****************************************************************
changed: [172.16.1.31]
​
TASK [04 echo ip] ***********************************************************************
changed: [172.16.1.31]
​
TASK [05 show mount info] ***************************************************************
changed: [172.16.1.31]
​
TASK [debug] ****************************************************************************
ok: [172.16.1.31] => (item=[u'Export list for 172.16.1.31:', u'/data *{rw}']) => {
    "msg": [
        "Export list for 172.16.1.31:", 
        "/data *{rw}"
    ]
}
ok: [172.16.1.31] => (item=[u'10.0.0.31']) => {
    "msg": [
        "10.0.0.31"
    ]
}
ok: [172.16.1.31] => (item=[u'nfs-31']) => {
    "msg": [
        "nfs-31"
    ]
}
​
PLAY RECAP ******************************************************************************
172.16.1.31                : ok=7    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
​
[root@nfs-31 ~]#cat /tmp/ip.log 
10.0.0.31
[root@nfs-31 ~]#cat /tmp/hostname.log 
nfs-31

判断当配置文件变化后,就重启服务(register+when)

yaml作用是判断配置文件变化后,就重启服务

1. 你以前写的剧本
都是固定的,定义tasks任务列表
1. 修改配置文件(判断配置文件是否变化)
2. 重启服务(只有配置文件变化,才有必要重启服务,否则是没变要)
register和when都是针对tasks任务列表下,某一个任务设置的
- name: 01 修改配置文件
  register
- name: 02 重启服务
​
作用分别是
1.获取某任务的命令执行结果(返回值) register
​
2.利用when条件判断,针对返回值的不同状态(决定做什么事) ,ansible进阶篇,都是在大量使用编程语言的特性,以及语法了

我们重启配置服务的标准是,修改了配置文件,否则无须重启

例如,判断rsyncd.conf文件状态发生变化后,就重启服务。

- name: www.haloyang.top
  hosts: backup
  tasks:
  - name: 01 copy rsyncd.conf
    copy: src=/script/rsyncd.conf dest=/etc/
    register: conf_status
​
  - name: 02 start rsyncd.service
    systemd: name=rsyncd state=started enabled=yes
​
  - name: 03 restart rsyncd.service
    systemd:
      name: rsyncd
      state: restarted
    when: conf_status.changed

查看rsync进程端口是否更新,即为rsyncd服务是否重启

[root@master-61 ~]#ansible backup -m shell -a "netstat -tunlp|grep rsync"
172.16.1.41 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:873             0.0.0.0:*               LISTEN      635/rsync           
tcp6       0      0 :::873                  :::*                    LISTEN      635/rsync

修改rsyncd.conf,再次执行剧本

[root@master-61 ~]#ansible backup -m shell -a "netstat -tunlp|grep rsync"
172.16.1.41 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:873             0.0.0.0:*               LISTEN      8274/rsync          
tcp6       0      0 :::873                  :::*                    LISTEN      8274/rsync

image-20240924211654884

when条件判断语句

官方文档

使用场景

判断nfs配置文件是否存在
​
1.存在,则显示其文件内容
2.不存在,则输出 /etc/exports is not exists。

答案

- name: www.haloyang.top
  hosts: backup
  vars:
    backup_file: /etc/exports
  tasks:
  - name: 01 check nfs config
    shell: "cat {{backup_file}}"
    register: backup_result
    ignore_errors: true
​
  - name: 02 debug nfs config
    debug:
      msg: "{{ansible_hostname}} has {{backup_file}},file content is : {{backup_result.stdout}}"
    when: backup_result is success
​
  - name: 03 debug nfs not exists
    debug: msg="{{backup_file}} is not exists."
    when: backup_result is failed

对上面的yml文件含义的解释

1.vars 自定义 关于文件名的变量名 nfs file。
2.在tasks任务列表中,利用register注册变量,获取 cat 命令的执行结果。
3.利用when语句,以及提供 is failed,is sucees 判断 注册变量是否正确执行。
4.来决定执行02任务还是03任务。

高级特性handler

1.剧本提供的handler特性,是专门用于实现,任务触发的模块,机制 
- 当你改了配置文件,是必须得重启服务
- /etc/exports           systemctl restart nfs
- /etc/nginx/nginx.conf    systemctl restart nginx
文件变化,则重启
文化不变化,则无序重启
利用了register+when 这俩结合,实现了 判断文件变化,才重启程序
​
2.其实ansible还提供了更专业的 handler机制

官方文档

handler这个机制一般用于服务状态管理,如:

  • 你在tasks中修改了nginx的配置文件,你就必须得重启nginx服务,才能生效

handler解决什么问题
1.现状、配置文件修改了,程序不可能自己重启,得手动restart
​
利用handler添加在剧本中,实现效果
1.配置文件没变化,就不执行restart重启
2.配置文件发生了变化,就执行restart重启

low办法实现

[root@master-61 ~]#cat restart_rsync.yaml 
- name: www.haloyang.top
  hosts: backup
  remote_user: root
  tasks:
  - name: copy rsyncd.conf
    copy:
      src=/script/rsyncd.conf
      dest=/etc/
  - name: restart rsyncd.service
    systemd:
      name=rsyncd
      state=restarted

你会发现,这个用法,无论你配置文化改没改,都必然会执行重启的task,这个写法就不太合理。

改造为handler

1.handlers中的任务会被tasks调用
2.只有tasks的确执行了,发生了change 状态,handler才会执行
3.在tasks任务列表中,定义`notify`属性,用于触发`handler`的执行

剧本

- name: www.haloyang.top
  hosts: backup
  remote_user: root
  tasks:
  - name: 01 copy rsyncd.conf
    copy:
      src=/script/rsyncd.conf
      dest=/etc/
    notify:
      - restart rsyncd.service
​
  handlers:
  - name: restart rsyncd.service
    systemd:
      name: rsyncd
      state: restarted

细节注意

1.handlers必须写在结尾
2.handlers定义的任务名字,必须和notify一致

给task打上tag标签

你写了一个很长的 playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务。

这个时候我们该怎么办呢?我们可以借助tags实现这个需求。

见名知义,tags可以帮助我们对任务进行'打标签'

当任务存在标签以后,我们就可以在执行playbook时,借助标签,指定执行哪些任务,或者指定不执行哪些任务了。

tag作用
调试,选择性的执行某个tas

1.部署nfs-server剧本参考

- name: www.haloyang.top
  hosts: nfs
  tasks:
  - name: 01 安装nfs-utils 服务
    yum: name=nfs-utils state=installed
    tags: 01_install_nfs_service
​
  - name: 02 安装rpcbind 服务
    yum: name=rpcbind state=installed
    tags: 02_install_rpcbind_service
​
  - name: 03 创建组
    group: name=www gid=666
    tags: 03_add_group
​
  - name: 04 创建用户
    user: name=www uid=666 group=www create_home=no shell=/sbin/nologin
    tags: 04_add_user
​
  - name: 05 创建共享目录
    file: path=/data owner=www group=www state=directory
    tags: 05_create_data_dir
​
  - name: 06 拷贝配置文件
    copy: src=/script/exports dest=/etc/exports
    tags: 06_copy_nfs_exports
​
  - name: 07 创建关于rsync密码文件
    copy: content='yuchao666' dest=/etc/rsync.passwd mode=600
    tags: 07_create_rsync_passwd
​
  - name: 08 启动rpcbind
    service: name=rpcbind state=started enabled=yes
    tags: 08_start_rpcbind
​
  - name:  09 启动nfs
    systemd: name=nfs state=started enabled=yes
    tags: 09_start_nfs

2.打印剧本中可用的标签

也就是你可以直接选择执行哪些任务

[root@master-61 ~]#ansible-playbook --list-tags tag_nfs.yaml 
​
playbook: tag_nfs.yaml
​
  play #1 (nfs): yuchaoit.cn    TAGS: []
      TASK TAGS: [01_install_nfs_service, 02_install_rpcbind_service, 03_add_group, 04_add_user, 05_create_data_dir, 06_copy_nfs_exports, 07_create_rsync_passwd, 08_start_rpcbind, 09_start_nfs]

3.指定运行某个标签

[root@master-61 ~]#ansible-playbook -t 01_install_nfs_service tag_nfs.yaml 
[root@master-61 ~]#ansible-playbook -t 04_add_user tag_nfs.yaml

4.指定运行多个标签

指定运行01、05、04这三个标签。

[root@master-61 ~]#ansible-playbook -t 01_install_nfs_service,05_create_data_dir,04_add_user tag_nfs.yaml

5.指定不运行一个/多个标签

指定跳过01、05、04这三个标签,执行其余标签。

[root@master-61 ~]#ansible-playbook --skip-tags  01_install_nfs_service,05_create_data_dir,04_add_user tag_nfs.yaml

选择tasks执行

使用场景

1.调试剧本时,task数量太多,不想从头执行,可以指定执行位置

查看task列表

[root@master-61 ~]#ansible-playbook --list-tasks tag_nfs.yaml 
​
playbook: tag_nfs.yaml
​
  play #1 (nfs): www.haloyang.top    TAGS: []
    tasks:
      01 安装nfs-utils 服务    TAGS: [01_install_nfs_service]
      02 安装rpcbind 服务    TAGS: [02_install_rpcbind_service]
      03 创建组    TAGS: [03_add_group]
      04 创建用户    TAGS: [04_add_user]
      05 创建共享目录    TAGS: [05_create_data_dir]
      06 拷贝配置文件    TAGS: [06_copy_nfs_exports]
      07 创建关于rsync密码文件    TAGS: [07_create_rsync_passwd]
      08 启动rpcbind    TAGS: [08_start_rpcbind]
      09 启动nfs    TAGS: [09_start_nfs]

选择执行的task位置

从第五步骤开始

[root@master-61 ~]#ansible-playbook --start-at-task '05 创建共享目录' tag_nfs.yaml

总结(playbook规范流程)

通过这个流程,去编写、检验、阅读所有的playbook都是一个靠谱的办法

1.检查剧本语法

如果语法不对,ansible会具体告诉你错误的位置

[root@master-61 ~]#ansible-playbook --syntax-check tag_nfs.yaml 
​
playbook: tag_nfs.yaml

2.检查该剧本操作的主机有哪些

搞清楚这个剧本会影响到哪些主机,关乎于这些机器的作用

[root@master-61 ~]#ansible-playbook --list-hosts get_ip.yaml 
​
playbook: get_ip.yaml
​
  play #1 (web): web    TAGS: []
    pattern: [u'web']
    hosts (3):
      172.16.1.7
      172.16.1.8
      172.16.1.9

3.查看剧本有哪些任务

轻松的搞清楚这个剧本有什么作用,整体的工作流程

[root@master-61 ~]#ansible-playbook --list-tasks tag_nfs.yaml 
​
playbook: tag_nfs.yaml
​
  play #1 (nfs): www.haloyang.top    TAGS: []
    tasks:
      01 安装nfs-utils 服务    TAGS: [01_install_nfs_service]
      02 安装rpcbind 服务    TAGS: [02_install_rpcbind_service]
      03 创建组    TAGS: [03_add_group]
      04 创建用户    TAGS: [04_add_user]
      05 创建共享目录    TAGS: [05_create_data_dir]
      06 拷贝配置文件    TAGS: [06_copy_nfs_exports]
      07 创建关于rsync密码文件    TAGS: [07_create_rsync_passwd]
      08 启动rpcbind    TAGS: [08_start_rpcbind]
      09 启动nfs    TAGS: [09_start_nfs]

4.模拟剧本执行

模拟执行,查看执行流程是否存在错误,以及执行的状态

[root@master-61 ~]#ansible-playbook -C tag_nfs.yaml

5.真正执行剧本

对目标机器发生实质性的改变、修改操作

[root@master-61 ~]#ansible-playbook  tag_nfs.yaml