安装

  1. [root@node3 ~]# yum -y install epel-release ansible

配置公钥登录主机

  1. [root@node3 ~]# ssh-keygen -t rsa -P ""
  2. [root@node3 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@node1.eagleslab.com
  3. [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配置文件类似:

  1. [root@node3 ~]# vim /etc/ansible/hosts
  2. [webserver]
  3. node1.eagleslab.com
  4. node2.eagleslab.com

方括号[]中是组名,用于对系统进行分类,便于对不同系统进行个别的管理.
案例
端口号不一样

  1. badwolf.example.com:5309

设置别名

  1. jumper ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50

一组相似的 hostname , 可简写如下

  1. [webservers]
  2. www[01:50].example.com

数字的简写模式中,01:50 也可写为 1:50,意义相同.你还可以定义字母范围的简写模式

  1. [databases]
  2. db-[a:f].example.com

对于每一个 host,你还可以选择连接类型和连接用户名:

  1. [targets]
  2. localhost ansible_connection=local
  3. other1.example.com ansible_connection=ssh ansible_ssh_user=mpdehaan
  4. other2.example.com ansible_connection=ssh ansible_ssh_user=mdehaan

主机变量

分配变量给主机很容易做到,这些变量定义后可在 playbooks 中使用

  1. [atlanta]
  2. host1 http_port=80 maxRequestsPerChild=808
  3. host2 http_port=303 maxRequestsPerChild=909

组的变量

  1. [atlanta]
  2. host1
  3. host2
  4. [atlanta:vars]
  5. ntp_server=ntp.atlanta.example.com
  6. proxy=proxy.atlanta.example.com

Inventory 参数的说明

通过设置下面的参数,可以控制 ansible 与远程主机的交互方式

  1. ansible_ssh_host
  2. 将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.
  3. ansible_ssh_port
  4. ssh端口号.如果不是默认的端口号,通过此变量设置.
  5. ansible_ssh_user
  6. 默认的 ssh 用户名
  7. ansible_ssh_pass
  8. ssh 密码(这种方式并不安全,我们强烈建议使用 --ask-pass SSH 密钥)
  9. ansible_sudo_pass
  10. sudo 密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass)
  11. ansible_sudo_exe (new in version 1.8)
  12. sudo 命令路径(适用于1.8及以上版本)
  13. ansible_connection
  14. 与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行.
  15. ansible_ssh_private_key_file
  16. ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况.
  17. ansible_shell_type
  18. 目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 'fish'.
  19. ansible_python_interpreter
  20. 目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python
  21. 不是 2.X 版本的 Python.我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26).
  22. ansible_python_interpreter 的工作方式相同,可设定如 ruby perl 的路径....

一个主机文件的例子:

  1. some_host ansible_ssh_port=2222 ansible_ssh_user=manager
  2. aws_host ansible_ssh_private_key_file=/home/example/.ssh/aws.pem
  3. freebsd_host ansible_python_interpreter=/usr/local/bin/python
  4. ruby_module_host ansible_ruby_interpreter=/usr/bin/ruby.1.9.3

Patterns

在Ansible中,Patterns 是指我们怎样确定由哪一台主机来管理. 意思就是与哪台主机进行交互. 但是在:doc:playbooks 中它指的是对应主机应用特定的配置或执行特定进程.

  1. webservers:dbservers:&staging:!phoenix

上面这个例子表示“‘webservers’ 和 ‘dbservers’ 两个组中隶属于 ‘staging’ 组并且不属于 ‘phoenix’ 组的机器才执行命令”

Ad-Hoc Commands

如果我们敲入一些命令去比较快的完成一些事情,而不需要将这些执行的命令特别保存下来, 这样的命令就叫做 ad-hoc 命令.
这里ssh-agent会fork出10个子进程(bash),以并行的方式执行reboot命令.如前所说“每次重启10个” 即是以这种方式实现

  1. [root@node3 ~]# ansible all -a "/sbin/reboot" -f 10

