ansible的playbook(四)
前面已经对ansible的安装,配置文件,常用模块记录等等,这里进入重点部分,playbook(用于配置管理的脚本)。
这里的记录的内容主要是结合书《奔跑吧ansible》和参考了网上的一些博客和一些自己线上生产环境的一些思考。
一、先用两个简单的playbook进入剧本世界
1.1 Playbook Keywords-翻译官网
这些是常见剧本对象上可用的关键字。 关键字是配置Ansible行为的几种来源之一。
官网链接:https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html
Play:
any_errors_fatal: 强制任何主机上任何未处理的任务错误传播到所有主机并结束play.。
become: 布尔值,用于控制是否在任务执行时使用特权升级。 由begin插件实现。
become_exe: 用于提升特权的可执行文件的路径。 由begin插件实现。
become_flags: 当become为true时,将传递给特权升级程序的一串标志。
become_method:使用哪种特权提升方法(例如sudo或su)。
become_user:使用权限提升后成为“become”用户的用户。 远程/登录用户必须具有成为该用户的权限。
check_mode:一个布尔值,用于控制任务是否以“check”模式执行。
collections:collection名称空间列表,用于搜索modules, plugins, and roles。
connection:允许你更改用于在目标上执行任务的连接插件。
debugger:根据任务结果的状态启用调试任务。
diff:切换以执行任务返回“ diff”信息或不返回“ diff”信息。
environment:一个字典,该字典将转换为环境var,以便在执行时为任务提供该字典。 这只能与模块一起使用。 其他任何类型的插件,Ansible本身或配置都不支持此功能,它只是为负责执行任务的代码设置变量。 建议不要使用此方法来传递机密数据。
fact_path:设置由collect_facts控制的事实收集插件的事实路径选项。
force_handlers:即使主机在 play期间失败,也将强制主机执行通知的处理程序。 如果 play本身失败,将不会触发。
gather_facts:一个布尔值,用于控制剧本是否会自动运行“setup”任务来为hosts收集信息。
gather_subset:允许你将子集选项传递给由collect_facts控制的信息收集插件。
gather_timeout:允许你设置由collect_facts控制的信息收集插件的超时。
handlers:包含被视为处理程序的任务的部分,只有在完成每个任务部分的通知后,这些部分才能正常执行。 处理程序的监听字段不可模板化。
hosts:A list of groups, hosts or host,可转换为作为play目标的主机的列表。
ignore_errors:布尔值,使你可以忽略任务失败并继续play。 它不会影响连接错误。
ignore_unreachable:布尔值,使你可以忽略由于主机不可达而导致的任务失败,并继续play。 这不会影响其他任务错误(请参见ignore_errors),但对于volatile/ephemeral (易失性/临时)主机组很有用。
max_fail_percentage:在当前批处理中给定百分比的主机失败后,可用于中止运行。
module_defaults:指定模块的默认参数值。
name:标识符。 可用于documentation, tasks/handlers。
no_log:控制信息公开的布尔值。
order:控制用于执行play的主机的排序。 可能的值为inventory (default),sorted, reverse_sorted, reverse_inventory and shuffle。
port:用于覆盖连接中使用的默认端口。
post_tasks:在tasks section之后要执行的任务列表。
pre_tasks: roles之前要执行的任务列表。
remote_user:用户曾经通过连接插件登录到目标。
roles:要导入到剧本中的角色列表
run_once:布尔值,它将绕过主机循环,从而迫使任务尝试在第一个可用主机上执行,然后将所有结果和信息应用于同一批中的所有活动主机。
serial:明确定义Ansible如何在剧本目标上分批执行当前剧本。通过串行设置批次大小。
strategy:允许您选择用于play的connection plugin。
tags:应用于任务或包含的任务的标记,这允许从命令行选择任务的子集。
tasks:要在play中执行的任务的主要列表,它们在roles 之后和post_tasks之前运行。
throttle:限制并发任务在 task, block and playbook level上运行的数量。 这与forks and serial设置无关,但不能设置为高于这些限制。 例如,如果将forks设置为10,将throttle设置为15,则最多可以并行操作10个主机。
timeout:任务执行的时间限制(如果超过该时间限制),Ansible将中断并使任务失败。
vars:Dictionary/map of variables。
vars_files:包含要包含在play中的var的文件列表。
vars_prompt:提示输入的变量列表。
Role:
any_errors_fatal、become、become_exe、become_flags、become_method、become_user、check_mode、collections、connection、debugger、diff、environment、ignore_errors、ignore_unreachable、module_defaults、name、no_log、port、remote_user、run_once、tags、throttle、timeout、vars
delegate_facts:布尔值,使你可以将信息应用于委派的主机,而不是stocking_hostname。
delegate_to:主机执行任务,而不是目标(inventory_hostname)。 来自委托主机的连接变量也将用于该任务。
when:条件表达式确定是否运行任务的迭代。
Block:
always:block 中执行的任务列表,无论块中是否存在错误都将执行。
rescue:如果主块列表中有任务错误,则运行的块中的任务列表。
any_errors_fatal、become、become_exe、become_flags、become_method、become_user、check_mode、collections、connection、debugger、delegate_facts、delegate_to、diff、environment、ignore_errors、ignore_unreachable、module_defaults、name、no_log、port、remote_user、run_once、tags、throttle、timeout、vars、when
Task:
action:要执行任务的“action”,通常会转化为C(模块)或动作插件。
args:在任务中添加参数的第二种方法。 使用字典,其中键映射到选项和值。
async:如果C(action)支持,则异步运行任务。 值是最大运行时间(以秒为单位)。
changed_when:条件表达式会覆盖任务的正常“changed”状态。
delay:重试之间延迟的秒数。 此设置仅与until结合使用。
local_action:与动作相同,但也暗含delegate_to: localhost
loop:获取要迭代的任务的列表,将每个列表元素保存到item变量中(可通过loop_control配置)
loop_control:此处的几个键可让你modify/set 任务中的循环行为。
module_defaults:指定模块的默认参数值。
notify:在任务返回“ changed = True”状态时通知的处理程序列表。
poll:设置异步任务的轮询间隔(以秒为单位)(默认为10s)。
register:包含任务状态和模块返回数据的变量名称。
remote_user:用户曾经通过连接插件登录到目标。
retries:在直至循环中放弃之前的重试次数。 此设置仅与until结合使用。
until:此关键字表示“retries loop(重试循环)”,该循环将一直持续到满足此处提供的条件或我们达到重试限制为止。
with_<lookup_plugin>:与循环相同,但神奇地添加了任何查找插件的输出以生成项目列表。
any_errors_fatal、become、become_exe、become_flags、become_method、become_user、check_mode、collections、connection、debugger、delegate_facts、delegate_to、diff、environment、ignore_errors、ignore_unreachable、name、no_log、port、run_once、tags、throttle、timeout、vars、when
1.2 控制Ansible的行为方式:优先级规则-翻译官网
为了在管理环境方面提供最大的灵活性,Ansible提供了许多方法来控制Ansible的行为:它如何连接到受管节点,连接后如何工作。 如果使用Ansible管理大量服务器,网络设备和云资源,则可以在几个不同的位置定义Ansible行为,然后以几种不同的方式将该信息传递给Ansible。 这种灵活性很方便,但是如果您不了解优先级规则,则会适得其反。
这些优先规则适用于可以多种方式定义的任何设置(通过configuration settings, command-line options, playbook keywords, variables)。
Ansible提供了四个来源来控制其行为。 按照从最低(最容易覆盖)的优先级到最高(覆盖所有其他优先级)的优先顺序,这些类别是:
Configuration settings Command-line options Playbook keywords Variables
每个类别都会覆盖所有低优先级类别中的所有信息。 例如,playbook关键字将覆盖任何配置设置。
在每个优先级类别中,都有特定的规则适用。 但是,通常来说,“last defined”会获胜,并且会覆盖之前的所有定义。
Configuration settings
配置设置包括ansible.cfg文件中的值和环境变量。 在此类别内,配置文件中设置的值具有较低的优先级。 Ansible使用找到的第一个ansible.cfg文件,而忽略所有其他文件。 Ansible按以下顺序在以下位置搜索ansible.cfg:
ANSIBLE_CONFIG (environment variable if set) ansible.cfg (in the current directory) ~/.ansible.cfg (in the home directory) /etc/ansible/ansible.cfg
环境变量的优先级高于ansible.cfg中的条目。 如果你在控制节点上设置了环境变量,则它们将覆盖任何Ansible.cfg文件Ansible加载中的设置。 任何给定环境变量的值都遵循常规的Shell优先级:定义的最后一个值将覆盖先前的值。
Command-line options(命令行选项)
任何命令行选项都将覆盖任何配置设置。
当你直接在命令行中键入内容时,你可能会感觉到你手工制作的值应该覆盖所有其他值,但是Ansible不能那样工作。 命令行选项的优先级较低-它们仅覆盖配置。 它们不会覆盖剧本关键字,清单中的变量或剧本中的变量。
你可以通过在命令行上使用-e extra变量来覆盖命令行中所有其他优先级类别中来自所有其他来源的所有其他设置,但这不是命令行选项,而是一种传递变量的方式。
在命令行中,如果为仅接受单个值的参数传递多个值,则最后一个定义的值将获胜。 例如,此临时任务将以carol, not as mike进行连接:
ansible -u mike -m ping myhost -u carol
一些参数允许多个值。 在这种情况下,Ansible将附加清单文件清单1和清单2中列出的主机的所有值:
ansible -i /path/inventory1 -i /path/inventory2 -m ping all
每个命令行工具的帮助均列出了该工具的可用选项。
任何playbook关键字都将覆盖任何命令行选项和任何配置设置。
在剧本关键字中,优先级与剧本本身一起流动; 越具体的wins越笼统:
play (most general) blocks/includes/imports/roles (optional and can contain tasks and each other) tasks (most specific)
A simple example:
- hosts: all connection: ssh tasks: - name: This task uses ssh. ping: - name: This task uses paramiko. connection: paramiko ping:
在此示例中,在play level将connection关键字设置为ssh。 第一个任务继承该值,并使用ssh连接。 第二个任务继承该值,将其覆盖,然后使用paramiko连接。 相同的逻辑也适用于blocks and roles。 剧本中的所有任务,blocks and roles都将继承剧本级关键字; 通过在task, block, or role内为该关键字定义不同的值,任何task, block, or role都可以覆盖任何关键字。
请记住,这些是关键字,而不是变量。 playbooks and variable files都是在YAML中定义的,但是它们具有不同的意义。 剧本是Ansible的命令或“state description”结构,变量是我们用来帮助使剧本更具动态性的数据。
Variables
任何变量都将覆盖任何playbook关键字,任何命令行选项以及任何配置设置。
具有等效的剧本关键字,命令行选项和配置设置的变量称为“Connection ”变量。最初是为连接参数设计的,此类别已扩展为包括其他核心变量,例如临时目录和python解释器。
像所有变量一样,Connection variables可以通过多种方式和位置进行设置。你可以为清单中的主机和组定义变量。你可以在剧本vars: blocks中定义任务和剧本的变量。但是,它们仍然是变量-它们是数据,而不是关键字或配置设置。覆盖剧本关键字,命令行选项和配置设置的变量与任何其他变量一样,遵循相同的变量优先级规则。
在剧本中设置时,变量遵循与剧本关键字相同的继承规则。你可以设置play的值,然后在task, block, or role中覆盖它:
- hosts: cloud gather_facts: false become: yes vars: ansible_become_user: admin tasks: - name: This task uses admin as the become user. dnf: name: some-service state: latest - block: - name: This task uses service-admin as the become user. # a task to configure the new service - name: This task also uses service-admin as the become user, defined in the block. # second task to configure the service vars: ansible_become_user: service-admin - name: This task (outside of the block) uses admin as the become user again. service: name: some-service state: restarted
Variable scope: how long is a value available?
在playbook中设置的变量值仅存在于定义它们的playbook对象中。 这些“playbook object scope”变量不适用于后续对象,包括其他剧本。
与主机或组直接相关的变量值,包括在清单中定义的变量,通过vars插件或使用诸如set_fact和include_vars之类的模块,可用于所有plays。 这些“host scope”变量也可以通过hostvars[] 字典获得
Using -e extra variables at the command line
要覆盖所有其他类别中的所有其他设置,可以在命令行中使用附加变量:--extra-vars或-e。 与-e一起传递的值是变量,而不是命令行选项,它们将覆盖配置设置,命令行选项和剧本关键字以及在其他位置设置的变量。 例如,此任务将以brian而不是carol进行连接:
ansible -u carol -e 'ansible_user=brian' -a whoami all
必须使用--extra-vars指定变量名称和值。
1.3 YAML Syntax(YAML语法)-翻译官网
官网链接:https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
这里提供了正确的YAML语法的基本概述,这是Ansible剧本(我们的配置管理语言)的表达方式。Ansible之所以使用YAML,是因为与其他常见的数据格式(例如XML或JSON)相比,人类更容易读写。 此外,在大多数编程语言中都提供了可用于YAML的库。
YAML Basics(YAML基础)
对于Ansible,几乎每个YAML文件都以列表开头。 列表中的每个项目都是键/值对的列表,通常称为“hash” or a “dictionary”。 因此,我们需要知道如何在YAML中编写列表和字典。
YAML还有一个小怪癖。 所有YAML文件(无论是否与Ansible关联)都可以选择以---开头,并以....结尾。这是YAML格式的一部分,指示文档的开始和结束。
列表的所有成员都是以“-”(破折号和空格)开头的相同缩进级别的行:
--- # A list of tasty fruits - Apple - Orange - Strawberry - Mango ...
字典用一个简单的键表示:值形式(冒号后面必须有一个空格):
# An employee record martin: name: Martin D'vloper job: Developer skill: Elite
可能有更复杂的数据结构,例如字典列表,值是列表的字典或两者的混合:
# Employee records - martin: name: Martin D'vloper job: Developer skills: - python - perl - pascal - tabitha: name: Tabitha Bitumen job: Developer skills: - lisp - fortran - erlang
如果确实希望:字典和列表也可以用缩写形式表示:
--- martin: {name: Martin D'vloper, job: Developer, skill: Elite} ['Apple', 'Orange', 'Strawberry', 'Mango']
这些称为“Flow collections(流集合)”。Ansible并没有真正使用太多,但是您也可以以几种形式指定布尔值(true/false):
create_key: yes needs_agent: no knows_oop: True likes_emacs: TRUE uses_cvs: false
如果要与默认yamllint选项兼容,请在字典中使用小写的“ true”或“ false”作为布尔值。
值可以使用|跨越多行。 或>。 使用“Literal Block Scalar(文字块标量)”跨多行| 将包括换行符和任何尾随空格。 使用“Folded Block Scalar(折叠标量)”>会将换行符折叠到空格; 它可以使原本很长的行变得更易于阅读和编辑。 无论哪种情况,缩进都将被忽略。 例如:
include_newlines: | exactly as you see will appear these three lines of poetry fold_newlines: > this is really a single line of text despite appearances
虽然在上面的示例中,所有换行符都被折叠到空格中,但是有两种方法可以强制保留换行符:
fold_some_newlines: > a b c d e fsame_as: "a b\nc d\n e\nf\n"
让我们在一个任意的YAML示例中结合到目前为止所学到的知识。 这确实与Ansible无关,但是会带给你格式的感觉:
--- # An employee record name: Martin D'vloper job: Developer skill: Elite employed: True foods: - Apple - Orange - Strawberry - Mango languages: perl: Elite python: Elite pascal: Lame education: | 4 GCSEs 3 A-Levels BSc in the Internet of Things
这就是你真正需要YAML才能开始编写Ansible剧本的全部。
Gotchas(陷阱)
虽然你可以将几乎所有内容放入无引号的标量中,但也有一些例外。 冒号后跟一个空格(或换行符)“:”是映射的指示符。 空格后跟井号“#”开始注释。因此,以下将导致YAML语法错误:
foo: somebody said I should put a colon here: so I did windows_drive: c:
…但这会起作用:
windows_path: c:\windows
将需要使用冒号,后跟空格或行尾来引用哈希值:
foo: 'somebody said I should put a colon here: so I did' windows_drive: 'c:'
……然后冒号将被保存下来。或者,你可以使用双引号:
foo: "somebody said I should put a colon here: so I did" windows_drive: "c:"
单引号和双引号之间的区别在于,在双引号中可以使用转义符:
foo: "a \t TAB and a \n NEWLINE"
允许的转义列表可以在YAML规范的“转义序列”(YAML 1.1)或“转义字符”(YAML 1.2)下找到。以下是无效的YAML:
foo: "an escaped \' single quote"
此外,Ansible对变量使用“ {{var}}”。 如果冒号后的值以“ {”开头,则YAML会认为它是字典,因此必须将其引号,如下所示:
foo: "{{ variable }}"
如果你的值以引号开头,则必须用整个引号引起来,而不仅仅是其中一部分。 以下是一些有关如何正确引用事物的示例:
foo: "{{ variable }}/additional/string/literal" foo2: "{{ variable }}\\backslashes\\are\\also\\special\\characters" foo3: "even if it's just a string literal it must all be quoted"
无效:
foo: "E:\\path\\"rest\\of\\path
除'and'以外,还有许多特殊字符(或保留字符),不能用作无引号标量的第一个字符: [] {} > | * & ! % # ` @ ,.
你还应该知道吗? : -. 在YAML中,如果后跟非空格字符,则允许在字符串的开头,但是YAML处理器的实现有所不同,因此最好使用引号。
在Flow Collections中,规则更加严格:
a scalar in block mapping: this } is [ all , valid flow mapping: { key: "you { should [ use , quotes here" }
布尔转换是有帮助的,但是当你希望将字面值yes或其他布尔值作为字符串时,这可能会成为问题。 在这些情况下,只需使用引号即可:
non_boolean: "yes" other_string: "False"
YAML将某些字符串转换为浮点值,例如字符串1.0。 如果你需要指定版本号(例如,在requirements.yml文件中),则在看起来像浮点值的情况下,需要用引号引起来:
version: "1.0"
1.4 编写一个简单的安装nginx的playbook
# cd /etc/ansible/playbooks/ #先直接到此目录下面进行剧本编写,正常线上肯定不这么玩,有严格的目录结构。
查看playbook文件
# vim nginx_install.yaml #剧本的名字一般都是以.yaml结尾的,因为是yaml语言编写的嘛。
- name: Configure webserver with nginx #这里是整个play的注释部分,表示这组play是做什么的,会在开始打印出来 hosts: webservers #每个play必须包含两项,hosts(让哪些主机进行此操作),这里指定了webservers主机组运行执行此剧本 sudo: True #如果为真,ansible会在运行task的时候使用sudo命令切换为root用户,当然如果是root用户运行就不用了。 tasks: #每个play必须包含两项,上面的hosts已经说了,这里就是task(需要执行的任务) - name: yum install #第一个task的注释,注释下面是引用的shell模块以及后面执行的操作 shell: yum install openssl* pcre pcre-devel libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel ncurses ncurses-devel curl curl-devel gd gd2 gd-devel gd2-devel -y - name: create tools dir #这里是第二个task的注释,下面是引用了file模块。并且创建了一个/opt/tools的目录如果不存在 file: path=/opt/tools state=directory - name: nginx download #这里是第三个task的注释,下面是引用了get_url模块,将nginx下载到指定的目录下面 get_url: dest=/opt/tools url=http://nginx.org/download/nginx-1.10.3.tar.gz - name: nginx install #这里是第四个task的注释,下面是编译nginx的过程,用的是shell模块可以执行多条命令 shell: cd /opt/tools && tar zxf nginx-1.10.3.tar.gz && cd nginx-1.10.3 && ./configure --prefix=/opt/nginx-1.10.3 --with-http_ssl_module && make && make install - name: nginx link #这里是第五条task的注释,下面用了>然后file模块后面的参数就可以换行写了 file: > src=/opt/nginx-1.10.3 dest=/opt/nginx state=link - name: copy index.html #这里是第六条task的注释,使用了template模块的功能,一般模板文件都是playbook使用Jinja2模板引擎对变量取值,源是ansible服务器当前目录下的nginx_index.html.j2文件,目标当然是目标被安装服务器的目录了 template: src=nginx_index.html.j2 dest=/opt/nginx/html/index.html mode=0644 - name: restart nginx #这里是第七条task的注释,因为是编译安装的不能使用server命令,当然这里要注意如果nginx已经启动了,这里这样的话在执行过程中是会有红色的报错提示nginx服务已经启动了。 shell: /opt/nginx/sbin/nginx
#从上面可以看出playbook就是一组play组成的列表,playbook其实就是一个字典组成的列表。play必须有host和task两个选项。列表通过-作为分隔符。
列出playbook中的task
# ansible-playbook --list-tasks /etc/ansible/playbooks/nginx_install.yaml #通过--list-tasks参数打印出playbook中所有任务的名字,方便总结playbook都做了那些事。下面是执行命令的输出:
playbook: /etc/ansible/playbooks/nginx_install.yaml play #1 (Configure webserver with nginx): yum install create tools dir nginx download nginx install nginx link copy index.html restart nginx
查看inventory文件
# cat /etc/ansible/hosts
[webservers] 192.168.1.112 192.168.1.113 [dbservers] 192.168.1.109 192.168.1.111 [masterhost] 192.168.1.108
查看模板文件
# cat /etc/ansible/playbooks/nginx_index.html.j2
<html> <head> <title>welcome to ansible</title> </head> <body> <p>{{ ansible_managed }} </p> #这里用{{}}引用了一个特别的ansible变量,叫做ansible_managed,当ansible渲染这个模板的时候,会将这个变量替换为何这个末班文件生成时间相关的信息。 </body> </html>
执行playbook文件
# ansible-playbook nginx_install.yaml
#从上图可以看出执行成功了,7个task外加1个收集facts信息都成功了,所以最后的结果提示ok=8,黄色部分为改变部分也就是操作部分,绿色部分为未改变也就是要操作的结果已经操作过了不再操作了,所以最后提示changed=3。因为没有失败所以提示failed=0.
#如果主机过多会在GATHERING FACTS这里卡一段时间,因为ansible开始运行playbook的时候,它做的第一件事就是从它连接的服务器上收集各种信息。就是前面记录的setup模块的功能。因为可能需要这些信息去更改模板的内容。如果不需要这些信息,加快执行playbook的时间,可通过配置文件关于gathering这里的设置来关闭 fact gathering功能来节省执行时间。
最后浏览器查看一下结果:
1.5 了解下组内变量
# cat /etc/ansible/hosts #写了一个简单小例子
[webservers] 192.168.1.112 192.168.1.113 [dbservers] 192.168.1.109 192.168.1.111 [masterhost] 192.168.1.108 [all:vars] #先定义了一个全局变量,格式就是要执行的组后面跟:vars,就知道你下面定义的是变量了。 ntp_server=0.centos.pool.ntp.org #定义了一个变量ntp_server,所对应的值是一个域名,这个域名大家不陌生了哈 [webservers:vars] #又给webservers组定义了一个变量,这就算局部变量,变量还是ntp_server。 ntp_server=1.centos.pool.ntp.org [dbservers:vars] #同上,这里一是展示一下组内变量,而是测试一下这个变量到底是怎么个引用顺序。 ntp_server=2.centos.pool.ntp.org
# cat /etc/ansible/playbooks/ntp.conf.j2 #定义了一个最简单的模板文件,变量也只能在模板里面用{{}}引用
{{ ntp_server }} #这里我们用{{}}将上面组内定义的变量ntp_server引用了一下
# cat /etc/ansible/playbooks/ntp_server.yaml #写一个最简单的剧本,将模板发送给客户机并将j2模板里面的变量赋值
- name: var test hosts: all #我这里定义主机组就是所有inventory里面的主机了有一个算一个 tasks: - name: update ntpdate server template: src=ntp.conf.j2 dest=/opt/ntp.conf mode=0644
# ansible-playbook ntp_server.yaml #执行一下剧本文件
# ansible all -m shell -a "cat /opt/ntp.conf" #查看一下ntp.conf文件,看看里面的内容
#从结果可以看出组都是先引用自己组内的局部变量,没有再去全局找,变量引用是一个由内而外的顺序。
1.6 nginx更新配置文件添加TLS支持
这个例子挺好的,因为我们的nginx的SSL的配置文件一般得根据本地的配置去更改一些参数,而且像nginx做SSL已经很普遍了一般证书可能过个一两年就要更换下的什么的,所以这个例子还是对线上有帮助的。
http://blog.51niux.com/?id=136 #这里有nginx自己做SSL的记录
编辑playbook文件
# vim /etc/ansible/playbooks/nginx_config.yaml
--- #这里把标准的开头---加上了当然不加也木有事 - name: nginx config update and tls hosts: webservers vars: #vars级别跟tasks一样,这里就是定义我们playbook要用到的变量,下面每个变量一行 IP: "{{ ansible_eth0['ipv4']['address'] }}" #这里是根据playbook一启动的时候根据setup采集到的客户端的facter,然后就是字典吗,取到的客户机IP的值。 CPU_NUM: "{{ansible_processor_vcpus}}" #这里是取到的CPU的数量的值,就是setup返回的信息里面的一个键ansible_processor_vcpus。切记要加双引号,不然报错。 key_file: /opt/nginx/ssl/ca.crt #这种就是静态定义变量了,也就相当于ansible服务端定义的一些静态变量,所以不用加双引号 cert_file: /opt/nginx/ssl/ca_nopass.key conf_file: /opt/nginx/conf/nginx.conf nginx_file: /opt/nginx nginx_html: "{{nginx_file}}/html" #这里来了一个变量的引用,就是引用nginx_file的变量然后再在后面加东西,也是要双引号括起来的 tasks: - name: create ssl dir file: path=/opt/nginx/ssl state=directory - name: copy TLS key copy: src=/root/keys/ca_nopass.key dest={{ key_file }} owner=root mode=0600 #这里就是引用了key_file变量 - name: copy TLS certificate copy: src=/root/keys/ca.crt dest={{ cert_file }} - name: copy nginx file template: src=nginx.conf.j2 dest={{ conf_file }} - name: copy index html template: src=nginx_index.html.j2 dest={{ nginx_html }}/index.html #这里在引用变量nginx_html在其后面加东西就不用在加双引号了。 notify: # notify这个action可用于在每个play的最后被触发,这样可以避免多次有改变发生时每次都执行指定的操作,取而代之,仅在所有的变化发生完成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的操作。 - restart nginx #如果还有其他的调用,直接在下一行照着这个格式来下就好了,这里就是下面要调用的操作的name的值。 handlers: #这里是notify通知了handlers才会执行 - name: restart nginx #这里的name里面的值正好对应了上面notify的调用 shell: /opt/nginx/sbin/nginx -s reload #这里是action要进行的操作
#关于hanlers:
Handlers 也是一些 task 的列表,通过名字来引用,它们和一般的 task 并没有什么区别。Handlers 是由通知者进行 notify, 如果没有被 notify,handlers 不会执行。不管有多少个通知者进行了 notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次。最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到了。
编写一个nginx.conf模板:
# cat /etc/ansible/playbooks/nginx.conf.j2 #这里编写了一个关于nginx最简单的ssl模板
worker_processes {{ CPU_NUM }}; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 443 ssl; server_name {{ IP }} ssl on; ssl_certificate {{ cert_file }}; ssl_certificate_key {{ key_file }}; } }
#这里面用{{}}包含了几个在第一步操作里面vars下面定义的一些变量名
编写一个index.html模板用于web显示:
# cat /etc/ansible/playbooks/nginx_index.html.j2 #编写了一个简单的web页面,里面主要是变量为了等会web显示的时候来查看效果
<html> <head> <title>welcome to ansible</title> </head> <body> <p>key_file: {{ key_file }} </p> <p>cert_file: {{ cert_file }} </p> <p>nginx_html: {{ nginx_html }} </p> <p>IP: {{ IP }} </p> <p>CPU_NUM: {{ CPU_NUM }} </p> </body> </html>
#上面的这些不用解释了,就是用{{}}调用我们定义的变量,等传送到客户端之后,就会把这些变量变成客户端对应的变量的值。
执行playbook并查看效果
# ansible-playbook nginx_config.yaml
# ansible webservers -m shell -a "cat /opt/nginx/conf/nginx.conf" #查看下webservers组所在主机的配置文件
然后查看下浏览器,ssl是否生效了:
#测试结果是成功的,但是这只是一个简单的小例子,生产环境比复杂很多,比如IP地址的选取就没有这么简单,因为网卡名称可能并非eth0还可能是em1等。操作系统什么的,等等,再实际操作过程中还是根据线上的环境来写,当然这些原理性的东西都懂了,剩下的就是灵活运用了。
1.7 Return Values-翻译官网
Common
backup_file
对于那些在处理文件时实现backup=no|yes的模块,将创建备份文件的路径。
"backup_file": "./foo.txt.32729.2020-07-30@06:24:19~"
changed
一个布尔值,指示任务是否必须进行更改。
"changed": true
diff
有关先前状态与当前状态之间差异的信息。 通常是字典,前后都有条目,然后将由回调插件将其格式化为差异视图。
failed
指示任务是否失败的布尔值。
"failed": false
invocation
有关如何调用模块的信息。
"invocation": { "module_args": { "_original_basename": "foo.txt", ......
msg
带有通用消息的字符串将中继给用户。
"msg": "line added"
rc
有些模块执行命令行实用程序,或者适合直接执行命令(raw, shell, command等),此字段包含这些实用程序的“return code”。
"rc": 257
results
如果存在此键,则表明存在该任务的循环,并且该循环包含每个项目的常规模块“result”的列表。
"results": [ { "ansible_loop_var": "item", "backup": "foo.txt.83170.2020-07-30@07:03:05~", "changed": true, "diff": [ ......
skipped
指示任务是否被跳过的布尔值
"skipped": true
stderr
一些模块执行命令行实用程序,或者适合直接执行命令(raw, shell, command等),此字段包含这些实用程序的错误输出。
"stderr": "ls: foo: No such file or directory"
stderr_lines
当返回stderr时,我们还将始终提供此字段,该字段是字符串列表,每行从原始行开始一项。
"stderr_lines": [ "ls: doesntexist: No such file or directory" ]
stdout
一些模块执行命令行实用程序,或者适合直接执行命令(raw, shell, command等)。 该字段包含这些实用程序的正常输出。
"stdout": "foo!"
stdout_lines
当返回stdout时,Ansible总是提供一个字符串列表,每个字符串在原始输出的每一行中都包含一个项目。
"stdout_lines": [ "foo!" ]
Internal use
这些键可以由模块添加,但是将从已注册的变量中删除。 他们被Ansible自己“consumed”了。
ansible_facts
该密钥应包含一个词典,该词典将附加到分配给主机的信息上。 这些可以直接访问,不需要使用注册变量。
exception
此项可以包含由模块中的异常引起的回溯信息。 它只会以高详细度(-vvv)显示。
warnings
此项包含将显示给用户的字符串列表。
deprecations
此项包含将显示给用户的词典列表。 字典的键是msg和version,值是字符串,版本键的值可以是空字符串。
二、变量与fact
上面我们已经看到了fact和变量在playbook中的应用。
2.1 查看变量的值和注册变量
第一个注册变量的例子(参考书中的例子):
playbook执行的时候输出很简单,就是OK、CHANGE什么的,如果我们想看我们某条执行的详细输出呢?
# cat /etc/ansible/playbooks/debug_test.yaml
- name: var test hosts: all tasks: - name: test output command: whoami register: login 使用register来注册一个变量,将上面命令whomai的命令的输出捕获到名为login的变量中,使用register关键字设置的变量肯定是字典类型,但是字典的具体key是不同的,这取决于所调用的模块。 - debug: var=login #debug模块来打印任意信息,这里是打印login的变量的输出。
debug模块的输出如下:
TASK: [debug var=login] ******************************************************* ok: [192.168.1.113] => { "login": { #这就是key的名称,也就是我们上面定义的login模块 "changed": true, "cmd": [ "whoami" ], "delta": "0:00:00.003253", "end": "2017-07-03 20:20:46.662840", "invocation": { "module_args": "whoami", "module_name": "command" }, "rc": 0, "start": "2017-07-03 20:20:46.659587", "stderr": "", "stdout": "root", "stdout_lines": [ "root" ] } }
#第一个"changed",输出中名为changed的key出现在所有ansible模块的返回值中,并且ansible用它来判断是否发生了状态改变。对于command和shell模块来说,总是会将changed的value设置为true,除非使用changed_when语句覆盖了changed的值。
#第二个"cmd",为名cmd的key对应的value是用字符串列表形式描述的被调用的命令。
#第三个"rc",名为rc的key对应的value是返回值。如果这个值非零,ansible将会认为任务执行失败。
#第四个"stderr",名为stderr的key对应的value是输出到标准错误的文本组成的单一字符串,这里""就是表示没有错误
#第五个"stdout",名为stdout的key对应的value是输出到标准输出的文本组成的单一字符串,这里“root”就是它的输出
#第六个"stdout_lines",名为stdout_lines的key对应的value是按换行符分割的输出到标准输出的文本。这是一个由输出中的每一行作为元素组成的列表。
简化输出
当然上面的输出太多了,我只想要stdout来查看变量的值
# vim debug_test.yaml
- name: var test hosts: webservers tasks: - name: test output command: whoami register: login - debug: msg="Logged in as user {{login.stdout}}" #就将这里变化了,因为上面我们已经看到了login输出的是一个字典的形式,我们就取字典login字典里的stdout键就可以了
忽略模块返回错误继续执行:
ansible执行task时从上向下顺序执行的,一个失败的task会造成在失败的主机上面停止执行后续的task,如果我们的输出在错误的task之后就不能查看输出了,可以通过ignore_errors让此条task就算发送错误了依旧继续运行。
# cat /etc/ansible/playbooks/debug_test.yaml
- name: var test hosts: webservers tasks: - name: nginx start shell: mkdir /opt/nginx #/opt/nginx已经存在了,所以这条task执行会报错,正常情况下它要报错了,这个错误机器就不会在运行下面的task了。 ignore_errors: True #加了这么一句话,就算本条task执行报错,依旧会继续向下走。这句话是针对本条task的。 - name: test output command: whoami register: login - debug: msg="Logged in as user {{login.stdout}}" #当然字典的另外一种写法login['stdout']这样也是可以的
查看执行结果(从下图中可以看到虽然报错了,但是有个...ignoring,又继续向下执行task了):
2.2 打印fact中的值
脚本中取fact中的某个键
# ansible 7servers -m setup -a "filter=ansible_dis*" #我这里新加了个组7servers里面是centos7的系统,然后我们用filter过滤,将所有是ansible_dis开头的键打印出来。
192.168.1.121 | success >> { "ansible_facts": { "ansible_distribution": "CentOS", "ansible_distribution_major_version": "7", "ansible_distribution_release": "Core", "ansible_distribution_version": "7.2.1511" }, "changed": false } 192.168.1.122 | success >> { "ansible_facts": { "ansible_distribution": "CentOS", "ansible_distribution_major_version": "7", "ansible_distribution_release": "Core", "ansible_distribution_version": "7.2.1511" }, "changed": false }
现在用playbook取下fact中系统的键,看看是否能输出我们想要的值:
# cat /etc/ansible/playbooks/fact_test.yaml
- name: fact test hosts: 7servers gather_facts: True #前面说过了每次playbook运行都要收集一边客户端的fact信息,但是好多脚本可能不需要,为了提高执行效率可能配置文件要这只成:gathering = explicit 来关闭fact收集。所以如果你想收集的话,这里要加上这句话。 tasks: - debug: var=ansible_distribution_version #这是取上面setup输出结果中ansible_distribution_version键,如果配置文件关闭了facter,gather_facts: True就要设置上
#注:
取的fact变量都是setup得出的ansible开头的哪些变量作为键。
本地fact
另外还可以定义本地的fact。如在/etc/ansible/facts.d目录下创建.ini格式、JSON格式名为example.fact的文件
# cat /etc/ansible/facts.d/example.fact #这个文件要存在于被操作的客户机上。
[local_book] #这里类似于组名用[]括起来等待被调用 title=local fact test #变量赋值的形式 conten=ansible blog
# cat /etc/ansible/playbooks/fact_test.yaml
- name: fact test hosts: 7servers gather_facts: True tasks: - name: print fact_local title debug: msg="The title is {{ ansible_local.example.local_book.title }},the conten is {{ ansible_local.example.local_book.conten }}" #ansible_local变量的值要注意下,ansible_local变量是一个字典,example是我们客户机上面文件的名称是一个key,然后下面local_book就是此文件中定义的key了。
下面查看下部分执行结果:
TASK: [print fact_local title] ************************************************ ok: [192.168.1.122] => { "msg": "The title is local fact test,the conten is ansible blog" }
2.3 内置变量
ansible还允许使用set_fact模块在task中设置fact(实际上与定义一个新变量是一样的。)
hostvars
描述:字典,key为ansible主机的名字,value为所有变量名与相应变量值映射组成的字典。
例子:比如说我们再部署web服务器的时候,可能要配置mysql的IP地址,而如果mysql既有公网IP又有私有IP,比如ansible记录的是公网IP,如果我们想获取mysql服务器的内网IP地址,假如内网IP绑定在eth1网卡上面,我们不知道这个内网IP地址是多少,我们要做这么一个变量,让所有的web服务器的配置文件里面指向这个mysql的内网IP,就需要用到hostvars功能了,如下面设置变量(假如我们的mysql对外IP地址是192.168.1.113):
mysql_ip={{hostvars['192.168.1.113'].ansible_eth1.ipv4.address}}
inventory_hostname
inventory_hostname是ansible所识别的当前主机的主机名,如果定义了别名,那么这里就是哪个别名。
groups
当需要访问一组主机的变量时,groups变量就很有用,比如配置一台负载均衡器服务器,负载均衡器配置文件里面需要web群组中所有服务器的IP地址。
# cat nginx_proxy.conf.j2 #写个简单的小例子,要将这个模板文件传到对端的/tmp下面做个测试,这里的for循环是写到j2文件里面的。
upstream img_backend { {% for host in groups.webservers %} #对webservers组进行循环,赋值给host变量 server {{ hostvars[host].inventory_hostname }}:80 #取出host变量,也就是每行的主机名,我们这里是IP所以就是将webservers组里面的IP取出来 {% endfor %} }
#执行个简单剧本将此模板传到我们需要部署的机器上面去,然后查看一下文件:
# cat /tmp/nginx.conf
upstream img_backend { server 192.168.1.112:80 server 192.168.1.113:80 }
除上面以外还有:
group_names:列表,由当前主机所属的所有群组组成
play_hosts:列表,成员是当前play涉及的主机的inventory主机名
ansible_version:字典,由ansible版本信息组成
2.4 在命令行设置变量
ansible-playbook example.yaml -e var=value的方法,允许外部传递参数,而且是最高优先级,就算yaml中有变量也会被覆盖。
ansbile-playbook example.yaml -e @filename.yaml #这种方法可以传递多个参数进去,可以在filename.yaml中定义多个变量,如
# cat filename.yaml #如果要被引用变量的话,就直接将变量以及对应的值写到yaml文件中
input1: '192.168.1.111' input2: '192.168.1.112'
# cat nginx_proxy.conf.j2 #就照着上面的例子改吧,再在其下面加入两个变量:
upstream img_backend { {% for host in groups.webservers %} server {{ hostvars[host].inventory_hostname }}:80 {% endfor %} } {{input1}} {{input2}}
然后再执行下剧本groups_test.yaml :
--- - name: groups test hosts: 7servers tasks: - name: copy index html template: src=nginx_proxy.conf.j2 dest=/tmp/nginx.conf
# cat /tmp/nginx.conf #在被控制端查看下产生的文件是否被传参了,通过下面的结果可以看出被传参了。
upstream img_backend { server 192.168.1.112:80 server 192.168.1.113:80 } 192.168.1.111 192.168.1.112
2.5 变量执行的优先级
如果对某一主机的相同变量重复多次定义的话,且设置为不同的值。如果规避不了,优先级(优先级绝对变量赋予哪个值)规则如下:
ansible-playbook -e var=value 方式的优先级最高
一些其他的方法。
通过inventory文件或者YAML文件定义的主机变量或群组变量
Fact获得的变量的值。
在role的defaults/main.yaml文件中定义的变量。
三、使用迭代(with_items)安装多个软件包
上面写的playbook中为了yum安装软件包简单,就用的shell模块来进行yum安装,如果使用yum模块来安装呢,就要采取列表的形式了。
# cat /etc/ansible/playbooks/yum_install.yaml
- name: yum item install hosts: 7servers tasks: - name: list yum install yum: name={{ item }} #向yum模块传递一个item sudo: True #绝大部分task可能使用使用root权限来执行,如果像上面的playbook这句话放到全局位置就会造成所有的task都用root身份运行,这里就是针对需要用root的task with_items: #这里就跟上面的item对应上了,上面的item作为一个占位变量,会被with_items语句中列表的每一个元素分别替代。 - lrzsz #每个要yum的软件就像这样 - 软件名称,再有就新启一行。 - pcre
下面是ansible执行过程中客户端的进程截图:
#从此图可以看出,ansible在执行的时候会在被执行的客户机上面的/root/.ansible/tmp/下面创建python可读的临时文件,然后执行成功之后还会删除掉,这就是我们进入这个tmp目录会发现不了文件的原因。这也是为了就算客户端没有脚本之类的,只要服务器端有可执行文件依旧可以执行的原因。其实跟puppet那种意思也差不多,先把要干的事情在你本地列出来,然后就按照说明去做就可以了,就是puppet不会删除,ansible会删除。
另外更复杂的循环,下面对各种循环结构做一个总结:
with_items :输入列表,对列表元素进行循环
with_lines: 要执行的命令,对命令输出结果进行逐行循环
with_fileglob: 输入golb,对文件名进行循环
with_first_found : 输入路径的列表,输入中第一个存在的文件
with_dict:输入字典,对字典元素进行循环
with_flattened : 输入列表的列表,对所有列表的元素顺序循环
with_indexed_items : 输入列表,单次迭代
with_nested : 输入列表,循环嵌套
with_random_choice : 输入列表,单次迭代
with_sequence : 输入整数数组,对数组进行循环
with_subelements : 输入字典的列表,嵌套循环
with_together : 输入列表的列表,对多个列表并行循环
with_inventory_hostnames: 输入主机匹配模式,对匹配的主机进行循环