三Ansible之playbook

Posted 锦书致南辞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三Ansible之playbook相关的知识,希望对你有一定的参考价值。

一、Ad-Hoc问题

通过对AD-HOC的学习,我们发现AD-HOC每次只能在被管理节点上执行简单的命令。

而日常的工作中,我们往往面临的是一系列的复制的操作,例如我们有可能需要安装软件、更新配置、启动服务等等一系列的操作的结合。此时再通过AD-HOC去完成任务就力不从心了。

在这种场景下,Ansible引入了playbook来帮我们解决这个问题。

二、playbook是什么?

PlayBook 也被大家翻译成剧本。

可以认为它是Ansible自定义的一门语言(可以将playbook比作Linux中的shell,而Ansible中的Module可以比作Linux中的各种命令)。

三、YAML 学习

Playbook遵循YAML语法格式。

因此在学校Playbook前,我们必须弄清楚yaml的语法。

1.YAML特点

YAML 文件以 # 为注释符
YAML 文件以 .yml 或者.yaml 结尾
YAML 文件以 --- 开始,以 ... 结束 但是开始和结束的标识都是可选的

2.基本语法

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时使用Tab键还是空格要一定要一致,建议使用空格
  • 相同层次的元素必须左侧对齐即可

YAML 支持的数据结构有三种

  • 字符串
  • 列表
  • 字典

2.1 字符串

---
# YAML 中的字符串可以不使用引号,即使里面存在空格的时候,当然了使用单引号和双引号也没有错
this is a string
\'this is a string\'
"this is a string"

# YAML 中如果一行写不下你要表述的内容的时候,可以进行折行。写法如下:
long_line: |
    Example 1
	Example 2
	Example 3

# 或者
long_line: >
    Example 1
	Example 2
	Example 3

2.2 列表

---
# 若熟悉python的话,可以认为它就是python中的list,若熟悉 C语言的话,可以认为它是 c中的数组
# 如何定义:以短横线开头 + 空格 + 具体的值
- red
- green
- blue
# 以上的值假如转换成python中的list会是这样:
# [\'red\',\'green\',\'blue\']

2.3 字典

---
# 若熟悉python的话,可以认为它就是python中的Dict
# 如何定义:key + 冒号(:) + 空格 + 值(value),即 key: value

name: Using Ansible
code: D1234

# 转换为 python 的 Dict
# {\'name\': \'Using Ansible\', \'code\': \'D1234\'}

2.4 混合结构

以上,针对 YAML 的所有基础知识点就介绍完了,但日常生活中,往往需要的数据结构会特别复杂,有可能会是字符串、列表、字典组合形式。

例如:
所有人都上过学,都知道学校是以班级为单位。我们去使用列表和字典的形式去描述一个班的组成。

---
class: 
  - name: stu1
    num: 001
  - name: stu2
    num: 002
  - name: stu3
    num: 003

# {\'class\': [{\'name\': \'stu1\', \'num\': 1 }, {\'name\': \'stu2\', \'num\':2}, ...]}

2.5 验证 YAML 语法

# YAML 文件,通过 python 的 YAML 模块验证,若不正确则报错,若正确则会输出 YAML 里的内容。
# 注意使用时,一定确保安装了yaml的安装包
python -c \'import yaml,sys; print yaml.load(sys.stdin)\' < myyaml.yml
python3 -c \'import yaml,sys; print (yaml.load(sys.stdin))\' < myyaml.yml

练习

# 正确的情况
# cat myyaml.yml
---
- red
- green
- blue

