运维技术分享平台

 找回密码
 立即注册
首页
查看: 837|回复: 0

Ansible学习笔记

[复制链接]

297

主题

306

帖子

1788

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
1788
发表于 2020-4-15 17:17:43 | 显示全部楼层 |阅读模式
一、 Ansible 介绍及其安装
1. Ansible 简介
Ansible 是新出现的自动化运维工具,基于 Python 开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。Ansible 是基于模块工作的,本身没有批量部署的能力。真正具有批量部署的是 Ansible 所运行的模块,Ansible 只是提供一种框架。主要包括:
1) 连接插件 connection plugins:负责和被监控端实现通信;
2) host inventory:指定操作的主机,是一个配置文件里面定义监控的主机;
3) 各种模块核心模块、command 模块、自定义模块;
4) 借助于插件完成记录日志邮件等功能;
5) playbook:剧本执行多个任务时,非必需可以让节点一次性运行多个任务
2. 需要安装些什么
Ansible默认通过 SSH 协议管理机器.
安装Ansible之后,不需要启动或运行一个后台进程,或是添加一个数据库.只要在一台电脑(可以是一台笔记本)上安装好,就可以通过这台电脑管理一组远程的机器.在远程被管理的机器上,不需要安装运行任何软件,因此升级Ansible版本不会有太多问题.
3. 选择哪一个版本?
因为Ansible可以很简单的从源码运行,且不必在远程被管理机器上安装任何软件,很多Ansible用户会跟进使用开发版本。若你希望使用Ansible的最新版本,并且你使用的操作系统是 Red Hat Enterprise Linux (TM), CentOS, Fedora, Debian, Ubuntu,我们建议使用系统的软件包管理器.
另有一种选择是通过”pip”工具安装,”pip”是一个安装和管理Python包的工具.
若你希望跟进开发版本,想使用和测试最新的功能特性,我们会分享如何从源码运行Ansible的方法.从源码运行程序不需要进行软件安装.
4. 对管理主机的要求
目前,只要机器上安装了 Python 2.6 或 Python 2.7 (windows系统不可以做控制主机),都可以运行Ansible。 主机的系统可以是 Red Hat, Debian, CentOS, OS X, BSD的各种版本,等等。
2.0版本开始,ansible使用了更多句柄来管理它的子进程,对于OS X系统,你需要增加ulimit值才能使用15个以上子进程,方法 sudo launchctl limit maxfiles 1024 2048,否则你可能会看见”Too many open file”的错误提示.
5. 对托管节点的要求
通常我们使用 ssh 与托管节点通信,默认使用 sftp.如果 sftp 不可用,可在 ansible.cfg 配置文件中配置成 scp 的方式. 在托管节点上也需要安装 Python 2.4 或以上的版本.如果版本低于 Python 2.5 ,还需要额外安装一个模块:
python-simplejson
注意没安装python-simplejson,也可以使用Ansible的”raw”模块和script模块,因此从技术上讲,你可以通过Ansible的”raw”模块安装python-simplejson,之后就可以使用Ansible的所有功能了.
注意如果托管节点上开启了SElinux,你需要安装libselinux-python,这样才可使用Ansible中与copy/file/template相关的函数.你可以通过Ansible的yum模块在需要的托管节点上安装libselinux-python.
注意Python 3 与 Python 2 是稍有不同的语言,大多数Python程序还不能在 Python 3 中正确运行.一些Linux发行版(Gentoo, Arch)没有默认安装 Python 2.X 解释器.在这些系统上,你需要安装一个 Python 2.X 解释器,并在 inventory (详见 Inventory文件) 中设置 ‘ansible_python_interpreter’ 变量指向你的 2.X Python.你可以使用 ‘raw’ 模块在托管节点上远程安装Python 2.X.
例如:ansible myhost --sudo -m raw -a "yum install -y python2 python-simplejson" 这条命令可以通过远程方式在托管节点上安装 Python 2.X 和 simplejson 模块.
Red Hat Enterprise Linux, CentOS, Fedora, and Ubuntu 等发行版都默认安装了 2.X 的解释器,包括几乎所有的Unix系统也是如此.
6. 安装管理主机
n 从源码运行
从项目的checkout中可以很容易运行Ansible,Ansible的运行不要求root权限,也不依赖于其他软件,不要求运行后台进程,也不需要设置数据库.因此我们社区的许多用户一直使用Ansible的开发版本,这样可以利用最新的功能特性,也方便对项目做贡献.因为不需要安装任何东西,跟进Ansible的开发版相对于其他开源项目要容易很多.
[root@centos6 ~]#  git clone git://github.com/ansible/ansible.git --recursive
Initialized empty Git repository in /root/ansible/.git/
remote: Enumerating objects: 137, done.
remote: Counting objects: 100% (137/137), done.
remote: Compressing objects: 100% (121/121), done.
remote: Total 361346 (delta 100), reused 16 (delta 16), pack-reused 361209
Receiving objects: 100% (361346/361346), 131.08 MiB | 42 KiB/s, done.
Resolving deltas: 100% (229343/229343), done.
[root@centos6 ~]# cd ansible/
[root@centos6 ansible]# source ./hacking/env-setup
如果没有安装pip, 请先安装对应于你的Python版本的pip:
easy_install pip
以下的Python模块也需要安装
pip install paramiko PyYAML Jinja2 httplib2 six
一旦运行env-setup脚本,就意味着Ansible从源码中运行起来了.默认的inventory文件是 /etc/ansible/hosts.inventory文件也可以另行指定
echo "127.0.0.1" > ~/ansible_hosts
export ANSIBLE_HOSTS=~/ansible_hosts
测试一条ping命令:
[root@centos6 ~]# ansible all -m ping --ask-pass
SSH password:
192.168.1.105 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.1.91 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
注意:需要添加主机的指纹到known_hosts文件中,或者修改配置文件host_key_checking = False
n Yum安装
yum install ansible
n 通过 Pip 安装
easy_install pip
pip install ansible
二、 新手上路
1. 远程连接概述
Ansible 1.3及之后的版本默认会在本地的 OpenSSH可用时会尝试用其进行远程通讯.这会启用ControlPersist(一个性能特性),Kerberos,和在~/.ssh/config中的配置选项如 Jump Host setup.然而,当你使用Linux企业版6作为主控机(红帽企业版及其衍生版如CentOS),其OpenSSH版本可能过于老旧无法支持ControlPersist. 在这些操作系统中,Ansible将会退回并采用 paramiko (由Python实现的高质量OpenSSH库). 如果你希望能够使用像是Kerberized SSH之类的特性,烦请考虑使用Fedora, OS X, 或 Ubuntu 作为你的主控机直到相关平台上有更新版本的OpenSSH可供使用,或者启用Ansible的“accelerated mode”
Ansible 1.2 及之前的版本,默认将会使用 paramiko. 本地OpenSSH必须通过-c ssh 或者 在配置文件中设定。你偶尔会遇到不支持SFTP的设备.虽然这很少见,但你会有概率中奖.你可以通过在配置文件(Ansible的配置文件)中切换至 SCP模式来与之链接.
说起远程设备,Ansible会默认假定你使用 SSH Key(我们推荐这种)但是密码也一样可以.通过在需要的地方添加 -ask-pass选项 来启用密码验证.如果使用了sudo 特性,当sudo需要密码时,也同样适当的提供了-ask-sudo-pass选项.
也许这是常识,但也值得分享:任何管理系统受益于被管理的机器在主控机附近运行.如果在云中运行,可以考虑在使用云中的一台机器来运行Ansible.
作为一个进阶话题,Ansible不止支持SSH来远程连接.连接方式是插件化的而且还有许多本地化管理的选项诸如管理 chroot, lxc, 和 jail containers.一个叫做‘ansible-pull’的模式能够反转主控关系并使远程系统通过定期从中央git目录检出 并 拉取 配置指令来实现背景连接通信。
7. 你的第一条命令
现在你已经安装了Ansible,是时候从一些基本知识开始了. 编辑(或创建)/etc/ansible/hosts 并在其中加入一个或多个远程系统.你的public SSH key必须在这些系统的``authorized_keys``中:
192.168.1.105
192.168.1.91
我们假定你使用SSH Key来授权.为了避免在建立SSH连接时,重复输入密码你可以这么 做:
ssh-agent bash
ssh-add ~/.ssh/id_rsa
也可以使用Ansible的 --private-key 选项,通过指定pem文件来代替SSH Key来授权。
[root@centos6 ~]# ansible all -m ping
192.168.1.105 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.1.91 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
}
[root@centos6 ~]# ansible all -k -a "/bin/echo hello"
SSH password:                                    #输入密码:两台机器密码相同
192.168.1.105 | SUCCESS | rc=0 >>
hello

192.168.1.91 | SUCCESS | rc=0 >>
hello
[root@centos6 ~]# ansible all -m ping -u bruce     #-u 指定用户名
8. 公钥认证
Ansible1.2.1及其之后的版本都会默认启用公钥认证.
如果有个主机重新安装并在“known_hosts”中有了不同的key,这会提示一个错误信息直到被纠正为止.在使用Ansible时,你可能不想遇到这样的情况:如果有个主机没有在“known_hosts”中被初始化将会导致在交互使用Ansible或定时执行Ansible时对key信息的确认提示.
如果你想禁用此项行为并明白其含义,你能够通过编辑 /etc/ansible/ansible.cfg or ~/.ansible.cfg来实现:
host_key_checking = False
或者你也可以通过设置环境变量来实现:
export ANSIBLE_HOST_KEY_CHECKING=False
同样注意在paramiko 模式中 公钥认证相当的慢.因此,当使用这项特性时,切换至’SSH’是推荐做法。
Ansible将会对远程系统模块参数记录在远程的syslog中,除非一个任务或者play被标记了“no_log: True”属性,稍后解释. 在主控机上启用基本的日志功能参见 Ansible的配置文件 文档 并 在配置文件中设置’log_path’。企业用户可能也对 Ansible Tower 感兴趣.
塔提供了非常实用数据库日志.它使一次次向下钻取并查看基于主机,项目,和特定的结果集成为可能———— 同时提供了图形和 RESTful API.
9. Inventory文件
Ansible 可同时操作属于一个组的多台主机,组和主机之间的关系通过 inventory 文件配置. 默认的文件路径为 /etc/ansible/hosts。除默认文件外,你还可以同时使用多个 inventory 文件(后面会讲到),也可以从动态源,或云上拉取 inventory 配置信息.详见 动态 Inventory。
n 主机与组
/etc/ansible/hosts 文件的格式与windows的ini配置文件类似:
mail.example.com

[webservers]
foo.example.com
bar.example.com

