• ansible


    Ansible - Playbook

    Playbook简介

    palybook 是由一个或多个paly组成的列表。play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible 中的 task 定义好的角色。从根本上来讲,所谓 task 无非是调用 ansible 的一个 module。将多个 play组织在一个 playbook 中,即可以让它们联同起来按事先编排好的机制同唱一台大戏。

    一个playbook由以下几个部分组成:

    • Inventory
    • Modules
    • Ad Hoc Commands
    • Playbooks
      • tasks:即调用模块完成的操作
      • variables:变量
      • templates:模板
      • handlers:触发器,由某子任务触发执行操作
      • roles:角色

    Inventory

    ansible的主要功能在于批量主机操作,为了便捷地使用其中的部分主机,可以在inventory file中将其分组命名。默认的inventory file/etc/ansible/hosts

    inventory文件遵循INI文件风格,中括号中的字符为组名。可以将同一个主机同时归并到多个不同的组中;此外,当如若目标主机使用了非默认的SSH端口,还可以在主机名称之后使用冒号加端口号来标明。

    [webservers]
    www1.magedu.com:2222
    www2.magedu.com
    
    [dbservers]
    db1.magedu.com
    db2.magedu.com
    db3.magedu.com
    

    如果主机名称遵循相似的命名模式,还可以使用列表的方式标识各主机,例如:

    [webservers]
    www[01:50].example.com
    
    [databases]
    db-[a:f].example.com
    

    inventory file 中也可以设置 变量 ,方便后期 ansible 执行调用。

    node2 ansible_ssh_host=192.168.118.15 ansible_ssh_user=root http_port=80
    node3 ansible_ssh_host=192.168.118.16 ansible_ssh_user=root http_port=8080
    

    上面配置中 http_port 就是自定义的变量。当然在组中也可以设置该组所有主机相同的变量:

    [webservers]
    www1.magedu.com
    www2.magedu.com
    
    [webservers:vars]
    ntp_server=ntp.magedu.com
    nfs_server=nfs.magedu.com
    

    注意:这里 webservers:varsvars 是固定关键字写法。

    Inventory 常用参数

    这里只是列举常用参数:

    ansible_ssh_host: 被管理主机及ip
    ansible_ssh_port: 被管理主机端口
    ansible_ssh_pass: 如果没有设置密钥认证,则需要填写密码
    ansible_sudo_pass: linux sudo 切换用户时密码
    ansible_shell_type: shell 类型 bash 还是 xsh
    

    Modules

    ansible modules 有很多,之后会专门写一篇关于 modules 总结的文档。

    Ad Hoc Commands

    什么是 ad-hoc ?

    当我们需要敲一些命令去快速的查看或者完成一项工作时,而不需要持久的存储这些命令,这样的命令就叫做 ad-hoc

    ansible 就提供了两种方式去完成任务,一是 ad-hoc 命令,另一种是 ansible playbook ,前者可以解决一些简单的任务,后者解决较复杂的任务。

    比如,当需要马上查看主机端口时,使用 ad-hoc 就是很高效的,

    如下:ansible [hostname] -m shell -a 'netstat -ntplu'

    Playbook

    playbook 核心元素:

    • hosts:执行的远程主机列表
    • tasks:任务集
    • variables:内置变量或自定义变量在 playbook中使用
    • notify 和 handlers 结合使用,由特定的条件触发操作,满足条件执行,否则不执行
    • tags:标签,指定某条件执行,用于选择运行 playbook 中的部分代码

    Playbook 语法

    playbook 使用 yaml 语法格式,后缀为 yaml 也可以是 yml 要求及格式如下:

    • 在单一一个playbook文件中,可以连续三个连子号(---)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾,也可省略。
    • 次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。
    • 使用#号注释代码。
    • 缩进必须统一,不能空格和tab混用。
    • 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。
    • YAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感
    • k/v的值可同行写也可以换行写。同行使用:分隔。
    • v可以是个字符串,也可以是一个列表
    • 一个完整的代码块功能需要最少元素包括 name: task

    下面通过一个安装维护 httpd 服务来逐步引出 playbook中知识点

    通过一个简单的示例查看 playbook 语法及格式:

    首先在 /etc/ansible/hosts 中定义主机及主机组

    [root@localhost ~]#cat /etc/ansible/hosts
    [test_hosts]
    node2 ansible_ssh_host=192.168.118.15 ansible_ssh_user=root
    node3 ansible_ssh_host=192.168.118.16 ansible_ssh_user=root
    

    编写 playbook 脚本:

    [root@localhost ~]#cat test.yml 
    - hosts: test_hosts
      remote_user: root
      tasks:
      - name: "echo hello hukey."
        debug:
          msg: "hello, hukey."
    

    前三行基本是固定格式:

    • hosts: [hostname | groupname] 要执行任务的主机或主机组
    • remote_user: [username] 在主机组中执行任务的用户名
    • tasks: 所有需要执行的任务集

    示例1:在 test_hosts 主机上安装 httpd 服务 然后启动服务。

    - hosts: test_hosts
      remote_user: root
      tasks:
      - name: install httpd
        yum: name=httpd state=latest
      - name: start httpd
        systemd: name=httpd enabled=yes state=started
    

    上面的这个 playbook 就是安装 httpd 并启动服务,前三行是固定模式,在 tasks 中,每一个以 name 开头的就是一个小任务。所以 tasks 中就是由一个一个小任务组成的。

    Playbook 中的变量

    如果要在 playbook 中使用变量,则需要 vars 关键字来定义:

    - hosts: test_hosts
      remote_user: root
      vars:
      - package: httpd
      - service: httpd
      tasks:
      - name: install {{ package }}
        yum: name={{ package }} state=latest
      - name: start {{ service }}
        systemd: name={{ service }} enabled=yes state=started
    

    这里定义了两个变量 package=httpdservice=httpd
    在引用变量时,用两个大括号:{{ variable_name }}

    执行 Playbook

    编写好 playbook 后,就需要通过 ansible-playbook 执行,常用参数如下:

    --check  or -C    #只检测可能会发生的改变,但不真正执行操作
    --syntax-check	  # 语法检查,不执行
    --list-hosts      #列出运行任务的主机
    --list-tags       #列出playbook文件中定义所有的tags
    --list-tasks      #列出playbook文件中定义的所以任务集
    --limit           #主机列表 只针对主机列表中的某个主机或者某个组执行
    -f                #指定并发数,默认为5个
    -t                #指定tags运行,运行某一个或者多个tags。(前提playbook中有定义tags)
    -v                #显示过程  -vv  -vvv更详细
    

    通常,直接使用 ansible-playbook apache.yml 执行:

    [root@localhost ~]#ansible-playbook apache.yml 
    
    PLAY [test_hosts] ***********************************************************
    
    TASK [Gathering Facts] ******************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [install httpd] ********************************************************
    changed: [node3]
    changed: [node2]
    
    TASK [start httpd] **********************************************************
    changed: [node3]
    changed: [node2]
    
    PLAY RECAP ******************************************************************
    node2: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
    node3: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    
    

    执行 playbook 时

    1. 发现主机是否存在或连通
    2. 开始执行tasks 中的子任务

    上面的 playbook中只有2个子任务,通过执行过程可以看出,ansible 在执行 playbook 时,是按照任务为中心的思想来执行的, 也就是 第一个任务,在所有的主机上执行完毕,然后在将第二个任务在所有主机上执行。其中 ok 表示每一步执行的是否成功,而 changed 则表示执行该playbook 被管理主机是否发生了更改,如果被管理主机没有发生更改则不会有 changed,再次执行该playbook:

    [root@localhost ~]#ansible-playbook apache.yml 
    
    PLAY [test_hosts] *************************************************************
    
    TASK [Gathering Facts] ********************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [install httpd] ********************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [start httpd] *******************************************************
    ok: [node3]
    ok: [node2]
    
    PLAY RECAP **************************************************************
    node2: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    node3: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    
    

    因为第一次已经安装启动了, 再次执行 playbook 则不发生任何更改。

    需求又来了, 要修改主机的 httpd 端口为 8080

    思路:ansible 主机需要有一份 修改好端口的配置文件,将配置文件推送到被管理主机,然后重启服务。

    修改如下:

    - hosts: test_hosts
      remote_user: root
      vars:
      - package: httpd
      - service: httpd
      tasks:
      - name: install {{ package }}
        yum: name={{ package }} state=latest
      - name: copy configuration file
        copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
      - name: restart {{ service }}
        systemd: name={{ service }} enabled=yes state=restarted
    
    

    copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf

    /root/conf/httpd.conf 为 ansible 主机本地路径,在该配置文件中修改端口为: 8080 覆盖到被管理主机的 /etc/httpd/conf/httpd.conf

    对比刚开始的 playbook 做了改动,加入了 -name: copy configration file 并将 start httpd 修改为 restart 执行如下:

    [root@localhost ~]#ansible-playbook apache.yml 
    
    PLAY [test_hosts] **********************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node2]
    ok: [node3]
    
    TASK [install httpd] *******************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [copy configuration file] *********************************************
    changed: [node3]
    changed: [node2]
    
    TASK [restart httpd] *******************************************************
    changed: [node2]
    changed: [node3]
    
    PLAY RECAP *****************************************************************
    node2: ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    node3: ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    
    

    OK,执行没有任何问题,以后每次修改端口都可以直接在本地修改,然后执行下 playbook 推送再重启就好了,但是上面的写法并不完美。就算配置文件没有修改依然会重启服务,这是没有必要的。有没有一种方法,当修改了配置文件才重启服务, 如果没有修改则不重启呢?这就需要用到 notify + handlers 处理机制。

    notify + handlers

    Handlers 和 notify 结合使用,由特定条件触发的操作,满足条件才执行,否则不执行。这里需要满足的条件是,当配置文件修改,playbook 如下:

    - hosts: test_hosts
      remote_user: root
      vars:
      - package: httpd
      - service: httpd
      tasks:
      - name: install {{ package }}
        yum: name={{ package }} state=latest
      - name: copy configuration file
        copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
        notify:
        - restart httpd
      - name: start {{ service }}
        systemd: name={{ service }} enabled=yes state=started
      handlers:
      - name: restart httpd
        systemd: name=httpd state=restarted
    

    copy 下一行加入了 notify 然后在最后添加 handlers 注意格式!

    修改配置文件:/root/conf/httpd.conf Listen:8000 然后执行 playbook

    [root@localhost ~]#ansible-playbook apache.yml 
    
    PLAY [test_hosts] **********************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node2]
    ok: [node3]
    
    TASK [install httpd] *******************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [copy configuration file] *********************************************
    changed: [node2]
    changed: [node3]
    
    TASK [start httpd] *********************************************************
    ok: [node2]
    ok: [node3]
    
    RUNNING HANDLER [restart httpd] ********************************************
    changed: [node2]
    changed: [node3]
    
    PLAY RECAP *****************************************************************
    node2: ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    node3: ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    

    配置文件修改,在执行子任务时触发了 notify 定义触发器,执行了 restart ,再次执行 playbook 试试:

    [root@localhost ~]#ansible-playbook apache.yml 
    
    PLAY [test_hosts] **********************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node2]
    ok: [node3]
    
    TASK [install httpd] *******************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [copy configuration file] *********************************************
    ok: [node2]
    ok: [node3]
    
    TASK [start httpd] *********************************************************
    ok: [node3]
    ok: [node2]
    
    PLAY RECAP *****************************************************************
    node2: ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    node3: ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    
    

    再次执行时,配置文件并没有发生任何变化,所以这里没有触发 restart httpd 这样比上面 每次执行都重启要智能很多了吧?但是依然不完美。每次执行 playbook 都会执行一些没必要的子任务,比如:TASK [install httpd]TASK [start httpd] 这两个子任务在安装执行之后一般不会在用到了, 但是删除了又不能保证 playbook 的完整性,这时候就需要 tags 出马了。

    Tags

    tags 便签的意思,也就是为子任务打一个标签,然后就可以通过 ansible-playbook 执行打标签的部分,其他子任务不予执行,如下:

    - hosts: test_hosts
      remote_user: root
      vars:
      - package: httpd
      - service: httpd
      tasks:
      - name: install {{ package }}
        yum: name={{ package }} state=latest
      - name: copy configuration file
        copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
        notify:
        - restart httpd
        tags:
        - updateConf
      - name: start {{ service }}
        systemd: name={{ service }} enabled=yes state=started
      handlers:
      - name: restart httpd
        systemd: name=httpd state=restarted
    

    [name: copy configuration file] 子任务最后加入了 tags: updateConf ,然后修改配置文件 /root/conf/httpd.conf 端口为 9090

    通过 --list-tags 查看标签:

    [root@localhost ~]#ansible-playbook apache.yml --list-tags
    
    playbook: apache.yml
    
      play #1 (test_hosts): test_hosts	TAGS: []
          TASK TAGS: [updateConf]
    

    通过 ansible-playbook apache.yml -t updateConf 执行打标签的子任务:

    [root@localhost ~]#ansible-playbook apache.yml -t updateConf
    
    PLAY [test_hosts] **********************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node2]
    ok: [node3]
    
    TASK [copy configuration file] *********************************************
    changed: [node3]
    changed: [node2]
    
    RUNNING HANDLER [restart httpd] ********************************************
    changed: [node2]
    changed: [node3]
    
    PLAY RECAP *****************************************************************
    node2: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    node3: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    

    可以看到,所有子任务中,只是执行了 TASK [copy configuration file] 子任务,这样的 playbook 就趋近完美了。可以再次执行,再次执行时由于httpd.conf 没有发生修改,所以不会触发 restart httpd,如下:

    [root@localhost ~]#ansible-playbook apache.yml -t updateConf
    
    PLAY [test_hosts] **********************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node2]
    ok: [node3]
    
    TASK [copy configuration file] *********************************************
    ok: [node2]
    ok: [node3]
    
    PLAY RECAP *****************************************************************
    node2: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
    node3: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    

    总结一下

    通过上面的安装、维护 httpd 服务编写的 playbook 中,使用到了:

    • 变量:通过 vars 关键字定义变量;
    • notify + handlers 子任务触发器,触发执行,不触发则不执行;
    • tags:标签,使用 ansible-playbook xxx.yml -t [tag_name] 只执行打标签的子任务,其他任务不予执行。

    除了上面,还有一些功能也是非常使用的。

    条件判断

    很多文中这里都是 流程控制,我觉得用 条件判断 来定义更简单易懂。

    playbook 中使用 when 关键字来进行条件判断,这里的 when 相当于 shell 中的 if ,它是 jinja2 的语法。

    有这样一个需求:如果主机的 IP 为: 192.168.118.15 则打印它的主机名,实现如下:

    有这样一个需求:如果主机的 IP 为: 192.168.118.15 则打印它的主机名,实现如下:
    - hosts: all
      remote_user: root
      tasks:
      - name: IP -> Host
        debug: msg={{ ansible_fqdn }}
        when: ansible_all_ipv4_addresses[0] == '192.168.118.15'
    

    关键字: when 定义条件判断, 当 ip 为:192.168.118.15 的时候打印 主机的 fqdn 否则跳过,执行如下:

    [root@localhost ~]#ansible-playbook hosts.yml 
    
    PLAY [all] *****************************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [IP -> Host] **********************************************************
    ok: [node2] => {
        "msg": "node2.super.com"
    }
    skipping: [node3]
    
    PLAY RECAP *****************************************************************
    node2: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
    node3: ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0 ignored=0
    

    定义了两台主机,其中 node3 不满足条件, 则跳过 skip

    循环

    在playbook中使用循环的场景还是很多的, 比如安装一些 程序包,处理一堆文件,创建一批用户等。

    playbook 中还有循环功能,关键字: itemwith_items

    需求:为 test_hosts 主机组创建一批新用户
    - hosts: test_hosts
      remote_user: root
      tasks:
      - name: add users
        user: name={{ item }}
        with_items:
        - user10
        - user20
        - user30
    

    item 为固定变量关键字,循环内容为 with_items 中的值,执行结果如下:

    [root@localhost ~]#ansible-playbook adduser.yml 
    
    PLAY [test_hosts] **********************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node3]
    ok: [node2]
    
    TASK [add users] ***********************************************************
    changed: [node3] => (item=user10)
    changed: [node2] => (item=user10)
    changed: [node3] => (item=user20)
    changed: [node2] => (item=user20)
    changed: [node2] => (item=user30)
    changed: [node3] => (item=user30)
    
    PLAY RECAP *****************************************************************
    node2: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
    node3: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
    

    reigster

    在 playbook 中 task 之间 可以通过 register 接收结果并传递

    - hosts: node2
      remote_user: root
      tasks:
      - name: register hostname
        shell: "hostname"
        register: info
      - name: display vars
        debug: msg="{{ info.stdout }}"
    

    执行结果:

    [root@localhost ~]#ansible-playbook host.yml 
    
    PLAY [node2] ***************************************************************
    
    TASK [Gathering Facts] *****************************************************
    ok: [node2]
    
    TASK [register hostname] ***************************************************
    changed: [node2]
    
    TASK [display vars] ********************************************************
    ok: [node2] => {
        "msg": "node2.super.com"
    }
    
    PLAY RECAP *****************************************************************
    node2: ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
    

    通过上面的示例,可以观察出 子任务1通过 register获取到执行结果,在子任务2中通过 变量名 info 打印出来。这样的方式可以通过执行命令来判断结果是否是想要的结果。

  • 相关阅读:
    面试中遇到递归算法题别慌--常见递归算法题的解题思路
    Xml日志记录文件最优方案(附源代码)
    linux下源码安装软件
    文本比较算法Ⅴ——回顾贴,对前面几篇文章的回顾与质疑
    从内存中直接运行PE程序
    谈谈Linux内核驱动的coding style
    【全面解禁!真正的Expression Blend实战开发技巧】第六章 认识ListBox
    玩转C链表
    麻省理工《C内存管理和C++面向对象编程》笔记---第一讲:认识C和内存管理
    Dll注入技术之输入法注入
  • 原文地址:https://www.cnblogs.com/hukey/p/13157772.html
Copyright © 2020-2023  润新知