安装
[root@node3 ~]# yum -y install epel-release ansible
配置公钥登录主机
[root@node3 ~]# ssh-keygen -t rsa -P ""
[root@node3 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@node1.eagleslab.com
[root@node3 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@node2.eagleslab.com
Inventory文件
Ansible 可同时操作属于一个组的多台主机,组和主机之间的关系通过 inventory 文件配置. 默认的文件路径为 /etc/ansible/hosts
主机与组
/etc/ansible/hosts 文件的格式与windows的ini配置文件类似:
[root@node3 ~]# vim /etc/ansible/hosts
[webserver]
node1.eagleslab.com
node2.eagleslab.com
方括号[]中是组名,用于对系统进行分类,便于对不同系统进行个别的管理.
案例
端口号不一样
badwolf.example.com:5309
设置别名
jumper ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50
一组相似的 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
主机变量
分配变量给主机很容易做到,这些变量定义后可在 playbooks 中使用
[atlanta]
host1 http_port=80 maxRequestsPerChild=808
host2 http_port=303 maxRequestsPerChild=909
组的变量
[atlanta]
host1
host2
[atlanta:vars]
ntp_server=ntp.atlanta.example.com
proxy=proxy.atlanta.example.com
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
Patterns
在Ansible中,Patterns 是指我们怎样确定由哪一台主机来管理. 意思就是与哪台主机进行交互. 但是在:doc:playbooks 中它指的是对应主机应用特定的配置或执行特定进程.
webservers:dbservers:&staging:!phoenix
上面这个例子表示“‘webservers’ 和 ‘dbservers’ 两个组中隶属于 ‘staging’ 组并且不属于 ‘phoenix’ 组的机器才执行命令”
Ad-Hoc Commands
如果我们敲入一些命令去比较快的完成一些事情,而不需要将这些执行的命令特别保存下来, 这样的命令就叫做 ad-hoc 命令.
这里ssh-agent会fork出10个子进程(bash),以并行的方式执行reboot命令.如前所说“每次重启10个” 即是以这种方式实现
[root@node3 ~]# ansible all -a "/sbin/reboot" -f 10
在执行 /usr/bin/ansible 时,默认是以当前用户的身份去执行这个命令.如果想以指定的用户执行 /usr/bin/ansible, 请添加 “-u username”选项,
[root@node3 ~]# ansible all -a "/usr/bin/whoami" -u root
如果想通过 sudo 去执行命令
[root@node3 ~]# ansible all -a "/usr/bin/whoami" -u root --sudo [--ask-sudo-pass]
如果你不是以 passwordless 的模式执行 sudo,应加上 —ask-sudo-pass (-K)选项,加上之后会提示你输入 密码.使用 passwordless 模式的 sudo, 更容易实现自动化,但不要求一定要使用 passwordless sudo.
ansible有许多模块,默认是 ‘command’,也就是命令模块,我们可以通过 -m 选项来指定不同的模块.在前面所示的例子中, 因为我们是要在 ALL 组下的服务器中执行 reboot 命令,所以就不需要显示的用这个选项指定 ‘command’ 模块,使用 默认设定就OK了.一会在其他例子中,我们会使用 -m 运行其他的模块。
command 模块不支持 shell 变量,也不支持管道等 shell 相关的东西.如果你想使用 shell相关的这些东西, 请使用’shell’ 模块
[root@node3 ~]# ansible all -m shell -a 'echo $HOSTNAME'
File Transfer
这是 /usr/bin/ansible 的另一种用法.Ansible 能够以并行的方式同时 SCP 大量的文件到多台机器
[root@node3 tmp]# ansible node1.eagleslab.com -m copy -a "src=/etc/hosts dest=/tmp/hosts"
将本机的 /etc/hosts
复制到远程主机的 /tmp/hosts
使用 file 模块可以做到修改文件的属主和权限,(在这里可替换为 copy 模块,是等效的):
[root@node3 ~]# ansible all -m file -a "dest=/srv/foo/a.txt mode=600"
[root@node3 ~]# ansible all -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"
Managing Packages
Ansible 提供对 yum 和 apt 的支持.这里是关于 yum 的示例.
确认一个软件包已经安装,但不去升级它
[root@node3 tmp]# ansible all -m yum -a "name=vi state=present"
确认一个软件包还没有安装,已安装的就卸载
[root@node3 tmp]# ansible all -m yum -a "name=vi state=absent"
对于不同平台的软件包管理工具,Ansible都有对应的模块.如果没有,你也可以使用 command 模块去安装软件
Users and Groups
使用 ‘user’ 模块可以方便的创建账户,删除账户,或是管理现有的账户:
[root@node3 ~]# ansible all -m user -a "name=testuser password=123456"
[root@node3 ~]# ansible all -m user -a "name=testuser state=absent"
Managing Services
确认某个服务在所有的webservers上都已经启动
[root@node3 ~]# ansible all -m yum -a "name=httpd state=latest"
[root@node3 ~]# ansible all -m service -a "name=httpd state=started"
在所有的webservers上重启某个服务
[root@node3 ~]# ansible all -m service -a "name=httpd state=restarted"
确认某个服务已经停止
[root@node3 ~]# ansible all -m service -a "name=httpd state=stopped"
Time Limited Background Operations
需要长时间运行的命令可以放到后台去,在命令开始运行后我们也可以检查运行的状态.如果运行命令后,不想获取返回的信息, 可执行如下命令
[root@node3 ~]# ansible all -B 1800 -P 5 -a "/usr/bin/long_running_operation --do-stuff"
其中 -B 1800
表示最多运行30分钟, -P 5
表示每隔5秒获取一次状态信息
Gathering Facts
在 playbooks 中有对于 Facts 做描述,它代表的是一个系统中已发现的变量.
[root@node3 ~]# ansible all -m setup | less
Playbooks
Playbooks 是 Ansible的配置,部署,编排语言.他们可以被描述为一个需要希望远程主机执行命令的方案,或者一组IT程序运行的命令集合.
Playbooks 介绍
Playbooks 的格式是YAML,语法做到最小化,意在避免 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基础
主机与用户
你可以为 playbook 中的每一个 play,个别地选择操作的目标机器是哪些,以哪个用户身份去完成要执行的步骤(called tasks).
hosts 行的内容是一个或多个组或主机的 patterns,以逗号为分隔符
remote_user 就是账户名:
---
- hosts: webservers
remote_user: root
在每一个 task 中,可以定义自己的远程用户:
---
- hosts: webservers
remote_user: root
tasks:
- name: test connection
ping:
remote_user: yourname
支持从 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
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 也很安全.
每一个 task 必须有一个名称 name,这样在运行 playbook 时,从其输出的任务执行信息中可以很好的辨别出是属于哪一个 task 的. 如果没有定义 name,‘action’ 的值将会用作输出信息中标记特定的 task.
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 }}
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 会按照声明的顺序执行
如果你想立即执行所有的 handler 命令,在1.2及以后的版本,你可以这样做
tasks:
- shell: some tasks go here
- meta: flush_handlers
- shell: some other tasks
执行一个 playbook
既然现在你已经学习了 playbook 的语法,那要如何运行一个 playbook 呢?这很简单,这里的示例是并行的运行 playbook,并行的级别 是10(译者注:是10个并发的进程?)
ansible-playbook playbook.yml -f 10
在执行一个 playbook 之前,想看看这个 playbook 的执行会影响到哪些 hosts
[root@localhost ~]# ansible-playbook playbook.yml --list-hosts
playbook: playbook.yml
play #1 (testserv): testserv TAGS: []
pattern: [u'testserv']
hosts (2):
192.168.37.132
192.168.37.131
Playbook 角色(Roles) 和 Include 语句
假如你希望在多个 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 支持另一种传递变量到 include files 的语法,这种语法支持结构化的变量:
tasks:
- include: wordpress.yml
vars:
wp_user: timmy
some_list_variable:
- alpha
- beta
- gamma
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
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/ 中的文件,不需要指明文件的路径。
如果 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'