1.让'青蛙'说出你的任务
# ubuntu/debian: apt install cowsay $ yum -y install cowsay $ export ANSIBLE_NOCOWS=0 ANSIBLE_COW_SELECTION=bud-frogs # 如果想关闭的话 $ export ANSIBLE_NOCOWS=0 # 或者修改配置文件ansible.cfg $ nocows=1
执行任意playbook,你将在每个任务得到...
$ ansible-playbook test.yml __________________ < PLAY [localhost] > ------------------ \ \ oO)-. .-(Oo /__ _\ /_ __\ \ \( | ()~() | )/ / \__|\ | (-___-) | /|__/ ' '--' ==`-'== '--' ' localhost : ok=1 changed=0 unreachable=0 failed=0
青蛙的一家三口...2333333.....
2. 在shell模块中使用脚本写法与jinja2
# 我们知道,任何能写变量的地方都能嵌套jinja2表达式 # 而shell模块中的内容到客户端渲染后也是一个可执行脚本 # 所以我们可以在shell内编写比平时更复杂的脚本 --- - name: Run shell shell: |- {% if foo is defined %} mkdir -p /tmp/{{ foo }} {% else %} if [[ ! ${foo} ]];then mkdir -p /var/lib/{{ bar }} echo 'success' else echo 'falure' fi {% endif %}
3.展开jinja2中的一行的表达式
# 在使用jiaja2 if判断一个变量的时候,为了防止空格跟换行,我们经常会写在一行判断,但是展示却不太友好 --- - set_fact: GET_REPO: "{% if ENV == 'UAT' %}git@192.168.1.24:xnph-devops/conf-test.git{% else %}git@192.168.1.24:xnph-devops/conf.git{% endif %}" # 一行冗长的表达式看着很不直观,我们可以借助jinja2的‘-’实现换行写,并忽略换行符 # 其中: # |- 为块标签忽略换行 # {%- -%} 为忽略换行 --- - set_fact: GIT_REPO: |- {%- if ENV == 'UAT' -%} git@192.168.1.24:xnph-devops/conf-test.git {%- else -%} git@192.168.1.24:xnph-devops/conf.git {%- endif -%}
4.简易的jinja2 'if else'表达式
# 我们在编写jinja2 if else的表达式时,通常都是像这样的: # 下面例子,索引ID小于3的都分配为master --- - set_fact: role: >- {%- if ansible_play_hosts.index(inventory_hostname) < 3 -%} master {%- else -%} slave {%- endif -%} # 我们利用python特性,可以简写如下: --- - set_fact: role: "{{ ['slave','master'][ansible_play_hosts.index(inventory_hostname) < 3] }}" # 即,先判断ansible_play_hosts.index(inventory_hostname) < 3是否成立, # 如果成立,即true,失败为false # 而python中true==>1,false==>0 # 再取['slave','master']列表索引 # 其中ansible_play_hosts为当前任务执行的所有主机
5.获取当前时间
# 在setup中其实有时间字段,但是被拆分,或者格式问题不好处理,我们通过内置方法能更便捷的获取 # 2019-07-02:11 # 格式可以自定义 - set_fact: datetime: "{{ '%Y-%d-%m:%H' | strftime() }}" # 但是这样取的时间在客户端时间出现不一致的时候可能会出现问题,所以,我们通过另外一个办法 # 即:通过lookup插件(lookup插件工作在控制端本机),统一获取本机时间 - set_fact: datetime: "{{ lookup('pipe','date +%Y-%d-%m:%H') }}"
6.单独变量嵌套
# 假设我们有如下playbook: --- - hosts: group_a vars: foo_var: foo bar_var: bar tasks: - debug: msg="I like the {{ {{ key }}_var }}" # 以上执行必然是回报错的, # 其中{{ key }}是我们想通过动态传入foo或者bar来获取foo_var或者bar_var的值 # 为此我们可以通过lookup的vars插件来查到目的,改造如下: --- - hosts: group_a vars: foo_var: foo bar_var: bar tasks: - debug: msg="I like the {{ lookup('vars', key + '_var') }}" # 执行传入key=foo ansible-playbook test.yml -e 'key=foo'
7.字典变量嵌套
# 假设我们有如下playbook --- - hosts: group_a vars: project: foo: foo_value bar: bar_value tasks: - debug: msg="I like the {{ project.{{ key }} }}" # 当然,以上执行是会报错的, # 其中{{ key }}即为我想通过动态传入foo或者bar来获取project下的键值 # 为此我们可以这么写: --- - hosts: group_a vars: project: foo: foo_value bar: bar_value tasks: - debug: msg="I like the {{ project[key] }}" # 执行传入key=foo ansible-playbook test.yml -e 'key=foo'
8.限制必须指定至少一个tags才能运行任务
# 我们对多个任务做了tags,而我们运行任务而不指定-t时,默认会运行所有的任务 # 有时候这并不是我们所期望的,所以想当不指定tags时候不运行后面的任务 --- - hosts: localhost gather_facts: false tasks: - fail: msg: "你必须指定一个tags[-t]" when: "ansible_run_tags == ['all']" run_once: true - debug: msg='hello {{ ansible_run_tags }}' tags: - abc # 即,只要ansible_run_tags默认值为['all']时,任务就直接失败 $ ansible-playbook abc.yml PLAY [localhost] ********************************************************************************************************************************************************** TASK [fail] *************************************************************************************************************************************************************** fatal: [localhost]: FAILED! => {"changed": false, "msg": "你必须指定一个tags[-t]"} PLAY RECAP **************************************************************************************************************************************************************** localhost : ok=0 changed=0 unreachable=0 failed=1 # 其中: # ansible_run_tags为ansible内置变量,默认值为['all']
9.访问其他主机的变量
# A主机有变量foo=archive,B主机想使用A主机的{{ foo }}怎么办呢? --- - hosts: A:B tasks: - debug: msg={{ hostvars['A']['foo'] }} # 大部分的变量对象都存放在hostvars中
10.计算不同主机中相同变量的和
# 什么意思呢,即,有N主机,有相同的变量score,但是值是不一样的,现在想计算它们的score和 # 假定inventory如下: $ cat inventory.ini [public] 10.18.12.213 score=77 10.18.16.166 score=100 10.18.16.167 score=81 10.18.16.168 score=56 # 计算score的和 --- - hosts: localhost tasks: - debug: msg={{ ansible_play_hosts_all | map('extract',hostvars,'score') | sum }} # 执行结果 $ ansible-playbook -i inventory.ini score_sum.yml PLAY [public] ************************************************************************************************************************************************************* TASK [Gathering Facts] **************************************************************************************************************************************************** ok: [10.18.16.167] ok: [10.18.16.166] ok: [10.18.16.168] ok: [10.18.12.213] TASK [debug] ************************************************************************************************************************************************************** ok: [10.18.12.213] => { "msg": "314" } ok: [10.18.16.166] => { "msg": "314" } ok: [10.18.16.167] => { "msg": "314" } ok: [10.18.16.168] => { "msg": "314" } PLAY RECAP **************************************************************************************************************************************************************** 10.18.12.213 : ok=2 changed=0 unreachable=0 failed=0 10.18.16.166 : ok=2 changed=0 unreachable=0 failed=0 10.18.16.167 : ok=2 changed=0 unreachable=0 failed=0 10.18.16.168 : ok=2 changed=0 unreachable=0 failed=0 # 其他例子: # 获取所有主机的序列号,并以','连接 - debug: msg={{ ansible_play_hosts_all | map('extract',hostvars,'ansible_product_serial') | join(',') }} # 获取所有主机内存的总和 - debug: msg={{ ansible_play_hosts_all | map('extract',hostvars,'ansible_memtotal_mb') | sum }} # 其中: # ansible_play_hosts_all为当前任务执行的所有主机 # map方法参考python map # sum为计算list的和
11.使用本地命令的值作为循环参数
# 使用ls /tmp的输出作为loop循环的值 # 其中:q == query # query与lookup都工作在本机 --- - hosts: localhost gather_facts: tasks: - debug: msg={{ item }} loop: "{{ q('lines','ls /tmp/') }}"
12.嵌套两个列表的循环任务
# 假定有2个列表 # a_list: [1,2] # b_list: ['jack', 'green', 'apple'] # 需要嵌套循环输出: # jack-1,,green-1,apple-1,jack-2,green-2... --- - hosts: localhost vars: a_list: [1,2] b_list: ['jack', 'green', 'apple'] gather_facts: false tasks: - debug: msg="{{ item[1] }}-{{ item[0] }}" loop: "{{ a_list | product(b_list) | list }}" $ ansible-playbook loops.yml PLAY [localhost] ********************************************************************************************************************************************************** TASK [debug] ************************************************************************************************************************************************************** ok: [localhost] => (item=[1, u'jack']) => { "msg": "jack-1" } ok: [localhost] => (item=[1, u'green']) => { "msg": "green-1" } ok: [localhost] => (item=[1, u'apple']) => { "msg": "apple-1" } ok: [localhost] => (item=[2, u'jack']) => { "msg": "jack-2" } ok: [localhost] => (item=[2, u'green']) => { "msg": "green-2" } ok: [localhost] => (item=[2, u'apple']) => { "msg": "apple-2" } PLAY RECAP **************************************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0
13.全局任务滚动执行
# 对于全局任务滚动执行 # 设置serial为1,或者其他的数值,会按这个数量的主机去滚动执行任务 # 每个任务结尾会停顿5s,再下一个主机 - hosts: public serial: 1 gather_facts: false tasks: - name: I\'m the first task debug: msg="hello {{ inventory_hostname }}" - name: Sleep task wait_for: delay: 5 when: (ansible_play_hosts_all.index[-1] != inventory_hostname) # 对于最后一台主机,是不需要执行wait_for任务的,所以判断最后一台主机跳过wait_for # 其中: # serial为滚动执行的关键字,可以是数值或者百分比(%) # ansible_play_hosts_all为当前任务执行的所有主机 # inventory_hostname为各个主机自己
14.部分任务滚动执行
# 把需要滚动执行的任务放入include_tasks中, # 利用loop循环所有当前执行的主机(ansible_play_hosts_all),即: # 只有放入other.yml的任务会一台一台执行 # 设置run_once避免每个主机都多次重复执行 --- - hosts: public tasks: - debug: msg="I'm the Top public task" - include_tasks: other.yml loop: "{{ ansible_play_hosts_all }}" loop_control: loop_var: target_host run_once: true # other.yml --- - block: - shell: hostname -I register: res - debug: msg="I'm {{ res.stdout }}" delegate_to: "{{ target_host }}" # 其中: # ansible_play_hosts_all为前期执行的所有host # loop_control的loop_var把item赋值给target_host,即,target_host==item,防止other里其他任务调用loop,item变量冲突 # run_once:每个循环对象只允许一次,不设置的话,会重复运行 # delegate_to委派任务 # 另外,该方法有个缺点是由于委派者都是第一台主机,对应的facts变量都是该委派者的,所以没法使用其他主机的内置变量 # 故案例中也是使用register+shell来获取的主机名,而不是{{ inventory_hostname }} # 执行结果: $ ansible-playbook rolling.yml PLAY [public] ************************************************************************************************************************************************************ TASK [Gathering Facts] *************************************************************************************************************************************************** ok: [10.18.16.167] ok: [10.18.16.166] ok: [10.18.16.168] ok: [10.18.12.213] TASK [debug] ************************************************************************************************************************************************************* ok: [10.18.12.213] => { "msg": "I'm the Top public task" } ok: [10.18.16.167] => { "msg": "I'm the Top public task" } ok: [10.18.16.166] => { "msg": "I'm the Top public task" } ok: [10.18.16.168] => { "msg": "I'm the Top public task" } TASK [include_tasks] ***************************************************************************************************************************************************** included: /tmp/other.yml for 10.18.12.213 included: /tmp/other.yml for 10.18.12.213 included: /tmp/other.yml for 10.18.12.213 included: /tmp/other.yml for 10.18.12.213 TASK [shell] ************************************************************************************************************************************************************* changed: [10.18.12.213 -> 10.18.12.213] TASK [debug] ************************************************************************************************************************************************************* ok: [10.18.12.213 -> 10.18.12.213] => { "msg": "I'm 10.18.12.213 " } TASK [shell] ************************************************************************************************************************************************************* changed: [10.18.12.213 -> 10.18.16.166] TASK [debug] ************************************************************************************************************************************************************* ok: [10.18.12.213 -> 10.18.16.166] => { "msg": "I'm 10.18.16.166 " } TASK [shell] ************************************************************************************************************************************************************* changed: [10.18.12.213 -> 10.18.16.167] TASK [debug] ************************************************************************************************************************************************************* ok: [10.18.12.213 -> 10.18.16.167] => { "msg": "I'm 10.18.16.167 " } TASK [shell] ************************************************************************************************************************************************************* changed: [10.18.12.213 -> 10.18.16.168] TASK [debug] ************************************************************************************************************************************************************* ok: [10.18.12.213 -> 10.18.16.168] => { "msg": "I'm 10.18.16.168 " } PLAY RECAP *************************************************************************************************************************************************************** 10.18.12.213 : ok=14 changed=4 unreachable=0 failed=0 10.18.16.166 : ok=2 changed=0 unreachable=0 failed=0 10.18.16.167 : ok=2 changed=0 unreachable=0 failed=0 10.18.16.168 : ok=2 changed=0 unreachable=0 failed=0
15.在when里使用jinja2表达式
# 在when里是可以使用jinja2表达式的,只要格式规范即可。 # 一行表达式: --- - hosts: target_host tasks: - name: Show debug debug: msg='条件满足你就会看到我。' when: '{% if def_a_var|d("abc") == "abc" %}{{ foo | d("true") }}{% endif %}' # 等价的展开表达式 --- - hosts: target_host tasks: - name: Show debug debug: msg='条件满足你就会看到我。' when: |- {%- if def_a_var|d("abc") == "abc" -%} {{ foo | d("true") }} {%- endif -%} # 执行(会有警告提示): ansible-playbook test.yml PLAY [localhost] ******************************************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************************************** ok: [localhost] TASK [Show debug] ******************************************************************************************************************************************* [WARNING]: when statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {% if def_a_var|d("abc") == "abc" %}{{ foo | d("true") }}{% endif %} ok: [localhost] => { "msg": "条件满足你就会看到我。" } PLAY RECAP ************************************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0
16.变量使用锚定和别名
# 通常我们在定义多个类似变量时,不同变量有定义相同的内容 # 比如: --- - hosts: localhost vars: project_1: port: 8080 jvm: '-Xms1G -Xmx1G' run_user: devops root: /data/project_1 project_2: port: 8080 jvm: '-Xms1G -Xmx1G' run_user: devops root: /data/project_2 tasks: - debug: var=project_1 - debug: var=project_2 # 可以看到`project_2`中的很多键值都跟`project_1`中是一样的, # 那么我们有什么办法简化呢? # 对此,yaml中提供了'&'来描定一个变量,并给描定的变量设置一个别名, # 其他变量要引用锚定变量的值,需要再使用'<<'来引用并用*指向设置的别名 # 另外,具有相同键的变量会被后者覆盖 # 即:project_2的root会覆盖引用project_1的root # 修改如下: --- - hosts: localhost vars: project_1: &conf_anchor port: 8080 jvm: '-Xms1G -Xmx1G' run_user: devops root: /data/project_1 project_2: <<: *conf_anchor root: /data/project_2 tasks: - debug: var=project_1 - debug: var=project_2 # 其中: # & 为锚定标识 # conf_anchor为设置的别名 # << 为合并键 # * 为引用符 # 执行如下(变量覆盖会有警告): $ ansible-playbook anchor.yml [WARNING]: While constructing a mapping from /tmp/anchor.yml, line 10, column 7, found a duplicate dict key (root). Using last defined value only. PLAY [localhost] ******************************************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************************************** ok: [localhost] TASK [debug] ************************************************************************************************************************************************ ok: [localhost] => { "project_1": { "jvm": "-Xms1G -Xmx1G", "port": 8080, "root": "/data/project_1", "run_user": "devops" } } TASK [debug] ************************************************************************************************************************************************ ok: [localhost] => { "project_2": { "jvm": "-Xms1G -Xmx1G", "port": 8080, "root": "/data/project_2", "run_user": "devops" } } PLAY RECAP ************************************************************************************************************************************************** localhost : ok=3 changed=0 unreachable=0 failed=0 # 其他示例 # 引用锚定变量 --- - hosts: localhost vars: app: version: &my_version 1.1.1 name: - "app_name" - *my_version tasks: debug: msg: app version is "{{ app.name | join('-') }}". # 执行 $ ansible-playbook app.yml PLAY [localhost] ******************************************************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************************************************* ok: [localhost] TASK [debug] *********************************************************************************************************************************************** ok: [localhost] => { "msg": "app version is \"app_name-1.1.1\"." } PLAY RECAP ************************************************************************************************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0 # 合并多个字典 --- - hosts: localhost vars: center: &C x: 1 y: 2 round: &R r: 3.1415 big: <<: [*C,*R] g: 3x10e tasks: - debug: var=big # 执行: ansible-playbook muti.yml PLAY [localhost] ******************************************************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************************************************* ok: [localhost] TASK [debug] *********************************************************************************************************************************************** ok: [localhost] => { "big": { "g": "3x10e", "r": 3.1415, "x": 1, "y": 2 } } PLAY RECAP ************************************************************************************************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0
17.定义带空格的key
# yaml是直接可以定义带空格的变量的 # 或者你可以用?:的方式 # 两者完全等价的。 - hosts: localhost vars: my_vars: # 直接定义 'this is a key1': 'this is a value1' # 或者使用?:的方式 ? 'this is a key2' : 'this is a value2' tasks: - debug: var=my_vars # 执行 $ ansible-playbook space_var.yml PLAY [localhost] ************************************************************************************************************************************************************** TASK [Gathering Facts] ******************************************************************************************************************************************************** ok: [localhost] TASK [debug] ****************************************************************************************************************************************************************** ok: [localhost] => { "my_vars": { "this is a key1": "this is a value1", "this is a key2": "this is a value2" } } PLAY RECAP ******************************************************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0
18.定义变量时,设置类型
# 其实在定义变量时,我们是可以设置类型的。 # 当然,你在加引号与不加引号时就能解决大多的定义问题 - hosts: localhost vars: type_vars: is_str: !!str 9111 is_int: !!int '8081' is_float: !!float 2019 is_bool: !!bool 'yes' tasks: - debug: var=type_vars # 执行: $ ansible-playbook type_vars.yml PLAY [localhost] ************************************************************************************************************************************************************** TASK [Gathering Facts] ******************************************************************************************************************************************************** ok: [localhost] TASK [debug] ****************************************************************************************************************************************************************** ok: [localhost] => { "type_vars": { "is_bool": true, "is_float": 2019.0, "is_int": 8081, "is_str": "9111" } } PLAY RECAP ******************************************************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0
19.数字精度
# 利用format可以保留精度 $ ansible localhost -m debug -a "msg={{ '%0.2f -- %0.3f -- %d'| format(123,2.7,4.44) }}" localhost | SUCCESS => { "msg": "123.00 -- 2.700 -- 4" }
20.循环(多实例)任务触发handlers重启任务
# 我们在使用ansible部署多实例任务时 # 多个实例完成后需要触发handler任务来重启各个实例(实例名称通常是动态/不固定的), # 而通常实例的服务名称是有区别的,而且是按需重启(满足状态变化),逐个重启 # 我们可以借助json_query的filter特性来获取changed状态满足条件的实例来循环重启 # 如,实例变量如下: --- vars: redis_ins_port: - 6379 - 6380 - 6381 # 我们要循环复制实例模板到客户端,触发handler后只重启发生变化的实例 # 复制模板并将任务状态注册到变量 - name: template: src=redis.conf.j2 dest=/etc/redis/redis_{{ server_port }}.conf" loop_control: loop_var: server_port loop: "{{ redis_ins_port }}" register: task_status notify: - Restart redis # handler任务 # 其中,json_query为查询task_status中changed的状态为true的所有server_port --- - name: Restart redis systemd: name=redis_{{ item }} state=restarted" loop: "{{ task_status | json_query('results[?changed].server_port') }}
21.修改列表内的所有字段值,生成新的列表
# 我们在创建例如mongodb replicasets时,使用模块示例如下: # Create a replicaset called 'rs0' with the 3 provided members - name: Ensure replicaset rs0 exists mongodb_replicaset: login_host: localhost login_user: admin login_password: admin replica_set: rs0 members: - mongodb1:27017 - mongodb2:27017 - mongodb3:27017 when:ansible_play_hosts.index(inventory_hostname) == 0 # 对于其中的member如何能不固定通过方法自动创建呢? # 试想,我们已经有了执行的主机列表ansible_play_hosts # (虽然里面的值可能是别名,即:inventory_hostname) # 但是这个列表么有对应的端口,即我们需要修改列表里的每一项,添加一个端口即可 # 像这样:['10.18.1.190:27017','10.18.1.191:27017','10.18.1.192:27017'] # 改动如下: - name: Ensure replicaset rs0 exists mongodb_replicaset: login_host: localhost login_user: admin login_password: admin replica_set: rs0 members: >- {{ anible_play_hosts | map('extract', hostvars, 'ansible_default_ipv4') | list | json_query('[*].address') |map('regex_replace','(.*)','\\1:' + '27017' | list }} when: ansible_play_hosts.index(inventory_hostname) == 0 # 其中: # ansible_play_hosts为当前执行的所有主机列表 # 第一个map extract为提取hostvars中每个ansible_play_hosts的值作为键,并取ansible_default_ipv4,就像:hostvars[inventory_hostname]['ansible_default_ipv4'] # list把map对象转换成列表 # json_query把列表内每个address提取出来变成新的列表,到此是为了提取IP地址列表,当然,你ansible_play_hosts是IP地址的话可以不用这么麻烦 # 第二个map regex_replace把每个IP地址变成IP+:27017, # 最后再把我们的map对象转成list,得到我们想要的:['10.18.1.190:27017','10.18.1.191:27017','10.18.1.192:27017']
22. 从执行主机中获取可用内存大于2G的机器,然后从这些大于2G的机器中任意选2台分发文件
--- tasks: # 先筛选出大于2G机器的列表 - set_fact: memory_filter: |- {%- for host in ansible_play_hosts -%} {%- if hostvars[host].ansible_memtotal_mb >= 2048 -%} {{ host }}{%- if not loop.last %},{%- endif -%} {%- endif -%} {%- endfor -%} # 在用when判断执行主机是否满足大于2G,且在列表前两台的条件 - copy: src=xx dest=yy when: inventory_hostname in memory_filter.split(',')[0:2]
23.待续...
如果你有更好的姿势跟建议,欢迎补充
没有帐号? 立即注册