我是先在本地用Markdown记的笔记,然后直接粘贴到博客园,可能格式上会有些问题,还请谅解。
一、playbook和yaml语法
1.1 YAML语法
基本语法规则:
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用Tab,只允许使用空格
- 缩进的空格数目不重要,相同层级的元素左侧对齐即可
- 使用
#
注释
支持三种数据结构
- 对象:键值对的集合
- 数组:一组按次序排列的值,以
-
开头 - 纯量:单个的,不可再分的值
1.2 playbook
ansible的配置、部署、编排语言。使用yaml语言编写。
1.2.1 示例:
- hosts: webservers # 主机或组
vars: # 变量
http_port: 80
max_clients: 200
remote_user: root # 远程用户
# sudo: yes 支持sudo执行命令
tasks:
- name: ensure apache is at the latest version # 任务名(必须有)
remote_user: yourname # tasks下也可以定义远程用户
sudo: yes # 支持sudo执行命令
sudo_user: postgres # 登录到postfres用户
yum: pkg=httpd state=latest # 使用yum模块安装apache
tags: t1 # 打标签
- name: write the apache config file
# template模块
template: src=/src/httpd.j2 dest=/etc/httpd.conf
# 当/src/httpd.j2文件被改动时,重启apache
notify: # notify会在playbook的每一个task结束时被触发
- restart apache
tags: t2
- name: ensure apache is running
service: name=httpd
tags:
- t3
- tag3 # 添加多个标签
使用sudo时指定密码,在运行
ansible-playbook
命令时加上选项--ask-sudo-pass(-K)
每一个play包括一个task列表,前一个task执行完成才会执行下一个task
1.2.2 执行一个playbook
# 查看所有选项
ansible-playbook --help
# 检查playbook的语法是否正确
ansible-playbook --syntax-check playbook.yml
# 列出运行任务的主机
ansible-playbook --list-hosts playbook.yml
# 模拟执行playbook
ansible-playbook --check playbook.yml
# 普通的运行playbook
ansible-playbook playbook.yml
# 开启十个并发的进程执行playbook
ansible-playbook playbook.yml -f 10
# 根据tags指定执行哪一个任务,指定多个标签用逗号隔开
ansible-playbook --tags=xxx playbook.yml
# 根据tags指定不执行哪一个任务
ansible-playbook --skip-tags=xxx playbook.yml
1.2.3 notify和Handlers
一个notify示例:当/src/htpd.j2文件被改动时,重启apache,即使有多个task通知改动的发生,notify也只会触发一次。
- restart apache
即是一个handlers,handlers其实是一种特殊的tasks。
- hosts: webservers
...
tasks:
...
- name: write the apache config file
template: src=/src/httpd.j2 dest=/etc/httpd.conf
notify:
- restart apache
handlers示例
handlers:
- name: restart apache
service: name=apache state=restarted
完整实例:
- hosts: webservers
remote_user: root
tasks:
- name: write the apache config file
template: src=/src/httpd.j2 dest=/etc/httpd.conf
notify:
- restart apache
handlers:
- name: restart apache
service: name=apache state=restarted
1.2.4 Roles和Include语句
引入Roles和 Include的目的是为了提高playbook的重用性。
5.2.4.1 include
在多个playbook中重用同一个task列表
一个task include file由一个普通的task列表所组成
# 文件名:tasks/foo.yml
- name: placeholder foo
command: /bin/foo
- name: placeholder bar
command: /bin/bar
在playbook中使用include语句导入
tasks:
- include: tasks/foo.yml
给include传递变量
tasks:
- include: wordpress.yml wp_user=timmy
- include: wordpress.yml wp_user=alice
- include: wordpress.yml wp_user=bob
tasks:
- include: wordpress.yml
vars:
wp_user: timmy
some_list_variable:
- alpha
- beta
- gamma
include也可用在handlers语句中
# handlers/handlers.yml
- name: restart apache
service: name=apache stae=restarted
然后在主playbook中使用include导入
handlers:
- include: handlers/handlers.yml
将一个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
1.2.4.2 Roles
Roles基于一个已知的文件结构、自动的加载某些vars_files,tasks以及handlers
一个项目结构如下所示:
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
以common为例,以上这段playbook的含义如下
-
如果roles/common/tasks/main.yml存在,其中的tasks将添加到play中
-
如果roles/common/handlers/main.yml存在,其中列出的handlers将被添加到play中
-
如果roles/common/vars/main.yml存在,其中列出的variables将被添加到play中
-
如果roles/common/meta/main.yml存在,其中列出的角色依赖将会添加到roles中
这个角色依赖没看懂是干啥用的
-
所有copy tasks都可以引用roles/common/file中的文件,不需要指明文件的路径
-
所有script tasks可以引用roles/common/file中的脚本,不需要知名文件的路径
-
所有template tasks可以引用roles/common/templates/中的文件,不需要指明文件的路径
-
所有include tasks可以引用roles/common/tasks/中的文件,不需要指明文件的路径
Roles是playbook编排的一种方式,可以在使用Roles的同时在playbook中松散地列出 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'" }
给roles分配指定的tags
- hosts: webservers
roles:
- { role: foo, tags: ["bar", "baz"]}
定义一些 tasks,让它们在 roles 之前以及之后执行
- hosts: webservers
pre_tasks:
- shell: echo 'hello'
roles:
- { role: some_role }
tasks:
- shell: echo 'still busy'
post_tasks:
- shell: echo 'goodbye'
1.2.5 Variables 变量
1.2.5.1 合法的变量名
- 变量名可以为字母,数字以及下划线.
- 变量始终应该以字母开头.
1.2.5.2 在playbook中定义变量
直接定义变量
- hosts: webservers
vars:
http_port: 80
在文件和role中定义变量
roles/item/vars/
1.2.5.3 使用变量
ansible使用Jinja2模板系统引用变量
template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg
{{ remote_install_path }}
部分就是变量引用部分
使用外部变量
- 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
vars/external_vars.yml文件内容如下:
somevar: somevalue
password: magic
命令行中传递变量
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"
1.2.6 条件选择语句 when
when的值是一个条件表达式,如果条件处理,tasks才执行。
示例:
tasks:
- name: "shutdown Debian flavored system"
command: /sbin/shutdown -t now
when:
- ansible_os_family == "Debian"
下面这段没看懂是啥意思
tasks:
- command: /bin/false
register: result
ignore_errors: True
# 当result等于failed时,执行
- command: /bin/something
when: result|failed
- command: /bin/something_else
when: result|success
- command: /bin/still/something_else
when: result|skipped
有些时候得到一个返回参数的值是一个字符串,并且你还想使用数学操作来比较它,那么可以执行以下操作:
tasks:
- shell: echo "only on Red Hat 6, derivatives, and later"
when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6
如果一个变量不存在,使用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
在roles和includes上应用when语句
- include: tasks/sometasks.yml
when: "'reticulationg splines' in output"
- hosts: webservers
roles:
- { role: debain_stock_config, when: ansible_os_family == 'Debian' }
1.2.7 循环
1.2.7.1 标准循环 with_item模块
使用with_items循环模块创建两个用户testuser1和testuser2
- name: add serveral users
user: name={{ item }} stat=present groups=wheel
with_items:
- testuser1
- testuser2
- name: add several users
user: name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
1.2.7.2 嵌套循环 with_nested模块
- 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' ]
1.2.7.3 对哈希表使用循环 with_dict
有如下哈希表
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}}"
debug: msg="User alice is Alice Appleworth (123-456-7890)"
debug: msg="User Bob is Bob Bananarama (987-654-3210)"
1.2.7.4 对文件列表使用循环 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/*
1.2.7.5 对并行数据集使用循环 with_together
alpha: [ 'a', 'b', 'c', 'd' ]
numbers: [ 1, 2, 3, 4 ]
tasks:
- debug: msg="{{ item.0 }} and {{ item.1 }}"
with_together:
- "{{alpha}}"
- "{{numbers}}"
1.2.7.6 对子元素使用循环 with_subelements
如下定义了一个复合结构的字典变量users,在with_subelements循环中,指定了users变量和users变量的子元素hobby
---
- hosts: testB
remote_user: root
gather_facts: no
vars:
users:
- name: bob
gender: male
hobby:
- Skateboard
- VideoGame
- name: alice
gender: female
hobby:
- Music
tasks:
- debug:
msg: "{{ item.0.name }}'hobby is {{item.1}}'"
with_subelements:
- "{{users}}"
- hobby
执行效果为
bob's hobby is Skateboard
bob's hobby is VideoGame
alice's hobby is Music
item.0获取到users变量的name、gender部分,item.1获取到hobby部分
1.2.7.7 对整数序列使用循环 with_sequence
---
- hosts: all
tasks:
- group: name=evens state=present
- group: name=odds state=present
# 创建四个用户,用户名为testuser00到testuser32
- user: name={{ item }} state=present groups=evens
with_sequence: start=0 end=32 format=testuser%02x
- file: dest=/var/stuff/{{ item }} state=directory
# stride 跨度
with_sequence: start=4 end=16 stride=2
- group: name=group{{ item }} state=present
with_sequence: count=4
1.2.7.8 随机选择 with_random_choice
- debug: msg={{ item }}
with_random_choice:
- "go through the door"
- "drink from the goblet"
- "press the red button"
- "do nothing"
1.2.7.9 Do-Until循环
重复一个任务直到某个条件满足
- action: shell /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
递归运行shell模块,直到模块结果中的stdout输出中包含all systems go字符串,或者该任务按照10秒的延迟重试超过5次
1.2.7.10 循环中使用注册器
使用register来注册变量,结果包含一个results属性
示例:
- shell: echo "{{ item }}"
with_items:
- one
- two
register: echo
返回如下数据结构
{
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": "echo "one" ",
"delta": "0:00:00.003110",
"end": "2013-12-19 12:00:05.187153",
"invocation": {
"module_args": "echo "one"",
"module_name": "shell"
},
"item": "one",
"rc": 0,
"start": "2013-12-19 12:00:05.184043",
"stderr": "",
"stdout": "one"
},
{
"changed": true,
"cmd": "echo "two" ",
"delta": "0:00:00.002920",
"end": "2013-12-19 12:00:05.245502",
"invocation": {
"module_args": "echo "two"",
"module_name": "shell"
},
"item": "two",
"rc": 0,
"start": "2013-12-19 12:00:05.242582",
"stderr": "",
"stdout": "two"
}
]
}
在tasks中循环注册变量,用来检测结果值
- name: Fail if return code is not 0
fail:
msg: "The command ({{{ item.cmd }}}) did not have a 0 return code"
when: item.rc != 0
with_items: "{{ echo.results }}"