Ansible学习实战手记-你想要知道的可能都在这里了

最近接触了ansible工具,查找了一些资料,也做了一些总结。希望能给刚接触的新手带来一些帮助。

此总结有实际例子,大部分也是从实践中用到才逐一总结的。

当然可能肯定一定会存在一些错误和纰漏,还望大家具体实践时进一步熟悉了解。

ansible本身的模块有几百个,按照官网的建议来说,不建议一次性学完。

我们需要一边学习一边实践一边总结。要经常查找官方文档。

官方文档如下:

Ansible Documentation — Ansible Community Documentation

Ansible学习
安装:
pip install ansible==2.4.1.0   
为什么要指定版本是:2.4.1.0
1、因为最新版本2.7.1在导入时,报错:
    root@ubuntu:/etc/ansible# ansible --version
    Traceback (most recent call last):
      File "/usr/bin/ansible", line 41, in <module>
        from ansible.utils.unicode import to_unicode
    ImportError: cannot import name to_unicode
暂时解决办法:将ansible版本降低到2.4.1.0问题解决
2、一些新特性只能在更高的ansible版本中使用,比如:include_tasks
    在低版本中使用时会报错:
    ERROR! no action detected in task

    The error appears to have been in '/etc/ansible/roles/newone/tasks/main.yml': line 8, column 4, but may
    be elsewhere in the file depending on the exact syntax problem.

    The offending line appears to be:


     - include_tasks: lala.yml
       ^ here

依赖包如下:
jinja2                                        Jinja2       2.10
PyYAML                                         PyYAML       3.11
paramiko                                     paramiko     2.4.2
cryptography                                 cryptography 2.3.1
setuptools                                     setuptools   20.7.0
MarkupSafe>=0.23                             MarkupSafe   0.23
pyasn1>=0.1.7                                 pyasn1       0.4.4
bcrypt>=3.1.3                                 bcrypt       3.1.4
pynacl>=1.0.1                                 PyNaCl       1.3.0
enum34; python_version < "3"                 enum34       1.1.6
asn1crypto>=0.21.0                             asn1crypto   0.24.0
cffi!=1.11.3,>=1.7                             cffi         1.11.5
idna>=2.1                                     idna         2.7
six>=1.4.1                                     six          1.10.0
ipaddress; python_version < "3"             ipaddress    1.0.22
pycparser                                     pycparser    2.19


ansible -h 参数解析
Usage: ansible <host-pattern> [options]

Options:
  -a MODULE_ARGS, --args=MODULE_ARGS    模块的参数,如果执行默认COMMAND的模块,即是命令参数,如:“date”,"pwd"等等
                        module arguments    模块参数
  -k, --ask-pass        ask for SSH password  登录密码,提示输入SSH密码而不是假设基于密钥的验证
  --ask-su-pass         ask for su password    su切换密码
  -K, --ask-sudo-pass   ask for sudo password  提示密码使用sudo,sudo表示提权操作
  --ask-vault-pass      ask for vault password
  -B SECONDS, --background=SECONDS     后台运行超时时间
                        run asynchronously, failing after X seconds
                        (default=N/A)
  -C, --check           don't make any changes; instead, try to predict some    
              只是测试一下会改变什么内容,不会真正去执行;相反,试图预测一些可能发生的变化
                        of the changes that may occur
  -c CONNECTION, --connection=CONNECTION   连接类型使用。可能的选项是paramiko(SSH),SSH和地方。当地主要是用于crontab或启动。
                        connection type to use (default=smart)
  -f FORKS, --forks=FORKS   并行任务数。NUM被指定为一个整数,默认是5
                        specify number of parallel processes to use
                        (default=5)
  -h, --help            show this help message and exit   打开帮助文档API
  -i INVENTORY, --inventory-file=INVENTORY    指定库存主机文件的路径,默认为/etc/ansible/hosts
                        specify inventory host file
                        (default=/etc/ansible/hosts)
  -l SUBSET, --limit=SUBSET    进一步限制所选主机/组模式  --limit=192.168.91.135 只对这个ip执行
                        further limit selected hosts to an additional pattern
  --list-hosts          outputs a list of matching hosts; does not execute
                        anything else
  -m MODULE_NAME, --module-name=MODULE_NAME   执行模块的名字,默认使用 command 模块,所以如果是只执行单一命令可以不用 -m参数
                        module name to execute (default=command)
  -M MODULE_PATH, --module-path=MODULE_PATH    要执行的模块的路径,默认为/usr/share/ansible/
                        specify path(s) to module library
                        (default=/usr/share/ansible/)
  -o, --one-line        condense output      压缩输出,摘要输出.尝试一切都在一行上输出。
  -P POLL_INTERVAL, --poll=POLL_INTERVAL    调查背景工作每隔数秒。需要- b
                        set the poll interval if using -B (default=15)
  --private-key=PRIVATE_KEY_FILE    私钥路径,使用这个文件来验证连接
                        use this file to authenticate the connection
  -S, --su              run operations with su    用 su 命令
  -R SU_USER, --su-user=SU_USER      指定SU的用户,默认是root用户
                        run operations with su as this user (default=root)
  -s, --sudo            run operations with sudo (nopasswd)    
  -U SUDO_USER, --sudo-user=SUDO_USER    sudo到哪个用户,默认为 root  
                        desired sudo user (default=root)
  -T TIMEOUT, --timeout=TIMEOUT    指定SSH默认超时时间,  默认是10S
                        override the SSH timeout in seconds (default=10)
  -t TREE, --tree=TREE  log output to this directory     将日志内容保存在该输出目录,结果保存在一个文件中在每台主机上。
  -u REMOTE_USER, --user=REMOTE_USER    远程用户, 默认是root用户
                        connect as this user (default=root)
  --vault-password-file=VAULT_PASSWORD_FILE  
                        vault password file
  -v, --verbose         verbose mode (-vvv for more, -vvvv to enable    如果命令执行成功,输出详细的结果
                        connection debugging)(-vv –vvv -vvvv)
  --version             show program's version number and exit   输出ansible的版本
  
  
ansible-playbook参数解析:
Options:
  --ask-vault-pass      
             #加密playbook文件时提示输入密码
  -C, --check           
             #模拟执行,不会真正在机器上执行(查看执行会产生什么变化)
  -D, --diff            
         #当更新的文件数及内容较少时,该选项可显示这些文件不同的地方,该选项结合-C用会有较好的效果
  -e EXTRA_VARS, --extra-vars=EXTRA_VARS
             #在Playbook中引入外部参数变量
  --flush-cache         
             #将fact清除到的远程主机缓存
  --force-handlers      
             #强制运行handlers的任务,即使在任务失败的情况下
  -f FORKS, --forks=FORKS
             #并行任务数。FORKS被指定为一个整数,默认是5
  -h, --help            
             #打开帮助文档API
  -i INVENTORY, --inventory-file=INVENTORY
             #specify inventory host path (default=/etc/ansible/hosts) or comma separated host list.
             #指定要读取的Inventory文件
  -l SUBSET, --limit=SUBSET
             #further limit selected hosts to an additional pattern
             #限定执行的主机范围
  --list-hosts          
             #outputs a list of matching hosts; does not execute anything else
             #列出执行匹配到的主机,但并不会执行
  --list-tags           
             #list all available tags
             #列出所有可用的tags
  --list-tasks          
             #list all tasks that would be executed
             #列出所有即将被执行的任务
  -M MODULE_PATH, --module-path=MODULE_PATH
             #specify path(s) to module library (default=None)
             #要执行的模块的路径
  --new-vault-password-file=NEW_VAULT_PASSWORD_FILE
             #new vault password file for rekey
             #
  --output=OUTPUT_FILE  
             #output file name for encrypt or decrypt; use - for stdout
             #
  --skip-tags=SKIP_TAGS
             #only run plays and tasks whose tags do not match these values
             #跳过指定的tags任务
  --start-at-task=START_AT_TASK
             #start the playbook at the task matching this name
             #从第几条任务(START_AT_TASK)开始执行
  --step                
             #one-step-at-a-time: confirm each task before running
             #逐步执行Playbook定义的任务,并经人工确认后继续执行下一步任务
  --syntax-check        
             #perform a syntax check on the playbook, but do not execute it
             #检查Playbook中的语法书写,并不实际执行
  -t TAGS, --tags=TAGS  
             #only run plays and tasks tagged with these values
             #指定执行该tags的任务
  --vault-password-file=VAULT_PASSWORD_FILE
             #vault password file
             #
  -v, --verbose         
             #verbose mode (-vvv for more, -vvvv to enable connection debugging)
             #执行详细输出
  --version             
             #show program's version number and exit
             #显示版本

  Connection Options:
    control as whom and how to connect to hosts

    -k, --ask-pass      
             #ask for connection password
             #
    --private-key=PRIVATE_KEY_FILE, --key-file=PRIVATE_KEY_FILE
             #use this file to authenticate the connection
             #
    -u REMOTE_USER, --user=REMOTE_USER
             #connect as this user (default=None)
             #指定远程主机以USERNAME运行命令
    -c CONNECTION, --connection=CONNECTION
             #connection type to use (default=smart)
             #指定连接方式,可用选项paramiko (SSH)、ssh、local,local方式常用于crontab和kickstarts
    -T TIMEOUT, --timeout=TIMEOUT
             #override the connection timeout in seconds(default=10)
             #SSH连接超时时间设定,默认10s
    --ssh-common-args=SSH_COMMON_ARGS
             #specify common arguments to pass to sftp/scp/ssh (e.g.ProxyCommand)
             #
    --sftp-extra-args=SFTP_EXTRA_ARGS
             #specify extra arguments to pass to sftp only (e.g. -f, -l)
             #
    --scp-extra-args=SCP_EXTRA_ARGS
             #specify extra arguments to pass to scp only (e.g. -l)
             #
    --ssh-extra-args=SSH_EXTRA_ARGS
             #specify extra arguments to pass to ssh only (e.g. -R)
             #

  Privilege Escalation Options:
    control how and which user you become as on target hosts

    -s, --sudo          
             #run operations with sudo (nopasswd) (deprecated, use become)
             #相当于Linux系统下的sudo命令
    -U SUDO_USER, --sudo-user=SUDO_USER
             #desired sudo user (default=root) (deprecated, use become)
             #使用sudo,相当于Linux下的sudo命令
    -S, --su            
             #run operations with su (deprecated, use become)
             #
    -R SU_USER, --su-user=SU_USER
             #run operations with su as this user (default=root)(deprecated, use become)
    -b, --become        
             #run operations with become (does not imply password prompting)
             #
    --become-method=BECOME_METHOD
             #privilege escalation method to use (default=sudo),valid choices: 
        [ sudo | su | pbrun | pfexec | doas |dzdo | ksu | runas ]
             #
    --become-user=BECOME_USER
             #run operations as this user (default=root)
             #
    --ask-sudo-pass     
             #ask for sudo password (deprecated, use become)
             #传递sudo密码到远程主机,来保证sudo命令的正常运行
    --ask-su-pass       
             #ask for su password (deprecated, use become)
             #
    -K, --ask-become-pass
             #ask for privilege escalation password
             #

