• devops工具-Ansible进阶playbook&roles


    一、playbook介绍

        playbook 是 Ansible 管理配置、部署应用的核心所在,一个playbook由有多“play组成”,而一个play实际就是一个task,每个task是由多个ansible基本模块构成,这样我们可以用 playbook 来描述想在远程主机执行的一些列操作,包括安装部署、配置管理、任务处理等等。
        playbook是通过yaml格式来定义的,支持同步和异步方式来运行,运行顺序是从上到下运行每个我们定义的task,从而实现各种复杂任务。关于yaml语法可以参考这里。 

    二、核心元素

    一个playbook中比较核心的组成由以下几部分组成:
    • Hosts和User:主机列表和用户,定义一个playbook操作的远程主机以及登录的用户
    • Tasks:tasks代表任务集,定义远程主机运行的任务列表,
    • Variables:变量,可以在playbook中传递变量
    • Templates:模版,复用配置文件,jinja2模版引擎
    • Handlers和Notify:触发器,用于在task任务结束后,可以触发其他操作 

    hosts和users

      hosts代表是playbook中的主机列表,可以是主机组、单独主机、多个主机(中间以冒号分隔开)、还可以使用通配符,users代表登录目标主机的用户,使用remote_user指定,当需要使用sudo进行操作时候,需要制定become参数。
    ---
    - hosts: test       #指定主机组
      remote_user: root #指定ssh登录的用户
      tasks:           
        - name: test server
          ping:

    使用sudo切换

    ---
    - hosts: test
      remote_user: root
      become: yes             #become参数在2.6版本用于指定sudo
      become_user: admin       #sudo的用户
      tasks:
        - name: test server
          ping:

    tasks

      Tasks是playbook中核心组成成分部分,用于在定义远程主机执行的一系列操作,这里成为任务集。每个task运行顺序按照playbook文件中从上到下依次运行,当某个task运行出错时停止(可以使用ignore_erros参数忽略错误改变)。每个task须有个name用于标记此次的任务名称,同时也能友好方便我们阅读结果。
    定义语法: 
    tasks:           
        - name: task_name   #任务名称
          module: module_args #使用的模块,以及模块参数,列如yum: name=nginx state=present

    示例:
    安装一个httpd服务并启动: 
    ---
    - hosts: 10.1.210.51
      remote_user: root
      tasks:
       - name: install package   #使用yum安装包
         yum: name=httpd state=present
       - name: start httpd       #启动该服务
         service: name=httpd state=started

    执行:

    variables

      playbook可使用变量来代替某些重复性的字符串,定义变量方式有多种,而引用变量只需要通过双括号引用(Jinji2模版语法),例如:{{ var_name }}。 变量命名仅能由字母、数字和下划线组成,且只能以字母开头。
    变量定义:
    1.直接使用facts
    在上一篇文章红介绍了setup模块收集facts信息,而这些信息可以用来作为我们playbook的变量使用,例如ansible_all_ipv4_addresses这个facts变量保存的是主机的IP地址。
    示例:
    ---
    - hosts: 10.1.210.53
      remote_user: root
      tasks:
        - name: test facts var
          shell: echo "{{ ansible_all_ipv4_addresses }}" > host_ip.txt

    2.主机清单中定义变量
    格式: 
    #单独主机变量,优先级高于公共变量
    host  varname=value
    #主机组变量
    [groupname:vars] #为指定主机组自定义变量,vars为关键字
    varname=value

    列如:

    vi /etc/ansible/hosts 
    10.1.210.51 business=card   #变量名为business值为card
    10.1.210.53
    
    [dev]
    10.1.210.33
    10.1.210.32
    
    [dev:vars]                  #组变量定义
    myname=wd

    示例:

    [root@app52 ~]# cat test.yaml 
    ---
    - hosts: 10.1.210.51
      remote_user: root
      tasks:
        - name: test facts from hosts
          debug: var=business  #使用debug打印变量的值,var参数测试变量不需要用{{ }}

    运行playbook:

    3.playbook中定义变量

    格式:

    vars:
      - varname1: value1
      - varname2: value2

    示例:

    ---
    - hosts: 10.1.210.51
      remote_user: root
      vars:
        - pkg_name: httpd
        - pkg_cmd: /usr/sbin/httpd
      tasks:
        - name: restart httpd
          service: name={{ pkg_name }} state=restarted
        - name: copy cmd
          copy:  src={{ pkg_cmd }} dest=/tmp/

    运行playbook:

    4.命令行中定义变量

    ansible-playbook -e 'varname=value’  #该方式定义的变量优先级最高

    5.通过文件引用变量。

    [root@app52 ~]# cat /tmp/myvars.yml 
    var1: value1
    var2: value2

    playbook中使用

    ---
    - hosts: 10.1.210.51
      remote_user: root
      vars_files:
        - /tmp/myvars.yml      #变量文件
      tasks:
        - name: test myvar
          debug: msg="{{ var1 }}/{{ var2 }}" #这里的var1变量和var2变量来自于文件/tmp/myvars.yml

    运行结果:

    6.在roles中定义变量,后续在介绍roles在讲解。

    Templates

      不同的远程主机配置文件是一般不相同的,不可能每次都要修改配置文件,所有就出现了模板技术(魔模块template实现)。通过定义一份模板,在进行配置文件推送时候,会根据我们定义好的变量进行替换,从而达到不同的主机配置文件是不同且符合我们预期的。 templates模版语法采用Jinja2模版语法,参考这里
    使用方法:
    1.定义模版,文件名称必须以.j2结尾
    vim /tmp/nginx.conf.j2
    
    user  nginx;
    worker_processes  {{ ansible_processor_vcpus }};  #使用cpu个数作为woeker数量
    error_log /var/log/nginx_error.log crit;
    pid       /var/run/nginx.pid;
    include /usr/share/nginx/modules/*.conf;
    worker_rlimit_nofile 65535;
    events {
        worker_connections  1024;
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
        sendfile        on;
        keepalive_timeout  65;
        gzip  on;
     server
      {
        listen 80;  
        server_name   {{ ansible_fqdn }} #使用主机名作为hostname
        access_log    /var/log/access.log;
        error_log    /var/log/error.log;
    }
    }

    2.playbook中使用

    ---
    - hosts: 10.1.210.53
      remote_user: root
      vars:
      - package_name: nginx
      - config_path: /etc/nginx/nginx.conf
      tasks:
        - name: install epel
          yum:  name=epel-release
        - name: install package
          yum: name={{ package_name }}
        - name: copy config file
          template: src=/tmp/nginx.conf.j2 dest={{ config_path }} backup=yes #backup用于备份
        - name: start service
          service: name={{ package_name }} state=started

    运行playbook

    Handlers和notify

      Handlers是task列表,这些task与tasks中的task并没有本质上的不同,用于当关注的资源发生变化时,才会采取一定的操作。而触发的行为则是由notify产生,可以理解为notify通知Handlers触发某个task。
        需要注意的是,不管发生多少次notify行为,等到play中的所有task执行完成之后,handlers中的task才会被执行,而这些task只会被执行一次;
       notify可以定义多个,多个notify用列表方式来表示。
    示例:
    - hosts: 10.1.210.53
      remote_user: root
      vars:
      - package_name: nginx
      - config_path: /etc/nginx/nginx.conf
      tasks:
        - name: install epel
          yum:  name=epel-release
        - name: install package
          yum: name={{ package_name }}
        - name: copy config file
          template: src=/tmp/nginx.conf.j2 dest={{ config_path }} backup=yes
          notify:  #当配置文件发生变化时候,通知hanler触发task
            - stop service
            - start service
      handlers:
        - name: stop service
          service: name={{ package_name }} state=stopped
        - name: start service
          service: name={{ package_name }} state=started

    运行playbook

    三、playbook进阶技巧 

    使用tag标记任务

    为某个任务打一个标签,可以使用ansible-playbook 命令选择性的执行任务。打标签可通过tags标签指定,可以是与其相关的参数:
    • --list-tags :列出playbook中所有的tag
    • --skip-tags:跳过playbook中被标记的tag任务
    • -t 或 --tags:指定运行playbook中被标记的tag任务
     
    以下playbook中分别定义了将两个tag进行了标记:
    ---
    - hosts: 10.1.210.51
      remote_user: root
      tasks:
        - name: exec tag1
          shell: cat /etc/passwd
          tags: tag1
        - name: exec tag2
          file: path=/tmp/b.txt state=touch
          tags: tag2
        - name: view password
          shell: cat /etc/passwd

    列出tag

    指定运行某个tag任务

    跳过tag任务运行

    从以上结果可看出,跳过了tag1和tag2任务,只运行了view password任务。

    条件测试when

    when关键字可实现根据某些条件判断当前的task是否需要执行。when可根据变量、facts(setup)或此前任务的执行结果来作为判断依据,同时支持逻辑运算符操作,比较强大和实用。
    需要注意的是:
    • when判断的对象是task,所以和tas k在同一列表层次。它的判断结果决定它所在task是否执行,而不是它下面 的task是否执行。
    • when中引用变量的时候不需要加{{ }}符号。

    示例一:使用facts变量测试

    tasks:
      - name: "shut down Debian flavored systems"
        command: /sbin/shutdown -t now
        when: ansible_facts['os_family'] == "Debian"     #当操作系统是Debian才执行该任务

    示例二:使用逻辑运算进行多条件判断

    tasks:
      - name: "shut down CentOS 6 and Debian 7 systems"
        command: /sbin/shutdown -t now
        when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
              (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")

    两个条件使用and还可以如下表示

    tasks:
      - name: "shut down CentOS 6 systems"
        command: /sbin/shutdown -t now
        when:
          - ansible_facts['distribution'] == "CentOS"
          - ansible_facts['distribution_major_version'] == "6"  #同时满足系统是CentOS且版本是6

    示例三:根据任务的执行结果状态来判断任务是否执行,这里使用register来保存了任务执行结果,后续会介绍。

    ---
    - hosts: 10.1.210.51
      name: test when
      remote_user: root
      tasks:
        - name: task1
          command: /bin/false
          register: result            #将本次任务结果保存在变量中
          ignore_errors: True          #忽略错误执行,可确保任务错误也能执行下一个任务
    
        - name: pre task failed
          file: path=/tmp/failed.txt state=touch    
          when: result is failed          #判断result结果是否是failed 
    
        - name: pre task successed state=touch
          file: path=/tmp/success.txt
          when: result is succeeded       #判断result结果是否是succeeded
    
        - name: pre task skipped  state=touch
          file: path=/tmp/skip.txt
          when: result is skipped          #判断结果是否是skipped

    运行一下:

    如上图,红色的部分任务都跳过了未执行,这是因为result结果是failded,所以只执行了“pre task failed”这个任务。

    示例4:使用变量进行测试

    ---
    - hosts: 10.1.210.51
      name:  test var when
      remote_user: root
      vars:
        - flag: true        #变量定义
      tasks:
        - name: test server
          ping:
          when: flag        #判断变量flag为true执行
    
        - name: create file
          file: path=/tmp/myfile.txt state=touch
          when: not flag    #变量flag不为true执行

    执行结果:

    同样可以看到create file 任务被跳过了。

    使用register保存任务结果

      register注册变量,用于保存任务结果,可用于保存配置或when关键字判断,这是非常有用了,在上个示例中使用了任务结果作为判断条件。例如,你可以将文件中的配置或者json内容保存在变量中,可以供后续使用:

    ---
    - hosts: 10.1.210.51
      name: test register
      remote_user: root
      tasks:
        - name: read conf
          shell: cat /tmp/nginx.conf
          register: nginx_conf  #注册变量
    
        - name: copy conf
          copy: content={{ nginx_conf }} dest=/etc/nginx/nginx.conf #使用变量

    还可以配合循环使用,以下示例展示了批量创建软链接:

    - name: registered variable usage as a loop list
      hosts: all
      tasks:
    
        - name: retrieve the list of home directories
          command: ls /home
          register: home_dirs
    
        - name: add home dirs to the backup spooler
          file:
            path: /mnt/bkspool/{{ item }}
            src: /home/{{ item }}
            state: link
          loop: "{{ home_dirs.stdout_lines }}"

    使用迭代进行重复性操作或任务

    当有需要重复性执行的任务时,可以使用迭代机制。其迭代的内容会保存在特殊变量item中,ansible提供很多迭代指令,大多都是以with_开头,以下是几种常见的循环。 
    1.with_items迭代列表
    示例一:创建多个用户 
    ---
    - hosts: localhost
      remote_user: root
      tasks:
       - name: create user
         user: name={{ item }} state=present
         with_items:
           - zabbix
           - admin

    执行结果:

    示例二:循环中使用register注册变量

    - hosts: localhost
      remote_user: root
      tasks:
         - command: echo {{ item }}
           with_items: [ 0, 2, 4, 6, 8, 10 ]
           register: num
         - debug: msg="{% for i in num.results %} {{i.stdout}} {% endfor %}"

    注意,将with_items迭代后的结果注册为变量时,其注册结果也是列表式的,且其key"results"。具体的结果比较长,可以使用debug模块的varmsg参数观察变量的结果。以上示例运行结果如下:

    2.with_dict迭代字典

    使用"with_dict "可以迭代字典项。迭代时,使用"item.key"表示字典的key"item.value"表示字典的值。
    示例一:
    ---
    - hosts: localhost
      remote_user: root
      tasks:
         - debug: msg="{{ item.key }} / {{ item.value }}"
           with_dict: { ip: 10.1.210.51, hostname: app52, gateway: 10.1.210.1}

    以上示例中字典是已经存在了,除此之外字典可以来源于变量、facts等。例如使用facts进行迭代

    ---
    - hosts: localhost
      remote_user: root
      tasks:
        - debug: msg="{{item.key}} / {{item.value}}"
          with_dict: "{{ ansible_cmdline }}"

    使用include简化playbook

     如果将所有的play都写在一个playbook中,很容易导致这个playbook文件变得臃肿庞大,且不易读。因此,可以将多个不同任务分别写在不同的playbook中,然后使用include将其包含进去即可。include可以导入两种文件:导入task文件、导入playbook。

    示例:创建task.yml任务列表

    vim task.yml
    - name: task1
      debug: msg="exec task1"
    - name: task2
      debug: msg="exec task2"

    在目标playbook中倒入任务

    ---
    - hosts: 10.1.210.51
      remote_user: root
      tasks:
        - include: task.yml

    执行playbook:

    示例2:直接导入其他playbook,不过在ansible2.8将移除,2.8中将使用import_playbook 

    ---
    - hosts: 10.1.210.51
      remote_user: root
      tasks:
        - include: task.yml
    
    - include: test_when.yml
    - include: test_with.yml

    四、roles(角色)介绍

    简介

      roles 就字面上来说有角色、作用的意思,但它的全名其实是 Playbooks Roles,我们可把它当成是 playbooks 的延伸使用,可以降低 playbooks 的复杂性,更可以增加 playbooks 的可用性。简单来讲,roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷地include它们的一种机制。

    使用场景

    • 同时安装多个不同的软件如:LNMP环境
    • 不同服务器组需要安装不同服务
    • 复杂的playbook,使用role可以具有阅读性 

    目录结构

    一个角色中目录包含以下目录:
    • files:用来存放由copy模块或script模块调用的文件。
    • templates:用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件。
    • tasks:此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件。
    • handlers:此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作。
    • vars:此目录应当包含一个main.yml文件,用于定义此角色用到的变量。
    • defaults:此目录应当包含一个main.yml文件,用于为当前角色设定默认变量。
    • meta:此目录应当包含一个main.yml文件,用于定义此角色的元数据信息。

       例如:一个nginx角色的目录结构可以是:

    .
    └── nginx
        ├── default
        ├── files
        ├── handlers
        ├── meta
        ├── tasks
        ├── templates
        └── vars

    多个role目录:

    ├── httpd             #http role
    │   ├── default
    │   ├── files
    │   ├── handlers
    │   ├── meta
    │   ├── tasks
    │   ├── templates
    │   └── vars
    └── nginx            #nginx role
        ├── default
        ├── files
        ├── handlers
        ├── meta
        ├── tasks
        ├── templates
        └── vars

    演示:使用role安装nginx

    一、创建对应的目录结构:

    [root@app52 ~]# mkdir -pv roles/nginx/{files,templates,vars,tasks,handlers,meta,default} 
    mkdir: 已创建目录 "roles"
    mkdir: 已创建目录 "roles/nginx"
    mkdir: 已创建目录 "roles/nginx/files"
    mkdir: 已创建目录 "roles/nginx/templates"
    mkdir: 已创建目录 "roles/nginx/vars"
    mkdir: 已创建目录 "roles/nginx/tasks"
    mkdir: 已创建目录 "roles/nginx/handlers"
    mkdir: 已创建目录 "roles/nginx/meta"
    mkdir: 已创建目录 "roles/nginx/default”

    二、定义变量

    [root@app52 ~]# vi roles/nginx/vars/main.yml
    pkg_name: nginx  #安装包名称
    listen_port: 80   #监听端口

    三、编写任务

    这里可以把任务模块化,最后在main.yml包含它们

    [root@app52 ~]# vi roles/nginx/tasks/yum.yml
    - name: install epel
      yum: name=epel-release state=present
    
    - name: install nginx pkg
      yum: name={{ pkg_name }} state=present
    
    [root@app52 ~]# vi roles/nginx/tasks/copy.yml 
    - name: copy nginx.conf
      template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    
    - name: copy index.html
      copy: src=index.html dest=/var/www/html/
      notify: reload
    
    [root@app52 ~]# vi roles/nginx/tasks/start_service.yml 
    - name: start nginx
      service: name=nginx state=restarted
    
    [root@app52 ~]# vi roles/nginx/tasks/main.yml 
    - include: yum.yml
    - include: copy.yml
    - include: start_service.yml

    四、准备配置文件以及index.html

    #index.html
    [root@app52 ~]# vi roles/nginx/files/index.html 
    <h1>Hello wd</h1>
    
    #配置文件模版
    [root@app52 ~]# vi roles/nginx/templates/nginx.conf.j2 
    user  nginx;
    worker_processes  {{ ansible_processor_vcpus }};  #使用cpu个数作为woeker数量
    error_log /var/log/nginx_error.log crit;
    pid       /var/run/nginx.pid;
    include /usr/share/nginx/modules/*.conf;
    worker_rlimit_nofile 65535;
    events {
        worker_connections  1024;
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
        sendfile        on;
        keepalive_timeout  65;
        gzip  on;
     server
      {
        listen {{ listen_port }};
        server_name   {{ ansible_all_ipv4_addresses[0] }} ; #使用IP地址作为server name
        root /var/www/html                ;
        access_log    /var/log/access.log;
        error_log    /var/log/error.log;
    }
    }

    五、编写handlers

    如果在task中使用了notify,则就需要写对应的handlers,上述我使用了reload这个handler,所以这里需要定义:

    [root@app52 ~]# vi roles/nginx/handlers/main.yml 
    - name: reload
      service: name=nginx state=reloaded

    六、在角色同级目录编写playbook引入角色

    [root@app52 ~]# vi roles/install_nginx.yml 
    - hosts: web             #指定使用role的主机或主机组
      remote_user: root      #指定用户
      roles:                 #使用的role,可以有多个
        - nginx

    最后的目录结构为:

    [root@app52 ~]# tree roles/
    roles/
    ├── install_nginx.yml
    └── nginx
        ├── default
        ├── files
        │   └── index.html
        ├── handlers
        │   └── main.yml
        ├── meta
        ├── tasks
        │   ├── copy.yml
        │   ├── main.yml
        │   ├── start_service.yml
        │   └── yum.yml
        ├── templates
        │   └── nginx.conf.j2
        └── vars
            └── main.yml
    
    8 directories, 9 files

    七、运行playbook并测试

    如红色部分,curl测试nginx 安装成功。 

  • 相关阅读:
    android: 建立和断开a2dp link 相关方法总结
    Java: volatile和synchronized的区别 (转)
    shell 将变量当命令执行的几种方式
    tcp 自连接(主机的端口和自己建立连接)
    sed使用
    海尔风冷冰箱冷冻室最底层结冰 问题解决
    k8s 传参数的两种方式一种是 环境变量 拼接 另一种说是yaml传参到配置文件的表示怀疑要验证????????????????????????????
    k8s 集群中的etcd故障解决
    dockerfile
    使用VIM/VI给文件加密和解密
  • 原文地址:https://www.cnblogs.com/wdliu/p/10592397.html
Copyright © 2020-2023  润新知