Playbook
Playbook是一个不同于使用Ansible命令行执行方式的模式,其功能更强大灵活,简单来说,Playbook是一个非常简单的配置管理和多主机部署系统,不同于任何已经存在的模式,可作为一个适合部署复杂应用程序的基础
模块执行是幂等性的,因此Playbook执行也是幂等性的,多次执行结果相同
值得注意的是Playbook是通过YAML格式来进行描述定义的
YAML标记语言
YAML是一种表达资料序列的格式,由于参考了其他多张语言,所以具有很高的可读性,其特性如下:
- 具有很好的可读性,易于实现
- 表达能力强,扩展性好
- 和脚本语言的交互性好
- 有一个一致的信息模型
- 可以基于流来处理
基本语法规则
- 大小写敏感
- 使用缩进表示层级关系
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- ‘#’表示注释
支持的数据结构
YAML 支持以下几种数据类型:
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
- 纯量(scalars):单个的、不可再分的值
YAML对象
对象键值对使用冒号结构表示 key: value,冒号后面一定要加一个空格
也可以使用 key:{key1: value1, key2: value2, …}
用缩进表示层级关系
yum:
name: httpd
state: latest
YAML数组
以 - 开头的行表示构成一个数组:
- A
- B
- C
# ["A","B","C"]
多维数组
key: [value1, value2, ...]
子成员是数组
-
- A
- B
- C
# [["A","B","C"]]
复合结构
例如:
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org
转为JSON为
{
languages: [ 'Ruby', 'Perl', 'Python'],
websites: {
YAML: 'yaml.org',
Ruby: 'ruby-lang.org',
Python: 'python.org',
Perl: 'use.perl.org'
}
}
核心字段
- hosts:执行的远程主机列表
- tasks:任务,由模块定义操作列表
- variables:内置变量或者自定义变量
- templates:模板,模板文件一般是由Jinja2语言编写的
- handlers:和nogity结合使用,为条件触发操作,满足条件则执行
- roles:角色
官方示例剧本
- name: update web servers # 剧本名称
hosts: webservers # 远程主机列表
remote_user: root # 指定远程用户
tasks:
- name: ensure apache is at the latest version # 安装httpd
yum:
name: httpd
state: latest
- name: write the apache config file # 编写httpd配置文件
template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
- name: update db servers # 剧本名称
hosts: databases # 远程主机列表
remote_user: root # 指定远程用户
tasks:
- name: ensure postgresql is at the latest version # 安装postgresql
yum:
name: postgresql
state: latest
- name: ensure that postgresql is started # 启动postgresql
service:
name: postgresql
state: started
简单案例
Apache部署
- 修改主机清单文件
[root@server1 ~]# vim /etc/ansible/hosts
[server2]
192.168.31.20
- 创建剧本配置文件
[root@server1 ~]# mkdir /playbook/file -p
[root@server1 ~]# vim /playbook/apache-virtualhost-port.yaml
- name: Update web servers
hosts: server2
remote_user: root
tasks:
- name: ensure apache is the latest version # 安装httpd
yum:
name: httpd
state: latest
- name: copy site1.conf to apache web server # 创建httpd虚拟主机配置文件
copy:
src: /playbook/file/site1.conf
dest: /etc/httpd/conf.d/site1.conf
- name: create directory1 for apache web server # 创建httpd网站根目录
file:
name: /data/site1/
state: directory
- name: create directory2 for apache web server # 创建httpd网站根目录
file:
name: /data/site2/
state: directory
- name: write index.html # 编写index.html文件
shell: echo "site1" > /data/site1/index.html && echo "site2" > /data/site2/index.html
- name: start apache web server # 启动httpd文件
service:
name: httpd
state: restarted
- name: test virtual hosts # 测试
shell: curl 192.168.31.20:8080 && curl 192.168.31.20:8090
- 创建虚拟主机配置文件
[root@server1 ~]# vim /playbook/file/site1.conf
Listen 8080
Listen 8090
<Directory "/data/">
Require all granted
</Directory>
<VirtualHost *:8080>
DocumentRoot "/data/site1/"
</VirtualHost>
<VirtualHost *:8090>
DocumentRoot "/data/site2/"
</VirtualHost>
- 检查并启动剧本
[root@server1 ~]# cd /playbook
[root@server1 playbook]# ansible-playbook apache-virtualhost-port.yaml --syntax-check
[root@server1 playbook]# ansible-playbook apache-virtualhost-port.yaml
- 测试网站
[root@server1 ~]# curl 192.168.31.20:8080
site1
[root@server1 ~]# curl 192.168.31.20:8090
site2
Nginx部署
- 修改主机清单文件
[root@server1 ~]# vim /etc/ansible/hosts
[server3]
192.168.31.30
- 创建剧本配置文件
[root@server1 ~]# vim /playbook/nginx-virtualhost-port.yaml
- name: Update web servers
hosts: server3
remote_user: root
tasks:
- name: install epel # 安装扩展源
yum:
name: epel-release.noarch
state: latest
- name: ensure nginx is the latest version # 安装Nginx
yum:
name: nginx
state: latest
- name: copy site1.conf to nginx web server # 创建Nginx虚拟主机配置文件
copy:
src: /playbook/file/site2.conf
dest: /etc/nginx/conf.d/site2.conf
- name: create directory for nginx web server # 创建网站根目录
file:
name: /data
state: directory
- name: write index.html # 编写index.html
shell: echo "nginx server" > /data/index.html
- name: start nginx web server # 启动服务
service:
name: nginx
state: restarted
- 创建虚拟主机配置文件
[root@server1 ~]# vim playbook/file/site2.conf
server {
listen 8080;
server_name 192.168.31.30:8080;
location /{
root /data;
index index.html;
}
}
- 检查并启动剧本
[root@server1 ~]# cd /playbook
[root@server1 playbook]# ansible-playbook nginx-virtualhost-port.yaml --syntax-check
[root@server1 playbook]# ansible-playbook nginx-virtualhost-port.yaml
- 测试网站
[root@server1 ~]# curl 192.168.31.30:8080
nginx server
剧本变量
变量的来源:
- 远程主机的所有变量都可以通过
ansible -m setup nodes
命令来获取 - 自定义变量
优先级
- 通过命令行指定变量优先级
- /etc/ansible/hosts定义的变量(针对单个主机定义,针对主机组进行定义)
- playbook中定义的变量
常见系统变量:
主机名: "{{ ansible_hostname }}"
inventory下面的所有主机和主机组信息:"{{ groups }}"
操作系统版本: "{{ ansible_distribution }}" "{{ ansible_distribution_version }}"
内核版本: "{{ ansible_kernel }}"
系统位数: "{{ ansible_userspace_bits }}"
网卡:"{{ ansible_eth0["device"] }}"
IP地址: "{{ ansible_eth0.ipv4.address}}"
子网掩码: "{{ ansible_eth0.ipv4.netmask}}"
总内存: "{{ ansible_memory_mb["real"]["total"] }}"
内存空闲: "{{ ansible_memfree_mb }}"
Ansible handler
handler的作用
可以让某些任务不做一些不必要的重复工作,减少一些无谓的操作
比如说在某个剧本中的任务有:修改Nginx配置文件、重启Nginx服务、修改Nginx主页
那么就可以使用handler,在修改Nginx配置文件的下面加上一个notify,notify对应handlers下配置的任务(重启Nginx服务),然后只有当真的修改了Nginx配置文件后才会调用handler,即重启服务
否则假如只是修改了个主页文件,还要把所有任务执行一遍(包括重启服务),对于Nginx服务器来说岂不是很没必要
下面会举出这个例子
handler的特点
- 在Ansible Playbook中,handler事实上也是个task,只不过这个task默认并不执行,只有在被触发时才执行
- handler通过
notify
来监视某个或者某几个task,一旦task执行结果发生变化,则触发handler,执行相应操作 - 在
notify
中定义内容一定要和handlers中定义的- name
内容一样,这样才能达到触发的效果,否则会不生效 - handler会在所有的play都执行完毕之后才会执行,并且handler下的任务只会执行一次
- 任务表中的任务都是有状态的,也就是任务执行后的结果:
changed
或者ok
,在Ansible中,只有在任务的执行状态为changed
时,才会执行该任务调用的handler - handler是按照定义的顺序执行的(即在
handlers
下面写的顺序),而不是按照在任务中的调用顺序执行的
- name: Update web servers
hosts: server3
remote_user: root
- name: copy configuration file # 修改配置文件
copy:
src: /playbook/file/site2.conf
dest: /etc/nginx/conf.d/site2.conf
notify:
- restart nginx web server
- name: write index.html # 编写主页文件
shell: echo "nginx server" > /data/index.html
handlers:
- name: restart nginx web server # 重启服务
service:
name: nginx
state: restarted
剧本模板
Ansible模板介绍
- 模板的本质是文本文件,内部嵌套有模板语言的脚本
- 编写语言为Jinja2语法,由Python编写
- 功能:将模板文件中的变量转换成对应的本地主机的确定值
语法:
- 字符串:使用单引号或者双引号代表字符串
- 数字
- 列表:使用中括号括起来表示的是列表[ ]
- 元组:使用小括号表示元组( )
- 字典:使用大括号表示字典{ }
- 算数运算:+ - * / ………
- 比较操作:== != > < ………
- 逻辑运算:and or not
Jinja2语法
变量
{{name}}
控制语句
{% if A.safe %}
A is safe.
{% elif A.dead %}
A is dead
{% else %}
A is okay
{% endif %}
模板继承
父模板
# base.html
<html>
<head>
{% block head %}
<title>
{% block title %}
{% end block %}
</title>
{% end block %}
</head>
<body>
{% block body %}
{% end block %}
</body>
</html>
继承模板
{% extends 'base.html' %} # 继承base.html文件
{% block head %}
{{ super() }} # 用于获取原有的内容
{% end block %}
{% block title %}
Index # 定制title部分的内容
{% end block %}
{% block body %}
<h1>hello world</h1> # 定制body部分的内容
{% end block %}
生成hosts解析文件
- 创建模板文件
/playbook/hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups.all %}
{{hostvars[host].ansible_ens33.ipv4.address}} {{hostvars[host].ansible_hostname}}
{% endfor %}
- 创建剧本配置文件
/playbook/hosts.yaml
- name: Config hosts file
hosts: all
remote_user: root
tasks:
- name: copy hosts.j2 to group servers
template:
src: hosts.j2
dest: /etc/hosts
- 检查并运行剧本
[root@server1 playbook]# ansible-playbook hosts.yaml --syntax-check
[root@server1 playbook]# ansible-playbook hosts.yaml
- 查看hosts文件(server1/server2/server3)
[root@server1 ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.31.10 server1
192.168.31.20 server2
192.168.31.30 server3
- 进行互ping测试
[root@server1 ~]# ping server1
[root@server1 ~]# ping server2
[root@server1 ~]# ping server3
PS:
如果出现这种情况,是因为没有给这个主机设置免密登录导致的,给这台主机设置一下免密登录就可以了
生成Nginx配置文件
- 创建模板文件
/playbook/nginx.j2
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.listen }}
{% if vhost.server_name is defined %}
server_name {{ vhost.server_name }}
{% endif %}
root {{ vhost.root }}
}
{% endfor %}
- 创建剧本配置文件
/playbook/nginx.yml
- hosts: all
remote_user: root
vars:
nginx_vhosts:
- web1:
listen: 8080
root: "/data/web1/"
- web2:
listen: 8080
server_name: "www.web2.com"
root: "/data/web2/"
- web3:
listen: 8080
server_name: "www.web3.com"
root: "/data/web3/"
tasks:
- name: template config to
template:
src: nginx.j2
dest: /etc/nginx/conf.d/nginx3.conf
- 检查并运行剧本
[root@server1 playbook]# ansible-playbook nginx.yaml --syntax-check
[root@server1 playbook]# ansible-playbook nginx.yaml
- 查看由剧本创建的Nginx虚拟主机配置文件(server1/server2/server3)
[root@server2 ~]# cat /etc/nginx/conf.d/nginx3.conf
server {
listen 8080
root /data/web1/
}
server {
listen 8080
server_name www.web2.com
root /data/web2/
}
server {
listen 8080
server_name www.web3.com
root /data/web3/
}
循环迭代
with_items(简单循环)
with_items
是Playbook标准循环,可以用于迭代一个列表或字典,通过{{ item }}
获取每次迭代的值
基于字符串列表
- hosts: all
remote_user: root
tasks:
- name: create rsyncd file
copy: src={{ item }} dest=/tmp/{{ item }}
with_items:
- a
- b
- c
- d
基于字典列表
- hosts: all
remote_user: root
tasks:
- name: add several users
user: name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name: 'testuser1' , groups: 'wheel'}
- { name: 'testuser2' , groups: 'root' }
with_list(嵌套循环)
with_items
和with_list
都可以用作简单循环,但是with_items
不能用在嵌套循环,它会将列表展开拉平然后将元素一个一个输出,而with_list
则不会将嵌套列表拉平
- hosts: all
tasks:
- name: print msg
debug:
msg: '{{ item }}'
with_list:
- [1,2,3]
- [a,b]
# 最后输出[1,2,3],[a,b]
# 如果是with_items则是 1,2,3,a,b
- 还有一个关键字
with_flattened
也是和with_items
一样的,会将嵌套列表拉平 with_list
只会处理最外层的列表,将最外层的列表中的每一项循环处理
with_together(合并对齐)
with_together
会将每个小列表中的值对齐合并
- hosts: all
tasks:
- name: print msg
debug:
msg: '{{ item }}'
with_together:
- [1,2,3]
- [a,b,c]
# 最后输出[1,a],[2,b],[3,c]
# 如果没有写c,那么最后一个会是[3,null]
loop
Ansible 支持使用 loop 关键字对一组项目迭代任务,可以配置循环以利用列表中的各个项目、列表中的各个文件的内容、生成的数字序列或更为复杂任务
基于字符串列表
- hosts: all
tasks:
- name: print users name
debug:
msg: '{{ item }}'
loop:
- tom
- bob
- alice
# 或者
- hosts: all
vars:
users:
- tom
- bob
- alice
tasks:
- name: print users name
debug:
msg: '{{ item }}'
loop: "{{ users }}"
基于字典列表
- hosts: all
tasks:
- name: create users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.group }}"
loop:
- name: jane
group: whell
- name: joe
group: root
# 或者
- hosts: all
vars:
users:
- name: jane
groups: teacher
- name: joe
groups: student
tasks:
- name: create users
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
loop: "{{ users }}"
剧本角色
介绍
角色(roles)是ansible自1.2版本开始引入的新特性,用于层次性,结构化地组织Playbook,分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷地调用它们
角色的使用,让代码复用度提高,可以解决一些运维复杂的场景问题
当有多个角色时,可以将他们分别放于roles目录下的独立子目录中
初始化一个角色:ansible-galaxy init 角色名
目录说明
tasks目录:角色需要执行的主任务文件放置在此目录中,默认的主任务文件名为main.yml
,当调用角色时,默认会执行main.yml文件中的任务;可以将其他需要执行的任务文件通过include
的方式包含在tasks/main.yml
文件中
handlers目录:当角色需要调用handlers时,默认会在此目录中的main.yml文件中查找对应的handlers
defaults目录:角色会使用的变量可以写入到此目录中的main.yml文件中,通常,defaults/main.yml文件中的变量都用于设置默认值
vars目录:角色会使用的变量可以写入到此目录中的main.yml文件中,看到这里你肯定会有疑问
PS:vars/main.yml文件和defaults/main.yml的区别:
defaults/main.yml
文件中的变量的优先级是最低的,而vars/main.yml
文件中的变量的优先级非常高如果只是想提供一个默认的配置,那么可以把对应的变量定义在defaults/main.yml中,如果想要确保在调用角色时,使用的值就是指定的值,则可以将变量定义在vars/main.yml中,因为定义在vars/main.yml文件中的变量的优先级非常高,所以其值比较难以覆盖
meta目录:如果你想要赋予这个角色一些元数据,则可以将元数据写入到meta/main.yml文件中,这些元数据用于描述角色的相关属性,比如作者信息、角色主要作用等等,你也可以在meta/main.yml文件中定义这个角色依赖于哪些其他角色,或者改变角色的默认调用设定
templates目录: 角色相关的模板文件可以放置在此目录中,当使用角色相关的模板时,如果没有指定路径,会默认从此目录中查找对应名称的模板文件
files目录:角色可能会用到的其他文件可以放置在此目录中,比如,当你定义nginx角色时,需要配置https,那么相关的证书文件即可放置在此目录中
安装Httpd
- 准备角色
[root@server1 roles]# ansible-galaxy init apache
- Role apache was created successfully
- 编写角色主配置文件
[root@server1 roles]# vim site.yml
- name: Install Apache web server
hosts: server3
remote_user: root
roles:
- apache
- 编写角色tasks里的main.yaml文件
[root@server1 roles]# vim apache/tasks/main.yml
---
# tasks file for apache
- name: Install httpd # 安装httpd服务
yum:
name: httpd
state: present
- name: start httpd # 启动httpd服务
service:
name: httpd
state: restarted
- name: write conf file # 编写index.html
copy:
content: "hello apache\n"
dest: "/var/www/html/index.html"
- 检查并运行剧本
[root@eagle roles]# ansible-playbook site.yaml --syntax-check
[root@eagle tasks]# ansible-playbook site.yaml
- 验证结果
[root@server1 roles]# curl 192.168.31.30
hello apache
安装Halo博客
Halo是一个博客系统,官方网址https://halo.run/
使用Ansible去自动部署Halo博客系统需要准备的有:Java Nginx Halo
- 准备角色
[root@server1 ~]# cd /etc/ansible/roles/
[root@server1 roles]# ansible-galaxy init halo
- Role halo was created successfully
[root@server1 roles]# ansible-galaxy init java
- Role java was created successfully
[root@server1 roles]# ansible-galaxy init nginx
- Role nginx was created successfully
- 编写剧本主配置文件site.yaml
[root@server1 roles]# vim site.yml
- name: Install Halo Blog
hosts: server2
remote_user: root
strategy: free
pre_tasks:
- name: config nginx repo for centos7
yum_repository:
name: nginx
description: nginx
baseurl: http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck: no
roles:
- nginx
- 查看目录结构
[root@server1 roles]# tree
.
├── halo
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
├── java
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
├── nginx
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
└── site.yml
Nginx角色配置
- 编写Nginx角色主配置剧本文件
[root@server1 roles]# vim nginx/tasks/main.yml
---
# tasks file for nginx
- name: make sure nginx state is installed # 安装Nginx
yum:
name: nginx
state: present
- name: copy halo to nginx config file # 虚拟主机反向代理配置文件
template:
src: halo.conf
dest: "/etc/nginx/conf.d/halo.conf"
- name: make sure nginx service is running # 启动Nginx
service:
name: nginx
state: restarted
- name: make sure port is open # 等待端口启用
wait_for:
port: "{{ nginx_port }}"
- 修改Nginx角色的设定及其依赖关系
[root@server1 roles]# vim nginx/meta/main.yml
dependencies:
# List your role dependencies here, one per line. Be sure to remove the'[]' above,
# if you add dependencies to this list.
- role: java
- role: halo
- 编写Nginx角色的反向代理配置
[root@server1 roles]# vim nginx/templates/halo.conf
upstream halo{
server 127.0.0.1:8090;
}
server {
listen 80;
listen [::]:80;
server_name {{ halo_domain }};
client_max_body_size 1024m;
location / {
proxy_pass http://halo;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Java角色配置
- 编写Java角色主配置剧本文件
[root@server1 roles]# vim java/tasks/main.yml
---
# tasks file for java
- name: install java # 安装Java环境
yum: name=java-11-openjdk state=installed
Halo角色配置
- 编写Halo角色主配置剧本文件
[root@server1 roles]# vim halo/tasks/main.yml
---
# tasks file for halo
- name: get halo # 下载halo源码包
get_url:
url: https://dl.halo.run/release/halo-1.4.11.jar
dest: "{{ halopath }}"
- name: add halo service file # 添加halo服务文件
template:
src: halo.service
dest: "/etc/systemd/system/halo.service"
- name: touch ~/.halo directory # 创建.halo文件夹
file:
path: ~/.halo
state: directory
- name: copy halo config file # 拷贝application.yaml文件
template:
src: application.yaml
dest: "~/.halo/application.yaml"
- name: start halo # 启动halo服务
systemd:
daemon_reload: yes
name: halo
state: started
enabled: yes
- 编写Halo角色变量配置文件
[root@server1 roles]# cat halo/vars/main.yml
---
# vars file for halo
memory: 512m
halo_port: 8090
halopath: /root/halo.jar
halo_domain: 192.168.31.20
nginx_port: 80
- 下载Halo的service文件并配置,下载保存位置需要和halo的tasks中的main.yml编写的位置对应
[root@server1 roles]# wget https://dl.halo.run/config/halo.service -O halo/templates/halo.service
[root@server1 roles]# vim halo/templates/halo.service
[Unit]
Description=Halo Service
Documentation=https://docs.halo.run
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/java -server -Xms256m -Xmx256m -jar /root/halo.jar
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=always
StandOutput=syslog
StandError=inherit
[Install]
WantedBy=multi-user.target
- 下载Halo的application.yaml文件,下载保存位置需要和halo的tasks中的main.yml编写的位置对应
[root@server1 roles]# wget https://dl.halo.run/config/application-template.yaml -O halo/templates/application.yaml
运行与测试
检查并运行site.yaml剧本
[root@server1 roles]# ansible-playbook site.yaml --syntax-check
[root@server1 roles]# ansible-playbook site.yaml
- 进入浏览器查看结果192.168.31.20