当然,我们对于一些具体的学习还要参考一些文档
https://docs.ansible.com/ansible/2.4/intro_installation.html


Ansible学习实践:
1.在A主机上创建密钥对,实现对其他主机无密码访问,执行:
# ssh-keygen -t rsa -f ~/.ssh/id_rsa.pub -P ""
# ssh-copy-id -i /root/.ssh/id_rsa.pub root@172.18.19.188
此时会在远程机的/root/.ssh/authorized_keys文件中,生成id_rsa.pub文件的内容。

执行具体回显
root@docker-02:~# ssh-copy-id -i /root/.ssh/id_rsa.pub root@172.18.19.188
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '172.18.19.188 (172.18.19.188)' can't be established.
ECDSA key fingerprint is SHA256:BLDdJTy5lNOuopbtXDVojySMfc1y2lmJSPwvKIyvSVM.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@172.18.19.188's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@172.18.19.188'"
and check to make sure that only the key(s) you wanted were added.

root@docker-02:~# ssh 'root@172.18.19.188'
Welcome to Ubuntu 16.04 LTS (GNU/Linux 4.4.0-21-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

177 packages can be updated.
24 updates are security updates.


*** System restart required ***
Last login: Wed Nov 22 19:39:52 2017 from 172.18.19.94
root@docker-01:~# exit
logout
Connection to 172.18.19.188 closed.

2.A主机管理清单的配置
    # cd /etc/ansible
    # cp hosts{,.bak}
    # vim hosts
添加如下内容:
[remote]
172.18.19.188
测试执行是否成功:
# ansible remote -m command -a "ls" 
172.18.19.188 | SUCCESS | rc=0 >>
# ansible all -m command -a "ls" 
172.18.19.188 | SUCCESS | rc=0 >>
两种方法都可以解决执行问题。

3.常用模块

  1.command模块:在远程主机上执行的命令
    相关选项:
      creates:一个文件名,当该文件存在,则该命令不执行
      free_form:要执行的linux指令
      chdir:在执行指令之前,先切换到该目录
      removes:一个文件名,当该文件不存在,则该选项不执行
      executable:切换shell来执行指令,该执行路径必须是一个绝对路径
/*示例*/
ansible remote -m command -a "ls"

        值得留意的时,command模块执行的命令是获取不到$HOME这样的环境变量的,
        一些运算符,例如”<“ 、”>“ 在command模块上也是不能使用的。

  2.setup模块:查看远程主机的相关facts变量信息
/*示例*/
ansible all -m setup
ansible 192.168.43.130 -m setup

  3.shell模块:让远程主机在shell进程下执行命令,从而支持shell的特性,如管道等
/*示例*/
ansible all -m shell -a "echo "test" | passwd --stdin test1"
        相当于增强版的command

  4.copy模块:复制本地文件至远程主机上
    相关选项:
      backup:在覆盖之前,将源文件备份,备份文件包含时间信息。有两个选项:yes|no
      content:用于替代“src”,可以直接设定指定文件的值
      dest:必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录
      directory_mode:递归设定目录的权限,默认为系统默认权限
      force:如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的
          目标位置不存在该文件时,才复制。默认为yes
      others:所有的file模块里的选项都可以在这里使用
      src:被复制到远程主机的本地文件,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。
          在这种情况下,如果路径使用“/”来结尾,则只复制目录里的内容,如果没有使用“/”来结尾,
          则包含目录在内的整个内容全部复制,类似于rsync。
      owner,group,mode...
/*示例*/
ansible remote -m copy -a "src=/etc/fstab dest=/root/ owner=root group=root mode=0644"

  5.file模块:设置文件属性
    相关选项:
      force:需要在两种情况下强制创建软链接,一种是源文件不存在,但之后会建立的情况下;另一种是目标软链接已存在,
          需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no
      group:定义文件/目录的属组
      mode:定义文件/目录的权限
      owner:定义文件/目录的属主
      path:必选项,定义文件/目录的路径
      recurse:递归设置文件的属性,只对目录有效
      src:被链接的源文件路径,只应用于state=link的情况
      dest:被链接到的路径,只应用于state=link的情况
      state:
             directory:如果目录不存在,就创建目录
             file:即使文件不存在,也不会被创建
             link:创建软链接
             hard:创建硬链接
             touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间
             absent:删除目录、文件或者取消链接文件
/*示例*/
ansible remote -m file -a "path=/root/fstab owner=root group=root mode=600"
ansible storm_cluster -m file -a "src=/etc/resolv.conf dest=/tmp/resolv.conf state=link"创建链接文件
ansible storm_cluster -m file -a "path=/tmp/resolv.conf state=absent"  删除链接文件

  6.cron模块:计划任务的实现
    相关选项:
      minute=/hour=/day=/month=/weekday= 某个值不写,默认就是*
      name:必选项,任务描述信息
      job:执行的任务,要加引号
      state:present(创建)/absent(删除)
/*示例*/
ansible remote -m cron -a "minute=*/1 job='/usr/bin/echo 'hello'' name=hello"

  7.yum模块:管理安装相关程序包
    相关选项:
      name:程序包名称,可带版本号
      state:present、installed、latest(安装)/absent、removed(删除)

  8.service模块:管理服务
    相关选项:
      name:服务名称
      state:started/stopped/restarted
      enabled:true/false
      runlevel:运行级别

    9.group模块:管理用户组模块
    相关选项:
      name:组名称
      gid:指定GID
      state:present/absent
      system:yes/no
/*示例*/
ansible all -m group -a "name=test_grp state=present"

  10.user模块:管理用户模块
    相关选项:
      由于user模块的选项众多,这里只介绍一些常用的选项:
      name:用户名
      password:为用户设置登陆密码,此密码是明文密码加密后的密码
      update_password:always/on_create
          always:只有当密码不相同时才会更新密码(默认)
          on_create:只为新用户设置密码
      shell:用户的shell设定
      groups:用户组设定
      home:指定用户的家目录
      state:present/absent
      append:yes/no
        yes:增量添加group
        no:全量变更group,只设置groups指定的group组(默认)
      remove:配合state=absent使用,删除用户的家目录->remove=yes
      expires:设置用户的过期时间,值是一个时间戳
/*示例*/
ansible all -m user -a "name=test2 state=present groups=test2,test_grp shell=/bin/bash append=yes"
    11.ping 用来测试远程主机的运行状态
/*示例*/
ansible all -m ping
172.18.19.188 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

ansible-doc 
Usage: ansible-doc [-l|-s] [options] [-t <plugin type] [plugin]

plugin documentation tool

Options:
  -a, --all             **For internal testing only** Show documentation for
                        all plugins.
  -h, --help            show this help message and exit
  -l, --list            List available plugins
  -M MODULE_PATH, --module-path=MODULE_PATH
                        prepend colon-separated path(s) to module library
                        (default=[u'/root/.ansible/plugins/modules',
                        u'/usr/share/ansible/plugins/modules'])
  -s, --snippet         Show playbook snippet for specified plugin(s)
  -t TYPE, --type=TYPE  Choose which plugin type (defaults to "module")
  -v, --verbose         verbose mode (-vvv for more, -vvvv to enable
                        connection debugging)
  --version             show program's version number and exit

ansible-doc command    ===>会打印出command模块的使用帮助

4.Playbook
playbook是由一个或多个“play”组成的列表,可以让它们联同起来按事先编排的机制执行;所谓task无非是调用ansible的
  一个module,而在模块参数中可以使用变量;模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致。
执行模型:task list中的各任务按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个任务后再开始第二个。
  在顺序运行某playbook时,如果中途发生错误,所有已执行任务都将回滚,因此,在修改playbook后重新执行一次即可;
task组成:每个task都应该有其name,用于playbook的执行结果输出,建议其内容尽可能清晰地描述任务执行步骤。
如果未提供name,则action的结果将用于输出;
notify指定handler的执行机制:“notify”这个action可用于在每个play的最后被触发,在notify中列出的操作称为handler,
仅在所有的变化发生完成后一次性地执行指定操作。
5.实践ansible自动化安装nginx
首先、配置 /etc/hosts:
IP test
第二、配置 /etc/ansible/hosts:
[hadoop]
test

第三、创建目录
mkdir -p /ansible/roles/nginx/{defaults,files,handlers,meta,tasks,templates,vars}

第四、编辑文件
在files目录下创建shell安装脚本,并将nginx的压缩包也放到files下面
install_nginx.sh:


#!/bin/bash 
yum -y install zlib zlib-devel openssl openssl-devel pcre-devel
groupadd -r nginx
useradd -s /sbin/nologin -g nginx -r nginx
cd /tmp
tar xf nginx-1.9.9.tar.gz;cd nginx-1.9.9
mkdir /var/run/nginx/;chown nginx.nginx /var/run/nginx/
./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--pid-path=/var/run/nginx/nginx.pid \
--user=nginx \
--group=nginx \
--with-http_ssl_module
make && make install
sed  "/^\s*index / i proxy_pass http://localhost:8080;" /etc/nginx/nginx.conf
/usr/sbin/nginx 

在tasks目录中放置main.yml文件
main.yml:

- name: copy nginx_tar_gz to client
  copy: src=nginx-1.9.9.tar.gz dest=/tmp/nginx-1.9.9.tar.gz
- name: copy install_shell to client
  copy: src=install_nginx.sh dest=/tmp/install_nginx.sh
- name: install nginx
  shell: /bin/bash /tmp/install_nginx.sh
  
在ansible目录下放置webservice.yml文件
webservice.yml:

- hosts: hadoop
  remote_user: root
  roles:
    - nginx

第五、执行 
cd /ansible
ansible-playbook webservice.yml

目录结构解析如下:
roles/             \\ ansible所有的信息都放到此目录下面对应的目录中
└── nginx          \\ 角色名称
    ├── default    \\ 为当前角色设定默认变量时使用此目录,应当包含一个main.yml文件;
    ├── files      \\ 存放有copy或script等模块调用的文件
    ├── handlers   \\ 此目录应当包含一个main.yml文件,用于定义各角色用到的各handler
    ├── meta       \\ 应当包含一个main.yml,用于定义角色的特殊设定及其依赖关系;1.3及以后版本支持
    ├── tasks      \\ 至少包含一个名为main.yml的文件,定义了此角色的任务列表,可使用include指令
    ├── templates  \\ template模块会自动在此目录中寻找Jinja2模板文件
    └── vars       \\ 应当包含一个main.yml文件,用于定义此角色用到的变量
├───────────├──────────────────────────────────────────────────────────────────│
│  目录名    │                           说明                                    │
├───────────├──────────────────────────────────────────────────────────────────│
├defaults   │    为当前角色设定默认变量时使用此目录,应当包含一个main.yml文件           │
├───────────├──────────────────────────────────────────────────────────────────│
├handlers   │此目录中应当包含一个main.yml文件,用于定义此角色用到的各handler,         │
│           │ 在handler中使用include包含的其它的handler文件也应该位于此目录中         │
├───────────├──────────────────────────────────────────────────────────────────│
├meta       │ 应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系            │
├───────────├──────────────────────────────────────────────────────────────────│
├tasks      │ 至少应该包含一个名为main.yml的文件,其定义了此角色的任务列表,            │
│           │   此文件可以使用include包含其它的位于此目录中的task文件                 │
├───────────├──────────────────────────────────────────────────────────────────│
├templates  │        template模块会自动在此目录中寻找Jinja2模板文件                 │
├───────────├──────────────────────────────────────────────────────────────────│
├vars       │                   定义当前角色使用的变量                             │
├───────────├──────────────────────────────────────────────────────────────────│
├files      │              存放由copy或script等模块调用的文件                      │
├───────────├──────────────────────────────────────────────────────────────────│
├tests      │                   在playbook中角色的使用样例                        │
├───────────├──────────────────────────────────────────────────────────────────│


---
- name: create user
  hosts: remote
  remote_user: root
  gather_facts: false
  vars:
  - say: "tiger"
  tasks:
  - name: Copy file to client
#    copy: src=/tmp/tiger dest=/tmp/tigress
    template: src=/tmp/tiger dest=/tmp/{
 
 { say }}
    

ansible-playbook -i /root/xxx.cfg  /root/app/main.yml  --limit "lala_xxx" -e "user=wawo"

解析:
-i         指定要运行的配置文件
--limit    指定运行的ip地址
-e         指定运行的外部参数
运行的控制 YAML 文件为: /root/app/main.yml
---
- hosts: all
  roles:
    - xxx

hosts指定所有(all)的主机,但是由于在外部已经指定了主机的配置,所以all由外部指定参数来进行
roles指定要执行的具体剧本
roles的任务执行顺序
### 首先执行meta下的main.yml文件内容     可以设置该role和其它role之前的关联关系。 dependencies
### 然后执行tasks下的main.yml文件内容
### 用到的变量,会直接加载defaults/vars目录下的main.yml文件
### 用到的需要拷贝到远程机器的文件,会放到files目录下
### 用到模板文件,会放到 templates 目录下
### 在执行的task中,使用了notify后,会调用 handlers 目录下的main.yml文件


记录一些基本的使用模块
1、ansible中的include, include_tasks 和 import_tasks 的差别
include 被 deprecated(不建议使用)了. 建议使用 include_tasks 和 import_tasks

include_tasks
是动态的: 在运行时展开. when只应用一次. 被include的文件名可以使用变量.

import_tasks
是静态的: 在加载时展开. when在被import的文件里的每个task, 都会重新检查一次. 因为是加载时展开的, 
文件名的变量不能是动态设定的.
请确保文件名中使用到的变量被定义在vars中、vars_files中、或者extra-vars中,静态的import不支持其他方式传入的变量。

When using static includes, ensure that any variables used in their names are defined in 
vars/vars_files or extra-vars passed in from the command line. Static includes cannot use 
variables from inventory sources like group or host vars.
除了上述不同之处,在使用"循环操作"和"条件判断"时,"include_tasks"和"import_tasks"也有很多不同点需要注意,注意点如下。
如果想要对包含的任务列表进行循环操作,则只能使用"include_tasks"关键字,不能使用"import_tasks"关键字,
"import_tasks"并不支持循环操作,
也就是说,使用"loop"关键字或"with_items"关键字对include文件进行循环操作时,只能配合"include_tasks"才能正常运行。
when关键字对"include_tasks"和"import_tasks"的实际操作有着本质区别,区别如下:
当对"include_tasks"使用when进行条件判断时,when对应的条件只会应用于"include_tasks"任务本身,
当执行被包含的任务时,不会对这些被包含的任务重新进行条件判断。
当对"import_tasks"使用when进行条件判断时,when对应的条件会应用于被include的文件中的每一个任务,当执行被包含的任务时,
会对每一个被包含的任务进行同样的条件判断。

对于tags和handler 
与"include_tasks"不同,当为"import_tasks"添加标签时,tags是针对被包含文件中的所有任务生效的,与"include"关键字的效果相同。
"include_tasks"与"import_tasks"都可以在handlers中使用,并没有什么不同,不过在当前2.7.0版本中,如果在handlers
中使用"import_tasks"引用任务列表,会出现bug,期待修复。

  tasks:
  - include_tasks:
      file: in.yml
      apply:
        tags:
        - t1
    tags: always

2、setup模块用于收集远程主机的一些基本信息。而在playbook中,默认参数 ” gather_facts: True ” 的含义就是在远程主机
运行setup模块,并将收集的信息记录起来。
gather_facts: False    不使用远程主机的setup模块,
tasks:
    - set_fact: mode=1   设置远程主机的参数 mode=1 


3、一点疑惑
---
- hosts: webserver
  vars:          
    logserver: 10.127.2.170
  gather_facts: True 
  tasks:
  - name: add conf to config files to CentOS6
    lineinfile: dest=/etc/rsyslog.conf line="*.*            @{
 
 { logserver }}"
    when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == "6"
  - name: restart syslog @CentOS6
    when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == "6"
    service: name=rsyslog state=restarted
  - name: add conf to config files to RedHat 5
    lineinfile: dest=/etc/syslog.conf line="*.*            @{
 
 { logserver }}"
    when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == "5"
  - name: restart syslog @RedHat 5
    when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == "5"
    service: name=syslog state=restarted
