Ansible学习笔记
Ansible是一个自动化IT工具,能够配置系统、部署软件、编排复杂的IT任务(例如CD、零停机滚动更新)。
Ansible默认通过 SSH 协议管理,在管理机需要Python2.7环境,在托管机上需要Python 2环境。
1 2 3 4 5 6 7 8 |
# 下面的软件包在老版本Ubuntu上叫做python-software-properties sudo apt-get install software-properties-common sudo apt-add-repository ppa:ansible/ansible sudo apt-get update sudo apt-get install ansible # 被管理的主机上需要安装Python2 sudo apt-get install python-minimal |
1 |
/home/alex/Python/3.5.1/bin/pip install ansible |
Ansible支持从多个位置读取配置选项,包括环境变量、命令行参数、名为ansible.cfg的ini文件。
ini文件的搜索顺序为:
- 环境变量ANSIBLE_CONFIG指定的位置
- 当前目录的ansible.cfg
- ~/.ansible.cfg
- /etc/ansible/ansible.cfg
配置文件中以#或者;开头的行为注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
[defaults] ; 主机清单文件位置 inventory = /etc/ansible/hosts ; 远程SSH端口 remote_port = 22 ; SSH超时 timeout = 10 ; 远程用户 remote_user = root ; SSH身份验证使用的私钥位置 private_key_file =/home/alex/Documents/puTTY/gmem.key ; 禁止由于不在known_hosts中导致的警告 ; 等价于 export ANSIBLE_HOST_KEY_CHECKING=False host_key_checking = False ; 和远程主机通信时,并行进程的数量 forks = 10 ; 角色根目录 roles_path = /app/tops/roles ; 主机清单 inventory = /app/tops/host_vars/all.ini ; 控制Fact(远程系统变量)收集行为: ; implicit 每次执行剧本,都收集 ; smart 避免不必要的收集,节约时间 gathering = smart ; Fact存储方式 fact_caching = jsonfile ; 连接到Fact存储的方式,对于jsonfile,需要指定一个目录 fact_caching_connection = ./.cache ; Fact缓存过期时间 fact_caching_timeout = 3600 ; 剧本的标准输出回调,决定输出的显示方式 ; json ; debug ; skippy stdout_callback = debug ; 额外启用的回调,从2.0开始Ansible的所有回调插件可用,但是默认是禁用状态。该设置让你可以指定哪些回调插件启用 ; timer 这个回调插件可以计算整个 playbook 的运行时间 ; mail 这个回调插件可以实现发送邮件的功能 ; profile_roles 这个插件是在执行中添加耗时 ; callback_whitelist = profile_tasks |
全部可用配置项请参考:https://ansible-tran.readthedocs.io/en/latest/docs/intro_configuration.html
所谓主机清单(Host Inventory)是一个配置文件,来指定需要被管理的主机列表。主机清单的默认位置在/etc/ansible/hosts。
所有主机都属于 all组,此外可以自定义组:
1 2 3 4 5 6 7 8 |
; 方括号内的是组名,每个主机都可以属于多个组 [k8s] xenial-100.gmem.cc xenial-101.gmem.cc xenial-102.gmem.cc xenial-103.gmem.cc xenial-104.gmem.cc xenial-105.gmem.cc |
一个组,可以作为另外一个组的成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
; 组1 [atlanta] host1 host2 ; 组2 [raleigh] host2 host3 ; 组的组 该关键字表示定义组的成员 [southeast:children] atlanta raleigh ; 父组的变量 [southeast:vars] some_server=foo.southeast.example.com halon_system_timeout=30 self_destruct_countdown=60 escape_pods=2 |
1 2 3 4 5 |
[vms] xenial-[100-200].gmem.cc ; 1:50等价 www[01:50].example.com db-[a:f].example.com |
1 2 3 4 |
; 可以为主机设置别名,并指定主机变量 ; 别名 ansible_开头的预定义变量 ; SSH端口 SSH IP地址 通过SSH连接 jumper ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50 ansible_connection=ssh |
变量可以在Playbook(剧本)中引用。
除了主机变量,还可以定义属于整个组的变量:
1 2 3 4 5 6 7 8 |
[atlanta] host1 host2 ; 组名 这个关键字表示该段定义的是变量 [atlanta:vars] ntp_server=ntp.atlanta.example.com proxy=proxy.atlanta.example.com |
在清单中定义所有变量不是最佳实践,你可以将它们放在独立的YAML中,这些YAML会和清单文件关联。
假设清单文件 /etc/ansible/hosts中有一个主机xenial-100,它属于两个组master、worker,则以下三个文件中定义的变量可以被该主机使用:
/etc/ansible/group_vars/master 组变量
/etc/ansible/group_vars/worker 组变量
/etc/ansible/host_vars/xenial-100 主机变量
上述三个文件,还可以创建为目录,并在里面定义任意数量的文件,在这些文件里面定义变量。只需要改目录名和组/主机名一致即可:
/etc/ansible/group_vars/master/sysctl
/etc/ansible/group_vars/master/connection
Ansible 1.2+,group_vars、host_vars可以放在:
- inventory文件所在目录
- playbook文件所在目录
如果两个目录下都有,则playbook覆盖inventory。
建议将inventory和变量一起纳入版本控制管理。
变量 | 说明 |
ansible_ssh_host |
将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置 |
ansible_ssh_port | ssh端口号 |
ansible_ssh_user | ssh 用户名 |
ansible_ssh_pass | ssh 密码,不安全,建议使用 --ask-pass 或 SSH 密钥 |
ansible_sudo_pass | sudo 密码 |
ansible_sudo_exe | sudo 命令路径 |
ansible_connection |
与主机的连接类型.比如:local, ssh 或者 paramiko Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行 |
ansible_ssh_private_key_file | ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况 |
ansible_shell_type | 目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish' |
ansible_python_interpreter |
目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python |
inventory_hostname | 当前(正在Play中迭代的)主机在主机清单中的名字 |
group_names | 当前主机所属的组名列表 |
groups | 当前主机清单中所有组的名字,到组包含的主机列表的字典 |
hostvars | 当前主机清单中所有主机的名字,到主机所有变量的字典 |
playbook_dir | 传递给ansible-playbook命令行的剧本文件的路径 |
role_name | 当前正在执行的角色名称 |
role_path | 当前正在执行的角色所在目录的路径 |
ansible_facts | inventory_hostname的所有事实数据,通常又setup模块在play中自动收集,不过任何模块都可以返回事实 |
ansible_local | inventory_hostname的所有收集/缓存的本地事实(local fact)数据 |
使用ansible命令可以群发即席的命令给一群主机: ansible <host-pattern> [options],命令示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 匹配目标主机的模式 执行的模块 传递给模块的参数 进一步限制主机范围 ansible <pattern_goes_here> -m <module_name> -a <arguments> -l <group-or-@file> # 示例 ansible webservers -m service -a "name=httpd state=restarted" -l @hosts.txt # 组 模块 登陆到机器的用户 改变身份 询问密码 ansible all -m ping -u root --sudo --sudo-user batman --ask-sudo-pass # 在所有主机上执行Shell命令 ansible all -u root -a "/bin/echo hello" # 拷贝当前主机上的文件/etc/hosts到所有k8s组中的主机 ansible k8s -m copy -a "src=/etc/hosts dest=/tmp/hosts" # 全部重启 并发10个进程来执行 ansible -u root lb -a "/sbin/reboot" -f 10 # 额外的角色搜索路径 roles_path = /opt/mysite/roles:/opt/othersite/roles |
模式 | 说明 |
all * | 匹配所有主机 |
192.168.1.* *.example.com |
局部通配符 |
webservers[0-25] | 范围匹配 |
~(web|db).*\.example\.com | 正则式匹配 |
webservers:dbservers | 或 |
webservers:!phoenix | 非 |
webservers:dbservers:&staging:!phoenix | 更加复杂的逻辑操作 |
为了避免重复输入命令,Ansibel提供了Playbook脚本功能,脚本文件的格式为YAML。调用脚本的语法如下:
1 2 |
# 剧本文件 并发度 ansible-playbook deploy.yml -f 10 |
下面是一个脚本示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
--- # 主机名、组名、或者all - hosts: web # 定义变量 vars: http_port: 80 max_clients: 200 # 远程登陆用户 remote_user: root # 需要在远程主机上执行的任务列表,安装定义的顺序依次执行 tasks: - name: ensure apache is at the latest version # 安装软件 yum: pkg=httpd state=latest # 参数也可以作为子元素 yum: pkg: httpd state: lastest - name: Write the configuration file # 写入配置文件 template: src=templates/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf # 发布事件 notify: - restart apache - name: ensure apache is running service: name=httpd state=started # 事件监听器 handlers: - name: restart apache service: name=httpd state=restarted # 可以针对另外一组主机声明脚本 - hosts: k8s |
模块就是Ansible的命令。调用模块是可以指定不同的参数,就像调用Bash命令那样。
使用 -m参数指定module的名字, -a参数指定module的参数,示例:
1 2 3 |
# 参数格式"name1=value1 name2=value2" ansible all -m copy -a "src=/etc/hosts dest=/tmp/hosts" ansible web -m yum -a "name=httpd state=present" |
在Playbook脚本中每个任务(Task的子元素)都对应一个模块调用。
所有模块的文档参考:http://docs.ansible.com/ansible/modules_by_category.html
模块名称 | 说明 | ||
ping |
测试一个节点有没有配置好,执行检查:
没有参数 |
||
debug |
打印调试信息,类似于echo。支持变量替换 示例: ansible -m debug -a "var=hostvars[inventory_hostname]" playbook示例:
|
||
copy |
从当前的机器上复制文件到远程节点上,并且设置合理的文件权限。拷贝文件的时候,会先比较下文件的Checksum,如果相同则不会拷贝。 注意,目标目录必须存在,如果不存在可以预先使用file模块创建 示例:
|
||
synchronize |
同步当前机器上的目录到远程节点:
|
||
template |
从当前的机器上复制文件到远程节点上,并进行变量替换。变量使用 {{ }}包围。示例:
|
||
file |
设置远程值机上的文件、软链接和文件夹的权限,或者创建、删除文件。示例:
|
||
user |
增、删、改Linux远程节点的用户账户,并为其设置账户的属性。示例:
|
||
yum |
用来管理Redhat系(RHEL,CentOS,Fedora 21-)的Linux上的安装包。示例:
|
||
apt |
用来管理Debain系Linux上的软件包。示例:
|
||
package |
通用的包管理器,使用底层的操作系统包管理器来安装、删除、升级软件包
|
||
apt_key |
添加或者删除APT key。示例:
|
||
apt_repository |
管理APT仓库。示例:
|
||
service |
管理远程节点上的服务。示例:
|
||
shell |
通过/bin/sh在远程节点上执行命令,支持$HOME和”<”, “>”, “|”, “;” and “&”。示例:
|
||
command | 类似shell, 但是不支持$HOME和”<”, “>”, “|”, “;” and “&” | ||
git |
下载Git仓库的内容到指定位置:
|
||
set_fact |
使用该模块,可以设置per-host的变量(事实)。这些变量可以被当前ansible-playbook调用的、后续的play使用 如果设置cacheable为yes,则记录在缓存中,允许后续playbook调用使用
|
||
systemd |
控制Systemd服务
|
||
modprobe |
加载或卸载内核模块:
|
剧本比即席任务复杂而强大的多,它可以:
- 编排有序的执行过程,在多组机器间,来回有序的执行特别指定的步骤
- 可以同步或异步的发起任务
Playbook的格式是YAML,它由一个或多个plays组成,它的内容是一个以plays为元素的列表。
在每个Play中,一组机器被映射为定义好的角色。
Play中包含Tasks,每个Task典型情况下是对某个Ansible模块的调用。
下面是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# 只有一个Play,它包含目标、变量、任务列表信息 --- # 针对的主机,可以是主机、组的Pattern。所有任务都针对相同的主机 - hosts: webservers # 可以定义变量 vars: http_port: 80 max_clients: 200 # 变量可以放在外部文件中,例如敏感信息 vars_files: - /vars/external_vars.yml # 以什么用户登陆执行 remote_user: root # 支持sudo sudo: yes sudo_user: postgres # 任务列表,注意任务是串行执行的,只有前一个执行完毕(不管有多少主机),后一个才开始执行 tasks: # 每个任务都具有名称 - name: ensure apache is at the latest version yum: pkg=httpd state=latest # 每个Task都可以覆盖用户 remote_user: yourname - name: write the apache config file # 老版本中,需要写作 action: template,限制直接写模块名即可 template: src=/srv/httpd.j2 dest=/etc/httpd.conf # 如果发生了变动(对于这个模块来说,就是目标文件内容发生了变化) # 则执行 notify: # 需要注意,如果有多个Task都通知需要执行此Handler,只会执行一次 - restart apache - name: ensure apache is running service: name=httpd state=started - name: run this command and ignore the result # 对于shell或command,如果不关心退出码,可以 shell: /usr/bin/somecommand || /bin/true # 或者 ignore_errors: True - name: Copy ansible inventory file to client # 参数太长,可以换行编写 copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts owner=root group=root mode=0644 # 引用变量 - name: create a virtual host file for {{ vhost }} template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }} # 供引用的、发生变动后执行的处理器。通常都用来重启服务 handlers: - name: restart apache service: name=httpd state=restarted |
需要注意:
- 如果一个 host 执行 task 失败,这个 host 将会从整个 playbook 的 rotation 中移除
- 每个 task 的目标在于执行一个 moudle, 通常是带有特定的参数来执行.在参数中可以使用{{变量}}
- 模块具有幂等性,多次执行,它只会做必要的动作,此外当远端系统被人改动时,可以重放 playbooks 达到恢复的目的。对于command、shell这样的模块,多次执行相当于反复调用命令,是否幂等取决于底层命令
注意:很多字段Role也可以用。
剧本、角色中都可以定义Task。Task具有以下通用字段:
字段 | 说明 |
action | 该任务执行的动作,通常翻译为C(模块)或Action插件 |
any_errors_fatal | 任何主机上的单个未处理的错误被传递到所有主机级别,并导致剧本失败 |
args | 辅助的为Task增加参数的方式,字典 |
async | 如果C(动作)支持,异步的执行Task,该字段的值是异步最大运行时间 |
poll | 异步任务的轮询间隔 |
become | 执行此任务时是否需要改变运行用户 |
become_user | 改变为什么用户 |
changed_when | 用于修改Ansible默认changed状态的判断逻辑的表达式 |
check_mode | 控制任务是否运行在check模式 |
connection | 修改在目标主机上执行时使用的connection插件 |
debugger | 基于任务结果状态进行调试 |
delay | 重试Task的间隔,必须和until联用 |
retries | 重试Task的次数,必须和until联用 |
delegate_facts | 是否将Fact应用到被代理的主机 |
delegate_to | 在指定的主机上,而非目标主机上执行任务 |
local_action | 等价于: delegate_to: localhost |
run_once | 用于控制禁止遍历主机列表逐个执行任务,如果为True则Task仅在第一个主机上执行 |
diff | 是否返回diff信息 |
environment | 提供给Task的环境变量字典 |
failed_when | 覆盖Ansible默认failed状态判断逻辑 |
ignore_errors | 任务失败时是否忽略并继续Play,和connection errors无关 |
ignore_unreachable | 忽略无法连接的主机并且继续Play |
loop | 迭代一组任务,每个条目默认存入名为item的变量 |
loop_control | 用于控制循环行为,例如修改item变量的名字 |
name | 任务标识符 |
no_log | 是否隐藏信息 |
notify | 当changed=true时执行的处理器 |
port | 修改连接的默认端口 |
register | 用于存放任务状态、模块返回数据的变量 |
remote_user | connection plugin使用的用户 |
tags | 为Task打标签 |
throttle | 控制任务并发数量,也就是任务在同一时刻,最多在几个主机上并行执行 |
vars | 变量列表 |
when | 条件满足时才执行任务 |
对于任何一个主机,一个特定的Handler只会执行一次,不管它被Notify了多少次
Handlers 会在 'pre_tasks', 'roles', 'tasks', 和 'post_tasks' 之间自动执行,你也可以手工立即调用Handler:
1 2 3 4 5 |
tasks: - shell: some tasks go here # 立即调用 - meta: flush_handlers - shell: some other tasks |
编写一个巨大的Playbook文件,不利于维护。 可以将具有重用价值的部分独立编写,然后通过include引用。
include主要用于包含来自其它文件的Task,也可以包含来自其它文件的Play。Handlers本质上也是Task,亦可被引用。
用于被引用的Task列表,形式如下:
1 2 3 4 5 6 |
--- - name: placeholder foo command: /bin/foo - name: placeholder bar command: /bin/bar |
在Playbook中,include上述列表的语法:
1 2 |
tasks: - include: tasks/foo.yml |
include还支持多种写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 1.0引入的语法 tasks: - include: wordpress.yml vars: wp_user: timmy some_list_variable: - alpha - beta - gamma # 1.4之后,可以使用下面的语法 tasks: - { include: wordpress.yml, wp_user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] } |
include的时候,可以传递变量,所谓参数化include:
1 2 3 4 5 6 |
# tasks: # 变量列表,在被包含文件中引用变量 {{ wp_user }} - include: wordpress.yml wp_user=timmy - include: wordpress.yml wp_user=alice - include: wordpress.yml wp_user=bob |
除了显式传递的变量之外,在Play的vars段中声明的变量,均可在被包含的列表中引用。
include也可以出现在handlers段:
1 2 |
handlers: - include: handlers/handlers.yml |
甚至,include还可以将Playbook包含到另外一个Playbook中,这种情况下,不支持传递变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Play - name: this is a play at the top level of a file hosts: all remote_user: root tasks: - name: say hi tags: foo shell: echo "hi..." # 来自其它文件的Plays - include: load_balancers.yml - include: webservers.yml - include: dbservers.yml |
使用Role,使组织Playbook的最佳方式。Roles 基于特定的文件结构,去自动的加载某些 vars_files,tasks 以及 handlers。基于 roles 对内容进行分组,使得我们可以容易地与其他用户分享 roles
考虑下面的Ansible项目结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 剧本 site.yml webservers.yml fooservers.yml # 角色,可以被剧本引用 roles/ common/ # 部分目录可以不存在 files/ templates/ tasks/ handlers/ vars/ defaults/ meta/ webservers/ files/ # copy、script模块引用的文件 templates/ # template模块引用的模版 tasks/ # main.yaml为该角色的主任务列表,main.yaml可以include该目录的其它文件中定义的任务 handlers/ # main.yaml为该角色的Handler列表 vars/ # 自动添加到引用该角色的Play中 defaults/ # 为角色、该角色依赖的其它角色,定义默认变量 meta/ # 依赖的其它角色 |
其中剧本webservers.yml的内容:
1 2 3 4 5 6 |
--- - hosts: webservers # Play包含了两个角色 roles: - common - webservers |
对于该剧本包含的任何Role x:
- 如果 roles/x/tasks/main.yml 存在, 其中列出的 tasks 将被添加到 play 中
- 所有 include tasks 可以引用 roles/x/tasks/ 中的文件,不需要指明文件的路径
- 如果 roles/x/handlers/main.yml 存在, 其中列出的 handlers 将被添加到 play 中
- 如果 roles/x/vars/main.yml 存在, 其中列出的 variables 将被添加到 play 中
- 如果 roles/x/meta/main.yml 存在, 其中列出的 “角色依赖” 将被添加到 roles 列表中,需要1.3+
- 所有 copy tasks 可以引用 roles/x/files/ 中的文件,不需要指明文件的路径
- 所有 script tasks 可以引用 roles/x/files/ 中的脚本,不需要指明文件的路径
- 所有 template tasks 可以引用 roles/x/templates/ 中的文件,不需要指明文件的路径
包含角色时,可以指定参数:
1 2 3 4 5 |
- hosts: webservers roles: - common - { role: foo_app_instance, dir: '/opt/a', port: 5000 } - { role: foo_app_instance, dir: '/opt/b', port: 5001 } |
1 2 3 4 5 |
--- - hosts: webservers roles: - { role: some_role, when: "ansible_os_family == 'RedHat'" } |
对于Role 的每个Task,都会执行when中的判断,仅当结果为true,该Task才被包含到剧本中。
1 2 3 4 5 |
--- - hosts: webservers roles: - { role: foo, tags: ["bar", "baz"] } |
Tags 是一种实现部分运行 playbook 的机制
如果你想在Role执行之前、之后,执行一些Task,使用下面的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
--- - hosts: webservers pre_tasks: - shell: echo 'hello' roles: - { role: some_role } tasks: - shell: echo 'still busy' post_tasks: - shell: echo 'goodbye' |
注意,使用了Role的Play,如果同时还定义了tasks段,则其中的任务都在Role中的Task结束后才执行。
你可以为角色、以及这些角色依赖(dependent,包含的其它,定义在角色的meta/main.yaml)的角色,设置默认变量。
这些变量在所有可用变量中拥有最低优先级,可能被其他地方定义的变量(包括 inventory 中的变量)所覆盖。
可以将其它角色,自动拉取到当前角色中。依赖的角色定义在 meta/main.yaml 中:
1 2 3 4 5 6 7 8 9 |
--- dependencies: # 可以传递参数 - { role: postgres, dbname: blarg, other_parameter: 12 } # 可以使用绝对路径引用Role - { role: '/path/to/common/roles/foo', x: 1 } # 甚至引用Git服务器、压缩包中的角色,可以指定友好角色名 - { role: 'git+http://git.example.com/repos/role-foo,v1.1,foo' } - { role: '/path/to/tar/file.tgz,,friendly-name' } |
我们知道,Role定义了完整的可执行实体,Playbook可以仅简单的引用、传递参数给它。
当Role依赖于其它Role时,被依赖的Role先执行,这个过程可以是递归的。
默认情况下,被依赖的Role仅仅会执行一次。如果有两个Role同时依赖它,第二个依赖被忽略。要改变此行为,可以修改依赖规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
--- allow_duplicates: yes dependencies: - { role: tire } - { role: brake } # 在上述配置下 --- dependencies: - { role: wheel, n: 1 } - { role: wheel, n: 2 } - { role: wheel, n: 3 } - { role: wheel, n: 4 } # 多次依赖 --- allow_duplicates: yes dependencies: - { role: tire } - { role: brake } # 执行序列: # tire(n=1) # brake(n=1) # wheel(n=1) # tire(n=2) # brake(n=2) # wheel(n=2) # ... # |
如果你自己开发了一个Ansible 模块,你甚至可以将它作为角色的一部分,进行分发。
1 2 3 4 5 6 7 8 9 10 11 |
roles/ my_custom_modules/ library/ module1 module2 - hosts: webservers roles: - my_custom_modules - some_other_role_using_my_custom_modules - yet_another_role_using_my_custom_modules |
这些自定义模块,在当前Role,以及后面执行的Role中可用。
你可以为主机或分组定义变量,具体参考前面的章节。
在Play中,你可以直接定义变量:
1 2 3 |
- hosts: webservers vars: http_port: 80 |
角色有vars、defaults目录,存放变量信息。
为了使用变量,你需要在Playbook以及它引用的文件中,使用Jinja模板。该模板语言使用 {{}}作为变量占位符:
1 |
template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg |
需要注意,YAML语法要求,如果值以{{ }}开头的话我们需要将整行用双引号包起来,防止错误的解释为YAML字典:
1 2 3 4 5 6 7 8 9 |
# 错误 - hosts: app_servers vars: app_path: {{ base_path }}/22 # 正确 - hosts: app_servers vars: app_path: "{{ base_path }}/22 |
类似于Go Template的函数语法: {{ my_variable|default('my_variable is not defined') }}
Jinja2包含许多内置过滤器:https://jinja.palletsprojects.com/en/2.11.x/templates/#builtin-filters
Ansible模块通常会返回一个数据结构,你可以把模块调用的结果存放到一个变量中:
1 2 3 4 5 6 7 8 9 10 11 12 |
- hosts: web_servers tasks: - shell: /usr/bin/foo # 注册结果为变量 register: foo_result ignore_errors: True - shell: /usr/bin/bar # 使用变量进行判断 when: foo_result.rc == 5 |
这个数据结构包含的字段如下:
字段 | 说明 |
backup_file | 某些操控文件的模块支持 backup=no|yes,这个字段存放备份文件的路径 |
changed | bool,提示任务是否对目标进行了变更 |
diff | 数组,说明执行任务前后的变化 |
failed | bool,提示任务是否失败 |
invocation | 对象,模块调用的细节信息 |
msg | 一般性的、中继给用户的消息 |
rc | 某些模块,例如raw shell command,会执行命令,这个字段存放命令退出码 |
results | 数组。如果该字段存在,则提示任务是一个loop,该字段存放每次loop的结果数据结构 |
skipped | bool,提示任务是否被跳过 |
stderr | 标准错误 |
stderr_lines | 标准错误,每行一个元素,构成数组 |
stdout | 标准输出 |
stdout_lines | 标准输出,每行一个元素,构成数组 |
1 2 3 4 |
# 方括号语法 {{ ansible_eth0["ipv4"]["address"] }} # 点好导航语法 {{ ansible_eth0.ipv4.address }} |
Ansible会自动提供给你一些变量,即使你并没有定义过它们,这些变量名是预留的,用户不应当覆盖它们:
- hostvars:可以让你访问其它主机的变量、包括哪些主机中获取到的facts
- group_names:当前主机所在所有群组的列表
- groups:inventory中所有群组/主机的列表。可以用来列举主机:
123{% for host in groups['app_servers'] %}{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}{% endfor %}
可以在调用Ansible命令时传递变量:
1 2 3 4 5 6 7 8 9 10 11 |
# --- # - hosts: '{{ hosts }}' # remote_user: '{{ user }}' # tasks: # - ... ansible-playbook release.yml --extra-vars "hosts=vipers user=starbuck" # 支持JSON --extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}' |
从高到低:
命令行-e ⇨
Inventory中定义的连接变量(例如ansible_ssh_user)⇨
Play中的变量、included的变量、Role中的变量⇨
Inventory中的其它变量 ⇨
Facts ⇨
Role默认变量
这是一类变量,但是它们的值是Ansible自动检测出来的,而非你去指定的。
使用命令 ansible hostname -m setup可以获取Facts,数据量非常大:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
"ansible_all_ipv4_addresses": [ "REDACTED IP ADDRESS" ], "ansible_all_ipv6_addresses": [ "REDACTED IPV6 ADDRESS" ], "ansible_architecture": "x86_64", "ansible_bios_date": "09/20/2012", "ansible_bios_version": "6.00", "ansible_cmdline": { "BOOT_IMAGE": "/boot/vmlinuz-3.5.0-23-generic", "quiet": true, "ro": true, "root": "UUID=4195bff4-e157-4e41-8701-e93f0aec9e22", "splash": true }, "ansible_date_time": { "date": "2013-10-02", "day": "02", "epoch": "1380756810", "hour": "19", "iso8601": "2013-10-02T23:33:30Z", "iso8601_micro": "2013-10-02T23:33:30.036070Z", "minute": "33", "month": "10", "second": "30", "time": "19:33:30", "tz": "EDT", "year": "2013" }, "ansible_default_ipv4": { "address": "REDACTED", "alias": "eth0", "gateway": "REDACTED", "interface": "eth0", "macaddress": "REDACTED", "mtu": 1500, "netmask": "255.255.255.0", "network": "REDACTED", "type": "ether" }, "ansible_default_ipv6": {}, "ansible_devices": { "fd0": { "holders": [], "host": "", "model": null, "partitions": {}, "removable": "1", "rotational": "1", "scheduler_mode": "deadline", "sectors": "0", "sectorsize": "512", "size": "0.00 Bytes", "support_discard": "0", "vendor": null }, "sda": { "holders": [], "host": "SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)", "model": "VMware Virtual S", "partitions": { "sda1": { "sectors": "39843840", "sectorsize": 512, "size": "19.00 GB", "start": "2048" }, "sda2": { "sectors": "2", "sectorsize": 512, "size": "1.00 KB", "start": "39847934" }, "sda5": { "sectors": "2093056", "sectorsize": 512, "size": "1022.00 MB", "start": "39847936" } }, "removable": "0", "rotational": "1", "scheduler_mode": "deadline", "sectors": "41943040", "sectorsize": "512", "size": "20.00 GB", "support_discard": "0", "vendor": "VMware," }, "sr0": { "holders": [], "host": "IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)", "model": "VMware IDE CDR10", "partitions": {}, "removable": "1", "rotational": "1", "scheduler_mode": "deadline", "sectors": "2097151", "sectorsize": "512", "size": "1024.00 MB", "support_discard": "0", "vendor": "NECVMWar" } }, "ansible_distribution": "Ubuntu", "ansible_distribution_release": "precise", "ansible_distribution_version": "12.04", "ansible_domain": "", "ansible_env": { "COLORTERM": "gnome-terminal", "DISPLAY": ":0", "HOME": "/home/mdehaan", "LANG": "C", "LESSCLOSE": "/usr/bin/lesspipe %s %s", "LESSOPEN": "| /usr/bin/lesspipe %s", "LOGNAME": "root", "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:", "MAIL": "/var/mail/root", "OLDPWD": "/root/ansible/docsite", "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "PWD": "/root/ansible", "SHELL": "/bin/bash", "SHLVL": "1", "SUDO_COMMAND": "/bin/bash", "SUDO_GID": "1000", "SUDO_UID": "1000", "SUDO_USER": "mdehaan", "TERM": "xterm", "USER": "root", "USERNAME": "root", "XAUTHORITY": "/home/mdehaan/.Xauthority", "_": "/usr/local/bin/ansible" }, "ansible_eth0": { "active": true, "device": "eth0", "ipv4": { "address": "REDACTED", "netmask": "255.255.255.0", "network": "REDACTED" }, "ipv6": [ { "address": "REDACTED", "prefix": "64", "scope": "link" } ], "macaddress": "REDACTED", "module": "e1000", "mtu": 1500, "type": "ether" }, "ansible_form_factor": "Other", "ansible_fqdn": "ubuntu2.example.com", "ansible_hostname": "ubuntu2", "ansible_interfaces": [ "lo", "eth0" ], "ansible_kernel": "3.5.0-23-generic", "ansible_lo": { "active": true, "device": "lo", "ipv4": { "address": "127.0.0.1", "netmask": "255.0.0.0", "network": "127.0.0.0" }, "ipv6": [ { "address": "::1", "prefix": "128", "scope": "host" } ], "mtu": 16436, "type": "loopback" }, "ansible_lsb": { "codename": "precise", "description": "Ubuntu 12.04.2 LTS", "id": "Ubuntu", "major_release": "12", "release": "12.04" }, "ansible_machine": "x86_64", "ansible_memfree_mb": 74, "ansible_memtotal_mb": 991, "ansible_mounts": [ { "device": "/dev/sda1", "fstype": "ext4", "mount": "/", "options": "rw,errors=remount-ro", "size_available": 15032406016, "size_total": 20079898624 } ], "ansible_nodename": "ubuntu2.example.com", "ansible_os_family": "Debian", "ansible_pkg_mgr": "apt", "ansible_processor": [ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz" ], "ansible_processor_cores": 1, "ansible_processor_count": 1, "ansible_processor_threads_per_core": 1, "ansible_processor_vcpus": 1, "ansible_product_name": "VMware Virtual Platform", "ansible_product_serial": "REDACTED", "ansible_product_uuid": "REDACTED", "ansible_product_version": "None", "ansible_python_version": "2.7.3", "ansible_selinux": false, "ansible_ssh_host_key_dsa_public": "REDACTED KEY VALUE" "ansible_ssh_host_key_ecdsa_public": "REDACTED KEY VALUE" "ansible_ssh_host_key_rsa_public": "REDACTED KEY VALUE" "ansible_swapfree_mb": 665, "ansible_swaptotal_mb": 1021, "ansible_system": "Linux", "ansible_system_vendor": "VMware, Inc.", "ansible_user_id": "root", "ansible_userspace_architecture": "x86_64", "ansible_userspace_bits": "64", "ansible_virtualization_role": "guest", "ansible_virtualization_type": "VMware" |
Facts可以直接在Playbook中引用,例如获取第一块磁盘的型号: {{ ansible_devices.sda.model }}
你可以为Play禁用Fact收集:
1 2 |
- hosts: whatever gather_facts: no |
一般情况下,Facts都是Ansible自动检测的,目标主机的信息。
你可以在目标主机下放置配置文件,硬编码一些变量值:
1 2 3 |
[general] asdf=1 bar=2 |
这将产生如下变量:
1 2 3 4 5 6 7 8 9 10 |
# ansible <hostname> -m setup -a "filter=ansible_local" "ansible_local": { "preferences": { "general": { "asdf" : "1", "bar" : "2" } } } |
可以在剧本或模板中引用这种变量: {{ ansible_local.preferences.general.asdf }}
在一个主机上运行的剧本中,引用另外一个主机中的变量,是可能的: {{ hostvars['asdf.example.com']['ansible_os_family'] }}
如果没有Facts缓存,则Ansible需要在当前主机和另外一台主机进行通信,获取必要的变量。这在大规模集群中,可能造成负担。
Facts缓存是一种分布式缓存,可以依托Jsonfile或Redis实现,解决上述问题。
可以使用逻辑表达式、Jinja2过滤器,来编写when,在运行时决定Task是否执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
tasks: - name: "shutdown Debian flavored systems" command: /sbin/shutdown -t now when: ansible_os_family == "Debian" # 使用过滤器 when: result|success when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6 # 取反 when: not var0 # 是否定义 when: bar is not defined # 字符串包含 when: "'reticulating splines' in output" tasks: - command: echo {{ item }} # 在循环中使用 with_items: [ 0, 2, 4, 6, 8, 10 ] when: item > 5 |
除了Task,你还可以在roles、include语句中使用when:
1 2 3 4 5 6 7 |
- include: tasks/sometasks.yml when: "'reticulating splines' in output" - hosts: webservers roles: - { role: debian_stock_config, when: ansible_os_family == 'Debian' } |
如果想在一个任务中干很多事,比如创建一群用户、安装很多包、或者重复一个轮询步骤直到收到某个特定结果,考虑使用循环支持。
循环变量是简单值:
1 2 3 4 5 6 7 8 9 |
- name: add several users # 通过item引用当前循环变量 user: name={{ item }} state=present groups=wheel # 循环 with_items: - testuser1 - testuser2 # 可以引用变量定义的列表 with_items: "{{somelist}}" |
循环变量是字典:
1 2 3 4 5 |
- name: add several users user: name={{ item.name }} state=present groups={{ item.groups }} with_items: - { name: 'testuser1', groups: 'wheel' } - { name: 'testuser2', groups: 'root' } |
循环变量是哈希表的条目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
--- users: alice: name: Alice Appleworth telephone: 123-456-7890 bob: name: Bob Bananarama telephone: 987-654-3210 tasks: - name: Print phone records # 访问键 访问值 debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})" with_dict: "{{users}}" |
1 2 3 4 5 6 7 |
- name: give users access to multiple databases mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo with_nested: # 外层变量 - [ 'alice', 'bob' ] # 内层变量 - [ 'clientdb', 'employeedb', 'providerdb' ] |
对于变量定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
--- users: - name: alice authorized: - /tmp/alice/onekey.pub - /tmp/alice/twokey.pub mysql: password: mysql-password hosts: - "%" - "127.0.0.1" - "::1" - "localhost" privs: - "*.*:SELECT" - "DB1.*:ALL" - name: bob authorized: - /tmp/bob/id_rsa.pub mysql: password: other-mysql-password hosts: - "db1" privs: - "*.*:SELECT" - "DB2.*:ALL" |
如果我们想给每个name创建系统用户,并且为每个用户的每个authorized创建密钥登陆,可以:
1 2 3 4 5 6 7 8 9 10 11 12 |
- user: name={{ item.name }} state=present generate_ssh_key=yes # 遍历每个用户,创建用户 with_items: "{{users}}" # items.0表示当前外层变量 # items.1表示当前内层变量 - authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'" with_subelements: # 外层是用户 - users # 内层是当前用户的authorized列表(users[x].authorized) - authorized |
类似的,创建MySQL用户
1 2 3 4 5 6 |
- name: Setup MySQL users mysql_user: name={{ item.0.user }} password={{ item.0.mysql.password }} host={{ item.1 }} priv={{ item.0.mysql.privs | join('/') }} with_subelements: - users - mysql.hosts |
1 2 3 4 5 6 7 8 9 10 11 12 |
--- - hosts: all tasks: # 确保目录存在 - file: dest=/etc/fooapp state=directory # 拷贝所有匹配Pattern的文件 - copy: src={{ item }} dest=/etc/fooapp/ owner=root mode=600 with_fileglob: - /playbooks/files/fooapp/* |
1 2 3 4 5 6 7 8 9 10 11 |
--- alpha: [ 'a', 'b', 'c', 'd' ] numbers: [ 1, 2, 3, 4 ] tasks: - debug: msg="{{ item.0 }} and {{ item.1 }}" with_together: - "{{alpha}}" - "{{numbers}}" |
1 2 3 |
- name: indexed loop demo debug: msg="at array position {{ item.0 }} there is a value {{ item.1 }}" with_indexed_items: "{{some_list}}" |
可以从列表中随机选取一组值,然后循环:
1 2 3 4 5 6 |
- debug: msg={{ item }} with_random_choice: - "go through the door" - "drink from the goblet" - "press the red button" - "do nothing" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
--- - hosts: all tasks: # create groups - group: name=evens state=present - group: name=odds state=present - user: name={{ item }} state=present groups=evens # 起 止 格式化item with_sequence: start=0 end=32 format=testuser%02x - file: dest=/var/stuff/{{ item }} state=directory # 步进 with_sequence: start=4 end=16 stride=2 - group: name=group{{ item }} state=present with_sequence: count=4 |
1 2 3 4 5 6 7 8 |
- action: shell /usr/bin/foo # 结果注册为变量 register: result # 循环判断 until: result.stdout.find("all systems go") != -1 # 最大重试次数和延迟 retries: 5 delay: 10 |
1 2 3 |
- name: Example of looping over a command result shell: /usr/bin/frobnicate {{ item }} with_lines: /usr/bin/frobnications_per_host --param {{ inventory_hostname }} |
默认情况下playbook中的任务执行时会一直保持连接,直到该任务在每个节点都执行完毕。
对于长时间运行的任务,可以一起异步发动,然后进行轮询,来判断是否结束。
1 2 3 4 5 6 7 8 9 10 11 |
- 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 # 异步等待45s async: 45 # 每5s检查一下 poll: 5 |
默认情况下,一旦出现错误,后续任务就不在该主机上运行,可以改变此行为:
1 2 |
- name: this will not be counted as a failure command: /bin/false |
默认情况下,退出码决定是否失败,你可以修改为基于stderr判断:
1 2 3 4 |
- name: this command prints FAILED when it fails command: /usr/bin/example-command -x -y -z register: command_result failed_when: "'FAILED' in command_result.stderr" |
前面提到过Tags,它的作用是,仅仅运行Playbook的一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 可以对include添加tag include: foo.yml tags=web,foo tasks: - yum: name={{ item }} state=installed with_items: - httpd - memcached tags: - packages - template: src=templates/src.j2 dest=/etc/foo.conf tags: - configuration |
1 2 3 4 5 |
# 仅仅执行具有某些标签的任务 ansible-playbook example.yml --tags "configuration,packages" # 跳过具有特定标签的任务 ansible-playbook example.yml --skip-tags "notification" |
Ansible支持两个特殊标签:
- always,具有这种标签的role或play总是会运行,除非明确给出 --skip-tags always
- never,具有这种标签的role不会执行,除非明确给出 --tags never
Ansible 1.5的新版本中,作为Vault Ansible 的一项新功能可将例如passwords、keys等敏感数据文件进行加密,而非存放在明文的 playbooks 或 roles 中。这些 vault 文件可以分散存放也可以集中存放。
创建加密文件:
1 |
ansible-vault create foo.yml |
你需要指定密码,算法AES。未来使用Vault时需要该密码。包括编辑、查看它时:
1 2 |
ansible-vault edit foo.yml ansible-vault view foo.yml bar.yml baz.yml |
可以修改、批量修改多个Vault的密码:
1 |
ansible-vault rekey foo.yml bar.yml baz.yml |
解密:
1 |
ansible-vault decrypt foo.yml bar.yml baz.yml |
1 2 3 4 5 6 |
# 询问Vault密码 ansible-playbook site.yml --ask-vault-pass # 密码可以存放在文件或脚本中 ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt ansible-playbook site.yml --vault-password-file ~/.vault_pass.py |
过滤器输入显示为第一个参数:
过滤器 | 说明 | ||||
abs(number) | 取绝对值 | ||||
attr(obj, name) | 获取对象属性 | ||||
capitalize(s) | 转为大写 | ||||
center(value, width=80) | 将字符置于中间 | ||||
default(value, default=u'', boolean=False) |
别名:d 如果变量未定义,则返回指定的默认值:
如果需要当变量可以转换为false时,返回默认值,则第二个参数需要设置为true:
|
||||
escape(s) | 转换为HTML安全的序列 | ||||
filesizeformat(value, binary=False) | 转换为人类可读的尺寸格式,例如13kb 4.1MB | ||||
first(seq) | 返回序列的第一个元素 | ||||
float(value, default=0.0) | 转换为浮点数 | ||||
format(value, *args, **kwargs) |
C风格的格式化:
|
||||
indent(s, width=4, indentfirst=False) | 缩进文本 | ||||
int(value, default=0) | 转换为整数 | ||||
join(value, d=u'', attribute=None) | 连接列表元素为字符串: {{ [1, 2, 3]|join('|') }} | ||||
last(seq) | 返回列表的最后一个元素 | ||||
length(object) |
别名:count 返回对象长度 |
||||
list(value) | 转换为列表,如果输入是字符串,则返回字符列表 | ||||
lower(s) | 转换为小写 | ||||
map() |
针对序列的每个对象进行转换操作,可以:
|
||||
pprint(value, verbose=False) | 漂亮打印,由于调试 | ||||
random(seq) | 随机返回一个条目 | ||||
reject() | 工作方式类似于map,根据属性/过滤器来筛掉某些元素 | ||||
replace(s, old, new, count=None) |
字符串替换:
|
||||
reverse(value) | 反转列表或迭代器 | ||||
round(value, precision=0, method='common') | 舍入 | ||||
select() |
工作方式类似于map,根据属性/过滤器来选择某些元素
|
||||
sort(value, reverse=False, case_sensitive=False, attribute=None) |
排序:
|
||||
string(object) | 转换为字符串 | ||||
striptags(value) | 脱去SGML/XML标记,合并多余的空白为单个空格 | ||||
sum(iterable, attribute=None, start=0) | 求和 | ||||
trim(value) | 去掉首尾空白 | ||||
truncate(s, length=255, killwords=False, end='...') |
截断字符串:
|
||||
upper(s) | 转为大写 | ||||
urlencode(value) | 转换为URL中适用的字符串,UTF-8编码 | ||||
wordcount(s) | 统计单词数量 |
对于测试 test(arg,arg1,arg2...),在if语句中需要这样: {% if arg is test(arg1,arg2...) %}
测试 | 说明 |
callable(object) | 是否可调用 |
defined(value) | 变量是否已定义 |
divisibleby(value, num) | 是否可以整除 |
escaped(value) | 是否已经转义过 |
even(value) | 是否偶数 |
iterable(value) | 是否可迭代 |
lower(value) | 是否小写 |
mapping(value) | 是否是映射,例如dict |
none(value) | 是否为None |
number(value) | 是否为数字 |
odd(value) | 是否为奇数 |
sameas(value, other) | 判断是否和另外一个对象具有相同的内存地址 |
sequence(value) | 是否可迭代 |
string(value) | 是否字符串 |
undefined(value) | 是否未定义 |
upper(value) | 是否为大写 |
函数 | 说明 | ||
range([start], stop[, step]) |
返回一个等差数列的列表:
|
||
dict(**items) | 转换为字典, {'foo' : 'bar'} 与 dict (foo=bar)等价 |
要在模板中插入一段表达式,需要使用双括号 {{ expr }}
要在模板中插入一段语句,需要使用 {% statement%}
注释语法为: {# #}
要转义双括号,可以使用: {{ '{{' }}
对于大段文本,需要保持原样,可以:
1 2 3 4 5 6 7 |
{% raw %} <ul> {% for item in seq %} <li>{{ item }}</li> {% endfor %} </ul> {% endraw %} |
操作符 | 说明 |
+ - * / | 加减乘除 |
// | 返回整数商 |
% | 取余数 |
** | N次幂 |
== != > >= < <= | 比较操作符 |
and or not | 逻辑运算符 |
in | 元素是否在集合中判断: <span class="pre">{{ 1 in [1,2,3] }} |
is | 运行测试 |
| | 应用过滤器 |
~ | 将操作数转换为字符串并连接: <span class="pre">{{ "Hello " ~ name ~ "!" }} |
. [] | 获取对象属性 |
() | 执行调用 |
类型 | 示例 |
字符串 | "Hello World" |
数字 | 42 / 42.23 分别是整数、浮点数 |
列表 | [ 'list', 'of', 'objects' ] |
元组 | ( 'tuple', 'of', 'values' ) |
字典 | { 'dict': 'of', 'key': 'and', 'value': 'pairs' } |
布尔 | true / false |
支持下标和点号两种导航语法:
1 2 |
{{ foo.bar }} {{ foo['bar'] }} |
可以使用过滤器修改变量,过滤器和变量之间用管道符号 | 分隔,过滤器可以链式调用,前一个的输出,是后一个的输入:
1 |
{{ name|striptags|title }} |
过滤器支持参数,参数需要放在括号中:
1 |
{{ list|join(', ') }} |
1 |
{% set pipe = joiner("|") %} |
可以用 - 符号提示,需要将定界符左侧或右侧的空白移除:
1 2 3 4 5 |
# 移除右侧 {% for item in seq -%} {{ item }} {%- endfor %} # 移除左侧 |
要测试一个变量或表达式,你要在变量后加上一个 is 以及测试的名称:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 测试变量是否定义 {% if name is defined %} # 测试是否可以整除 {% if loop.index is divisibleby 3 %} {% if loop.index is divisibleby(3) %} # if-elif-else {% if kenny.sick %} Kenny is sick. {% elif kenny.dead %} You killed Kenny! You bastard!!! {% else %} Kenny looks okay --- so far {% endif %} |
1 2 3 4 5 6 7 8 9 10 |
# 迭代列表 {% for user in users %} <li>{{ user.username|e }}</li> {% endfor %} # 迭代字典 {% for key, value in my_dict.iteritems() %} <dt>{{ key|e }}</dt> <dd>{{ value|e }}</dd> {% endfor %} |
变量 | 描述 |
loop.index | 当前循环迭代的次数(从 1 开始) |
loop.index0 | 当前循环迭代的次数(从 0 开始) |
loop.revindex | 到循环结束需要迭代的次数(从 1 开始) |
loop.revindex0 | 到循环结束需要迭代的次数(从 0 开始) |
loop.first | 如果是第一次迭代,为 True 。 |
loop.last | 如果是最后一次迭代,为 True 。 |
loop.length | 序列中的项目数。 |
loop.cycle | 在一串序列间期取值的辅助函数。见下面的解释。 |
用于新建一个作用域:
1 2 3 4 5 |
{% with %} {% set foo = 42 %} {{ foo }} foo is 42 here {% endwith %} foo is not visible here any longer |
1 2 3 4 5 6 7 |
- name: Rendering init service job tempalte to local directory vars: download-script: "{{ lookup('file', 'files/download.sh') }}" template: src: "init.yaml.j2" dest: "{{ role_path }}/files/init-{{item.name}}.yaml" with_items: "{{ services }}" |
在上面的模板init.yaml.j2中,你可以直接引用变量download-script:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
apiVersion: batch/v1 kind: Job metadata: name: init-{{ item.name }} spec: template: spec: containers: - command: - /bin/bash - -c - | {{ download_script | indent( width=10, indentfirst=True) }} |
使用indent函数,参考上面。
Leave a Reply