zoukankan      html  css  js  c++  java
  • Ansible系列(三):YAML语法和playbook写法

    ansible的playbook采用yaml语法,它简单地实现了json格式的事件描述。yaml之于json就像markdown之于html一样,极度简化了json的书写。在学习ansible playbook之前,很有必要把yaml的语法格式、引用方式做个梳理。

    初步说明


    以一个简单的playbook为例,说明yaml的基本语法。
    ```
    ---
    - hosts: 192.168.100.59,192.168.100.65
    remote_user: root
    pre_tasks: 
    - name: set epel repo for Centos 7
    yum_repository: 
    name: epel7
    description: epel7 on CentOS 7
    baseurl: http://mirrors.aliyun.com/epel/7/$basearch/
    gpgcheck: no
    enabled: True

    tasks: 
    # install nginx and run it
    - name: install nginx
    yum: name=nginx state=installed update_cache=yes
    - name: start nginx
    service: name=nginx state=started

    post_tasks: 
    - shell: echo "deploy nginx over"
    register: ok_var
    - debug: msg="{{ ok_var.stdout }}"
    ```
    1. yaml文件以`---`开头,以表明这是一个`yaml`文件,就像`xml`文件在开头使用`<?xml version="1.0" encoding="utf-8"?>`宣称它是`xml`文件一样。但即使没有使用`---`开头,也不会有什么影响。

    2. `yaml`中使用"#"作为注释符,可以注释整行,也可以注释行内从"#"开始的内容。

    3. `yaml`中的字符串通常不用加任何引号,即使它包含了某些特殊字符。但有些情况下,必须加引号,最常见的是在引用变量的时候。具体见后文。

    4. 关于布尔值的书写格式,即`true/false`的表达方式。其实`playbook`中的布尔值类型非常灵活,可分为两种情况:

    - 模块的参数: 这时布尔值作为字符串被`ansible`解析。接受`yes/on/1/true/no/off/0/false`。例如上面示例中的`update_cache=yes`。
    - 非模块的参数: 这时布尔值被`yaml`解释器解析,完全遵循`yaml`语法。接受不区分大小写的`true/yes/on/y/false/no/off/n`。例如上面的`gpgcheck=no`和`enabled=True`。

    建议遵循`ansible`的官方规范,模块的布尔参数采用`yes/no`,非模块的布尔参数采用`True/False`。

    ### 列表
    使用`"- "`(减号加一个或多个空格)作为列表项,也就是`json`中的数组。`yaml`的列表在`playbook`中极重要,必须得搞清楚它的写法。

    例如:
    ```
    - zhangsan
    - lisi
    - wangwu
    ```

    还支持内联写法:使用中括号。
    ```
    [zhangsan,lisi,wangwu]
    ```

    它们等价于json格式的:
    ```
    [
    "zhangsan",
    "lisi",
    "wangwu"
    ]
    ```

    再例如:

    ```
    - 班名: 初中1班
    人数: 35
    班主任: 隔壁老张
    今天的任务: 扫操场

    - 班名: 初中2班
    人数: 38
    班主任: 隔壁老王
    今天的任务: 搬桌子
    ```

    具体在`ansible playbook`中,列表所描述的是局部环境,它不一定要有名称,不一定要从同一个属性开始,只要使用`"-"`,它就表示圈定一个范围,范围内的项都属于该列表。例如:

    ```
    ---
    - name: list1 # 列表1,同时给了个名称
    hosts: localhost # 指出了hosts是列表1的一个对象
    remote_user: root # 列表1的属性
    tasks: # 还是列表1的属性

    - hosts: 192.168.100.65 # 列表2,但是没有为列表命名,而是直入主题
    remote_user: root
    sudo: yes
    tasks:
    ```

    唯一要注意的是,每一个`playbook`中必须包含`hosts`和`tasks`项。更严格地说,是每个`play`的顶级列表必须包含这两项。就像上面的例子中,就表示该`playbook`中包含了两个`play`,每个`play`的顶级列表都包含了`hosts`和`tasks`。其实绝大多数情况下,一个`playbook`中都只定义一个`play`,所以只有一个顶级列表项。顶级列表的各项,其实可以将其看作是`ansible-playbook`运行时的选项。

    另外,`playbook`中某项是一个动作、一个对象或一个实体时,一般都定义成列表的形式。

    ### 字典
    官方手册上这么称呼,其实就是`key=value`的另一种写法。使用"冒号+空格"分隔,即`key: value`。它一般当作列表项的属性。

    例如:

    ```
    - 班名: 初中1班
    人数: 
    总数: 35
    男: 19
    女: 16
    班主任: 
    大名: 隔壁老张
    这厮多大: 39
    这厮任教多少年: 15
    今天的任务: 扫操场

    - 班名: 初中2班
    人数: 
    总数: 38
    男: 19
    女: 19
    班主任: 
    大名: 隔壁老王
    这厮多大: 30
    喜调戏女老师: True
    今天的任务: 搬桌子
    未完成任务怎么办:
    - 继续搬,直到完成
    - 写检讨
    ```

    具体到`playbook`中,一般"虚拟性"的内容都可以通过字典的方式书写,而实体化的、动作性的、对象性的内容则应该定义为列表形式。


    ```
    ---
    - hosts: localhost # 列表1
    remote_user: root
    tasks:
    - name: test1 # 子列表,下面是shell模块,是一个动作,所以定义为列表,只不过加了个name
    shell: echo /tmp/a.txt
    register: hi_var
    - debug: var=hi_var.stdout # 调用模块,这是动作,所以也是列表
    - include: /tmp/nginx.yml # 同样是动作,包含文件
    - include: /tmp/mysql.yml
    - copy: # 调用模块,定义为列表。但模块参数是虚拟性内容,应定义为字典而非列表
    src: /etc/resolv.conf # 模块参数1
    dest: /tmp # 模块参数2

    - hosts: 192.168.100.65 # 列表2
    remote_user: root
    vars:
    nginx_port: 80 # 定义变量,是虚拟性的内容,应定义为字典而非列表
    mysql_port: 3306
    vars_files: 
    - nginx_port.yml # 无法写成key/value格式,且是实体文件,因此定义为列表
    tasks:
    - name: test2
    shell: echo /tmp/a.txt
    register: hi_var # register是和最近一个动作绑定的
    - debug: var=hi_var.stdout
    ```

    从上面示例的`copy`模块可以得出,模块的参数是虚拟性内容,也能使用字典的方式定义。

    字典格式的`key/value`,也支持内联格式写法:使用大括号。

    ```
    {大名: 隔壁老王,这厮多大: 30,喜调戏女老师: True}
    {nginx_port: 80,mysql_port: 3306}
    ```

    这等价于json格式的:

    ```
    {
    "大名": "隔壁老王",
    "这厮多大": 30,
    "喜调戏女老师": "True"
    }
    {
    "nginx_port": 80,
    "mysql_port": 3306
    }
    ```

    再结合其父项,于是转换成json格式的内容:

    ```
    "班主任": {
    "大名": "隔壁老王",
    "这厮多大": 30,
    "喜调戏女老师": "True"
    }

    "vars": {
    "nginx_port": 80,
    "mysql_port": 3306
    }
    ```

    再加上列表项(使用中括号),于是:

    ```
    [
    {
    "hosts": "192.168.100.65",
    "remote_user": "root",
    "vars": {
    "nginx_port": 80,
    "mysql_port": 3306
    },
    "vars_files": [
    "nginx_port.yml"
    ],
    "tasks": [
    {
    "name": "test2",
    "shell": "echo /tmp/a.txt",
    "register": "hi_var"
    },
    {
    "debug": "var=hi_var.stdout"
    }
    ]
    }
    ]
    ```
    ### 分行写
    `playbook`中有3种方式进行续行。

    在`"key: "`的后面使用大于号。
    在`"key: "`的后面使用竖线。这种方式可以像脚本一样写很多行语句。
    多层缩进。
    例如,下面的3中方法。

    ```
    ---
    - hosts: localhost
    tasks: 
    - shell: echo 2 >>/tmp/test.txt
    creates=/tmp/haha.txt # 比模块shell缩进更多
    - shell: > # 在"key: "后使用大于号
    echo 2 >>/tmp/test.txt
    creates=/tmp/haha.txt
    - shell: | # 指定多行命令
    echo 2 >>/tmp/test.txt
    echo 3 >>/tmp/test.txt
    args:
    creates: /tmp/haha.txt
    ```

    ### 向模块传递参数
    模块的参数一般来说是`key=value`格式的,有3种传递的方式:

    直接写在模块后,此时要求使用`key=value`格式。这是让`ansible`内部去解析字符串。因为可分行写,所以有多种写法。

    写成字典型,即`key: value`。此时要求多层缩进。这是让`yaml`去解析字典。
    使用内置属性args,然后多层缩进定义参数列表。这是让`ansible`明确指定用`yaml`来解析。
    例如:

    ```
    ---
    - hosts: localhost
    tasks: 
    - yum: name=unix2dos state=installed # key=value直接传递
    - yum: 
    name: unxi2dos
    state: installed # "key: value"字典格式传递
    - yum: 
    args: # 使用args传递
    name: unix2dos
    state:installed
    ```

    但要注意,当模块的参数是`free_form`时,即格式不定,例如`shell`和`command`模块指定要执行的命令,它无法写成`key/value`格式,此时不能使用上面的第二种方式。也就是说,下面第一个模块是正确的,第二个模块是错误的,因为`shell`模块的命令`"echo haha"`是自由格式的,无法写成`key/value`格式。

    ```
    ---
    - hosts: localhost
    tasks: 
    - yum: 
    name: unxi2dos
    state: installed
    - shell: 
    echo haha
    creates: /tmp/haha.txt
    ```

    所以,调用一个模块的方式就有了多种形式。例如:

    ```
    ---
    - hosts: localhost
    tasks:
    - shell: echo 1 >/tmp/test.txt creates=/tmp/haha.txt
    - shell: echo 2 >>/tmp/test.txt
    creates=/tmp/haha.txt
    - shell: echo 3 >>/tmp/test.txt
    args:
    creates: /tmp/haha.txt
    - shell: >
    echo 4 >>/tmp/test.txt
    creates=/tmp/haha.txt
    - shell: |
    echo 5.1 >>/tmp/test.txt
    echo 5.2 >>/tmp/test.txt
    args:
    creates: /tmp/haha.txt
    - yum: 
    name: dos2unix
    state: installed
    ```
    ### playbook和play的关系
    一个`playbook`中可以包含多个`play`。每个`play`都至少包含有`tasks`和`hosts`这两项,还可以包含其他非必须项,如`vars,vars_files,remote_user`等。`tasks`中可以通过模块调用定义一系列的`action`。只不过,绝大多数时候,一个`playbook`都只定义一个`play`。

    所以,大致关系为:

    ```
    playbook: [play1,play2,play3]
    play: [hosts,tasks,vars,remote_user...]
    tasks: [module1,module2,...]
    ```

    也就是说,每个顶级列表都是一个`play`。例如,下面的`playbook`中包含了两个`play`。

    ```
    ---
    - name: list1
    hosts: localhost
    remote_user: root
    tasks:

    - hosts: 192.168.100.65
    remote_user: root
    sudo: yes
    tasks:
    ```

    需要注意,有些时候`play`中使用了`role`,可能看上去没有`tasks`,这是因为`role`本身就是整合`playbook`的,所以没有也没关系。但没有使用`role`的时候,必须得包含`hosts`和`tasks`。例如:

    ```
    ---
    - hosts: centos
    remote_user: root
    pre_tasks: 
    - name: config the yum repo for centos 7
    yum_repository:
    name: epel
    description: epel
    baseurl: http://mirrors.aliyun.com/epel/7/$basearch/
    gpgcheck: no
    when: ansible_distribution_major_version == "7"

    - name: config the yum repo for centos 6
    yum_repository:
    name: epel
    description: epel
    baseurl: http://mirrors.aliyun.com/epel/6/$basearch/
    gpgcheck: no
    when: ansible_distribution_major_version == "6"

    roles: 
    - nginx

    post_tasks:
    - shell: echo 'deploy nginx/mysql over'
    register: ok_var
    - debug: msg='{{ ok_var.stdout }}'
    ```

    ### playbook中什么时候使用引号
    `playbook`中定义的都是些列表和字典。绝大多数时候,都不需要使用引号,但有两个特殊情况需要考虑使用引号。

    出现大括号`{}`。
    出现冒号加空格`: `。
    大括号要使用引号包围,是因为不使用引号时会被`yaml`解析成内联字典。例如要使用大括号引用变量的时候,以及想输出大括号符号的时候。


    ```
    ---
    - hosts: localhost
    tasks:
    - shell: echo "{{inventory_hostname}}:haha"
    ```

    冒号尾随空格时要使用引号包围,是因为它会被解析为`key: value`的形式。而且包围冒号的引号还更严格。例如下面的debug模块中即使使用了引号也是错误的。

    ```
    ---
    - hosts: localhost
    tasks:
    - shell: echo "{{inventory_hostname}}:haha"
    register: hello
    - debug: msg="{{hello.stdout}}: heihei"
    ```

    因为它把`{{...}}`当成`key`,`heihei`当成`value`了。因此,必须将整个`debug`模块的参数都包围起来,显式指定这一段是模块的参数。但这样会和原来的双引号冲突,因此使用单引号。

    ```
    ---
    - hosts: localhost
    tasks:
    - shell: echo "{{inventory_hostname}}:haha"
    register: hello
    - debug: 'msg="{{hello.stdout}}: heihei"'
    ```

    但是,如果将`shell`模块中的冒号后也尾随上空格,即写成`echo "{{inventory_hostname}}: haha"`,那么`shell`模块也会报错。因此也要使用多个引号,正确的如下:

    ```
    ---
    - hosts: localhost
    tasks:
    - shell: 'echo "{{inventory_hostname}}: haha"'
    register: hello
    - debug: 'msg="{{hello.stdout}}: heihei"'
    ```

  • 相关阅读:
    安卓学习第一课——电话拨号器
    CodeForces 644B【模拟】
    hdu5861【线段树】
    CodeForces 41A+43A【课上无聊刷水题系列】
    hdoj5493【树状数组+二分】
    HDU5894【组合数学】
    Codeforces643A【一种暴力】
    CodeForces 689C【二分】
    CodeForces 665B 【水-暴力】
    CodeForces 653A【水】
  • 原文地址:https://www.cnblogs.com/gushiren/p/9642336.html
Copyright © 2011-2022 走看看