有同学要问,为什么要进行四次when判断,两次不就够了,写成这样

  - name: restart syslog @CentOS6
    when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == "6"
    lineinfile: dest=/etc/rsyslog.conf line="*.*            @{
 
 { logserver }}"
    service: name=rsyslog state=restarted
这是不行的,ansible要求每一个play里面只能使用一个模块,使用多个会报错
ERROR: multiple actions specified in task

4、tasks/main.yml 里面有如下行:
    - name: Configure Tomcat server
      template: src=server.xml dest=/usr/share/tomcat/conf/
      notify: restart tomcat

    - name: Configure Tomcat user
      template: src=tomcat-users.xml dest=/usr/share/tomcat/conf/
      notify: restart tomcat

template模块官方的解释为: Templates a file out to a remote server. 大概意思就是当 src=config_file 
这些文件发生变化的时候,触发notify的动作
templates目录就是存放这些文件用的(一般都是一些配置文件)

handlers目录里有一个main.yml文件,就是用来执行notify动作的

大概的流程为:
    templates/config_file 发生变化 --> 触发notify: action --> action定义在 handlers/main.yml 中
    notify后面的动作名字必须与handlers/main.yml里面的name后面的名字一致,例:
    - name: Configure Tomcat user
      template: src=tomcat-users.xml dest=/usr/share/tomcat/conf/
      notify: restart tomcat

    handlers:
      - name: restart tomcat
        service: name=tomcat state=restart