...
[root@ansible-01 ~]# python -c \'import yaml,sys; print yaml.safe_load(sys.stdin)\' < myyaml.yml
[\'red\', \'green\', \'blue\']

# 错误的情况下
# cat myyaml.yml
---
- red
- green
-blue

[root@ansible-01 ~]# python -c \'import yaml,sys; print yaml.safe_load(sys.stdin)\' < myyaml.yml
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib64/python2.7/site-packages/yaml/__init__.py", line 93, in safe_load
    return load(stream, SafeLoader)
  File "/usr/lib64/python2.7/site-packages/yaml/__init__.py", line 71, in load
    return loader.get_single_data()
  File "/usr/lib64/python2.7/site-packages/yaml/constructor.py", line 37, in get_single_data
    node = self.get_single_node()
  File "/usr/lib64/python2.7/site-packages/yaml/composer.py", line 36, in get_single_node
...

四、playbook的编写

1. play的定义

由于playbook是由一个或者多个play组成,那么如果我们熟悉play的写法,就自然掌握了我们这章内容

那么如何定义play呢?
1、每个play都是以短横杠开始的
2、每一个play都是一个YAML字典格式

根据上面两条play的规则,一个假象的play应该是下面的样子

---
- key1: value1
- key2: value2
- key3: value3
....

由于一个playbook是由一个或者多个play构成,那么一个含有多个play的playbook 结构上应该是如下的样子

---
# 一个含有3个play的伪playbook构成
- key1: value1
  key2: value2
  key3: value3
- key4: value1
  key5: value2
  key6: value3
- key1: value1
  key2: value2
  key3: calue3
  ...

2. play的属性

常用属性

  • name 属性:每个play的名字
  • hosts 属性:每个play涉及的被管理服务器,同ad hoc 中的资产选择器
  • tasks 属性:每个play中具体要完成的任务,以列表形式表达
  • become 属性:如果需要提权,则加上become属性
  • become_user 属性:若提权的话,提到哪个用户
  • remote_user 属性:指定连接到远程节点的用户,就是在远程服务器上执行具体操作的用户,若不指定,则默认使用当前执行ansible playbook的用户

3. 一个完整剧本

下面来看一个含有play的playbook应该是下面这样的

---
- name: the first play example
  hosts: all
  remote_user: root
  tasks:
    - name: install nginx package
	  yum : name=nginx state=present
	- name: copy nginx.conf to remote server
	  copy: src=nginx.conf dest=/etc/nginx/nginx.conf
	- name: start nginx server
	  service: 
	    name: nginx 
		enabled: true/yes
		state: started

4. tasks 属性中任务的多种写法

# 以启动 nginx 服务,并加入开机自启为例

# 一行的形式
service: name=nginx enabled=true statu=started

# 多行形式
service: name=nginx
         enalbed=true
		 state=started
 
# 多行写成字典的形式
service:
  name: nginx
  enabled: true
  state: started

5. 具有多个play的playbook

---
- name: manage web servers
  hosts: webservers
  remote_user: root
  tasks:
    - name: install nginx package
	  yum: name=nginx state=present
	- name: copy nginx.conf to remote server
	  copy: src=nginx.conf dest=/tmp/nginx/nginx.conf
	- name: start nginx server
	  service:
	    name: nginx
		enabled: yes|true
		state: started

- name: manager db servers
  hosts: db_servers
  tasks:
    -name: update database confg
	  copy: src=my.conf dest=/etc/my.conf
...

6. 如何对playbook进行语法校验

# ansible-playbook - hosts myplaybook.yml --syntax-check

因为Playbook属于YAML格式,我们同样可以检查YAML语法格式的方法进行检查playbook的语法的正确性

7. 如何允许playbook

# ansible-playbook -i hosts myyaml.yml

8. 如何单步跟从调试playbook

# 执行task中的任务,需要手动确认是否往下执行
ansible-playbook -i hosts  my_nginx.yml --step

9. 如何测试允许playbook

# 执行完整的playbook,但是使用Task上的行为都不会在远程服务器上执行,所有执行都是模拟行为
ansible-playbook -i hosts  my_nginx.yml -C
-C 为大写字母C

Ansible之主机清单及playbook剧本

一、主机清单

ansible默认的主机清单是/etc/ansible/hosts文件
主机清单可以手动设置,也可以通过Dynamic Inventory动态生成
一般主机名使用FQDN

vi /etc/ansible/hosts
[webserver]              #使用方括号设置组名
www1.example.org         #定义被监控主机,这边可以是主机名也可以是IP地址
www2.example.org:2222    #冒号后定义远程连接端口,默认是ssh的22端口

如果是名称类似的主机,可以使用列表的方式标识各个主机

[webserver]
//[01:50]表示匹配从0150,后面跟着内置变量,这里定义了ssh的访问的用户名和密码,用于免交互登录
www[01:50].example.org ansible_ssh_user=root ansible_ssh_pass=abc123

[dbbservers]
//[a:f]表示支持匹配a到f
db-[a:f].example.org

inventory变量参数

ansible_ssh_host #将要连接的远程主机名,与你想要设定的主机的别名不同的话,可以通过此变量设置。
ansible_ssh_port #ssh 端口号.如果不是默认的端口号,通过此变量设置。
ansible_ssh_user #默认的 ssh 用户名。
ansible_ssh_pass #ssh 密码(这种方式并不安全,强烈建议使用 --ask-pass 或SSH密钥)。
ansible_ssh_private_key_file #ssh使用的私钥文件,适用于有多个密钥,但你不想使用 SSH 代理的情况。
ansible_ssh_common_args #此设置附加到 sftp  scp 和ssh的缺省命令行。
ansible_sftp_extra_args #此设置附加到默认 sftp 命令行。
ansible_scp_extra_args #此设置附加到默认 scp 命令行。
ansible_ssh_extra_args #此设置附加到默认 ssh 命令行。
ansible_ssh_pipelining #确定是否使用SSH管道。这可以覆 ansible.cfg 中的设置。
ansible_shell_type #目标系统的shell类型。默认情况下,命令的执行使用  sh  语法,可设置为  csh    fish '
ansible_python_interpreter #目标主机的 python路径.适用于的情况: 系统中有多个 Python , 或者命令路径不是"/usr/bin/python",比如 *BSD, 或者 /usr/bin/python
ansible_*_interpreter #这里的" * "可以是 ruby  perl 或其他语言的解释器,作用和ansible_python_interpreter 类似。
ansible_shell_executable #这将设置 ansibie 控制器将在目标机器上使用的 shell ,覆盖 ansible.cfg 中的配置,默认为 /bin/sh。

Inventory中的变量

主机变量
[webserver]
//定义变量http_port(开放的端口信息)和maxRequestsChild(最大进程数)
www1.magedu.com http_port=80 maxRequestsChild=808
www2.magedu.com http_port=8080 maxRequestsChild=909

组变量
[servers:vars]
ntp_server=ntp.example.org
nfs_server=nfs.example.org

组嵌套
[apache]
http1.example.org
http2.example.org

[nginx]
ngx1.example.org
ngx2.example.org

#定义一个组名,将刚才定义的两个组名放入,即webservers组包含apache组和nginx组的主机
[webservers]
apache
nginx

二、YAML

YAML:另一种标记语言。是用来写配置文件的语言,非常简洁和强大。
YAML语法和其他语言类似,也可以表达散列表、标量等数据结构。
结构通过空格来展示;序列里配置项通过-来代表;Map里键值用:来分隔;YAML的扩展名为yaml

1.基本语法规则

(1)大小写敏感
(2)使用缩进表示层级关系
(3)缩进时不允许使用Tab键,只允许使用空格。
(4)缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

2.YAML支持的数据结构

对象
键值对的集合,又称为映射(mapping)/哈希(hashes)/字典(dictionary)

举例: name (): Example()

类class:(物品)
  对象1:(桌子)
    属性(名称,长、宽、高等)
    方法(动词,放东西)
    ...
  对象2
  对象3

数组
一组按次序排列的值,又称为序列(sequence)/列表(list)

举例:-Apple
     -Orange

纯量
单个的、不可再分的值

举例:number: 12.30
     sure: true

三、playbook

通过task调用ansible的模板将多个play组织在一个playbook中运行。

1.组成部分

Tasks:任务,即调用模块完成的某操作;原理和事务一样,要么一起执行,要么一起不执行。

Variables:变量;声明变量的三个场景:hosts文件中定义、剧本中定义、在命令中加上-e定义。

Templates:模板;定义模板同一格式,解决每个服务可能因格式不一样而不兼容的问题。

Handlers:处理器,当某条件满足时,触发执行的操作。

Roles:角色;将任务分类执行,彼此之间互不干扰。

举例

cd /opt
vim test1.yaml
---     #yaml文件以---开头,以表明这是一个yaml文件,可省略
- name: first play     #定义一个play的名称,可省略
  gather_facts: false    #设置不进行facts信息收集,这可以加快执行速度,可省略
  hosts: webservers    #指定要执行任务的被管理主机组,如多个主机组用冒号分隔
  remote_user: root    #指定被管理主机上执行任务的用户
  tasks:     #定义任务列表,任务列表中的各任务按次序逐个在hosts中指定的主机上执行
   - name: test connection    #自定义任务名称
     ping:     #使用 module: [options] 格式来定义一个任务
   - name: disable selinux
     command: '/sbin/setenforce 0'    #command模块和shell模块无需使用key=value格式
     ignore_errors: True     #如执行命令的返回值不为0,就会报错,tasks停止,可使用ignore_errors忽略失败的任务
   - name: disable firewalld
     service: name=firewalld state=stopped    #使用 module: options 格式来定义任务,option使用key=value格式
   - name: install httpd
     yum: name=httpd state=latest
   - name: install configuration file for httpd
     copy: src=/opt/httpd.conf dest=/etc/httpd/conf/httpd.conf    #这里需要一个事先准备好的/opt/httpd.conf文件
     notify: "restart httpd"    #如以上操作后为changed的状态时,会通过notify指定的名称触发对应名称的handlers操作
   - name: start httpd service
     service: enabled=true name=httpd state=started
  handlers:     #handlers中定义的就是任务,此处handlers中的任务使用的是service模块
   - name: restart httpd    #notify和handlers中任务的名称必须一致
     service: name=httpd state=restarted
##Ansible在执行完某个任务之后并不会立即去执行对应的handler,而是在当前play中所有普通任务都执行完后再去执行handler,这样的好处是可以多次触发notify,但最后只执行一次对应的handler,从而避免多次重启。

注:要事先准备好/opt/httpd.conf文件

2.运行playbook

ansible-playbook test1.yaml

#补充参数:
-k(–ask-pass):用来交互输入ssh密码
-K(-ask-become-pass):用来交互输入sudo密码
-u:指定用户

ansible-playbook test1.yaml --syntax-check    #检查yaml文件的语法是否正确
ansible-playbook test1.yaml --list-task       #检查tasks任务
ansible-playbook test1.yaml --list-hosts      #检查生效的主机
ansible-playbook test1.yaml --start-at-task='install httpd'     #指定从某个task开始运行

3.定义、引用变量

vim test2.yaml
---
- name: second play
  hosts: dbservers
  remote_user: root
  vars:                 #定义变量
   - groupname: mysql   #格式为 key: value
   - username: nginx
  tasks:
   - name: create group
     group: name={{groupname}} system=yes gid=306    #使用 {{key}} 引用变量的值
   - name: create user
     user: name={{username}} uid=306 group={{groupname}} 
   - name: copy file
     copy: content="{{ansible_default_ipv4}}" dest=/opt/vars.txt    #在setup模块中可以获取facts变量信息


ansible-playbook test2.yaml -e "username=nginx"     #在命令行里定义变量

4.指定远程主机sudo切换用户

vim test3.yaml
---
- hosts: dbservers
  remote_user: zz
  become: yes	                 #2.6版本以后的参数,之前是sudo,意思为切换用户运行
  become_user: root              #指定sudo用户为root
执行playbook时:ansible-playbook test3.yml -K <密码>

5.when条件判断

在Ansible中,提供的唯一一个通用的条件判断是when指令,当when指令的值为true时,则该任务执行,否则不执行该任务

when一个比较常见的应用场景是实现跳过某个主机不执行任务或者只有满足条件的主机执行任务

vim test4.yaml
---
- hosts: all
  remote_user: root
  tasks:
   - name: shutdown host 
     command: /sbin/shutdown -r now
     when: ansible_default_ipv4.address == "192.168.19.77"      #when指令中的变量名不需要手动加上 {{}}
 
     when: inventory_hostname == "<主机名>"
	
ansible-playbook test4.yaml

6.迭代

Ansible提供了很多种循环结构,一般都命名为with_items,作用等同于 loop 循环。

vim test5.yaml
---
- name: play1
  hosts: dbservers
  gather_facts: false
  tasks: 
    - name: create directories
      file:
        path: "{{item}}"
        state: directory
      with_items:          #等同于 loop:
        - /tmp/test1
        - /tmp/test2
    - name: add users
      user: name={{item.name}} state=present groups={{item.groups}}
      with_items:
        - name: test1
          groups: wheel
        - name: test2
          groups: root

      with_items:
        - {name:'test1', groups:'wheel'}
        - {name:'test2', groups:'root'}

ansible-playbook test5.yaml

四、Templates 模块

Jinja是基于Python的模板引擎。Template类是Jinja的一个重要组件,可以看作是一个编译过的模板文件,用来产生目标文本,传递Python的变量给模板去替换模板中的标记。

1.先准备一个以 .j2 为后缀的 template 模板文件,设置引用的变量
cp /etc/httpd/conf/httpd.conf /opt/httpd.conf.j2

vim /opt/httpd.conf.j2
Listen {{http_port}}				#42行,修改
ServerName {{server_name}}			#95行,修改
DocumentRoot "{{root_dir}}"          #119行,修改

2.修改主机清单文件,使用主机变量定义一个变量名相同,而值不同的变量
vim /etc/ansible/hosts       
[webservers]
192.168.19.66 http_port=192.168.19.66:80 server_name=www.feng.com:80 root_dir=/etc/httpd/htdocs

[dbservers]
192.168.19.77 http_port=192.168.19.77:80 server_name=www.dian.com:80 root_dir=/etc/httpd/htdocs

3.编写 playbook 
vim apache.yaml
---
- hosts: all
  remote_user: root
  vars:
    - package: httpd
    - service: httpd
  tasks:
    - name: install httpd package
      yum: name={{package}} state=latest
    - name: install configure file
      template: src=/opt/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf     #使用template模板
      notify:
        - restart httpd
    - name: create root dir
	  file: path=/etc/httpd/htdocs state=directory
    - name: start httpd server
      service: name={{service}} enabled=true state=started
  handlers:
    - name: restart httpd
      service: name={{service}} state=restarted

ansible-playbook apache.yaml



注:创建/etc/httpd/htdocs,同时被管理机要挂载。

五、tags 模块

可以在一个playbook中为某个或某些任务定义“标签”,在执行此playbook时通过ansible-playbook命令使用–tags选项能实现仅运行指定的tasks。
playbook还提供了一个特殊的tags为always。作用就是当使用always当tags的task时,无论执行哪一个tags时,定义有always的tags都会执行。

vim webhosts.yaml
---
- hosts: webservers
  remote_user: root
  tasks:
    - name: Copy hosts file
      copy: src=/etc/hosts dest=/opt/hosts
      tags:
        - only     #可自定义
    - name: touch file
      file: path=/opt/testhost state=touch
	  tags:
	    - always    #表示始终要运行的代码

ansible-playbook webhosts.yaml --tags="only"
vim dbhosts.yaml
---
- hosts: dbservers
  remote_user: root
  tasks:
    - name: Copy hosts file
      copy: src=/etc/hosts dest=/opt/hosts
      tags:
        - only
    - name: touch file
      file: path=/opt/testhost state=touch
      tags:
	    - always

ansible-playbook dbhosts.yaml --tags="only"

分别去两台被管理主机上去查看文件创建情况

六、Roles 模块

Ansible为了层次化、结构化地组织Playbook,使用了角色(roles),roles可以根据层次型结构自动

以上是关于三Ansible之playbook的主要内容,如果未能解决你的问题,请参考以下文章

Ansible之主机清单及playbook剧本

自动化运维管理工具 Ansible之playbook剧本的详细解读

三ansible-playbook简介编写语法检测

ansible学习笔记7-playbooks之执行一个playbook

运维自动化之ansible--(playbook模式)

Ansible之Playbook