Ansible 学习(PlayBook)
2017-02-28
原创作品:首发u3v3, 转载请保留
地址:https://www.u3v3.com/ar/1267
作者ID:noomrevils
首发日期:2017.2.28
引言
上篇文章Ansible 学习(初探) 介绍了Ansible的特性和概念, 并描述了在两台vps构建的一个私有网络上实践了环境搭建,使用一些基本模块。
实际的工作的场景不可能只靠简单条命令来解决复杂的问题,这里就需要引出本篇的主角playbook
playbook
是一系列ansible基础命令的组合和拓展,采用yaml语法,不同于ad-hoc命令一次只能执行一个模块, playbook
可以将变量配置和任务执行流程灵活的组织起来,从而能够完成一些复杂的部署任务。
playbook
的优势
- 采用yaml描述任务和配置,独立于编程语言,易于学习和维护。
- 模块化的结构鼓励片段复用,官方提供了Ansible Galaxy,社区的力量促进学习,分享和重用高质量的playbook。
- 支持jinja2模板语法,指令状态捕获和传递,条件和循环分支,顺序/异步执行方式,这些特征使
playbook
有着很强的表达能力
PlayBook的组成
下面列出的nginx.yml
文件是一个playbook中的一个play,它所完成的流程是在被控节点上安装并启动nginx。一个playbook可以包含多个这样针对不同节点清单的play。接下来简单介绍下组成这个play的元素
- hosts: web
vars:
- worker_processes: 4
remote_user: deploy
become: true
become_method: sudo
tasks:
- name: install nginx
yum: name=nginx state=installed update_cache=true
- name: update nginx config
template:
src: /home/deploy/ansible-playbook/nginx.conf
dest: /etc/nginx/nginx.conf
notify:
- reload nginx
- name: ensure nginx is running
service: name=nginx state=started
handlers:
- name: reload nginx
service: name=nginx state=reloaded
主机和用户
- hosts: web
vars:
- worker_processes: 4
remote_user: deploy
become: true
become_method: sudo
...
这里定义了执行目标主机清单,操作用户和对应权限,以及使用到的变量信息,其实这些内容就是命令行参数,配置文件的另一种组合表达形式,yaml的语法带来了可读性和易维护的优势
任务列表
...
tasks:
- name: install nginx
yum: name=nginx state=installed update_cache=true
- name: update nginx config
template:
src: /home/deploy/ansible-playbook/nginx.conf
dest: /etc/nginx/nginx.conf
notify:
- reload nginx
- name: ensure nginx is running
service: name=nginx state=reloaded
...
任务列表是playbook中的骨架,它定义了一系列顺序执行的步骤, 将独立的模块任务逻辑的联系在一起。 每一个具体的任务,都需要一个与之对应的名字,方便执行时打印信息和引用。
这里第一个任务借助yum模块,安装nginx,这里参数的调用采用的是key=value
的方式,也可以像第二个任务那样,采用类似字典的语法来指定。
第二个任务是将本地模板文件,通过jinja2模板引擎,展开变量worker_processes
渲染后,传输到被控节点nginx目录下
...
user nginx;
worker_processes {{ worker_processes }};
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
...
Handlers
handler和任务(task) 十分类似, 区别在于,handler区别是被动由一个任务在自身状态发生变换的时候触发,可以通过,notify
,和task的名字来指定调用handler的场景。
- name: update nginx config
template:
src: /home/deploy/ansible-playbook/nginx.conf
dest: /etc/nginx/nginx.conf
notify:
- reload nginx
...
handlers:
- name: reload nginx
service: name=nginx state=reloaded
...
这里在更新过nginx的配置文件后,我们通过ansible告诉被控节点reload nginx服务
执行PlayBook
$ ansible-playbook nginx.yml [参数]
常用参数:
–syntax-check :检查playbook的语法
–step:以单任务分步骤运行,方便做每一步的确认工作
–check:检测模式,playbook中定义的所有任务将在每台远程主机上进行检测,但并不直正执行
–verbose(-v):显示详细输出,也可以使用-vvvv显示精确到每分钟的输出
–forks:最大并行执行playbook的服务器数量
输出示例:
$ ansible-playbook nginx.yml
PLAY [web] *********************************************************************
TASK [setup] *******************************************************************
ok: [10.99.0.11]
TASK [install nginx] ***********************************************************
ok: [10.99.0.11]
TASK [update nginx config] *****************************************************
changed: [10.99.0.11]
TASK [ensure nginx is running] *************************************************
ok: [10.99.0.11]
RUNNING HANDLER [reload nginx] ************************************************
changed: [10.99.0.11]
PLAY RECAP *********************************************************************
10.99.0.11 : ok=5 changed=2 unreachable=0 failed=0
按Role来构建PlayBook
上面的playbook存在于一个单一文件中nginx.yml, 当流程和步骤变的更加复杂的时候,单个文件就显得混乱,不好维护了。Role
(角色)的概念本质是将playbook按照变量,任务,处理程序,文件等基本元素,以不同级别的目录层次进行拆分,从而达到将相关任务和数据封装到一个连贯独立的结构中。
通常一个Role下会有如下几类文件夹
- tasks 任务列表路径
- templates 模版存放路径
- handlers handlers存放路径
- vars roles内变量存放路径
- files 存放文件和脚本,copy模块文件搜索路径
- meta 依赖文件路径
- defaults 默认寻找路径
接下来结合一个具体的列子,来说明下一个基于Role的playbook的构建过程,通过这个playbook将在被控节点上部署一个 Nginx + uWSGI + Bottle 的 demo 服务。
bottle-nginx
├── hosts # 目标主机清单
├── roles
│ └── webserver
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ │ ├── bottle-nginx.conf
│ │ └── bottle-uwsgi.ini
│ └── vars
│ └── main.yml
└── site.yml # 主入口
hosts
文件定义了目标主机清单,优先级高于/etc/ansible/hosts
, 但是低于命令行指定
[web]
10.99.0.11
site.yml
是整个playbook的主入口,指名需要执行的roles, 和一些登录参数。
- hosts: web
remote_user: deploy
become: true
become_method: sudo
roles:
- webserver
在目录下一个定义webserver这个角色,vars
目录下 main.yml
放置了部署需要一些常量信息,
项目根目录,仓库地址等。
project_root: /var/www/bottle-nginx
project_name: bottle-nginx
project_repo: git@git.coding.net:usrname/ansible-bottle-demo.git
branch: master
再来看../task/main.yml
定义的主流程任务
- name: install nginx
yum: name=nginx state=installed update_cache=true
- name: ensure nginx is running
service: name=nginx state=started
- name: install dependent packages
yum: name={{ item }} state=installed
with_items:
- git
- uwsgi-plugin-python
- python-pip
- python-wheel
- name: pull source code
git:
dest: "{{ project_root }}"
version: "{{ branch }}"
accept_hostkey: yes
key_file: /home/deploy/.ssh/id_rsa
- name: pip install requirements
pip: requirements={{ project_root }}/requirements.txt
- name: update nginx config
template:
src: bottle-nginx.conf
dest: /etc/nginx/conf.d/bottle-nginx.conf
notify:
- reload nginx
- name: update uwsgi config
template:
src: bottle-uwsgi.ini
dest: /etc/uwsgi.d/bottle-uwsgi.ini
notify:
- reload uwsgi
在确保nginx正常运行后,我们安装了项目的依赖包, 这里因为使用的是yum,安装uWSGI需要指定含有python插件版本(uwsgi-plugin-python)的,这区别于直接使用pip安装,因为后面我们使用service模块来维护uWSGI。这里有个小坑,默认/etc/uwsgi.ini
中需要改动参数, 才能使服务正常启动
gid = nginx
...
#emperor-tyrant = true
接下来使用ansible中git module来拉取源代码, accept_hostkeys
是为仓库对应的url添加hostkey,key_file
是指定被控节点上私钥作为拉取代码的key,默认采用管理节点上的私钥
然后使用pip
模块安装项目python依赖, 项目本身非常简单,Bottle
是python的一个微框架,单文件,简洁明了,整个项目只有一个单文件,app.py
import bottle
from bottle import route, run
@route('/')
def index():
return 'Hello world!'
if __name__ == '__main__':
run(host='0.0.0.0', port=8000)
else:
app = application = bottle.default_app()
最后我们使用变量拓展nginx, uWSGI的配置文件,通知handler刷新服务。
#### bottle-uwsgi.ini
[uwsgi]
socket = 127.0.0.1:3030
chdir = {{ project_root }}/
master = true
processes = 1
threads = 1
module = app:app (filename: bottle_instance)
plugins = python (使用对应plugin)
#### bottle-nginx.conf
server{
listen 80;
server_name servername(ip);
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3030;
uwsgi_read_timeout 300;
}
}
贴出执行后的完整输出
$ ansible-playbook site.yml
PLAY [web] *********************************************************************
TASK [setup] *******************************************************************
ok: [10.99.0.11]
TASK [webserver : install nginx] ***********************************************
ok: [10.99.0.11]
TASK [webserver : ensure nginx is running] *************************************
ok: [10.99.0.11]
TASK [webserver : install dependent packages] **********************************
ok: [10.99.0.11] => (item=[u'git', u'uwsgi', u'python-pip', u'python-wheel'])
TASK [webserver : pull source code] ********************************************
ok: [10.99.0.11]
TASK [webserver : pip install requirements] ************************************
ok: [10.99.0.11]
TASK [webserver : update nginx config] *****************************************
ok: [10.99.0.11]
TASK [webserver : update uwsgi config] *****************************************
changed: [10.99.0.11]
RUNNING HANDLER [webserver : reload uwsgi] *************************************
changed: [10.99.0.11]
PLAY RECAP *********************************************************************
10.99.0.11 : ok=9 changed=2 unreachable=0 failed=0