而files目录下存放的是一些脚本, 通过copy模块可以transport到remote hosts上的,而后触发notify动作之后执行的脚本

5、语法验证
● 在执行playbook之前,最后好进行验证,确保内容无误

$  ansible-playbook --syntax-check  site.yml

playbook: site.yml
● 语法失败时将会报告错误(无法坚持模块内参数是否正确)

6、执行空运行
● -C选项。这会使ansible报告在执行该playbook时将会发生什么更改,但不会对受管主机进行任何实际更改

$  ansible-playbook -C  site.yml

7、特权升级属性:
● 特提供额外的属性,从而在playbook内定义特权升级参数。
become布尔值参数可用于启动或禁用特权升级,无论在ansible配置文件如何定义

become:  Ture/False

● 如果启用了特权升级,可以使用become_method属性来定义play期间所要使用的特权升级的方法sudo

become_method:  sudo

此外,启用特权升级时,become_user属性可以定义play上下文内用于特权升级的用户
become_user:  root 

8、用户属性:
● playbook中的任务通常通过网络连接多受管主机执行。与临时命令相同,用于这些任务执行的用户账号取决于ansible配置文件
/etc/ansible/ansible.cfg中的参数。执行任务的用户可以通过remote_user参数定义,不过,如果启用了特权升级,
become_user等其他参数也会发生作用 
● 如果用于任务执行的Ansible配置中定义的远程用户不合适,可以通过在play中使用remote_user属性覆盖

remote_user: devops

9、修改文件
lineinfile
用于检测文件是否存在特殊行或者使用后端正则表达式来替换匹配到的特殊行

10、replace
lineinfile的多行匹配版本,此模块会在文件中插入一段内容,并在内容开始和结束位置设置标签,后续可以使用标签可以
对此块内容进行操作
path参数:必须参数,指定要操作的文件,2.3版本之前,只能使用dest, destfile, name指定要操作的文件,2.4版本中,
仍然可以使用这些参数名,这些参数名作为path参数的别名使用。
regexp参数:必须参数,指定一个python正则表达式,文件中与正则匹配的字符串将会被替换。
replace参数: 指定最终要替换成的字符串。
backup参数:是否在修改文件之前对文件进行备份,最好设置为yes。


### 在ml2_conf.ini文件的[ml2]和[ml2_type_vlan]字段之间插入一段内容
- name: Enable ovn in neutron-server
  replace:
    dest: "{
 
 { node_config_directory }}/neutron-server/ml2_conf.ini"
    regexp: '\[ml2\][\S\s]*(?=\[ml2_type_vlan\])'
    replace: |+
      [ml2]
      type_drivers = local,flat,vlan,geneve
      tenant_network_types = geneve
      mechanism_drivers = ovn
      extension_drivers = port_security
      overlay_ip_version = 4

      [ml2_type_geneve]
      vni_ranges = 1:65536
      max_header_size = 38

      [ovn]
      ovn_nb_connection = tcp:{
 
 { api_interface_address }}:{
 
 { ovn_northdb_port }}
      ovn_sb_connection = tcp:{
 
 { api_interface_address }}:{
 
 { ovn_sourthdb_port }}
      ovn_l3_mode = False
      ovn_l3_scheduler = chance
      ovn_native_dhcp = True
      neutron_sync_mode = repair
    backup: yes
  when:
    - action == "deploy"
    - inventory_hostname in groups['network']
  notify:
    - Restart neutron-server container
    
11、ini_file
ini后缀格式文件修改
ini文件是十分常见的一种配置文件,ansible内置了ini配置文件的管理模块,用于对文件进行配置项的管理。
Ø 修改配置文件/root/demo.ini,selection为cron的选项组的crontime选项,把cron的值修改为10。
ansible all –m ini_file –a “dest=/root/demo.ini section=cron option=crontime value=10”

### 设置l3_agent.ini文件[DEFAULT]字段的external_network_bridge选项值为br-ex
- name: Set the external network bridge
  vars:
      agent: "{
 
 { 'neutron-vpnaas-agent' if enable_neutron_vpnaas | bool else 'neutron-l3-agent' }}"
  ini_file:
      dest: "{
 
 { node_config_directory }}/{
 
 { agent }}/l3_agent.ini"
      section: "DEFAULT"
      option: "external_network_bridge"
      value: "{
 
 { neutron_bridge_name | default('br-ex') }}"
      backup: yes
  when:
      - action == "deploy"
      - inventory_hostname in ovn_central_address
  delegate_to: "{
 
 { item }}"
  with_items: "{
 
 { groups['neutron-server'] }}"
  notify:
      - Restart {
 
 { agent }} container


12、循环控制
with_items
标准循环,用于执行重复任务,{
 
 { item }}类似宏展开

- name: add several users
  user:
    name: "{
 
 { item.name }}"
    state: present
    groups: "{
 
 { item.groups }}"
  with_items:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }
with_nested
嵌套循环

### 修改neutron-server组所有主机的ml2_conf.ini文件的对应字段值
- name: Enable ovn in neutron-server
  vars:
    params:
      - { section: 'ml2', option: 'type_drivers', value: 'local,flat,vlan,geneve' }
      - { section: 'ml2', option: 'tenant_network_types', value: 'geneve' }
      - { section: 'ml2', option: 'mechanism_drivers', value: 'ovn' }
      - { section: 'ml2', option: 'extension_drivers', value: 'port_security' }
      - { section: 'ml2', option: 'overlay_ip_version', value: '4' }
      - { section: 'securitygroup', option: 'enable_security_group', value: 'True' }
  ini_file:
    dest: "{
 
 { node_config_directory }}/neutron-server/ml2_conf.ini"
    section: "{
 
 { item[0].section }}"
    option: "{
 
 { item[0].option }}"
    value: "{
 
 { item[0].value }}"
    backup: yes
  when:
    - action == "deploy"
    - inventory_hostname in ovn_central_address
  delegate_to: "{
 
 { item[1] }}"
  with_nested:
    - "{
 
 { params }}"
    - "{
 
 { groups['neutron-server'] }}"
  notify:
    - Restart neutron-server container

13、流程控制
tags
设置任务标签

tasks:
  - yum: name={
 
 { item }} state=installed
    with_items:
      - httpd
      - memcached
    tags:
      - packages

  - template: src=templates/src.j2 dest=/etc/foo.conf
    tags:
      - configuration

### 执行playbook可以指定只执行标签对应任务或跳过标签对应任务
# ansible-playbook example.yml --tags "configuration,packages"
# ansible-playbook example.yml --skip-tags "notification"

14、failed_when
用来控制playbook退出

- name: Check if firewalld is installed
  command: rpm -q firewalld
  register: firewalld_check
  failed_when: firewalld_check.rc > 1
  when: ansible_os_family == 'RedHat'
15、pre_tasks/post_tasks
用来设置在执行roles模块之前和之后需要执行的任务

16、wait_for
等待一个端口变得可用或者等待一个文件变得可用

- local_action: wait_for port=22 host="{
 
 { ansible_ssh_host | default(inventory_hostname) }}" search_regex=OpenSSH delay=10 
   #等待openssh启动,10s检查一次

- name: Wait for container ssh
  wait_for:
    port: "22"
    delay: "{
 
 { ssh_delay }}"
    search_regex: "OpenSSH"
    host: "{
 
 { ansible_host }}"
  delegate_to: "{
 
 { physical_host }}"
  register: ssh_wait_check
  until: ssh_wait_check | success
  retries: 3
  when:
    - (_mc is defined and _mc | changed) or (_ec is defined and _ec | changed)
    - not is_metal | bool
  tags:
    - common-lxc

17、执行shell命令
### ignore_errors为true表示命令执行出错也不会退出playbook
- name: Check if clean is needed
  command: docker exec openvswitch_vswitchd ovs-vsctl br-exists br-tun
  register: result
  ignore_errors: True

18、切换用户
### 使用become会先切换成apache用户,再执行command命令,默认become_user用户为root
### (如果你ansible配置的就是root用户的免密码登入那就不需要become了)
- name: Run a command as the apache user
  command: somecommand
  become: true
  become_user: apache
检测链表是否为空

### pip_wheel_install为链表变量
- name: Install wheel packages
  shell: cd /tmp/wheels && pip install {
 
 { item }}*
  with_items:
    - "{
 
 { pip_wheel_install | default([]) }}"
  when: pip_wheel_install > 0
    

19、when中使用jinja2
when表达式中不建议直接使用{
 
 {}}的方式来获取变量值,如果变量是字符串可以使用管道操作| string来获取变量值

- name: Checking free port for OVN
  vars:
    service: "{
 
 { neutron_services[item.name] }}"
  wait_for:
    host: "{
 
 { hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
    port: "{
 
 { item.port }}"
    connect_timeout: 1
    state: stopped
  when:
    - container_facts[ item.facts | string ] is not defined
    - service.enabled | bool
    - service.host_in_groups | bool
  with_items:
    - { name: "ovn-nb-db-server", port: "{
 
 { ovn_northdb_port }}", facts: "ovn_nb_db" }
    - { name: "ovn-sb-db-server", port: "{
 
 { ovn_sourthdb_port }}", facts: "ovn_sb_db" }

20、uri web访问,类似执行curl命令
uri模块主要用于发送HTTP协议,通过使用uri模块,可以让目标主机向指定的网站发送如Get、Post这样的HTTP请求,
并且能得到返回的状态码。

- name: test proxy URL for connectivity
  uri:
    url: "{
 
 { repo_pkg_cache_url }}/acng-report.html"
    method: "HEAD"
  register: proxy_check
  failed_when: false
  tags:
    - common-proxy
    