[dbservers]
one.example.com
two.example.com
方括号[]中是组名,用于对系统进行分类,便于对不同系统进行个别的管理.
一个系统可以属于不同的组,比如一台服务器可以同时属于 webserver组 和 dbserver组.这时属于两个组的变量都可以为这台主机所用,至于变量的优先级关系将于以后的章节中讨论.
如果有主机的SSH端口不是标准的22端口,可在主机名之后加上端口号,用冒号分隔.SSH 配置文件中列出的端口号不会在 paramiko 连接中使用,会在 openssh 连接中使用.
端口号不是默认设置时,可明确的表示为:
badwolf.example.com:5309
假设你有一些静态IP地址,希望设置一些别名,但不是在系统的 host 文件中设置,又或者你是通过隧道在连接,那么可以设置如下:
jumper ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50
在这个例子中,通过 “jumper” 别名,会连接 192.168.1.50:5555.记住,这是通过 inventory 文件的特性功能设置的变量. 一般而言,这不是设置变量(描述你的系统策略的变量)的最好方式.后面会说到这个问题.
一组相似的 hostname , 可简写如下:
[webservers]
www[01:50].example.com
数字的简写模式中,01:50 也可写为 1:50,意义相同.你还可以定义字母范围的简写模式:
[databases]
db-[a:f].example.com
对于每一个 host,你还可以选择连接类型和连接用户名:
[targets]

localhost              ansible_connection=local
other1.example.com     ansible_connection=ssh        ansible_ssh_user=mpdehaan
other2.example.com     ansible_connection=ssh        ansible_ssh_user=mdehaan
所有以上讨论的对于 inventory 文件的设置是一种速记法,后面我们会讨论如何将这些设置保存为 ‘host_vars’ 目录中的独立的文件.
n 主机变量
前面已经提到过,分配变量给主机很容易做到,这些变量定义后可在 playbooks 中使用:
[atlanta]
host1 http_port=80 maxRequestsPerChild=808
host2 http_port=303 maxRequestsPerChild=909
n 组的变量
也可以定义属于整个组的变量:
[atlanta]
host1
host2

[atlanta:vars]
ntp_server=ntp.atlanta.example.com
proxy=proxy.atlanta.example.com
n 把一个组作为另一个组的子成员
可以把一个组作为另一个组的子成员,以及分配变量给整个组使用. 这些变量可以给 /usr/bin/ansible-playbook 使用,但不能给 /usr/bin/ansible 使用:
[atlanta]
host1
host2

[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

[usa:children]
southeast
northeast
southwest
如果你需要存储一个列表或hash值,或者更喜欢把 host 和 group 的变量分开配置,请看下一节的说明.
n 分文件定义 Host 和 Group 变量
inventory 主文件中保存所有的变量并不是最佳的方式.还可以保存在独立的文件中,这些独立文件与 inventory 文件保持关联. 不同于 inventory 文件(INI 格式),这些独立文件的格式为 YAML.详见YAML 语法:http://www.ansible.com.cn/docs/YAMLSyntax.html
假设 inventory 文件的路径为:
/etc/ansible/hosts
假设有一个主机名为'foosball', 主机同时属于两个组,一个是'raleigh', 另一个是'webservers'. 那么以下配置文件(YAML 格式)中的变量可以为'foosball'主机所用.依次为'raleigh'的组变量,'webservers'的组变量,'foosball'的主机变量:
/etc/ansible/group_vars/raleigh
/etc/ansible/group_vars/webservers
/etc/ansible/host_vars/foosball
举例来说,假设你有一些主机,属于不同的数据中心,并依次进行划分.每一个数据中心使用一些不同的服务器.比如 ntp 服务器, database 服务器等等. 那么 ‘raleigh’ 这个组的组变量定义在文件 '/etc/ansible/group_vars/raleigh'之中,可能类似这样:
ntp_server: acme.example.org
database_server: storage.example.org
这些定义变量的文件不是一定要存在,因为这是可选的特性.
还有更进一步的运用,你可以为一个主机,或一个组,创建一个目录,目录名就是主机名或组名.目录中的可以创建多个文件, 文件中的变量都会被读取为主机或组的变量.如下'raleigh' 组对应于 /etc/ansible/group_vars/raleigh/ 目录,其下有两个文件 db_settings 和 cluster_settings, 其中分别设置不同的变量:
/etc/ansible/group_vars/raleigh/db_settings
/etc/ansible/group_vars/raleigh/cluster_settings
'raleigh'组下的所有主机,都可以使用'raleigh'组的变量.当变量变得太多时,分文件定义变量更方便我们进行管理和组织. 还有一个方式也可参考,详见 Ansible Vault 关于组变量的部分. 注意,分文件定义变量的方式只适用于 Ansible 1.4 及以上版本.
Tip: Ansible 1.2 及以上的版本中,group_vars/ 和 host_vars/ 目录可放在 inventory 目录下,或是 playbook 目录下. 如果两个目录下都存在,那么 playbook 目录下的配置会覆盖 inventory 目录的配置.
Tip: 把你的 inventory 文件 和 变量 放入 git repo 中,以便跟踪他们的更新,这是一种非常推荐的方式.
n Inventory 参数的说明
如同前面提到的,通过设置下面的参数,可以控制 ansible 与远程主机的交互方式,其中一些我们已经讲到过:
ansible_ssh_host:将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.
ansible_ssh_port:ssh端口号.如果不是默认的端口号,通过此变量设置.
ansible_ssh_user:默认的 ssh 用户名
ansible_ssh_pass:ssh 密码(这种方式并不安全,我们强烈建议使用 --ask-pass 或 SSH 密钥)
ansible_sudo_pass:sudo 密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass)
ansible_sudo_exe (new in version 1.8):sudo 命令路径(适用于1.8及以上版本)
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 不是 2.X 版本的 Python.我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26).与 ansible_python_interpreter 的工作方式相同,可设定如 ruby 或 perl 的路径....
一个主机文件的例子:
some_host         ansible_ssh_port=2222     ansible_ssh_user=manager
aws_host          ansible_ssh_private_key_file=/home/example/.ssh/aws.pem
freebsd_host      ansible_python_interpreter=/usr/local/bin/python
ruby_module_host  ansible_ruby_interpreter=/usr/bin/ruby.1.9.3
10. 动态 Inventory
使用配置管理系统经常有一种需求,可能要在其他的软件系统中保存自己的 inventory 配置信息。Ansible 本身通过基于文本的方式来记录 inventory 配置信息,这在前面已介绍过(详见 Inventory文件 )。除此之外,Ansible 也支持用其他方式保存配置信息.
在其他软件系统保存配置信息的例子有:
1, 从云端拉取 inventory
2, LDAP(Lightweight Directory Access Protocol,轻量级目录访问协议)
4, 或者是一份昂贵的企业版的 CMDB(配置管理数据库) 软件.
对于这些需求,Ansible 可通过一个外部 inventory 系统来支持.在 ansible 的 “/plugins” 插件目录下已经含有一些选项 – 包括 EC2/Eucalyptus, Rackspace Cloud,and OpenStack,我们稍后会详细介绍它们.
[root@centos6 ~]# ls /usr/lib/python2.6/site-packages/ansible/plugins/
action    cliconf     httpapi       __init__.pyo  loader.pyc  netconf   terminal
cache     connection  __init__.py   inventory     loader.pyo  shell     test
callback  filter      __init__.pyc  loader.py     lookup      strategy  vars
Ansible Ansible Tower 提供了一个数据库来存储 inventory 配置信息, 这个数据库可以通过 web 访问,或通过 REST 访问. Tower 与所有你使用的 Ansible 动态 inventory 源保持同步,并提供了一个图形化的 inventory 编辑器. 有了这个数据库,便可以很容易的关联过去的事件历史,可以看到在上一次 playbook 运行时,哪里出现了运行失败的情况.
关于如何编写你自己的动态 inventory 源,请参见 开发动态的Inventory数据源:http://www.ansible.com.cn/docs/developing_inventory.html