在执行 /usr/bin/ansible 时,默认是以当前用户的身份去执行这个命令.如果想以指定的用户执行 /usr/bin/ansible, 请添加 “-u username”选项,

  1. [root@node3 ~]# ansible all -a "/usr/bin/whoami" -u root

如果想通过 sudo 去执行命令

  1. [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’ 模块

  1. [root@node3 ~]# ansible all -m shell -a 'echo $HOSTNAME'

File Transfer

这是 /usr/bin/ansible 的另一种用法.Ansible 能够以并行的方式同时 SCP 大量的文件到多台机器

  1. [root@node3 tmp]# ansible node1.eagleslab.com -m copy -a "src=/etc/hosts dest=/tmp/hosts"

将本机的 /etc/hosts 复制到远程主机的 /tmp/hosts

使用 file 模块可以做到修改文件的属主和权限,(在这里可替换为 copy 模块,是等效的):

  1. [root@node3 ~]# ansible all -m file -a "dest=/srv/foo/a.txt mode=600"
  2. [root@node3 ~]# ansible all -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"

使用 file 模块也可以创建目录,与执行 mkdir -p 效果类似

  1. $ ansible webservers -m file -a "dest=/path/to/c mode=755 owner=mdehaan group=mdehaan state=directory"

删除目录(递归的删除)和删除文件

  1. $ ansible webservers -m file -a "dest=/path/to/c state=absent"

Managing Packages

Ansible 提供对 yum 和 apt 的支持.这里是关于 yum 的示例.
确认一个软件包已经安装,但不去升级它

  1. [root@node3 tmp]# ansible all -m yum -a "name=vi state=present"

确认一个软件包还没有安装,已安装的就卸载

  1. [root@node3 tmp]# ansible all -m yum -a "name=vi state=absent"

对于不同平台的软件包管理工具,Ansible都有对应的模块.如果没有,你也可以使用 command 模块去安装软件

Users and Groups

使用 ‘user’ 模块可以方便的创建账户,删除账户,或是管理现有的账户:

  1. [root@node3 ~]# ansible all -m user -a "name=testuser password=123456"
  2. [root@node3 ~]# ansible all -m user -a "name=testuser state=absent"

Managing Services

确认某个服务在所有的webservers上都已经启动

  1. [root@node3 ~]# ansible all -m yum -a "name=httpd state=latest"
  2. [root@node3 ~]# ansible all -m service -a "name=httpd state=started"

在所有的webservers上重启某个服务

  1. [root@node3 ~]# ansible all -m service -a "name=httpd state=restarted"

确认某个服务已经停止

  1. [root@node3 ~]# ansible all -m service -a "name=httpd state=stopped"

Time Limited Background Operations

需要长时间运行的命令可以放到后台去,在命令开始运行后我们也可以检查运行的状态.如果运行命令后,不想获取返回的信息, 可执行如下命令

  1. [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 做描述,它代表的是一个系统中已发现的变量.

  1. [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:

  1. ---
  2. - hosts: webservers
  3. vars:
  4. http_port: 80
  5. max_clients: 200
  6. remote_user: root
  7. tasks:
  8. - name: ensure apache is at the latest version
  9. yum: pkg=httpd state=latest
  10. - name: write the apache config file
  11. template: src=/srv/httpd.j2 dest=/etc/httpd.conf
  12. notify:
  13. - restart apache
  14. - name: ensure apache is running
  15. service: name=httpd state=started
  16. handlers:
  17. - name: restart apache
  18. service: name=httpd state=restarted

playbook基础

主机与用户

你可以为 playbook 中的每一个 play,个别地选择操作的目标机器是哪些,以哪个用户身份去完成要执行的步骤(called tasks).
hosts 行的内容是一个或多个组或主机的 patterns,以逗号为分隔符
remote_user 就是账户名:

  1. ---
  2. - hosts: webservers
  3. remote_user: root

在每一个 task 中,可以定义自己的远程用户:

  1. ---
  2. - hosts: webservers
  3. remote_user: root
  4. tasks:
  5. - name: test connection
  6. ping:
  7. remote_user: yourname

支持从 sudo 执行命令:

  1. ---
  2. - hosts: webservers
  3. remote_user: yourname
  4. sudo: yes

你可以仅在一个 task 中,使用 sudo 执行命令,而不是在整个 play 中使用 sudo:

  1. ---
  2. - hosts: webservers
  3. remote_user: yourname
  4. tasks:
  5. - service: name=nginx state=started
  6. sudo: yes

可以登陆后,sudo 到不同的用户身份,而不是使用 root

  1. ---
  2. - hosts: webservers
  3. remote_user: yourname
  4. sudo: yes
  5. 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.

  1. tasks:
  2. - name: make sure apache is running
  3. service: name=httpd state=running

比较特别的两个 modudle 是 command 和 shell ,它们不使用 key=value 格式的参数

  1. tasks:
  2. - name: disable selinux
  3. command: /sbin/setenforce 0

使用 command module 和 shell module 时,我们需要关心返回码信息,如果有一条命令,它的成功执行的返回码不是0

  1. tasks:
  2. - name: run this command and ignore the result
  3. shell: /usr/bin/somecommand || /bin/true

或者是这样

  1. tasks:
  2. - name: run this command and ignore the result
  3. shell: /usr/bin/somecommand
  4. ignore_errors: True

如果 action 行看起来太长,你可以使用 space(空格) 或者 indent(缩进) 隔开连续的一行

  1. tasks:
  2. - name: Copy ansible inventory file to client
  3. copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
  4. owner=root group=root mode=0644

在 action 行中可以使用变量.假设在 ‘vars’ 那里定义了一个变量 ‘vhost’ ,可以这样使用它

  1. tasks:
  2. - name: create a virtual host file for {{ vhost }}
  3. 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

  1. - name: template configuration file
  2. template: src=template.j2 dest=/etc/foo.conf
  3. notify:
  4. - restart memcached
  5. - restart apache

‘notify’ 下列出的即是 handlers.

Handlers 也是一些 task 的列表,通过名字来引用,它们和一般的 task 并没有什么区别.Handlers 是由通知者进行 notify, 如果没有被 notify,handlers 不会执行.不管有多少个通知者进行了 notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次.

这里是一个 handlers 的示例

  1. handlers:
  2. - name: restart memcached
  3. service: name=memcached state=restarted
  4. - name: restart apache
  5. service: name=apache state=restarted

Handlers 最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到了.
handlers 会按照声明的顺序执行
如果你想立即执行所有的 handler 命令,在1.2及以后的版本,你可以这样做

  1. tasks:
  2. - shell: some tasks go here
  3. - meta: flush_handlers
  4. - shell: some other tasks

执行一个 playbook

既然现在你已经学习了 playbook 的语法,那要如何运行一个 playbook 呢?这很简单,这里的示例是并行的运行 playbook,并行的级别 是10(译者注:是10个并发的进程?)

  1. ansible-playbook playbook.yml -f 10

在执行一个 playbook 之前,想看看这个 playbook 的执行会影响到哪些 hosts

  1. [root@localhost ~]# ansible-playbook playbook.yml --list-hosts
  2. playbook: playbook.yml
  3. play #1 (testserv): testserv TAGS: []
  4. pattern: [u'testserv']
  5. hosts (2):
  6. 192.168.37.132
  7. 192.168.37.131

Playbook 角色(Roles) 和 Include 语句

假如你希望在多个 play 或者多个 playbook 中重用同一个 task 列表,你可以使用 include files 做到这一点。 当我们希望为系统定义一个角色时,使用 include 去包含 task 列表是一种很好用的方法。需要记住的是,一个 play 所要达成 的目标是将一组系统映射为多个角色。下面我们来看看具体是如何做的:
一个 task include file 由一个普通的 task 列表所组成,像这样:

  1. ---
  2. # possibly saved as tasks/foo.yml
  3. - name: placeholder foo
  4. command: /bin/foo
  5. - name: placeholder bar
  6. command: /bin/bar

Include 指令看起来像下面这样,在一个 playbook 中,Include 指令可以跟普通的 task 混合在一起使用:

  1. tasks:
  2. - include: tasks/foo.yml

你也可以给 include 传递变量。我们称之为’参数化的 include’。

举个例子,如果我们要部署多个 wordpress 实例,我们可将所有的 wordpress task 写在一个 wordpress.yml 文件中, 然后像下面这样使用 wordpress.yml 文件:

  1. tasks:
  2. - include: wordpress.yml wp_user=timmy
  3. - include: wordpress.yml wp_user=alice
  4. - include: wordpress.yml wp_user=bob

Ansible 支持另一种传递变量到 include files 的语法,这种语法支持结构化的变量:

  1. tasks:
  2. - include: wordpress.yml
  3. vars:
  4. wp_user: timmy
  5. some_list_variable:
  6. - alpha
  7. - beta
  8. - gamma

Include 语句也可以用在 ‘handlers’ section 中,比如,你希望定义一个重启 apache 的 handler, 你只需要定义一次,然后便可在所有的 playbook 中使用这个 handler。你可以创建一个 handlers.yml 文件如下:

  1. ---
  2. # this might be in a file like handlers/handlers.yml
  3. - name: restart apache
  4. service: name=apache state=restarted

然后在你的主 playbook 文件中,在一个 play 的最后使用 include 包含 handlers.yml:

  1. handlers:
  2. - include: handlers/handlers.yml

Roles

Roles 基于一个已知的文件结构,去自动的加载某些 vars_files,tasks 以及 handlers。基于 roles 对内容进行分组,使得我们可以容易地与其他用户分享 roles 。
一个项目的结构如下:

  1. site.yml
  2. webservers.yml
  3. fooservers.yml
  4. roles/
  5. common/
  6. files/
  7. templates/
  8. tasks/
  9. handlers/
  10. vars/
  11. defaults/
  12. meta/
  13. webservers/
  14. files/
  15. templates/
  16. tasks/
  17. handlers/
  18. vars/
  19. defaults/
  20. meta/

一个 playbook 如下:

  1. ---
  2. - hosts: webservers
  3. roles:
  4. - common
  5. - 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,这种方式通过添加变量来实现,比如:

  1. ---
  2. - hosts: webservers
  3. roles:
  4. - common
  5. - { role: foo_app_instance, dir: '/opt/a', port: 5000 }
  6. - { role: foo_app_instance, dir: '/opt/b', port: 5001 }

当一些事情不需要频繁去做时,你也可以为 roles 设置触发条件,像这样:

  1. ---
  2. - hosts: webservers
  3. roles:
  4. - { role: some_role, when: "ansible_os_family == 'RedHat'" }

它的工作方式是:将条件子句应用到 role 中的每一个 task 上。关于”条件子句”的讨论参见本文档后面的章节。

最后,你可能希望给 roles 分配指定的 tags。比如:

  1. ---
  2. - hosts: webservers
  3. roles:
  4. - { role: foo, tags: ["bar", "baz"] }

如果 play 仍然包含有 ‘tasks’ section,这些 tasks 将在所有 roles 应用完成之后才被执行。

如果你希望定义一些 tasks,让它们在 roles 之前以及之后执行,你可以这样做:

  1. ---
  2. - hosts: webservers
  3. pre_tasks:
  4. - shell: echo 'hello'
  5. roles:
  6. - { role: some_role }
  7. tasks:
  8. - shell: echo 'still busy'
  9. post_tasks:
  10. - shell: echo 'goodbye'