21、local_action 将任务放在ansible控制主机(运行ansible-playbook的主机)上执行

- name: Check if the git cache exists on deployment host
  local_action:
    module: stat
    path: "{
 
 { repo_build_git_cache }}"
  register: _local_git_cache
  when: repo_build_git_cache is defined
  
22、When语句官方文档

    在有的时候play的结果依赖于变量、fact或者是前一个任务的执行结果,从而需要使用到条件语句。

    有的时候在特定的主机需要跳过特定的步骤,例如在安装包的时候,需要指定主机的操作系统类型,
或者是当操作系统的硬盘满了之后,需要清空文件等

    在ansible中,我们可以使用如下比较运算符:
        ==  :比较两个对象是否相等,相等为真
        !=  :比较两个对象是否不等,不等为真
        >   :比较两个值的大小,如果左边的值大于右边的值,则为真
        <   :比较两个值的大小,如果左边的值小于右边的值,则为真
        >=  :比较两个值的大小,如果左边的值大于右边的值或左右相等,则为真
        <=  :比较两个值的大小,如果左边的值小于右边的值或左右相等,则为真
    逻辑运算符:
        and  :逻辑与,当左边与右边同时为真,则返回真
        or   :逻辑或,当左边与右边有任意一个为真,则返回真
        not  :取反,对一个操作体取反
        ( )  :组合,将一组操作体包装在一起,形成一个较大的操作体
        
    判断变量
    defined :判断变量是否已经定义,已经定义则返回真
    undefind :判断变量是否已经定义,未定义则返回真
    none :判断变量值是否为空,如果变量已经定义,但是变量值为空,则返回真
    判断执行结果
    success 或 succeeded:通过任务的返回信息判断任务的执行状态,任务执行成功则返回真
    failure 或 failed:通过任务的返回信息判断任务的执行状态,任务执行失败则返回真
    change 或 changed:通过任务的返回信息判断任务的执行状态,任务执行状态为changed则返回真
    skip 或 skipped:通过任务的返回信息判断任务的执行状态,当任务没有满足条件,而被跳过执行时,则返回真
    判断路径的使用方式:
    file : 判断路径是否是一个文件,如果路径是一个文件则返回真
    directory :判断路径是否是一个目录,如果路径是一个目录则返回真
    link :判断路径是否是一个软链接,如果路径是一个软链接则返回真
    mount:判断路径是否是一个挂载点,如果路径是一个挂载点则返回真
    exists:判断路径是否存在,如果路径存在则返回真
    在2.6及以后的版本,支持直接写下面的关键字;2.5之前的版本需要在前面加 is 或 is not 
    判断字符串:
    lower:判断包含字母的字符串中的字母是否是纯小写,字符串中的字母全部为小写则返回真
    upper:判断包含字母的字符串中的字母是否是纯大写,字符串中的字母全部为大写则返回真
    判断整除
    even :判断数值是否是偶数,是偶数则返回真
    odd :判断数值是否是奇数,是奇数则返回真
    divisibleby(num) :判断是否可以整除指定的数值,如果除以指定的值以后余数为0,则返回真
    
    subset:判断一个list是不是另一个list的子集,是另一个list的子集时返回真
    superset: 判断一个list是不是另一个list的父集,是另一个list的父集时返回真
    
    string:判断对象是否是一个字符串,是字符串则返回真
    number:判断对象是否是一个数字,是数字则返回真
    
    下面的例子表示为使用when语句,如下:

    tasks:
      - name: "shutdown Debian flavored systems"
        command: /sbin/shutdown -t now
        when: ansible_os_family == "Debian"

    也可以使用括号来表示一组条件,如下所示:
    tasks:
      - name: "shutdownCentOS6andDebian7systems"
        command: /sbin/shutdown -t now
        when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or
              (ansible_distribution == "Debian" and ansible_distribution_major_version == "7")

    假设需要忽略一个语句的错误,根据执行的结果是成功还是失败从而执行不同的命令,如下(使用的是jinja2的过滤):

    tasks:
      - command: /bin/false  没有 - name 时,此行将被默认成为标题-- TASK: [command: /bin/false] 
        register: result
        ignore_errors: True
      - command: /bin/something
        when: result|failed
      - command: /bin/something_else
        when: result|succeeded
      - command: /bin/still/something_else
        when: result|skipped

    当接收到一个变量是一个字符串的时候,然后想做一个数字的比较,那么可以使用如下的方式
(在这个例子中远程主机上需要有lsb_package包):

    tasks:
      - shell: echo "only on Red Hat 6, derivatives, and later"
        when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6

    在playbooks中或者inventory清单中定义的变量也是可以使用,假设任务的执行依赖于一个布尔变量,如下:
    vars:
      epic: true

    条件执行如下所示:
    tasks:
      - shell: echo "This certainly is epic!"
        when: epic

    或者使用如下形式:
    tasks:
      - shell: echo "This certainly isn't epic!"
        when: not epic

    如果需要的变量没有定义,那么可以skip或者使用jinja2的defined如下所示:

    tasks:
      - shell: echo "I've got '{
 
 { foo }}' and am not afraid to use it!"
        when: foo is defined

      - fail: msg="Bailing out. this play requires 'bar'"
        when: bar is undefined

    当结合使用when和with_items的时候,需要注意的是when语句会对每个item进行单独的处理,如下所示:

    tasks:
      - command: echo {
 
 { item }}
        with_items: [ 0,2,4,6,8,10 ]
        when: item > 5
     

    3、在roles中和include中使用when
    当几个任务都是使用相同的条件的时候,那么可以将条件写在include之中,那么当写在include的时候,
每个任务都会去判断条件,如下所示:

    - include: tasks/sometasks.yml
      when: "'reticulatingsplines'inoutput"

    或者在roles中使用,如下:

    - hosts: webservers
      roles:
        - { role:debian_stock_config,when:ansible_os_family == 'Debian' }

    4、条件导入
    在playbook中可能会根据一些特定的标准从而做不同的事情,在一个playbook中工作在不同的平台和os版本是最好的例子

    如下的例子表示,在centos和debian中apache的包是不同的,从而可以使用以下:

    ---
    - hosts: all
      remote_user: root
      vars_files:
        - "vars/common.yml"
        - [ "vars/{
 
 {ansible_os_family}}.yml","vars/os_defaults.yml" ]
      tasks:
      - name: make sure apache is running
        service: name={
 
 { apache }} state=running

    另外,在变量文件中只包含key和values,如下:

    ---
    # for vars/CentOS.yml
    apache: httpd
    somethingelse: 42

    如何工作的呢?
    当操作系统为centos的时候,那么会加载变量/vars/centos.yml,当文件不存在的时候,那么会加载defaults.yml,
当没有找到任何文件的时候,那么就会出错。当操作系统为debian的时候,那么就会加载变量/vars/debian.yml,
没有就加载defaults.yml

    当使用整个功能的时候,在运行playbook之前必须先安装facter或者ohai,也可以直接在playbook中使用如下所示:

    # for facter
    ansible -m yum -a "pkg=facter state=present"
    ansible -m yum -a "pkg=ruby-json state=present"
     
    # for ohai
    ansible -m yum -a "pkg=ohai state=present"

    5、基于变量选择文件和模板
    在有的时候,配置文件使用copy或者是template的时候,可能会依赖于变量。
    下面的例子中表示使用template输出一个配置文件,在centos和debian中不同,如下:
    - name: template a file
      template: src={
 
 { item }} dest=/etc/myapp/foo.conf
      with_first_found:
        - files:
           - {
 
 { ansible_distribution }}.conf
           - default.conf
          paths:
           - search_location_one/somedir/
           - /opt/other_location/somedir/

    6、注册变量
    在playbook中可以使用变量的值便于其他的任务用到。
    关键字register用来保存变量值,整个变量可以使用在template中,动作行中,或者是when语句中,如下所示:

    - name: test play
      hosts: all
      tasks:
          - shell: cat /etc/motd
            register: motd_contents
     
          - shell: echo "motd contains the word hi"
            when: motd_contents.stdout.find('hi') != -1

    注册的变量值可以用stdout得到,或者用with_items得到,也可以使用stdout_lines得到,
    如下所示:

    - name: registered variable usage as a with_items 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
            with_items: home_dirs.stdout_lines
            # same as with_items: home_dirs.stdout.split()
    
23、文件组装模块——assemble(主要用于把多份配置文件片段组装成一个配置文件)
Ø 将/root/demo下的片段组装后放到/root/target目录下
ansible all –m assemble –a “dest=/root/demo src=/root/target”

24、文件拉取模块——fetch
Ø 将远端主机的/etc/salt/minion文件收集回服务器/root/demo目录下
ansible all –m fetch –a “dest=/root/demo src=/etc/salt/minion “

25、文件管理模块——file
                    file        如果是directory,那么则会创建文件夹
                    link        如果是file,则会创建文件
state 默认值:file   directory   如果是link,则会创建链接
                    hard        如果是hard,则会创建硬链接
                    touch       如果是touch,则会创建文件
                    absent      如果是absent,则会删除文件

26、unarchive模块
用于解压文件,模块包含如下选项:
    copy:在解压文件之前,是否先将文件复制到远程主机,默认为yes。若为no,则要求目标主机上压缩包必须存在。
    creates:指定一个文件名,当该文件存在时,则解压指令不执行
    dest:远程主机上的一个路径,即文件解压的路径 
    grop:解压后的目录或文件的属组
    list_files:如果为yes,则会列出压缩包里的文件,默认为no,2.0版本新增的选项
    mode:解决后文件的权限
    src:如果copy为yes,则需要指定压缩文件的源路径 
    owner:解压后文件或目录的属主