11. Patterns
Ansible中,Patterns 是指我们怎样确定由哪一台主机来管理. 意思就是与哪台主机进行交互. 但是在:doc:playbooks 中它指的是对应主机应用特定的配置或执行特定进程.
我们再来复习下:doc:intro_adhoc 章节中介绍的命令用法,命令格式如下:
ansible <pattern_goes_here> -m <module_name> -a <arguments>
示例如下:
ansible webservers -m service -a "name=httpd state=restarted"
一个pattern通常关联到一系列组(主机的集合) –如上示例中,所有的主机均在 “webservers” 组中。不管怎么样,在使用Ansible前,我们需事先告诉Ansible哪台机器将被执行. 能这样做的前提是需要预先定义唯一的 host names 或者 主机组.
如下的patterns等同于目标为仓库(inventory)中的所有机器:
all或者*
也可以写IP地址或系列主机名:
one.example.com
one.example.com:two.example.com
192.168.1.50
192.168.1.*
如下patterns分别表示一个或多个groups.多组之间以冒号分隔表示的关系.这意味着一个主机可以同时存在多个组
webservers
webservers:dbservers
你也可以排队一个特定组,如下实例中,所有执行命令的机器必须隶属 webservers 组但同时不在 phoenix组:
webservers:!phoenix
你也可以指定两个组的交集,如下实例表示,执行命令有机器需要同时隶属于 webservers 和 staging 组.
webservers:&staging
你也可以组合更复杂的条件:
webservers:dbservers:&staging:!phoenix
上面这个例子表示“‘webservers’ 和 ‘dbservers’ 两个组中隶属于 ‘staging’ 组并且不属于 ‘phoenix’ 组的机器才执行命令” ... 哟!唷! 好烧脑的说!
你也可以使用变量如果你希望通过传参指定group,ansible-playbook通过 “-e” 参数可以实现,但这种用法不常用:
webservers:!{{excluded}}:&{{required}}
你也可以不必严格定义groups,单个的host names, IPs , groups都支持通配符:
*.example.com
*.com
Ansible同时也支持通配和groups的混合使用:
one*.com:dbservers
在高级语法中,你也可以在group中选择对应编号的server:
webservers[0]
或者一个group中的一部分servers:
webservers[0-25]
大部分人都在patterns应用正则表达式,但你可以.只需要以 ‘~’ 开头即可:
~(web|db).*\.example\.com
同时让我们提前了解一些技能,除了如上,你也可以通过 --limit 标记来添加排除条件,/usr/bin/ansible or /usr/bin/ansible-playbook都支持:
ansible-playbook site.yml --limit datacenter2
如果你想从文件读取hosts,文件名以@为前缀即可.从Ansible 1.2开始支持该功能:
ansible-playbook site.yml --limit @retry_hosts.txt
12. 介绍Ad-Hoc命令
在下面的例子中,我们将演示如何使用 /usr/bin/ansible 运行 ad hoc 任务。所谓 ad-hoc 命令是什么呢?
这其实是一个概念性的名字,是相对于写 Ansible playbook 来说的.类似于在命令行敲入shell命令和 写shell scripts两者之间的关系。如果我们敲入一些命令去比较快的完成一些事情,而不需要将这些执行的命令特别保存下来, 这样的命令就叫做 ad-hoc 命令。Ansible提供两种方式去完成任务,一是 ad-hoc 命令,一是写 Ansible playbook.前者可以解决一些简单的任务, 后者解决较复杂的任务.
一般而言,在学习了 playbooks 之后,你才能体会到 Ansible 真正的强大之处在哪里.
那我们会在什么情境下去使用ad-hoc 命令呢?比如说因为圣诞节要来了,想要把所有实验室的电源关闭,我们只需要执行一行命令 就可以达成这个任务,而不需要写 playbook 来做这个任务.
至于说做配置管理或部署这种事,还是要借助 playbook 来完成,即使用'/usr/bin/ansible-playbook'这个命令
n Parallelism and Shell Commands
Parallelism and Shell Commands(并行性shell命令)
举一个例子:这里我们要使用 Ansible 的命令行工具来重启 Atlanta 组中所有的 web 服务器,每次重启10个.
我们先设置 SSH-agent,将私钥纳入其管理:
ssh-agent bash
ssh-add ~/.ssh/id_rsa
如果不想使用 ssh-agent, 想通过密码验证的方式使用 SSH,可以在执行ansible命令时使用 --ask-pass (-k)选项, 但这里建议使用 ssh-agent.
现在执行如下命令,这个命令中,atlanta是一个组,这个组里面有很多服务器,”/sbin/reboot”命令会在atlanta组下 的所有机器上执行.这里ssh-agent会fork出10个子进程(bash),以并行的方式执行reboot命令.如前所说“每次重启10个” 即是以这种方式实现:
ansible atlanta -a "/sbin/reboot" -f 10
在执行 /usr/bin/ansible 时,默认是以当前用户的身份去执行这个命令.如果想以指定的用户执行 /usr/bin/ansible, 请添加 “-u username”选项,如下:
ansible atlanta -a "/usr/bin/foo" -u username
如果想通过 sudo 去执行命令,如下:
ansible atlanta -a "/usr/bin/foo" -u username --sudo [--ask-sudo-pass]
如果你不是以 passwordless 的模式执行 sudo,应加上 --ask-sudo-pass (-K)选项,加上之后会提示你输入 密码.使用 passwordless 模式的 sudo, 更容易实现自动化,但不要求一定要使用 passwordless sudo.
也可以通过``-sudo-user`` (-U)选项,使用 sudo 切换到其它用户身份,而不是 root(译者注:下面命令中好像写掉了-sudo):
ansible atlanta -a "/usr/bin/foo" -u username -U otheruser [--ask-sudo-pass]
注意:在有些比较罕见的情况下,一些用户会受到安全规则的限制,使用 sudo 切换时只能运行指定的命令.这与 ansible的 no-bootstrapping 思想相悖,而且 ansible 有几百个模块,在这种限制下无法进行正常的工作. 所以执行 ansible 命令时,应使用一个没有受到这种限制的账号来执行
在前面写出的命令中, -f 10 选项表示使用10个并行的进程.这个选项也可以在 Ansible的配置文件 中设置, 在配置文件中指定的话,就不用在命令行中写出了.这个选项的默认值是 5,是比较小的.如果同时操作的主机数比较多的话, 可以调整到一个更大的值,只要不超出你系统的承受范围就没问题.如果主机数大于设置的并发进程数,Ansible会自行协调, 花得时间会更长一点.
ansible有许多模块,默认是 ‘command’,也就是命令模块,我们可以通过 -m 选项来指定不同的模块.在前面所示的例子中, 因为我们是要在 Atlanta 组下的服务器中执行 reboot 命令,所以就不需要显示的用这个选项指定 ‘command’ 模块,使用 默认设定就OK了.一会在其他例子中,我们会使用 -m 运行其他的模块,
command 模块不支持 shell 变量,也不支持管道等 shell 相关的东西.如果你想使用 shell相关的这些东西, 请使用’shell’ 模块.两个模块之前的差别请参考 模块相关http://www.ansible.com.cn/docs/modules.html
使用 shell 模块的示例如下:
ansible raleigh -m shell -a 'echo $TERM'
使用 Ansible ad hoc 命令行接口时(与使用 Playbooks 的情况相反),尤其注意 shell 引号的规则. 比如在上面的例子中,如果使用双引号”echo $TERM”,会求出TERM变量在当前系统的值,而我们实际希望的是把这个命令传递 到其它机器执行.
n File Transfer(文件传输
这是 /usr/bin/ansible 的另一种用法.Ansible 能够以并行的方式同时 SCP 大量的文件到多台机器. 命令如下:
ansible atlanta -m copy -a "src=/etc/hosts dest=/tmp/hosts"
使用 file 模块可以做到修改文件的属主和权限,(在这里可替换为 copy 模块,是等效的):
ansible webservers -m file -a "dest=/srv/foo/a.txt mode=600"
ansible webservers -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"
使用 file 模块也可以创建目录,与执行 mkdir -p 效果类似:
ansible webservers -m file -a "dest=/path/to/c mode=755 owner=mdehaan group=mdehaan state=directory"
删除目录(递归的删除)和删除文件
ansible webservers -m file -a "dest=/path/to/c state=absent"
n Managing Packages(管理
Ansible 提供对 yum 和 apt 的支持.这里是关于 yum 的示例.
确认一个软件包已经安装,但不去升级它:
ansible webservers -m yum -a "name=acme state=present"
确认一个软件包的安装版本:
ansible webservers -m yum -a "name=acme-1.5 state=present"
确认一个软件包还没有安装:
ansible webservers -m yum -a "name=acme state=absent"
对于不同平台的软件包管理工具,Ansible都有对应的模块.如果没有,你也可以使用 command 模块去安装软件. 或者最好是来为那个软件包管理工具贡献一个相应的模块.请在 mailing list 中查看相关的信息和详情.
n Users and Groups
使用 ‘user’ 模块可以方便的创建账户,删除账户,或是管理现有的账户:
ansible all -m user -a "name=foo password=<crypted password here>"   #创建
ansible all -m user -a "name=foo state=absent"    #删除
n Deploying From Source Control
直接使用 git 部署 webapp:
ansible webservers -m git -a "repo=git://foo.example.org/repo.git dest=/srv/myapp version=HEAD"
因为Ansible 模块可通知到 change handlers ,所以当源码被更新时,我们可以告知 Ansible 这个信息,并执行指定的任务, 比如直接通过 git 部署 Perl/Python/PHP/Ruby, 部署完成后重启 apache.
n Managing Services
确认某个服务在所有的webservers上都已经启动:
ansible webservers -m service -a "name=httpd state=started"
或是在所有的webservers上重启某个服务(译者注:可能是确认已重启的状态?):
ansible webservers -m service -a "name=httpd state=restarted"
确认某个服务已经停止:
ansible webservers -m service -a "name=httpd state=stopped"
n Time Limited Background Operations
需要长时间运行的命令可以放到后台去,在命令开始运行后我们也可以检查运行的状态.如果运行命令后,不想获取返回的信息, 可执行如下命令:
ansible all -B 3600 -P 0 -a "/usr/bin/long_running_operation --do-stuff"
如果你确定要在命令运行后检查运行的状态,可以使用 async_status 模块.前面执行后台命令后会返回一个 job id, 将这个 id 传给 async_status 模块:
ansible web1.example.com -m async_status -a "jid=488359678239.2844"
获取状态的命令如下:
ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff"
其中 -B 1800 表示最多运行30分钟, -P 60 表示每隔60秒获取一次状态信息.
Polling 获取状态信息的操作会在后台工作任务启动之后开始.若你希望所有的工作任务快速启动, --forks 这个选项的值 要设置得足够大,这是前面讲过的并发进程的个数.在运行指定的时间(由``-B``选项所指定)后,远程节点上的任务进程便会被终止
一般你只能在把需要长时间运行的命令或是软件升级这样的任务放到后台去执行.对于 copy 模块来说,即使按照前面的示例想放到 后台执行文件传输,实际上并不会如你所愿.
n Gathering Facts(收集信息
playbooks 中有对于 Facts 做描述,它代表的是一个系统中已发现的变量.These can be used to implement conditional execution of tasks but also just to get ad-hoc information about your system. 可通过如下方式查看所有的 facts:
ansible all -m setup
我们也可以对这个命令的输出做过滤,只输出特定的一些 facts,详情请参考 “setup” 模块的文档.
13. Ansible的配置文件
n 配置文件读取顺序
Ansible的一些的设置可以通过配置文件完成.在大多数场景下默认的配置就能满足大多数用户的需求,在一些特殊场景下,用户还是需要自行修改这些配置文件
用户可以修改一下配置文件来修改设置,他们的被读取的顺序如下:
Ø ANSIBLE_CONFIG (一个环境变量)
Ø ansible.cfg (位于当前目录中)
Ø .ansible.cfg (位于家目录中)
Ø /etc/ansible/ansible.cfg
版本1.5之前的读取顺序如下:
Ø ansible.cfg (位于当前目录)
Ø ANSIBLE_CONFIG (一个环境变量)
Ø .ansible.cfg (位于家目录下)
Ø /etc/ansible/ansible.cfg
Ansible 将会按以上顺序逐个查询这些文件,直到找到一个为止,并且使用第一个寻找到个配置文件的配置,这些配置将不会被叠加
n 获取最新配置文件
如果使用程序包管理器安装ansible,最新的 ansible.cfg 配置文件有可能出现在 /etc/ansible 下并且命名为'.rpmnew', 也可能根据不同的更新命名为其它名称
如果你是通过 pip 或者其他方式安装,则可能需要自行创建这个文件,以免原配置文件被覆盖.Ansible 的默认设置将会将其覆盖
配置文件的详细参数以及取值范围请查看`ansible.cfg: https://raw.github.com/ansible/ansible/devel/examples/ansible.cfg
n 环境配置
Ansible 通过环境变量的形式来进行配置.这些设置后的环境变量将会覆盖掉所有配置文件读取的配置.为了节省篇幅,这些变量没有被列在这里,详情请见源代码目录中的 ‘constants.py’. 相对于配置文件它门会比当作遗产系统(legacy system) 来被使用,但是仍然有效
n 配置文件详解
配置文件被切割成了不同段.多数配置选项位于“general”段, 也有一些属于特定的链接类型(connection type)
# config file for ansible -- http://ansible.com/
# ==============================================

# nearly all parameters can be overridden in ansible-playbook
# or with command line flags. ansible will read ANSIBLE_CONFIG,
# ansible.cfg in the current working directory, .ansible.cfg in
# the home directory or /etc/ansible/ansible.cfg, whichever it
# finds first

[defaults]   --->通用默认配置

# some basic default values...

inventory      = /etc/ansible/hosts     这个是默认库文件位置,脚本,或者存放可通信主机的目录
#library        = /usr/share/my_modules/   Ansible默认搜寻模块的位置
remote_tmp     = $HOME/.ansible/tmp   Ansible 通过远程传输模块到远程主机,然后远程执行,执行后在清理现场.在有些场景下,你也许想使用默认路径希望像更换补丁一样使用
pattern        = *    如果没有提供“hosts”节点,这是playbook要通信的默认主机组.默认值是对所有主机通信
forks          = 5    在与主机通信时的默认并行进程数 ,默认是5d
poll_interval  = 15    当具体的poll interval 没有定义时,多少时间回查一下这些任务的状态, 默认值是5秒
sudo_user      = root   sudo使用的默认用户 ,默认是root
#ask_sudo_pass = True   用来控制Ansible playbook 在执行sudo之前是否询问sudo密码.默认为no
#ask_pass      = True    控制Ansible playbook 是否会自动默认弹出密码
transport      = smart   通信机制.默认 值为’smart’。如果本地系统支持 ControlPersist技术的话,将会使用(基于OpenSSH)‘ssh’,如果不支持讲使用‘paramiko’.其他传输选项包括‘local’, ‘chroot’,’jail’等等
#remote_port    = 22    远程SSH端口。 默认是22
module_lang    = C   模块和系统之间通信的计算机语言,默认是C语言

# plays will gather facts by default, which contain information about
# the remote system.
#
# smart - gather by default, but don't regather if already gathered
# implicit - gather by default, turn off with gather_facts: False
# explicit - do not gather by default, must say gather_facts: True
gathering = implicit   控制默认facts收集(远程系统变量). 默认值为’implicit’, 每一次play,facts都会被收集

# additional paths to search for roles in, colon separated
#roles_path    = /etc/ansible/roles   roles 路径指的是’roles/’下的额外目录,用于playbook搜索Ansible roles

# uncomment this to disable SSH key host checking
#host_key_checking = False    检查主机密钥

# change this for alternative sudo implementations
sudo_exe = sudo     如果在其他远程主机上使用另一种方式执sudu操作.可以使用该参数进行更换

# what flags to pass to sudo   传递sudo之外的参数
#sudo_flags = -H

# SSH timeout    SSH超时时间
timeout = 10

# default user to use for playbooks if user is not specified
# (/usr/bin/ansible will use current user as default)
#remote_user = root   使用/usr/bin/ansible-playbook链接的默认用户名,如果不指定,会使用当前登录的用户名

# logging is off by default unless this path is defined
# if so defined, consider logrotate
#log_path = /var/log/ansible.log     日志文件存放路径

# default module name for /usr/bin/ansible
#module_name = command     ansible命令执行默认的模块

# use this shell for commands executed under sudo
# you may need to change this to bin/bash in rare instances
# if sudo is constrained
#executable = /bin/sh     在sudo环境下产生一个shell交互接口. 用户只在/bin/bash的或者sudo限制的一些场景中需要修改

# if inventory variables overlap, does the higher precedence one win
# or are hash values merged together?  The default is 'replace' but
# this can also be set to 'merge'.
#hash_behaviour = replace    特定的优先级覆盖变量

# list any Jinja2 extensions to enable here:
#jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n      允许开启Jinja2拓展模块

# if set, always use this private key file for authentication, same as
# if passing --private-key to ansible or ansible-playbook
#private_key_file = /path/to/file         私钥文件存储位置

# format of string {{ ansible_managed }} available within Jinja2
# templates indicates to users editing templates files will be replaced.
# replacing {file}, {host} and {uid} and strftime codes with proper values.
ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}   这个设置可以告知用户,Ansible修改了一个文件,并且手动写入的内容可能已经被覆盖.

# by default, ansible-playbook will display "Skipping [host]" if it determines a task
# should not be run on a host.  Set this to "False" if you don't want to see these "Skipping"
# messages. NOTE: the task header will still be shown regardless of whether or not the
# task is skipped.
#display_skipped_hosts = True     显示任何跳过任务的状态 ,默认是显示

# by default (as of 1.3), Ansible will raise errors when attempting to dereference
# Jinja2 variables that are not set in templates or action lines. Uncomment this line
# to revert the behavior to pre-1.3.
#error_on_undefined_vars = False      如果所引用的变量名称错误的话, 将会导致ansible在执行步骤上失败

# by default (as of 1.6), Ansible may display warnings based on the configuration of the
# system running ansible itself. This may include warnings about 3rd party packages or
# other conditions that should be resolved if possible.
# to disable these warnings, set the following value to False:
#system_warnings = True    允许禁用系统运行ansible相关的潜在问题警告

# by default (as of 1.4), Ansible may display deprecation warnings for language
# features that should no longer be used and will be removed in future versions.
# to disable these warnings, set the following value to False:
#deprecation_warnings = True     允许在ansible-playbook输出结果中禁用“不建议使用”警告

# (as of 1.8), Ansible can optionally warn when usage of the shell and
# command module appear to be simplified by using a default Ansible module
# instead.  These warnings can be silenced by adjusting the following
# setting or adding warn=yes or warn=no to the end of the command line
# parameter string.  This will for example suggest using the git module
# instead of shelling out to the git command.
# command_warnings = False    当shell和命令行模块被默认模块简化的时,Ansible 将默认发出警告


# set plugin path directories here, separate with colons
action_plugins     = /usr/share/ansible_plugins/action_plugins  
callback_plugins   = /usr/share/ansible_plugins/callback_plugins
connection_plugins = /usr/share/ansible_plugins/connection_plugins
lookup_plugins     = /usr/share/ansible_plugins/lookup_plugins
vars_plugins       = /usr/share/ansible_plugins/vars_plugins
filter_plugins     = /usr/share/ansible_plugins/filter_plugins

# by default callbacks are not loaded for /bin/ansible, enable this if you
# want, for example, a notification or logging callback to also apply to
# /bin/ansible runs
#bin_ansible_callbacks = False    用来控制callback插件是否在运行 /usr/bin/ansible 的时候被加载. 这个模块将用于命令行的日志系统,发出通知等特性


# don't like cows?  that's unfortunate.
# set to 1 if you don't want cowsay support or export ANSIBLE_NOCOWS=1
#nocows = 1    默认ansible可以调用一些cowsay的特性   开启/禁用:0/1

# don't like colors either?
# set to 1 if you don't want colors, or export ANSIBLE_NOCOLOR=1
#nocolor = 1  输出带上颜色区别, 开启/关闭:0/1

# the CA certificate path used for validating SSL certs. This path
# should exist on the controlling node, not the target nodes
# common locations:
# RHEL/CentOS: /etc/pki/tls/certs/ca-bundle.crt
# Fedora     : /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
# Ubuntu     : /usr/share/ca-certificates/cacert.org/cacert.org.crt
#ca_file_path =   

# the http user-agent string to use when fetching urls. Some web server
# operators block the default urllib user agent as it is frequently used
# by malicious attacks/scripts, so we set it to something unique to
# avoid issues.
#http_user_agent = ansible-agent

# if set to a persistent type (not 'memory', for example 'redis') fact values
# from previous runs in Ansible will be stored.  This may be useful when
# wanting to use, for example, IP information from one group of servers
# without having to talk to them in the same playbook run to get their
# current IP information.
fact_caching = memory


# retry files
#retry_files_enabled = False
#retry_files_save_path = ~/.ansible-retry

[privilege_escalation]
#become=True
#become_method=sudo
#become_user=root
#become_ask_pass=False

[paramiko_connection]

# uncomment this line to cause the paramiko connection plugin to not record new host
# keys encountered.  Increases performance on new host additions.  Setting works independently of the
# host key checking setting above.
#record_host_keys=False

# by default, Ansible requests a pseudo-terminal for commands executed under sudo. Uncomment this
# line to disable this behaviour.
#pty=False

[ssh_connection]

# ssh arguments to use
# Leaving off ControlPersist will result in poor performance, so use
# paramiko on older platforms rather than removing it
#ssh_args = -o ControlMaster=auto -o ControlPersist=60s

# The path to use for the ControlPath sockets. This defaults to
# "%(directory)s/ansible-ssh-%%h-%%p-%%r", however on some systems with
# very long hostnames or very long path names (caused by long user names or
# deeply nested home directories) this can exceed the character limit on
# file socket names (108 characters for most platforms). In that case, you
# may wish to shorten the string below.
#
# Example:
# control_path = %(directory)s/%%h-%%r
#control_path = %(directory)s/ansible-ssh-%%h-%%p-%%r

# Enabling pipelining reduces the number of SSH operations required to
# execute a module on the remote server. This can result in a significant
# performance improvement when enabled, however when using "sudo:" you must
# first disable 'requiretty' in /etc/sudoers
#
# By default, this option is disabled to preserve compatibility with
# sudoers configurations that have requiretty (the default on many distros).
#
#pipelining = False

# if True, make ansible use scp if the connection type is ssh
# (default is sftp)
#scp_if_ssh = True

[accelerate]
accelerate_port = 5099
accelerate_timeout = 30
accelerate_connect_timeout = 5.0

# The daemon timeout is measured in minutes. This time is measured
# from the last activity to the accelerate daemon.
accelerate_daemon_timeout = 30

# If set to yes, accelerate_multi_key will allow multiple
# private keys to be uploaded to it, though each user must
# have access to the system via SSH to add a new key. The default
# is "no".
#accelerate_multi_key = yes

[selinux]
# file systems that require special treatment when dealing with security context
# the default behaviour that copies the existing context or uses the user default
# needs to be changed to use the file system dependant context.
#special_context_filesystems=nfs,vboxsf,fuse
三、 ansible常见模块
1. Commands modules






四、 Playbooks
Playbooks 是 Ansible的配置,部署,编排语言.他们可以被描述为一个需要希望远程主机执行命令的方案,或者一组IT程序运行的命令集合.
如果 Ansible 模块你是工作室中的工具,那么 playbooks 就是你设置的方案计划.
在基础层面, playbooks 可以被用来管理用于部署到远程主机的配置文件.在更高的层面上,playbooks 可以依次对多层式架构上的服务器执行上线包括滚动更新在内的操作并可以将操作委托给其他主机包括在此过程中发生的与监视服务器,负载均衡服务器的交互操作在内.
虽然这里讲发很多,但是不需要立刻一次性全部学完.你可以从小功能开始,当你需要的时候再来这里找对应的功能即可.
Playbooks 被设计的非常简单易懂和基于text language二次开发.有多种办法来组织 playbooks 和其附属的文件,同时我们也会提供一些关于学习 Ansible 的建议.
这里强烈建议在阅读的 playbook 文档的时候同步参阅 Example Playbooks <https://github.com/ansible/ansible-examples> 章节. 这些例子是最佳实战以及如何将各种概念灵活贯穿结合在一起.
1. Playbooks 介绍
n Playbooks 简介
Playbooks 与 adhoc 相比,是一种完全不同的运用 ansible 的方式,是非常之强大的.
简单来说,playbooks 是一种简单的配置管理系统与多机器部署系统的基础.与现有的其他系统有不同之处,且非常适合于复杂应用的部署.
Playbooks 可用于声明配置,更强大的地方在于,在 playbooks 中可以编排有序的执行过程,甚至于做到在多组机器间,来回有序的执行特别指定的步骤.并且可以同步或异步的发起任务.
我们使用 adhoc 时,主要是使用 /usr/bin/ansible 程序执行任务.而使用 playbooks 时,更多是将之放入源码控制之中,用之推送你的配置或是用于确认你的远程系统的配置是否符合配置规范.
在如右的连接中: ansible-examples repository: https://github.com/ansible/ansible-examples/tree/master/lamp_simple,有一些整套的playbooks,它们阐明了上述的这些技巧.我们建议你在另一个标签页中打开它看看,配合本章节一起看.
即便学完 playbooks 这个章节,仍有许多知识点只是入门的级别,完成本章的学习后,可回到文档索引继续学习.
n Playbook 语言的示例
Playbooks 的格式是YAML(详见:YAML 语法)http://www.ansible.com.cn/docs/YAMLSyntax.html,语法做到最小化,意在避免 playbooks 成为一种编程语言或是脚本,但它也并不是一个配置模型或过程的模型.
playbook 由一个或多个'plays' 组成.它的内容是一个以 'plays'为元素的列表.
play 之中,一组机器被映射为定义好的角色.在 ansible 中,play 的内容,被称为 tasks,即任务.在基本层次的应用中,一个任务是一个对 ansible 模块的调用,这在前面章节学习过.
‘plays’ 好似音符,playbook 好似由 ‘plays’ 构成的曲谱,通过 playbook,可以编排步骤进行多机器的部署,比如在 webservers 组的所有机器上运行一定的步骤, 然后在 database server 组运行一些步骤,最后回到 webservers 组,再运行一些步骤,诸如此类.
“plays” 算是一个体育方面的类比,你可以通过多个 plays 告诉你的系统做不同的事情,不仅是定义一种特定的状态或模型.你可以在不同时间运行不同的 plays.
对初学者,这里有一个 playbook,其中仅包含一个 play:
---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: pkg=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service: name=httpd state=started
  handlers:
    - name: restart apache
      service: name=httpd state=restarted
在下面,我们将分别讲解 playbook 语言的多个特性.
n playbook基础
1. 主机与用户
你可以为 playbook 中的每一个 play,个别地选择操作的目标机器是哪些,以哪个用户身份去完成要执行的步骤(called tasks)
hosts 行的内容是一个或多个组或主机的 patterns,以逗号为分隔符,详见 Patterns 章节。remote_user 就是账户名:
---
- hosts: webservers
  remote_user: root
参数 remote_user 以前写做 user,在 Ansible 1.4 以后才改为 remote_user.主要为了不跟 user 模块混淆(user 模块用于在远程系统上创建用户)。
再者,在每一个 task 中,可以定义自己的远程用户:
---
- hosts: webservers
  remote_user: root
  tasks:
    - name: test connection
      ping:
      remote_user: yourname
task 中的 remote_user 参数在 1.4 版本以后添加.
也支持从 sudo 执行命令:
---
- hosts: webservers
  remote_user: yourname
  sudo: yes
同样的,你可以仅在一个 task 中,使用 sudo 执行命令,而不是在整个 play 中使用 sudo:
---
- hosts: webservers
  remote_user: yourname
  tasks:
    - service: name=nginx state=started
      sudo: yes
你也可以登陆后,sudo 到不同的用户身份,而不是使用 root:
---
- hosts: webservers
  remote_user: yourname
  sudo: yes
  sudo_user: postgres
如果你需要在使用 sudo 时指定密码,可在运行 ansible-playbook 命令时加上选项 --ask-sudo-pass (-K). 如果使用 sudo 时,playbook 疑似被挂起,可能是在 sudo prompt 处被卡住,这时可执行 Control-C 杀死卡住的任务,再重新运行一次.
当使用 sudo_user 切换到 非root 用户时,模块的参数会暂时写入 /tmp 目录下的一个随机临时文件. 当命令执行结束后,临时文件立即删除.这种情况发生在普通用户的切换时,比如从 ‘bob’ 切换到 ‘timmy’, 切换到 root 账户时,不会发生,如从 ‘bob’ 切换到 ‘root’,直接以普通用户或root身份登录也不会发生. 如果你不希望这些数据在短暂的时间内可以被读取(不可写),请避免在 sudo_user 中传递未加密的密码. 其他情况下,’/tmp’ 目录不被使用,这种情况不会发生。Ansible 也有意识的在日志中不记录密码参数。
2. Tasks 列表
每一个 play 包含了一个 task 列表(任务列表).一个 task 在其所对应的所有主机上(通过 host pattern 匹配的所有主机)执行完毕之后,下一个 task 才会执行.有一点需要明白的是(很重要),在一个 play 之中,所有 hosts 会获取相同的任务指令,这是 play 的一个目的所在,也就是将一组选出的 hosts 映射到 task.(注:此处翻译未必准确,暂时保留原文)
在运行 playbook 时(从上到下执行),如果一个 host 执行 task 失败,这个 host 将会从整个 playbook 的 rotation 中移除. 如果发生执行失败的情况,请修正 playbook 中的错误,然后重新执行即可。
每个 task 的目标在于执行一个 moudle, 通常是带有特定的参数来执行.在参数中可以使用变量(variables)
modules 具有”幂等”性,意思是如果你再一次地执行 moudle(译者注:比如遇到远端系统被意外改动,需要恢复原状),moudle 只会执行必要的改动,只会改变需要改变的地方.所以重复多次执行 playbook 也很安全.
对于 command module 和 shell module,重复执行 playbook,实际上是重复运行同样的命令.如果执行的命令类似于 ‘chmod’ 或者 ‘setsebool’ 这种命令,这没有任何问题.也可以使用一个叫做 ‘creates’ 的 flag 使得这两个 module 变得具有”幂等”特性 (不是必要的)。
每一个 task 必须有一个名称 name,这样在运行 playbook 时,从其输出的任务执行信息中可以很好的辨别出是属于哪一个 task 的. 如果没有定义 name,‘action’ 的值将会用作输出信息中标记特定的 task。
如果要声明一个 task,以前有一种格式: “action: module options” (可能在一些老的 playbooks 中还能见到).现在推荐使用更常见的格式:”module: options” ,本文档使用的就是这种格式.
下面是一种基本的 task 的定义,service moudle 使用 key=value 格式的参数,这也是大多数 module 使用的参数格式:
tasks:
  - name: make sure apache is running
    service: name=httpd state=running
比较特别的两个 modudle 是 command 和 shell ,它们不使用 key=value 格式的参数,而是这样:
tasks:
  - name: disable selinux
    command: /sbin/setenforce 0
使用 command module 和 shell module 时,我们需要关心返回码信息,如果有一条命令,它的成功执行的返回码不是0, 你或许希望这样做:
tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true
或者是这样:
tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True                        #忽略错误让其继续执行
如果 action 行看起来太长,你可以使用 space(空格) 或者 indent(缩进) 隔开连续的一行:
tasks:
  - name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
            owner=root group=root mode=0644
action 行中可以使用变量.假设在 ‘vars’ 那里定义了一个变量 ‘vhost’ ,可以这样使用它:
tasks:
  - name: create a virtual host file for {{ vhost }}
    template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }}
这些变量在 tempates 中也是可用的,稍后会讲到.
在一个基础的 playbook 中,所有的 task 都是在一个 play 中列出,稍后将介绍一种更合理的安排 task 的方式:使用 ‘include:’ 指令.
n Action Shorthand
0.8 及以后的版本中,ansible 更喜欢使用如下的格式列出 modules:
template: src=templates/foo.j2 dest=/etc/foo.conf
在早期的版本中,使用以下的格式:
action: template src=templates/foo.j2 dest=/etc/foo.conf
早期的格式在新版本中仍然可用,并且没有计划将这种旧的格式弃用.
n Handlers: 在发生改变时执行的操作
上面我们曾提到过,module 具有”幂等”性,所以当远端系统被人改动时,可以重放 playbooks 达到恢复的目的. playbooks 本身可以识别这种改动,并且有一个基本的 event system(事件系统),可以响应这种改动.
(当发生改动时)’notify’ actions 会在 playbook 的每一个 task 结束时被触发,而且即使有多个不同的 task 通知改动的发生, ‘notify’ actions 只会被触发一次.
举例来说,比如多个 resources 指出因为一个配置文件被改动,所以 apache 需要重新启动,但是重新启动的操作只会被执行一次。
这里有一个例子,当一个文件的内容被改动时,重启两个 services:
- name: template configuration file
  template: src=template.j2 dest=/etc/foo.conf
  notify:
     - restart memcached
     - restart apache
‘notify’ 下列出的即是 handlers.
Handlers 也是一些 task 的列表,通过名字来引用,它们和一般的 task 并没有什么区别.Handlers 是由通知者进行 notify, 如果没有被 notify,handlers 不会执行.不管有多少个通知者进行了 notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次.
这里是一个 handlers 的示例:
handlers:
    - name: restart memcached
      service:  name=memcached state=restarted
    - name: restart apache
      service: name=apache state=restarted
Handlers 最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到了。handlers 会按照声明的顺序执行。Roles 将在下一章节讲述.值得指出的是,handlers 会在 ‘pre_tasks’, ‘roles’, ‘tasks’, 和 ‘post_tasks’ 之间自动执行. 如果你想立即执行所有的 handler 命令,在1.2及以后的版本,你可以这样做:
tasks:
   - shell: some tasks go here
   - meta: flush_handlers
   - shell: some other tasks
在以上的例子中,任何在排队等候的 handlers 会在执行到 ‘meta’ 部分时,优先执行.这个技巧在有些时候也能派上用场.
n 执行一个 playbook
既然现在你已经学习了 playbook 的语法,那要如何运行一个 playbook 呢?这很简单,这里的示例是并行的运行 playbook,并行的级别 是10(译者注:是10个并发的进程?):
ansible-playbook playbook.yml -f 10
n Ansible-Pull(拉取配置而非推送配置)
我们可不可以将 ansible 的体系架构颠倒过来,让托管节点从一个 central location 做 check in 获取配置信息,而不是 推送配置信息到所有的托管节点?是可以的.
Ansible-pull 是一个小脚本,它从 git 上 checkout 一个关于配置指令的 repo,然后以这个配置指令来运行 ansible-playbook.
假设你对你的 checkout location 做负载均衡,ansible-pull 基本上可以无限的提升规模.
可执行 ansible-pull --help 获取详细的帮助信息。
也有一个叫做 clever playbook 的东西: clever playbook . 这个可以通过 crontab 来配置 ansible-pull(from push mode).
n 提示与技巧
playbook 执行输出信息的底部,可以找到关于托管节点的信息.也可看到一般的失败信息,和严重的 “unreachable” 信息. 这两个是分开计数的.
如果你想看到执行成功的 modules 的输出信息,使用 --verbose flag(否则只有执行失败的才会有输出信息).这在 0.5 及以后的版本中可用.
如果安装了 cowsay 软件包,ansible playbook 的输出已经进行了广泛的升级.可以尝试一下!在执行一个 playbook 之前,想看看这个 playbook 的执行会影响到哪些 hosts,你可以这样做:
ansible-playbook playbook.yml --list-hosts
14. Playbook 角色(Roles) 和 Include 语句
n 简介
当我们刚开始学习运用 playbook 时,可能会把 playbook 写成一个很大的文件,到后来可能你会希望这些文件是可以方便去重用的,所以需要重新去组织这些文件。
基本上,使用 include 语句引用 task 文件的方法,可允许你将一个配置策略分解到更小的文件中。使用 include 语句引用 tasks 是将 tasks 从其他文件拉取过来。因为 handlers 也是 tasks,所以你也可以使用 include 语句去引用 handlers 文件。handlers 文件来自 ‘handlers:’ section。
Playbook 同样可以使用 include 引用其他 playbook 文件中的 play。这时被引用的 play 会被插入到当前的 playbook 中,当前的 playbook 中就有了一个更长的的 play 列表
当你开始思考这些概念:tasks, handlers, variables 等等,是否可以将它们抽象为一个更大的概念呢。我们考虑的不再是”将这些 tasks,handlers,variables 等等应用到这些 hosts 中”,而是有了更抽象的概念,比如:”这些 hosts 是 dbservers” 或者 “那些 hosts 是 webservers”(译者注:dbserver,webservers 即是”角色”)。这种思考方式在编程中被称为”封装”,将其中具体的功能封装了起来。举个例子,你会开车但并不需要知道引擎的工作原理(译者注:同样的道理,我们只需要知道”这些 hosts 是 dbservers”,而不需要知道其中有哪些 task,handlers 等)。
Roles 的概念来自于这样的想法:通过 include 包含文件并将它们组合在一起,组织成一个简洁、可重用的抽象对象。这种方式可使你将注意力更多地放在大局上,只有在需要时才去深入了解细节。
我们将从理解如何使用 include 开始,这样你会更容易理解 roles 的概念。但我们的终极目标是让你理解 roles,roles 是一个很棒的东西,每次你写 playbook 的时候都应该使用它。
ansible-examples 中有很多实例,如果你希望深入学习可以在单独的页面打开它。
n Task Include Files And Encouraging Reuse
假如你希望在多个 play 或者多个 playbook 中重用同一个 task 列表,你可以使用 include files 做到这一点。 当我们希望为系统定义一个角色时,使用 include 去包含 task 列表是一种很好用的方法。需要记住的是,一个 play 所要达成 的目标是将一组系统映射为多个角色。下面我们来看看具体是如何做的:
一个 task include file 由一个普通的 task 列表所组成,像这样:
---
# possibly saved as tasks/foo.yml

- name: placeholder foo
  command: /bin/foo

- name: placeholder bar
  command: /bin/bar
Include 指令看起来像下面这样,在一个 playbook 中,Include 指令可以跟普通的 task 混合在一起使用:
tasks:

  - include: tasks/foo.yml
你也可以给 include 传递变量。我们称之为 ‘参数化的 include’。
举个例子,如果我们要部署多个 wordpress 实例,我们可将所有的 wordpress task 写在一个 wordpress.yml 文件中, 然后像下面这样使用 wordpress.yml 文件:
tasks:
  - include: wordpress.yml wp_user=timmy
  - include: wordpress.yml wp_user=alice
  - include: wordpress.yml wp_user=bob
如果你运行的是 Ansible 1.4 及以后的版本,include 语法可更为精简,这种写法同样允许传递列表和字典参数:
tasks:
- { include: wordpress.yml, wp_user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }
使用上述任意一种语法格式传递变量给 include files 之后,这些变量就可以在 include 包含的文件中使用了。 关于变量的详细使用方法请查看 Variables 。变量可以这样去引用:
{{ wp_user }}
(除了显式传递的参数,所有在 vars section 中定义的变量也可在这里使用)
1.0 版开始,Ansible 支持另一种传递变量到 include files 的语法,这种语法支持结构化的变量:
tasks:

  - include: wordpress.yml
    vars:
        wp_user: timmy
        some_list_variable:
          - alpha
          - beta
          - gamma
Playbooks 中可使用 include 包含其他 playbook,我们将在稍后的章节介绍这个用法。
1.0 版开始,task include 语句可以在任意层次使用。在这之前,include 语句 只能在单个层次使用,所以在之前版本中由 include 所包含的文件,其中不能再有 include 包含出现。
Include 语句也可以用在 ‘handlers’ section 中,比如,你希望定义一个重启 apache 的 handler, 你只需要定义一次,然后便可在所有的 playbook 中使用这个 handler。你可以创建一个 handlers.yml 文件如下:
---
# this might be in a file like handlers/handlers.yml
- name: restart apache
  service: name=apache state=restarted
然后在你的主 playbook 文件中,在一个 play 的最后使用 include 包含 handlers.yml:
handlers:
  - include: handlers/handlers.yml
Include 语句可以和其他非 include 的 tasks 和 handlers 混合使用。
Include 语句也可用来将一个 playbook 文件导入另一个 playbook 文件。这种方式允许你定义一个 顶层的 playbook,这个顶层 playbook 由其他 playbook 所组成。
举个例子:
- 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..."

- include: load_balancers.yml
- include: webservers.yml
- include: dbservers.yml
注意:当你在 playbook 中引用其他 playbook 时,不能使用变量替换。
n Roles
你现在已经学过 tasks 和 handlers,那怎样组织 playbook 才是最好的方式呢?简单的回答就是:使用 roles ! Roles 基于一个已知的文件结构,去自动的加载某些 vars_files,tasks 以及 handlers。基于 roles 对内容进行分组,使得我们可以容易地与其他用户分享 roles
一个项目的结构如下:
site.yml
webservers.yml
fooservers.yml
roles/
   common/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/
   webservers/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/
一个 playbook 如下:
---
- hosts: webservers
  roles:
     - common
     - webservers
这个 playbook 为一个角色 ‘x’ 指定了如下的行为:
Ø 如果 roles/x/tasks/main.yml 存在, 其中列出的 tasks 将被添加到 play
Ø 如果 roles/x/handlers/main.yml 存在, 其中列出的 handlers 将被添加到 play
Ø 如果 roles/x/vars/main.yml 存在, 其中列出的 variables 将被添加到 play
Ø 如果 roles/x/meta/main.yml 存在, 其中列出的 “角色依赖” 将被添加到 roles 列表中 (1.3 and later)
Ø 所有 copy tasks 可以引用 roles/x/files/ 中的文件,不需要指明文件的路径。
Ø 所有 script tasks 可以引用 roles/x/files/ 中的脚本,不需要指明文件的路径。
Ø 所有 template tasks 可以引用 roles/x/templates/ 中的文件,不需要指明文件的路径。
Ø 所有 include tasks 可以引用 roles/x/tasks/ 中的文件,不需要指明文件的路径。
Ansible 1.4 及之后版本,你可以为”角色”的搜索设定 roles_path 配置项。使用这个配置项将所有的 common 角色 check out 到一个位置,以便在多个 playbook 项目中可方便的共享使用它们。查看 Ansible的配置文件 详细了解设置这个配置项的细节,该配置项是在 ansible.cfg 中配置。
如果 roles 目录下有文件不存在,这些文件将被忽略。比如 roles 目录下面缺少了 ‘vars/’ 目录,这也没关系。
注意:你仍然可以在 playbook 中松散地列出 tasks,vars_files 以及 handlers,这种方式仍然可用,但 roles 是一种很好的具有组织性的功能特性,我们强烈建议使用它。如果你在 playbook 中同时使用 roles 和 tasks,vars_files 或者 handlers,roles 将优先执行。
而且,如果你愿意,也可以使用参数化的 roles,这种方式通过添加变量来实现,比如:
---

- hosts: webservers
  roles:
    - common
    - { role: foo_app_instance, dir: '/opt/a',  port: 5000 }
    - { role: foo_app_instance, dir: '/opt/b',  port: 5001 }
当一些事情不需要频繁去做时,你也可以为 roles 设置触发条件,像这样:
---

- hosts: webservers
  roles:
    - { role: some_role, when: "ansible_os_family == 'RedHat'" }
它的工作方式是:将条件子句应用到 role 中的每一个 task 上。关于”条件子句”的讨论参见本文档后面的章节。
最后,你可能希望给 roles 分配指定的 tags。比如:
---

- hosts: webservers
  roles:
    - { role: foo, tags: ["bar", "baz"] }
如果 play 仍然包含有 ‘tasks’ section,这些 tasks 将在所有 roles 应用完成之后才被执行。
如果你希望定义一些 tasks,让它们在 roles 之前以及之后执行,你可以这样做:
---

- hosts: webservers

  pre_tasks:
    - shell: echo 'hello'

  roles:
    - { role: some_role }

  tasks:
    - shell: echo 'still busy'

  post_tasks:
    - shell: echo 'goodbye'
如果对 tasks 应用了 tags(tags 是一种实现部分运行 playbook 的机制,将在后面的章节讨论),需确保给 pre_tasks 以及 post_tasks 也同样应用 tags,并且将它们一并传递。特别是当 pre_tasks 和 post_tasks 被用来监视 “停止窗口控制” 或者 “负载均衡” 时要确保这样做。
n 角色默认变量(Role Default Variables)
角色默认变量允许你为 included roles 或者 dependent roles(见下) 设置默认变量。要创建默认变量,只需在 roles 目录下添加 defaults/main.yml 文件。这些变量在所有可用变量中拥有最低优先级,可能被其他地方定义的变量(包括 inventory 中的变量)所覆盖。
n 角色依赖(Role Dependencies)
“角色依赖” 使你可以自动地将其他 roles 拉取到现在使用的 role 中。”角色依赖” 保存在 roles 目录下的 meta/main.yml 文件中。这个文件应包含一列 roles 和 为之指定的参数,下面是在 roles/myapp/meta/main.yml 文件中的示例:
---
dependencies:
  - { role: common, some_parameter: 3 }
  - { role: apache, port: 80 }
  - { role: postgres, dbname: blarg, other_parameter: 12 }
“角色依赖” 可以通过绝对路径指定,如同顶级角色的设置:
---
dependencies:
   - { role: '/path/to/common/roles/foo', x: 1 }
“角色依赖” 也可以通过源码控制仓库或者 tar 文件指定,使用逗号分隔:路径、一个可选的版本(tag, commit, branch 等等)、一个可选友好角色名(尝试从源码仓库名或者归档文件名中派生出角色名):
---
dependencies:
  - { role: 'git+http://git.example.com/repos/role-foo,v1.1,foo' }
  - { role: '/path/to/tar/file.tgz,,friendly-name' }
“角色依赖” 总是在 role (包含”角色依赖”的role)之前执行,并且是递归地执行。默认情况下,作为 “角色依赖” 被添加的 role 只能被添加一次,如果另一个 role 将一个相同的角色列为 “角色依赖” 的对象,它不会被重复执行。但这种默认的行为可被修改,通过添加 allow_duplicates: yes 到 meta/main.yml 文件中。 比如,一个 role 名为 ‘car’,它可以添加名为 ‘wheel’ 的 role 到它的 “角色依赖” 中:
---
dependencies:
- { role: wheel, n: 1 }
- { role: wheel, n: 2 }
- { role: wheel, n: 3 }
- { role: wheel, n: 4 }
wheel 角色的 meta/main.yml 文件包含如下内容:
---
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)
...
car
n Ansible Galaxy
Ansible Galaxy 是一个自由网站,网站提供所有类型的由社区开发的 roles,这对于实现你的自动化项目是一个很好的参考。网站提供这些 roles 的排名、查找以及下载。
Ansible 1.4.2 及以后的版本已包含 Ansible Galaxy 的下载客户端 ‘ansible-galaxy’
15. Variables
n 合法的变量名
在使用变量之前最好先知道什么是合法的变量名. 变量名可以为字母,数字以及下划线.变量始终应该以字母开头. “foo_port”是个合法的变量名.”foo5”也是. “foo-port”, “foo port”, “foo.port” 和 “12”则不是合法的变量名.
n Inventory中定义变量
我们已经在其它文档中覆盖了大量关于使用变量的场景,所以这里没多少新的知识点,权当加深记忆.
通常你想基于一个机器位于哪个群组而设置变量.比如,位于波士顿的很多机器会使用 ‘boston.ntp.example.com’ 作为NTP服务器.
请看 Inventory文件 文档来学习在inventory中使用多种方式来定义变量.
n playbook中定义变量
playbook中,可以直接定义变量,如下所示:
- hosts: webservers
  vars:
    http_port: 80
这种所见即所得的方式非常好.
n 在文件和role中定义变量
事实上在其它地方我们也讲过这点了. 正如在 Playbook Roles and Include Statements 描述的一样,变量也可以通过文件包含在playbook中,该变量可以作为或者不作为“Ansible Role”的一部分.使用role是首选,因为它提供了一个很好的组织体系.
n 使用变量: 关于Jinja2
我们已经知道很多关于定义变量的知识,那么你知道如何使用它们吗?
Ansible允许你使用Jinja2模板系统在playbook中引用变量.借助Jinja你能做很多复杂的操作,首先你要学习基本使用. 例如,在简单的模板中你可以这样做:
My amp goes to {{ max_amp_value }}
这就是变量替换最基本的形式. 你也可以在playbook中直接这样用,你偶尔想这样做:
template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg
在上述的例子中,我们使用变量来决定文件放置在哪里. 在模板中你自动会获取在主机范围之内的所有变量的访问权.事实上更多,你可以读取其它主机的变量.我们将演示如何做.
注:在模板中Jinja2可以用循环和条件语句,而在playbook中则不行.Ansible playbook是纯粹的机器解析的YAML.这是一个非常重要的功能,这意味着根据文件可以生成代码,或者其它系统工具能够读取Ansible文件.虽然并不是所有人都需要这个功能,但我们不能封锁可能性.
n Jinja2过滤器
Note:这并不是常用的特性.只在合适的时候使用它们,这是一个附加知识点.
Jinja2中的过滤器可以把一个模板表达式转换为另一个.Jinja2附带了很多这样的功能.请参见Jinja2官方模板文档中的 builtin filters.
另外,Ansible还支持其它特性.请看 playbooks_filters 文档中关于一系列可用的过滤器及示例.
n YAML陷阱
YAML语法要求如果值以{{ foo }}开头的话我们需要将整行用双引号包起来.这是为了确认你不是想声明一个YAML字典.该知识点在 YAML 语法 页面有所讲述.
这样是不行的:
- hosts: app_servers
  vars:
      app_path: {{ base_path }}/22
你应该这么做:
- hosts: app_servers
  vars:
       app_path: "{{ base_path }}/22"
n 使用Facts获取的信息
还有其它地方可以获取变量,这些变量是自动发现的,而不是用户自己设置的.
Facts通过访问远程系统获取相应的信息. 一个例子就是远程主机的IP地址或者操作系统是什么. 使用以下命令可以查看哪些信息是可用的:
ansible hostname -m setup
这会返回巨量的变量数据,比如对于Ubutu 12.04系统,Ansible 1.4获取的信息显示如下:
"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",
......
可以在playbook中这样引用以上例子中第一个硬盘的模型:
{{ ansible_devices.sda.model }}
同样,作为系统报告的主机名如以下所示:
{{ ansible_nodename }}
不合格的主机名显示了句号(.)之前的字符串:
{{ ansible_hostname }}
在模板和条件判断(请看 playbook_conditionals )中会经常使用Facts.
还可以使用Facts根据特定的条件动态创建主机群组,请查看 模块相关 文档中的 ‘group_by’ 小节获取详细内容.以及参见 条件选择 章节讨论的广义条件语句部分.
n 关闭Facts
如果你不需要使用你主机的任何fact数据,你已经知道了你系统的一切,那么你可以关闭fact数据的获取.这有利于增强Ansilbe面对大量系统的push模块,或者你在实验性平台中使用Ansible.在任何playbook中可以这样做:
- hosts: whatever
  gather_facts: no
n 本地Facts(Facts.d)
正如在playbook章节讨论的一样,Ansible facts主要用于获取远程系统的数据,从而可以在playbook中作为变量使用.
通常facts中的数据是由Ansible中的 ‘setup’模块自动发现的.用户也可以自定义facts模块,在API文档中有说明.然而,如果不借助于fact模块,而是通过一个简单的方式为Ansible变量提供系统或用户数据?
比如,你想用户能够控制受他们管理的系统的一些切面,那么应该怎么做? “Facts.d”是这样的一种机制.
注:可能 “局部facts”有点用词不当,它与 “中心供应的用户值”相对应,为”局部供应的用户值”,或者facts是 “局部动态测定的值”.
如果远程受管理的机器有一个 “/etc/ansible/facts.d” 目录,那么在该目录中任何以 ”.fact”结尾的文件都可以在Ansible中提供局部facts.这些文件可以是JSON,INI或者任何可以返回JSON的可执行文件.
例如建设有一个 /etc/ansible/facts.d/perferences.fact文件:
[general]
asdf=1
bar=2
这将产生一个名为 “general” 的哈希表fact,里面成员有 ‘asdf’ 和 ‘bar’. 可以这样验证:
ansible <hostname> -m setup -a "filter=ansible_local"
然后你会看到有以下fact被添加:
"ansible_local": {
        "preferences": {
            "general": {
                "asdf" : "1",
                "bar"  : "2"
            }
        }
}
而且也可以在template或palybook中访问该数据:
{{ ansible_local.preferences.general.asdf }}
本地命名空间放置其它用户提供的fact或者playbook中定义的变量覆盖系统facts值.
如果你有个一个playook,它复制了一个自定义的fact,然后运行它,请显式调用来重新运行setup模块,这样可以让我们在该playbook中使用这些fact.否则,在下一个play中才能获取这些自定义的fact信息.这里有一个示例:
- hosts: webservers
  tasks:
    - name: create directory for ansible custom facts
      file: state=directory recurse=yes path=/etc/ansible/facts.d
    - name: install custom impi fact
      copy: src=ipmi.fact dest=/etc/ansible/facts.d
    - name: re-read facts after adding custom fact
      setup: filter=ansible_local
然而在该模式中你也可以编写一个fact模块,这只不过是多了一个选项.
n Fact缓存
正如该文档中其它地方所示,从一个服务器引用另一个服务器的变量是可行的.比如:
{{ hostvars['asdf.example.com']['ansible_os_family'] }}
如果禁用 “Fact Caching”,为了实现以上功能,Ansible在当前play之前已经与 ‘asdf.example.com’ 通讯过,或者在playbook有其它优先的play.这是ansible的默认配置.
为了避免这些,Ansible 1.8允许在playbook运行期间保存facts.但该功能需要手动开启.这有什么用处那?
想象一下,如果我们有一个非常大的基础设施,里面有数千个主机.Fact缓存可以配置在夜间运行,但小型服务器集群可以配置fact随时运行,或者在白天定期运行.即使开启了fact缓存,也不需要访问所有服务器来引用它们的变量和信息.
使用fact缓存可以跨群组访问变量,即使群组间在当前/user/bin/ansible-playbook执行中并没有通讯过.
为了启用fact缓存,在大多数plays中你可以修改 ‘gathering’ 设置为 ‘smart’ 或者 ‘explicit’,也可以设置 ‘gather_facts’ 为False.
当前,Ansible可以使用两种持久的缓存插件: redis和jsonfile.
可以在ansible.cfg中配置fact缓存使用redis:
[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 86400
# seconds
请执行适当的系统命令来启动和运行redis:
yum install redis
service redis start
pip install redis
请注意可以使用pip来安装Python redis库,在EPEL中的包版本对Ansible来说太旧了. 在当前Ansible版本中,该功能还处于试用状态,Redis插件还不支持端口或密码配置,以后会改善这点. 在ansible.cfg中使用以下代码来配置fact缓存使用jsonfile:
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /path/to/cachedir
fact_caching_timeout = 86400
# seconds
fact_caching_connection 是一个放置在可读目录(如果目录不存在,ansible会试图创建它)中的本地文件路径.
n 注册变量
变量的另一个主要用途是在运行命令时,把命令结果存储到一个变量中.不同模块的执行结果是不同的.运行playbook时使用-v选项可以看到可能的结果值. 在ansible执行任务的结果值可以保存在变量中,以便稍后使用它.在 条件选择 章节有一些示例.
这里有一个语法示例,在上面文档中也有所提及:
- hosts: web_servers
  tasks:
     - shell: /usr/bin/foo
       register: foo_result
       ignore_errors: True
     - shell: /usr/bin/bar
       when: foo_result.rc == 5
在当前主机接下来playbook运行过程中注册的变量是有效地.这与Ansile中的 “facts” 生命周期一样. 实际上注册变量和facts很相似.
n 访问复杂变量数据
在该文档中我们已经讨论了一些与facts有关的高级特性.
有些提供的facts,比如网络信息等,是一个嵌套的数据结构.访问它们使用简单的 {{ foo }} 语法并不够用,当仍然很容易.如下所示:
{{ ansible_eth0["ipv4"]["address"] }}
或者这样写:
{{ ansible_eth0.ipv4.address }}
相似的,以下代码展示了我们如何访问数组的第一个元素:
{{ foo[0] }}
n 访问其它主机的信息
Ansible会自动提供给你一些变量,即使你并没有定义过它们.这些变量中重要的有 ‘hostvars’,’group_names’,和 ‘groups’.由于这些变量名是预留的,所以用户不应当覆盖它们. ‘environmen’ 也是预留的. hostvars可以让你访问其它主机的变量,包括哪些主机中获取到的facts.如果你还没有在当前playbook或者一组playbook的任何play中访问那个主机,那么你可以获取变量,但无法看到facts值. 如果数据库服务器想使用另一个节点的某个 ‘fact’ 值,或者赋值给该节点的一个inventory变量.可以在一个模板中甚至命令行中轻松实现:
{{ hostvars['test.example.com']['ansible_distribution'] }}
另外, group_names 是当前主机所在所有群组的列表(数组).所以可以使用Jinja2语法在模板中根据该主机所在群组关系(或角色)来产生变化:
{% if 'webserver' in group_names %}
   # some part of a configuration file that only applies to webservers
{% endif %}
groups 是inventory中所有群组(主机)的列表.可用于枚举群组中的所有主机.例如:
{% for host in groups['app_servers'] %}
   # something that applies to all app servers.
{% endfor %}
一个经常使用的范式是找出该群组中的所有IP地址:
{% for host in groups['app_servers'] %}
   {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
比如,一个前端代理服务器需要指向所有的应用服务器,在服务器间设置正确的防火墙规则等.你需要确保所有主机的facts在使用前都已被获取到,例如运行一个play来检查这些facts是否已经被缓存起来(fact缓存是Ansible 1.8中的新特性).
另外, inventory_hostname 是Ansible inventory主机文件中配置的主机名称.由于其它一些神秘原因你不想使用自发现的主机名 ansible_hostname 时,你可以使用 inventory_hostname .如果主机的FQDN很长,那么*inventory_hostname_short*则会只包含域名第一个分号之前的部分,而舍弃其它部分.
play_hosts 是在当前play范围中可用的一组主机名.比如可以为多个主机填写模板,以便将这些主机注入负载均衡器规则.
n 变量文件分割
playbook置于源代码管理之下是个很好的注意,当你可能会想把playbook源码公开之余还想保持某些重要的变量私有.有时你也想把某些信息放置在不同的文件中,远离主playbook文件.
你可以使用外部的变量文件来实现:
---

- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - /vars/external_vars.yml
  tasks:
  - name: this is just a placeholder
    command: /bin/echo foo

n 命令行中传递变量
除了`vars_prompt`和`vars_files`也可以通过Ansible命令行发送变量.如果你想编写一个通用的发布playbook时则特别有用,你可以传递应用的版本以便部署:
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"
其它场景中也很有用,比如为playbook设置主机群组或用户.
Example:
---
- hosts: '{{ hosts }}'
  remote_user: '{{ user }}'
  tasks:
     - ...
ansible-playbook release.yml --extra-vars "hosts=vipers user=starbuck"
n 变量的优先级
Ø * extra vars (在命令行中使用 -e)优先级最高
Ø * 然后是在inventory中定义的连接变量(比如ansible_ssh_user)
Ø * 接着是大多数的其它变量(命令行转换,play中的变量,included的变量,role中的变量等)
Ø * 然后是在inventory定义的其它变量
Ø * 然后是由系统发现的facts
Ø * 然后是 "role默认变量", 这个是最默认的值,很容易丧失优先权
16. 条件选择
n When 语句
有时候用户有可能需要某一个主机越过某一个特定的步骤.这个过程就可以简单的像在某一个特定版本的系统上 少装了一个包一样或者像在一个满了的文件系统上执行清理操作一样. 这些操作在Ansible上,若使用`when`语句都异常简单.When语句包含Jinja2表达式(参见:doc:playbooks_variables). 实际上真的很简单:
tasks:
  - name: "shutdown Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_os_family == "Debian"
一系列的Jinja2 “过滤器” 也可以在when语句中使用, 但有些是Ansible中独有的. 比如我们想忽略某一错误,通过执行成功与否来做决定,我们可以像这样:
tasks:
  - command: /bin/false
    register: result
    ignore_errors: True
  - command: /bin/something
    when: result|failed
  - command: /bin/something_else
    when: result|success
  - command: /bin/still/something_else
    when: result|skipped
我知道,在这里讨论’register’语句命令,有点过于超前,我们将会在这议长靠后一点的地方接触这个.
友情提示,如果想查看哪些事件在某个特定系统中时允许的,可以执行以下命令:
ansible hostname.example.com -m setup
提示: 有些时候你得到一个返回参数的值是一个字符串,并且你还想使用数学操作来比较它,那么你可以执行一下操作:
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
如果一个变量不存在,你可以使用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 not defined
这个机制在选择引入变量文件时有时候特别有用,详情如下.
note当同时使用`when`he`with_items` (详见:doc:playbooks_loops), note`when`语句对于不同项目将会单独处理.这个源于原初设计:
tasks:
    - command: echo {{ item }}
      with_items: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5
n 加载客户事件
加载客户自己的事件,事实上也很简单,将在:doc:developing_modules 详细介绍.只要调用客户自己的事件,进而把所有的模块放在任务列表顶端, 变量的返回值今后就可以访问了:
tasks:
    - name: gather site specific fact data
      action: site_facts
    - command: /usr/bin/thingy
      when: my_custom_fact_just_retrieved_from_the_remote_system == '1234'
n roles 和 includes 上面应用’when’语句
note,如果你的很多任务都共享同样的条件语句的话,可以在选择语句后面添加inlcudes语句,参见下面事例. 这个特性并不适用于playbook的inclues,只有task 的 includes适用.所有的task都会被检验, 选择会应用到所有的task上面:
- include: tasks/sometasks.yml
  when: "'reticulating splines' in output"
或者应用于role:
- hosts: webservers
  roles:
     - { role: debian_stock_config, when: ansible_os_family == 'Debian' }
在系统中使用这个方法但是并不能匹配某些标准时,你会发现在Ansible中,有很多默认’skipped’的结果. 详情参见:doc:modules 文档中的 ‘group_by’ 模块, 你会找到更加赏心悦目的方法来解决这个问题.
n 条件导入
基于某个特定标准,又是你也许在一个playbook中你想以不同的方式做同一件事. 在不同平台或操作系统上使用痛一个playbook就是一个很好的例子.
举个例子,名字叫做Apache的包,在CentOS 和 Debian系统中也许不同, 但是这个问题可以一些简单的语法就可以被Ansible Playbook解决:
---
- 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
‘ansible_os_family’ 已经被导入到为vars_files定义的文件名列表中了.
提醒一下,很多的不同的YAML文件只是包含键和值:
---
# for vars/CentOS.yml
apache: httpd
somethingelse: 42
n 基于变量选择文件和模版
有时候,你想要复制一个配置文件,或者一个基于参数的模版. 下面的结构选载选第一个宿主给予的变量文件,这些可以比把很多if选择放在模版里要简单的多. 下面的例子展示怎样根据不同的系统,例如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/
n 注册变量
经常在playbook中,存储某个命令的结果在变量中,以备日后访问是很有用的. 这样使用命令模块可以在许多方面除去写站(site)特异事件,据哥例子 你可以检测某一个特定程序是否存在
这个 ‘register’ 关键词决定了把结果存储在哪个变量中.结果参数可以用在模版中,动作条目,或者 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’是可以访问. 这个注册了以后的结果,如果像上面展示的,可以转化为一个list(或者已经是一个list),就可以在任务中的”with_items”中使用. “stdout_lines”在对象中已经可以访问了,当然如果你喜欢也可以调用 “home_dirs.stdout.split()” , 也可以用其它字段切割:
- 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()
17. 循环
n 标准循环
为了保持简洁,重复的任务可以用以下简写的方式:
- name: add several users
  user: name={{ item }} state=present groups=wheel
  with_items:
     - testuser1
     - testuser2
如果你在变量文件中或者 ‘vars’ 区域定义了一组YAML列表,你也可以这样做:
with_items: "{{somelist}}"
以上写法与下面是完全等同的:
- name: add user testuser1
  user: name=testuser1 state=present groups=wheel
- name: add user testuser2
  user: name=testuser2 state=present groups=wheel
yum和apt模块中使用with_items执行时会有较少的包管理事务.
note使用 ‘with_items’ 用于迭代的条目类型不仅仅支持简单的字符串列表.如果你有一个哈希列表,那么你可以用以下方式来引用子项:
- name: add several users
  user: name={{ item.name }} state=present groups={{ item.groups }}
  with_items:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }
note如果同时使用 when 和 with_items (或其它循环声明),`when`声明会为每个条目单独执行.请参见 the_when_statement 示例.
n 嵌套循环
循环也可以嵌套:
- 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' ]
和以上介绍的’with_items’一样,你也可以使用预定义变量.:
- name: here, 'users' contains the above list of employees
  mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
  with_nested:
    - "{{users}}"
    - [ 'clientdb', 'employeedb', 'providerdb' ]
n 对哈希表使用循环
假如你有以下变量:
---
users:
  alice:
    name: Alice Appleworth
    telephone: 123-456-7890
  bob:
    name: Bob Bananarama
    telephone: 987-654-3210
你想打印出每个用户的名称和电话号码.你可以使用 with_dict 来循环哈希表中的元素:
tasks:
  - name: Print phone records
    debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
    with_dict: "{{users}}"
n 对文件列表使用循环
with_fileglob 可以以非递归的方式来模式匹配单个目录中的文件.如下面所示:
---
- hosts: all
  tasks:
    # first ensure our target directory exists
    - file: dest=/etc/fooapp state=directory
    # copy each file over that matches the given pattern
    - copy: src={{ item }} dest=/etc/fooapp/ owner=root mode=600
      with_fileglob:
        - /playbooks/files/fooapp/*
n 对并行数据集使用循环
假设你通过某种方式加载了以下变量数据:
---
alpha: [ 'a', 'b', 'c', 'd' ]
numbers:  [ 1, 2, 3, 4 ]
如果你想得到’(a, 1)’和’(b, 2)’之类的集合.可以使用’with_together’:
tasks:
    - debug: msg="{{ item.0 }} and {{ item.1 }}"
      with_together:
        - "{{alpha}}"
        - "{{numbers}}"
n 对子元素使用循环
假设你想对一组用户做一些动作,比如创建这些用户,并且允许它们使用一组SSH key来登录.
如何实现那? 先假设你有按以下方式定义的数据,可以通过”vars_files”或”group_vars/all”文件加载:
---
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"
那么可以这样实现:
- user: name={{ item.name }} state=present generate_ssh_key=yes
  with_items: "{{users}}"

- authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'"
  with_subelements:
     - users
     - authorized




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

首页

手机版|关于自己|运维技术分享平台

GMT+8, 2024-12-22 20:43 , Processed in 0.084105 second(s), 17 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc. Template By 【未来科技】【 www.wekei.cn 】

快速回复 返回顶部 返回列表