示例如下:
    - unarchive: src=foo.tgz dest=/var/lib/foo
    - unarchive: src=/tmp/foo.zip dest=/usr/local/bin copy=no
    - unarchive: src=https://example.com/example.zip dest=/usr/local/bin copy=no

27、fail
用于终止当前playbook的执行,通常与条件语句组合使用,当满足条件时,终止当前play的运行。可以直接由failed_when取代。
'changed_when'关键字的作用是在条件成立时,将对应任务的执行状态设置为changed
选项只有一个:
msg:终止前打印出信息
示例:
- fail: msg="The system may not be provisioned according to the CMDB status."
  when: cmdb_status != "to-be-staged"

28、pause
在playbook执行的过程中暂停一定时间或者提示用户进行某些操作
常用参数:
minutes:暂停多少分钟
seconds:暂停多少秒
prompt:打印一串信息提示用户操作
示例:
- name: wait on user input
  pause: prompt="Warning! Detected slight issue. ENTER to continue CTRL-C a to quit" 
- name: timed wait
  pause: seconds=30

29、关于 async 和 poll
有的任务执行起来却不那么直接,可能会花比较长的时间,甚至可能会比ssh的超时时间还要长。这种情况任务是不是没法执行了?
ansible考虑到了这种情况,官方文档介绍了这个问题的解决方法,就是让下发的任务执行的连接变为异步:任务下发之后,
长连接不再保持,而是每隔一段时间轮询结果,直到任务结束。
他们在playbook的任务中加入两个参数:

async和poll
async参数值代表了这个任务执行时间的上限值。即任务执行所用时间如果超出这个时间,则认为任务失败。
此参数若未设置,则为同步执行。
poll参数值代表了任务异步执行时轮询的时间间隔。如果poll为0,就相当于一个不关心结果的任务。

官方给出例子:
  ----
    hosts: all
    remote_user: root
    tasks:
      - name: simulate long running op (15 sec), wait for up to 45 sec, poll every 5 sec
        command: /bin/sleep 15
        async: 45
        poll: 5

如果还想要更方便地看轮询结果,ansible还提供了这个模块async_status。

  ---
    # Requires ansible 1.8+
    - name: 'YUM - fire and forget task'
      yum: name=docker-io state=installed
      async: 1000
      poll: 0
      register: yum_sleeper

    - name: 'YUM - check on fire and forget task'
      async_status: jid={
 
 { yum_sleeper.ansible_job_id }}
      register: job_result
      until: job_result.finished
      retries: 30
第一个job执行异步任务,并且注册了一个名字叫yum_sleeper,用于提供给第二个job作为轮询对象,并且poll设为0,它自己不再轮询。
第二个job使用async_status模块,进行轮询并返回轮询结果。准备检查30次。结果如下:

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
ok: [cloudlab001]

TASK [YUM - fire and forget task] **********************************************
ok: [cloudlab001]

TASK [YUM - check on fire and forget task] *************************************
FAILED - RETRYING: TASK: YUM - check on fire and forget task (29 retries left).
FAILED - RETRYING: TASK: YUM - check on fire and forget task (28 retries left).
FAILED - RETRYING: TASK: YUM - check on fire and forget task (27 retries left).
FAILED - RETRYING: TASK: YUM - check on fire and forget task (26 retries left).
FAILED - RETRYING: TASK: YUM - check on fire and forget task (25 retries left).
FAILED - RETRYING: TASK: YUM - check on fire and forget task (24 retries left).
changed: [cloudlab001]

PLAY RECAP *********************************************************************
cloudlab001                : ok=3    changed=1    unreachable=0    failed=0



---
- hosts: all
  gather_facts: no
  tasks:
  - shell: "ls /opt"
    register: returnvalue
  - debug:
      var: returnvalue

30、debug 调试模块,用于在调试中输出信息
常用参数:
msg:调试输出的消息,不能与var同时使用
var:将某个任务执行的输出作为变量传递给debug模块,debug会直接将其打印输出,不能与msg同时使用
verbosity:debug的级别(默认是0级,全部显示,如果设置为3时,会在 -vvv 时打印出信息)


31、内置变量 groups
配置文件 justtest 如下:

10.1.1.60
justtest.zsythink.net ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71
 
[testA]
test60 ansible_host=10.1.1.60
test61 ansible_host=10.1.1.61
 
[testB]
justtest ansible_host=10.1.1.70
 
[test:children]
testA
testB
上述清单中,显式的指定了三个组,testA组、testB组、test组,其中,testA组与testB组是test组的子组,
除了组中的主机,还有三台主机没有任何分组,直接写在了清单中。
现在,我们获取一下groups变量的值,看看会返回哪些信息,随便操作清单中的任意一台主机即可,示例如下
# ansible justtest -m debug -a "msg={
 
 {groups}}"
justtest | SUCCESS => {
    "changed": false, 
    "msg": {
        "all": [
            "10.1.1.60", 
            "justtest.zsythink.net", 
            "test71", 
            "test60", 
            "test61", 
            "justtest"
        ], 
        "test": [
            "test60", 
            "test61", 
            "justtest"
        ], 
        "testA": [
            "test60", 
            "test61"
        ], 
        "testB": [
            "justtest"
        ], 
        "ungrouped": [
            "10.1.1.60", 
            "justtest.zsythink.net", 
            "test71"
        ]
    }
}
从上述返回信息可以看出,所有主机默认被分成了组名为"all"的组,testA组中有两台主机,testB组中有一台主机,
由于testA组和testB组都属于test组的子组,所以testA组与testB组中的主机都属于test组,
由于有三台主机在清单中并未分组,所以,ansible自动将没有分组的主机分到了名为"ungrouped"的组中,即组名为"未分组"的组。
我们还能够通过组名,获取到指定组的分组信息,假设,我想要获取到上例中test组中的主机名称,则可以使用如下方法。
# ansible justtest -m debug -a "msg={
 
 {groups.test}}"
# ansible justtest -m debug -a "msg={
 
 {groups['test']}}"
# ansible justtest -m debug -a "msg={
 
 {groups.ungrouped}}"

32、handlers模块 之 meta模块
---
- hosts: justtest
  remote_user: root
  tasks:
  - name: task1
    file: path=/testdir/testfile
          state=touch
    notify: handler1
  - name: task2
    file: path=/testdir/testfile2
          state=touch
    notify: handler2
 
  - meta: flush_handlers
 
  - name: task3
    file: path=/testdir/testfile3
          state=touch
    notify: handler3
 
  handlers:
  - name: handler1
    file: path=/testdir/ht1
          state=touch
  - name: handler2
    file: path=/testdir/ht2
          state=touch
  - name: handler3
    file: path=/testdir/ht3
          state=touch
如上例所示,我在task1与task2之后写入了一个任务,我并没有为这个任务指定name属性,这个任务使用meta模块,
meta任务是一种特殊的任务,meta任务可以影响ansible的内部运行方式,上例中,meta任务的参数值为flush_handlers,
"meta: flush_handlers"表示立即执行之前的task所对应handler,什么意思呢?
意思就是,在当前meta任务之前,一共有两个任务,task1与task2,它们都有对应的handler,
当执行完task1与task2以后,立即执行对应的handler,而不是像默认情况那样在所有任务都执行完毕以后
才能执行各个handler,那么我们来实际运行一下上述剧本,运行结果如下

33、handlers模块 之 立即执行
我们还可以在一个task中一次性notify多个handler,怎样才能一次性notify多个handler呢?
你可能会尝试将多个handler使用相同的name,但是这样并不可行,因为当多个handler的name相同时,只有一个handler会被执行。
所以,我们并不能通过这种方式notify多个handler,如果想要一次notify多个handler,则需要借助另一个关键字,它就是'listen'。
你可以把listen理解成"组名",我们可以把多个handler分成"组",当我们需要一次性notify多个handler时,
只要将多个handler分为"一组",使用相同的"组名"即可,当notify对应的值为"组名"时,"组"内的所有handler都会被notify,
这样说可能还是不容易理解,我们来看个小示例,示例如下
---
- hosts: justtest
  remote_user: root
  tasks:
  - name: task1
    file: path=/testdir/testfile
          state=touch
    notify: handler group1
 
  handlers:
  - name: handler1
    listen: handler group1
    file: path=/testdir/ht1
          state=touch
  - name: handler2
    listen: handler group1
    file: path=/testdir/ht2
          state=touch

34、tags模块
---
- hosts: justtest
  remote_user: root
  tags: httpd
  tasks:
  - name: install httpd package
    tags: ['package']
    yum:
      name=httpd
      state=latest
 
  - name: start up httpd service
    tags:
      - service
      - always               # 意思是service的tag总会被执行
    service:
      name: httpd
      state: started
当tags写在play中而非task中时,play中的所有task会继承当前play中的tags,而上例中,两个任务都会继承httpd标签,
同时还有拥有自己的标签。

ansible-playbook --tags package,service testhttpd.yml
或者 ansible-playbook --tags  httpd testhttpd.yml
执行的结果是一样的,都会将两个标签进行执行
其实,ansible还预置了5个特殊tag,这5个特殊tag分别为:
always
never(2.5后的版本才有)
tagged
untagged
all

always:当我们把任务的tags的值指定为always时,那么这个任务就总是会被执行,除非你使用'--skip-tags'
选项明确指定不执行对应的任务, --skip-tags always 如果存在多个tag标记了 always,我们只想跳过某一个,
那么可以使用 --skip-tags service

never:在2.5版本的ansible中,引入了新的特殊标签 'never',
从字面上理解,never的作用应该与always正好相反,由于我当前使用的ansible版本为2.4(还没有引入never标签),
所以当指定任务的标签为never时,貌似被ansible当做了自定义标签,所以如果你安装了2.5版本的ansible,
可以尝试一下never标签的作用,由于还没有实际使用过2.5版本,所以此处暂时不进行示例。

ansible-playbook --tags tagged testtag.yml
上述命令表示只执行有标签的任务,没有任何标签的任务不会被执行。

ansible-playbook --skip-tags tagged testtag.yml
上述命令表示跳过包含标签的任务,即使对应的任务包含always标签,也会被跳过。

ansible-playbook --tags untagged testtag.yml
上述命令表示只执行没有标签的任务,但是如果某些任务包含always标签,那么这些任务也会被执行。

特殊标签all表示所有任务会被执行,不用指定,默认情况下就是使用这个标签。


35、变量-vars
使用 vars 可以在当前的play中设置变量
---
- hosts: all
  vars:
    v: wawo
    
  tasks:
  - name invoke v
    file:
      path: /home/{
 
 { v }}
      state: touch

也可以定义属性:
---
- hosts: all
  vars:
    nginx:
      conf80: /etc/nginx/conf.d/80.conf
      conf8080: /etc/nginx/conf.d/8080.conf
    
  tasks:
  - name invoke v
    file:
      path: "{
 
 { nginx.conf80 }}"    ==    "{
 
 { nginx['conf80'] }}"  两个方式等价
      state: touch
此处的变量增加了 " 引号,原因是使用变量是出于开头的位置。
在playbook中参数赋值,可以使用 : 也可以使用 = 。当使用 = 进行赋值时,就不需要考虑使用 " 引号了。
但是要使用 : 冒号时,就需要在紧邻参数的那个变量处添加 " 引号。
      path={
 
 { nginx.conf80 }}
      path: /home/{
 
 { v }}
在实际使用中,我们提倡"变量文件分离",可以通过 vars_files 关键字引入文件
  vars_files:
  - /testdir/ansible/nginx_vars.yml
  - yaml_file_path
  
  - include_vars:
    file: /testdir/ansible/testfile
    name: get_var                       将文件中的所有变量赋值给get_var
    extensions: [yaml,yml,json,varfile] 指定包含的文件
    depth: 1                            指定目录深度
    files_matching: "^var_.*"           指定过滤条件
    ignore_files: ["^var_.*",varintest.yaml] 忽略过滤条件
    

36、变量-注册变量 register
---
- hosts: justtest
  remote_user: root
  tasks:
  - name: test shell
    shell: "echo test > /var/testshellfile"
    register: testvar
  - name: shell module return values
    debug:
      var: testvar
testvar会将shell执行的结果进行保存
37、提示用户操作-交互操作
---
- hosts: 192.168.43.130
  vars_prompt:
    - name: "your_name"
      prompt: "What is your name"
      private: no                 # 可以让输入可见,不加此属性看不到对应的输入信息,适用于密码
    - name: "your_age"
      prompt: "How old are you"
  tasks:
  - name: output vars
    debug:
      msg: Your name is {
 
 {your_name}},You are {
 
 {your_age}} years old.
还可以使用选择的形式的如下:
  vars_prompt:
    - name: "solution"
      prompt: "Choose the solution you want \n
      A: solutionA\n
      B: solutionB\n
      C: solutionC\n"
      private: no
      default: A
38、外部设置变量-通过 -e 和 --extra-vars
通过对应的文件设置变量,调用时,需要使用 @file_path 进行引用
ansible-playbook cmdvar.yml -e "@/testdir/ansible/testvar.yml"

39、set_fact 定义变量
---
- hosts: justtest
  remote_user: root
  tasks:
  - set_fact:
      testvar: "testtest"
  - debug:
      msg: "{
 
 {testvar}}"

40、循环 with_list、with_items、with_flattened、with_together、with_cartesian
---
- hosts: justtest
  remote_user: root
  gather_facts: no
  vars:
    dirs:
    - "/opt/a"
    - "/opt/b"
    - "/opt/c"
    - "/opt/d"
  tasks:
  - file:
      path: "{
 
 {item}}"
      state: touch
    with_items: "{
 
 {dirs}}"

---
- hosts: justtest
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{
 
 {item}}"
    with_items: [ 1, 2, 3 ]
        ||
    with_items:
    - 1
    - 2
    - 3
with_list 
经过with_list处理后,每个嵌套在大列表中的小列表都被当做一个整体存放在item变量中,最终被debug作为一个
小整体输出了,而不会像with_items一样将小列表"展开拉平"后一并将小列表中的元素循环输出。
with_flattened 
拉平展开,与with_list基本一致
with_together
with_together可以将两个列表中的元素"对齐合并",单单用语言来描述,不是特别容易理解,不如来看一个小示例,
示例playbook如下:

---
- hosts: justtest
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{
 
 { item }}"
    with_together:
    - [ 1, 2, 3 ]
    - [ a, b, c ]
结果:
TASK [debug] ******************************
ok: [justtest] => (item=[1, u'a']) => {
    "changed": false,
    "item": [
        1,
        "a"
    ],
    "msg": [
        1,
        "a"
    ]
}
ok: [justtest] => (item=[2, u'b']) => {
    "changed": false,
    "item": [
        2,
        "b"
    ],
    "msg": [
        2,
        "b"
    ]
}
ok: [justtest] => (item=[3, u'c']) => {
    "changed": false,
    "item": [
        3,
        "c"
    ],
    "msg": [
        3,
        "c"
    ]
}
with_cartesian 是将两个列表进行笛卡尔积组合 与 with_nested 使用一致
---
- hosts: justtest
  remote_user: root
  gather_facts: no
  tasks:
  - file:
      state: directory
      path: "/testdir/testdir/{
 
 { item.0 }}/{
 
 { item.1 }}"
    with_cartesian:
    - [ a, b, c ]
    - [ test1, test2 ]
    
"with_indexed_items"应该与"索引"有关,没错,"with_indexed_items"的作用就是在循环处理列表时为
列表中的每一项添加"数字索引","索引"从0开始

with_sequence start=1 end=5 stride=1  从1到5,每次增加1
---
- hosts: justtest
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{
 
 { item }}"
    with_sequence: start=1 end=5 stride=1

with_file 获取主机文件的内容的


41、有几种分隔符。默认的Jinja分隔符配置如下:
{% ... %}   对于声明 可以直接将 for等语句放到里面执行
{
 
 { ... }}   对于表达式打印到模板输出
{# ... #}   for Comments不包含在模板输出中
#  ... ##   对于行语句

42、过滤器
变量可以通过过滤器修改。过滤器通过管道符号(|)与变量分隔,并且在括号中可以包含可选参数。可以链接
多个过滤器。一个过滤器的输出应用于下一个过滤器。
abs              绝对值
capitalize       首字母大写
center           将值集中在给定宽度的字段中
default          设置默认值
first            返回序列中第一项
float            转化成浮点型的数据
join     {
 
 { [1, 2, 3]|join('|') }}  -> 1|2|3   {
 
 { [1, 2, 3]|join }} -> 123
last             返回序列中最后一项
length  == count 返回长度
max              {
 
 { [1, 2, 3]|max }}
min              {
 
 { [1, 2, 3]|min }}
pprint           优雅打印
replace(s, old, new, count=None)   {
 
 { "Hello World"|replace("Hello", "Goodbye") }}
reverse          反向打印
round            round(value,precision = 0,method ='common' )
                将数字四舍五入到给定的精度。
                第一个参数指定精度(默认为0),第二个参数指定舍入方法:
                    'common' 向上或向下舍入
                    'ceil' 总是围捕
                    'floor' 总是四舍五入
safe(值)      将值标记为安全,这意味着在启用了自动转义的环境中,此变量不会被转义。
sort           对可迭代进行排序。
                默认情况下,它会按升序排序,如果您将其作为第一个参数传递,它将反转排序。
string         如果尚未创建字符串unicode。这样,标记字符串不会转换回unicode。
striptags(值) 剥离SGML / XML标记并将相邻的空格替换为一个空格。
sum            返回数字序列的总和加上参数'start'的值(默认为0)。当序列为空时,它返回start。
title          返回值的标题版本。即单词将以大写字母开头,所有剩余字符均为小写。
trim              #将字符串开头和结尾的空格去除
truncate          返回字符串的截断副本。
unique            去重
upper             #将字符串转换成纯大写
wordcount         计算该字符串中的单词。

---
- hosts: justtest
  remote_user: root
  vars:
    testvar: "abc123ABC 666"
    testvar1: "  abc  "
    testvar2: '123456789'
    testvar3: "1a2b,@#$%^&"
  tasks:
  - debug:
      #将字符串转换成纯大写
      msg: "{
 
 { testvar | upper }}"
  - debug:
      #将字符串转换成纯小写
      msg: "{
 
 { testvar | lower }}"
  - debug:
      #将字符串变成首字母大写,之后所有字母纯小写
      msg: "{
 
 { testvar | capitalize }}"
  - debug:
      #将字符串反转
      msg: "{
 
 { testvar | reverse }}"
  - debug:
      #返回字符串的第一个字符
      msg: "{
 
 { testvar | first }}"
  - debug:
      #返回字符串的最后一个字符
      msg: "{
 
 { testvar | last }}"
  - debug:
      #将字符串开头和结尾的空格去除
      msg: "{
 
 { testvar1 | trim }}"
  - debug:
      #将字符串放在中间,并且设置字符串的长度为30,字符串两边用空格补齐30位长
      msg: "{
 
 { testvar1 | center(width=30) }}"
  - debug:
      #返回字符串长度,length与count等效,可以写为count
      msg: "{
 
 { testvar2 | length }}"
  - debug:
      #将字符串转换成列表,每个字符作为一个元素
      msg: "{
 
 { testvar3 | list }}"
  - debug:
      #将字符串转换成列表,每个字符作为一个元素,并且随机打乱顺序
      #shuffle的字面意思为洗牌
      msg: "{
 
 { testvar3 | shuffle }}"
  - debug:
      #将字符串转换成列表,每个字符作为一个元素,并且随机打乱顺序
      #在随机打乱顺序时,将ansible_date_time.epoch的值设置为随机种子
      #也可以使用其他值作为随机种子,ansible_date_time.epoch是facts信息
      msg: "{
 
 { testvar3 | shuffle(seed=(ansible_date_time.epoch)) }}"

43、lookup 模块 
从2.5版本开始,官方加入了 loop 关键字进行循环操作,来代替 with_xxx 的风格
而实际上内部的操作,就是 loop + lookup 的操作

在说循环和lookup插件之间的关系,需要注意,不要错误的以为lookup插件只能实现循环操作,
lookup插件有很多,有的lookup插件与"循环操作"完全没有关系,
lookup类型的插件的主要作用是访问外部的数据源,
比如,获取到外部数据并赋值给某个变量,以便之后使用这些数据,lookup插件的操作都是在ansible主机中进行的,
与目标主机没有关系。

以"with_"开头的循环实际上就是"with_"和"lookup()"的组合,lookup插件可以作为循环的数据源,通过以上描述,
应该已经明白了我们之前总结的循环与各种lookup插件之间的关系了吧。

---
- hosts: justtest
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{
 
 { lookup('file','/testdir/testfile') }}"  获得/testdir/testfile文件的内容

  - debug:             可以获得多个文件的内容
      msg: "{
 
 { lookup('file','/testdir/testfile','/testdir/testfile1') }}"
      
  - debug:             对每个文件内容单独放在一个列表中 2.5 版本中添加的
      msg: "{
 
 { lookup('file','/testdir/testfile','/testdir/testfile1',wantlist=true) }}"

  - debug:             2.6版本中引入的错误判读
      msg: "{
 
 { lookup('file','/testdir/testfile',errors='ignore') }}"
      #errors的值需要使用引号引起,errors的值可以设置为ignore、warn或者strict,缺省值为strict
通过ansible-doc -t lookup -l命令查看到如下列表,
我们可以通过使用lookup + 下面的列表信息进行组合使用。
cartesian           返回列表的笛卡尔积                               
chef_databag        从Chef的databag获取数据         
consul_kv           从consul键值存储中获取元数据。         
credstash           在AWS上检索信用卡的秘密         
csvfile             从TSV或CSV文件读取数据         
cyberarkpassword    从CyberArk AIM获得秘密         
dict                从字典返回键/值对项         
dig                 使用DNSPython库查询DNS         
dnstxt              查询域的DNS TXT字段         
env                 读取环境变量的值         
etcd                从ETCD服务器获取信息         
file                读取文件内容         
fileglob            与模式匹配的列表文件         
filetree            递归地匹配目录树中的所有文件         
first_found         返回从列表中找到的第一个文件         
flattened           返回完全展开的单个列表         
hashi_vault         从HasiHCORP拱顶中找回秘密         
hiera               从HIELA数据获取信息         
indexed_items       重写列表以返回“索引项目”         
ini                 从INI文件读取数据         
inventory_hostnames 与主机模式匹配的库存主机列表         
items               项目清单         
keyring             从操作系统钥匙抓取秘密         
lastpass            从LASTPASS获取数据         
lines               从命令读取行         
list                简单地返回它所给予的。         
mongodb             从MangGDB查找信息         
nested              用其他列表的嵌套元素编写列表         
password            检索或生成随机密码,存储在文件中         
passwordstore       使用passwordstore.org的通行工具管理密码         
pipe                从命令读取输出         
random_choice       从列表返回随机元素         
redis_kv            从ReDIS获取数据         
sequence            基于数字序列生成列表         
shelvefile          从Python搁置文件读取密钥         
subelements         从字典列表中遍历嵌套密钥         
template            用Jinja2 检索模板后的文件内容         
together            将列表合并为同步化列表         
url                 从URL返回内容

44、block rescue always的使用
block 块,可以将多个任务整合到一起执行,可以添加条件判断 when 进行判断执行
---
- hosts: justtest
  remote_user: root
  tasks:
  - debug:
      msg: "task1 not in block"
  - block:
      - debug:
          msg: "task2 in block1"
      - debug:
          msg: "task3 in block1"
    when: 2 > 1
rescue 捕获 block 的执行失败任务(任意一个失败都会触发rescue)
执行block成功不会触发rescue
---                          不使用 rescue        ---
- hosts: justtest                                - hosts: justtest
  remote_user: root                              remote_user: root
  tasks:                                         tasks:
  - block:                                       - shell: 'ls /ooo'
      - shell: 'ls /ooo'                           register: return_value
    rescue:                                        ignore_errors: true
      - debug:                                   - debug:
          msg: 'I caught an error'                   msg: "I caught an error"
                                                   when: return_value is failed

我们能从中看到,使用这种方式的便捷性

always 不管怎么样都要执行的一部分
说到这里是不是感觉到了,有一种 try{}catch{}finally{} 的感觉了?

---
- hosts: justtest
  remote_user: root
  tasks:
  - block:
      - debug:
          msg: 'I execute normally'
      - command: /bin/false
      - debug:
          msg: 'I never execute, due to the above task failing'
    rescue:
      - debug:
          msg: 'I caught an error'
      - command: /bin/false
      - debug:
          msg: 'I also never execute'
    always:
      - debug:
          msg: "This always executes"

44、关于使用Jinja2 template 模块
为什么要用到 Jinja2 主要是为了根据变量动态生成适合的模板
比如多机器下的模板,可以根据不同的机器设置变量来生成对应的模板,并推送到对应的机器上。
就是为了方便快捷。
Jinja2的语法是由 variables (变量)和 statement (语句)组成,如下:
1、variables:可以输出数据
` my_variables `                                       
{
 
 { some_dudes_name | capitalize }}                     可以使用过滤器进行变量使用
2、statements: 可以用来创建条件和循环等
if语句:
{% if my_conditional %}                                进行语句判断
...
{% endif %}
for 语句:
{% for item in all_items %}
`item` 
……
{% endfor %}
                                  生成文件内容
# cat test.j2                     # cat test
jinja2 test                       jinja2 test
{
 
 { 3 + 2 }}                       5
{
 
 { 3 - 4 }}                       -1
{
 
 { 3 * 5 }}                       15
{
 
 { 2 ** 3 }}                      8
{
 
 { 7 / 5 }}                       1.4
{
 
 { 7 // 5 }}                      1
{
 
 { 17 % 5 }}                      2
                                  生成文件内容
# cat test.j2                     # cat test
jinja2 test                       jinja2 test
{
 
 { 1 in [1,2,3,4] }}              True
{
 
 { 1 not in [1,2,3,4] }}          False

条件:
{% if 条件一 %}
...
{% elif 条件N %}
...
{% else %}
...
{% endif %}

设置值:
{% set teststr='abc' %}
{
 
 { teststr }}

循环:
{% for 迭代变量 in 可迭代对象 %}
{
 
 { 迭代变量 }}                   
{% endfor %}                     # cat test.j2
                                 jinja2 test
{% for i in [3,1,7,8,2] %}       {% for i in [3,1,7,8,2] -%}  在for结束前加 - 
{
 
 { i }}                          {
 
 { i }}{
 
 { ' ' }}
{% endfor %}                     {%- endfor %}                endfor开始前加 - 可以避免换行

# cat test.j2                        # cat test.j2
jinja2 test                          jinja2 test
{% for i in [3,1,7,8,2] -%}          {% for key,val in {'name':'bob','age':18}.iteritems() %}
{
 
 { i~' ' }}        ~ 就是字符串连接符    {
 
 { key ~ ':' ~ val }}
{%- endfor %}                        {% endfor %}

loop.index       当前循环操作为整个循环的第几次循环,序号从1开始
loop.index0      当前循环操作为整个循环的第几次循环,序号从0开始
loop.revindex    当前循环操作距离整个循环结束还有几次,序号到1结束
loop.revindex0   当前循环操作距离整个循环结束还有几次,序号到0结束
loop.first       当操作可迭代对象中的第一个元素时,此变量的值为true
loop.last        当操作可迭代对象中的最后一个元素时,此变量的值为true
loop.length      可迭代对象的长度
loop.depth       当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从1开始
loop.depth0      当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从0开始
loop.cycle()  这是一个辅助函数,通过这个函数我们可以在指定的一些值中进行轮询取值,具体参考之后的示例

{% for i in [7,1,5,3,9] if i>3 %}    {% for i in [7,1,5,3,9] %}
{
 
 { i ~'----'~ loop.index }}            {% if loop.index is even %}
{% endfor %}                             {%continue%}
                                       {%endif%}
{% for i in [7,1,5,3,9] %}             {
 
 { i ~'----'~ loop.index }}
{% if i>3 %}                         {% endfor %}
{
 
 { i ~'----'~ loop.index}}
{% endif %}
{% endfor %}

ansible all -m template -a "src=test.j2 dest=/opt/test"
                        可以在结果:cat /opt/test
                                jinja2 test
                                False
                                True
                                abc
                                3
                                1
                                7
                                8
                                2

 参考的网站较多,不一一列举了,但有一个重要的参考是下面的这位的。

http://www.zsythink.net/archives/category/%e8%bf%90%e7%bb%b4%e7%9b%b8%e5%85%b3/ansible/

===========================END===========================

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值