💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
本人主要分享计算机核心技术:系统维护、数据库、网络安全、自动化运维、容器技术、云计算、人工智能、运维开发、算法结构、物联网、JAVA 、Python、PHP、C、C++等。
不同类型针对性训练,提升逻辑思维,剑指大厂,非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。
云平台管理之Ansible Playbook 原理与实践
Ansible Playbook 简介
Ansible Playbook 是 Ansible 的核心组件之一,它允许用户以 YAML 格式编写配置文件,用于描述希望远程系统执行的一系列任务。Playbook 提供了一种方式来编排和执行这些任务,确保配置的一致性,或者简单地执行一些常见的任务。Playbook 的使用使得 Ansible 成为一个强大的配置管理工具,尤其适用于需要跨多个系统执行多个操作的情况。
Playbook 的基本原理包括:
- 配置管理:通过 Playbook,可以定义和执行一系列任务,以管理和配置远程系统的状态。
- 编排任务:Playbook 可以对任务进行排序和编排,确保按照正确的顺序执行任务。
- 并行处理:通过异步执行和适当的配置,Playbook 可以实现并行处理多个任务,提高工作效率。
- 权限管理:通过使用 become 功能,Playbook 可以以不同的用户权限执行任务,满足不同的系统需求。
实践方面,使用 Playbook 可以大大简化复杂的应用部署和配置管理过程。通过编写 Playbook,可以清晰地定义部署流程、配置变更、系统状态检查等任务,并通过 Ansible 引擎执行这些任务。Playbook 的使用不仅提高了工作效率,还减少了人为错误的可能性,使得 IT 基础设施的管理更加可靠和高效。
Playbooks是Ansible的配置,部署和编排语言。playbook是由一个或多个play组成的列表,play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联合起来按事先编排的机制完成某一任务。
Ansible Palybook的编写是基于YAML语言的。YAML语言,即Yet Another Markup Language,是一种能被计算机直接识别的标记语言,同时也方便人的阅读,且方便和脚本语言交互。但是,YAML的配置即为严格,在配置时必须注重空格的数量。总的来看,YAML语言特性如下:
- 1、可读性强
- 2、和脚本语言的交互性好
- 3、使用实现语言的数据类型
- 4、一致的信息模型
- 5、易于实现
- 6、可以基于流来处理
- 7、可扩展性强
Ansible Playbook 组件
Ansible的Playbook有以下组件:
- Target
- 定义playbook的远程主机组,即控制的下游设备信息
- Variable
- 定义Playbook所使用的变量。
- Task
- 定义Playbook控制下游设备要执行的命令。
- Handler
- 定义在Palybook在Task执行完毕后要调用的任务。
Ansible Playbook各组件参数
Ansible的上述组件的参数如下:
Target常用参数
代码语言:javascript
hosts #定义Ansible用户控制的下游设备
remote_user #定义执行Ansible设置的用户
sudo #设置为yes时,执行任务时使用root权限
sudo_user #指定sudo的普通用户
connection #默认基于ssh链接客户端
gather_facts #获取远程主机facts基础信息
Variable常用参数
vars #定义变量
vars_files #指定变量文件
vars_prompt #用户交互模式自定义变量
setup #通过远程gather_facts获取的信息
Task常用参数
name #任务的名称,在Ansible运行的过程中起到提示的作用,会打印在屏幕上
action #Ansible控制下游设备的命令,通过Ansible的各个模块来进行控制
template #Ansible控制下游设备的模板
handler #定义一个调用,该调用在Handler处被定义,在所有的Task结束后被执行。
Playbook基本语法
playbook使用yaml语法格式,后缀可以是yaml,也可以是yml。
[root@localhost ~]$ vim test.yaml
--- #表示文档开始
- hosts: test # "- "表示一个块序列的节点,注意:横杠后面有空格,可以写主机名,主机组名,多个使用逗号隔开
remote_user: root #指定在进行远程操作时使用root用户进行操作
tasks: #使用tasks关键字指明要进行操作的任务列表,之后的行都属于tasks键值对中的值。
- name: Ping #每个任务都以"- "开头,每个任务都有自己的名字,任务名使用name关键字进行指定
ping: #ansible模块
- name: make directory test #第二个任务使用file模块,使用file模块时,指定了path参数与state参数的值。
file: #ansible模块
path: /data/test #模块的参数
state: directory
--- #标记文件的开始
- hosts: webservers #指定该playbook在哪个服务器上执行
vars: #表示下面是定义的变量,
http_port: 80 #变量的形式,key: value,这里http_port是变量名,80是值
max_clients: 200
remote_user: root #指定远程的用户名,这里缩进和vars保持了一致,说明变量的代码块已经结束。
tasks: #下面构成playbook的tasks,每个task都有 - name: 开始,name指定该任务的名称。
- name: ensure apache is at the latest version #指定该任务的名称。
yum: pkg=httpd state=latest #yum说明要是用的模板名称,后面指定对应的参数,这两行结合起来就相当于一个shell命令。
- name: write the apache config file #每个task之间可以使用空行来做区分。
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
#需要说明的是缩进的意义和python中缩进的意义是一样,是来区分代码块的。
检测语法
[root@localhost ~]$ ansible-playbook --syntax-check /path/to/playbook.yaml
测试运行
[root@localhost ~]$ ansible-playbook -C /path/to/playbook.yaml
运行
[root@localhost ~]$ ansible-playbook /path/to/playbook.yaml
示例:Playbook 创建用户
[root@localhost ~]$ vim sysuser.yml
---
- hosts: all
remote_user: root
tasks:
- name: create mysql user
user: name=mysql system=yes uid=36
- name: create a group
group: name=httpd system=yes
Playbook示例 安装nginx服务
[root@localhost ~]$ vim nginx.yml
- hosts: all
remote_user: root
tasks:
- name: add group nginx
user: name=nginx state=present
- name: add user nginx
user: name=nginx state=present group=nginx
- name: Install Nginx
yum: name=nginx state=present
- name: Start Nginx
service: name=nginx state=started enabled=yes
tags: 添加标签
可以指定某一个任务添加一个标签,添加标签以后,想执行某个动作可以做出挑选来执行,多个动作可以使用同一个标签。
示例:httpd.yml
代码语言:javascript复制
- hosts: websrvs
remote_user: root
tasks:
- name: Install httpd
yum: name=httpd state=present
tage: install
- name: Install configure file
copy: src=files/httpd.conf dest=/etc/httpd/conf/
tags: conf
- name: start httpd service
tags: service
service: name=httpd state=started enabled=yes
ansible-playbook -t install,conf httpd.yml 指定执行install,conf 两个标签
handlers
handlers和notify结合使用触发条件
Handlers 实际上就是一个触发器是task列表,这些task与前述的task并没有本质上的不同,用于当关注的资源发生变化时,才会采取一定的操作。
Notify此action可用于在每个play的最后被触发
这样可避免多次有改变发生时每次都执行指定的操作,仅在所有的变化发生完成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的操作
在系统中,我们修改了服务器的配置文件,这时候就需要重启操作服务,就可以使用到handlers。配合 notify使用。
代码语言:javascript
---
- hosts: control-node
remote_user: root
vars:
- pkg: httpd
- name: template configuration file
template: src=template.j2 dest=/etc/foo.conf #修改了配置文件然后依次启动memcached和apache服务。
notify: #使用notify来声明引用handlers。
- restart memcached
- restart apache
handlers: #下面定义了两个handlers
- name: restart memcached
service: name=memcached state=restarted
- name: restart apache
service: name=apache state=restarted
在使用handlers的过程中,有以下几点需要注意:
- handlers只有在其所在的任务被执行时,都会被运行;
- handlers只会在Play的末尾运行一次;如果想在一个Playbook的中间运行handlers,则需要使用meta模块来实现,例如:- meta: flush_handlers。
- 如果一个Play在运行到调用handlers的语句之前失败了,那么这个handlers将不会被执行。我们可以使用mega模块的–force-handlers选项来强制执行handlers,即使在handlers所在Play中途运行失败也能执行。
register 和when
register 用于注册一个变量,保存命令的结果(shell或command模块),这个变量可以在后面的task、when语句或模板文件中使用。
代码语言:javascript
- shell: /bin/pwd
register: pwd_result
debug:
#msg: "{{ pwd_result }}" # 输出全部信息
#msg: "{{ pwd_result.cmd }}" # 引用方式一
msg: "{{ pwd_result['stdout_lines'] }}" # 引用方式二
when 相当于shell脚本里的if 判断,when语句就是用来实现这个功能的,它是一个jinja2的语法,但是不需要双大括号,用法很简单。
- name: echo date #执行了一个 date 命令,register 关键字将 date 命令的输出存储到 date_output 变量名
command: date
register: date_output
- name: echo date_output #用 when 对关键字对分析后的进行判断,如果匹配,则执行这个 task,不匹配就不执行
command: echo "30"
when: date_output.stdout.split(' ')[2] == "30"
这里第 1 个 task 是执行了一个 date 命令,register 关键字将 date 命令的输出存储到 date_output 变量名。第 2 个 task 对输出进行分析,并使用 when 对关键字对分析后的进行判断,如果匹配,则执行这个 task,不匹配就不执行。这里要重点说下的,因为 register 获取到的输出内容都是字符串,而 ansible 又是 python 写的,你可以使用 python 字符串的方法对其做处理,比如本文中使用的 split,还可以使用 find 方法。
示例
tasks:
- name: "shutdown RedHat flavored systems"
shell: /sbin/shutdown -h now
when: ansible_os_family == "RedHat" #当系统属于红帽系列,执行shell模块
循环
标准循环关键字:”with_items” ,对迭代项的引用,固定变量名为"item”,使用with_item属性给定要迭代的元素。
[root@localhost ~]$ cat xh.yml
---
- hosts: all
gather_facts: no
tasks:
- name: dispaly list
debug: msg="{{item}}"
with_items:
- one
- two
- three
- four
[root@localhost ~]$ ansible-playbook -i hosts xh.yml
PLAY [all] *************************************************************************************************************
TASK [dispaly xunhuan] *************************************************************************************************
ok: [192.168.52.129] => (item=one) => {
"changed": false,
"item": "one",
"msg": "one"
}
ok: [192.168.52.129] => (item=two) => {
"changed": false,
"item": "two",
"msg": "two"
}
ok: [192.168.52.129] => (item=three) => {
"changed": false,
"item": "three",
"msg": "three"
}
ok: [192.168.52.129] => (item=four) => {
"changed": false,
"item": "four",
"msg": "four"
}
安装一堆软件包。
---
- hosts: localhost
tasks:
- yum: name="{{item}}" state=installed
with_items:
- pkg1
- pkg2
- pkg3
它会一个一个迭代到特殊变量"{{item}}"处。
loop
等价于with_list,从名字上可以知道它是遍历数组(列表)的,所以在loop指令中,每个元素都以列表的方式去定义。列表有多少个元素,就循环执行file模块多少次,每轮循环中,都会将本次迭代的列表元素保存在控制变量 item中。
安装多个软件
tasks:
- name: "Install Packages"
yum: name={{ item }} state=latest
loop:
- httpd
- mysql-server
- php
模板templates
Jinja2语言,使用字面量,有下面形式
字符串:使用单引号或双引号
数字:整数,浮点数
列表:[item1, item2, …]
元组:(item1, item2, …)
字典:{key1:value1, key2:value2, …}
布尔型:true/false
算术运算:+, -, *, /, //, %, **
比较操作:==, !=, >, >=, <, <=
逻辑运算:and,or,not
流表达式:For,If,When
template 的使用
templates是ansible的一个模块,其功能是根据模板文件动态生成配置文件,templates文件必须存放于templates目录下,且命名为".j2"结尾,yaml/yml文件需要和templates目录平级,这样我们在yml文件中调用模板的时候,就不需要写模板文件的路径,否则需要描述模板文件的路径,因为template模块会自动去找templates目录下的模板文件.
./
├── temnginx.yml
└── templates
└── nginx.conf.j2
template示例
示例:利用template 同步nginx配置文件,准备templates/nginx.conf.j2文件。
vim temnginx.yml
- hosts: websrvs
remote_user: root
tasks:
- name: template config to remote hosts
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
ansible-playbook temnginx.yml
for循环使用
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.listen }}
}
{% endfor %}
if单分支选择使用
{% if vhost.server_name is defined %}
server_name {{ vhost.server_name }}
{% endif %}
if多分支选择使用
{%if vhost.port is undefined %}
http_port=80
{%elif vhost.port == 81%}
http_port=81
{%else%}
http_port = 83
{%endif%}
单行注释
{#% for i in list %#}
roles
Roles介绍
ansible自1.2版本引入的新特性,用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令引入即可。简单来讲,roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷的include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。主要使用场景代码复用度较高的情况下。
Roles目录结构
各目录含义解释
roles: <--所有的角色必须放在roles目录下,这个目录可以自定义位置,默认的位置在/etc/ansible/roles
project: <---具体的角色项目名称,比如nginx、tomcat、php
files: <--用来存放由copy模块或script模块调用的文件。
templates: <--用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件。
tasks: <--此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件。
main.yml
handlers: <--此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作。
main.yml
vars: <--此目录应当包含一个main.yml文件,用于定义此角色用到的变量。
main.yml
defaults: <--此目录应当包含一个main.yml文件,用于为当前角色设定默认变量。
main.yml
meta: <--此目录应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系。
main.yml
roles/example_role/files/ #所有文件,都将可存放在这里
roles/example_role/templates/ #所有模板都存放在这里
roles/example_role/tasks/main.yml #主函数,包括在其中的所有任务将被执行
roles/example_role/handlers/main.yml #所有包括其中的 handlers 将被执行
roles/example_role/vars/main.yml #所有包括在其中的变量将在roles中生效
roles/example_role/meta/main.yml #roles所有依赖将被正常登入
Roles示例
通过ansible roles安装配置httpd服务,此处的roles使用默认的路径/etc/ansible/roles
。
#在ansible目录下面,建立roles目录
#修改配置文件,使系统能够读取roles目录
[root@ansible ~]$ cat /etc/ansible/ansible.cfg | grep roles
# additional paths to search for roles in, colon separated
#roles_path = /etc/ansible/roles #roles默认路径
# by default, variables from roles will be visible in the global variable
创建role的步骤:
- (1) 创建以roles命名的目录。
- (2) 在roles目录中分别创建以各角色名称命名的目录,如webservers等。
- (3) 在每个角色命名的目录中分别创建files、handlers、meta、tasks、templates和vars目录;用不到的目录可以创建为空目录,也可以不创建 。
- (4) 在playbook文件中,调用各角色。
实验: 创建httpd角色
#创建roles目录
mkdir roles/{httpd,mysql,redis}/tasks -pv
mkdir roles/httpd/{handlers,files}
#查看目录结构
tree roles/
roles/
├── httpd
│ ├── files
│ ├── handlers
│ └── tasks
├── mysql
│ └── tasks
└── redis
└── tasks
#创建目标文件
cd roles/httpd/tasks/
touch install.yml config.yml service.yml
#vim install.yml
- name: install httpd package
yum: name=httpd
vim config.yml
- name: config file
copy: src=httpd.conf dest=/etc/httpd/conf/ backup=yes
vim service.yml
- name: start service
service: name=httpd state=started enabled=yes
#创建main.yml主控文件,调用以上单独的yml文件,
main.yml定义了谁先执行谁后执行的顺序
vim main.yml
- include: install.yml
- include: config.yml
- include: service.yml
#准备httpd.conf文件,放到httpd单独的文件目录下
cp /app/ansible/flies/httpd.conf ../files/
#创建一个网页
vim flies/index.html
<h1> welcome to weixiaodong home <\h1>
#创建网页的yml文件
vim tasks/index.yml
- name: index.html
copy: src=index.html dest=/var/www/html
#将网页的yml文件写进mian.yml文件中
vim mian.yml
- include: install.yml
- include: config.yml
- include: index.yml
- include: service.yml
#在handlers目录下创建handler文件mian.yml
vim handlers/main.yml
- name: restart service httpd
service: name=httpd state=restarted
#创建文件调用httpd角色
cd /app/ansidle/roles
vim role_httpd.yml
---
# httpd role
- hosts: appsrvs
remote_user: root
roles: #调用角色
- role: httpd
#查看目录结构
tree
.
httpd
├── files
│ ├── httpd.conf
│ └── index.html
├── handlers
│ └── main.yml
└── tasks
├── config.yml
├── index.yml
├── install.yml
├── main.yml
└── service.yml
ansible-playbook role_httpd.yml
创建目录
[root@ansible ~]$ cd /etc/ansible/roles/
# 创建需要用到的目录
[root@ansible roles]$ mkdir -p httpd/{handlers,tasks,templates,vars}
[root@ansible roles]$ cd httpd/
[root@ansible httpd]$ tree .
.
├── handlers
├── tasks
├── templates
└── vars
4 directories, 0 file
变量文件准备vars/main.yml
[root@ansible httpd]$ vim vars/main.yml
PORT: 8088 #指定httpd监听的端口
USERNAME: www #指定httpd运行用户
GROUPNAME: www #指定httpd运行组
配置文件模板准备templates/httpd.conf.j2
# copy一个本地的配置文件放在templates/下并已j2为后缀
[root@ansible httpd]$ cp /etc/httpd/conf/httpd.conf templates/httpd.conf.j2
# 进行一些修改,调用上面定义的变量
[root@ansible httpd]$ vim templates/httpd.conf.j2
Listen {{ PORT }}
User {{ USERNAME }}
Group {{ GROUPNAME }}
任务剧本编写,创建用户、创建组、安装软件、配置、启动等
# 创建组的task
[root@ansible httpd]$ vim tasks/group.yml
- name: Create a Startup Group
group: name=www gid=60 system=yes
# 创建用户的task
[root@ansible httpd]$ vim tasks/user.yml
- name: Create Startup Users
user: name=www uid=60 system=yes shell=/sbin/nologin
# 安装软件的task
[root@ansible httpd]$ vim tasks/install.yml
- name: Install Package Httpd
yum: name=httpd state=installed
# 配置软件的task
[root@ansible httpd]$ vim tasks/config.yml
- name: Copy Httpd Template File
template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
notify: Restart Httpd
# 启动软件的task
[root@ansible httpd]$ vim tasks/start.yml
- name: Start Httpd Service
service: name=httpd state=started enabled=yes
# 编写main.yml,将上面的这些task引入进来
[root@ansible httpd]$ vim tasks/main.yml
- include: group.yml
- include: user.yml
- include: install.yml
- include: config.yml
- include: start.ym
编写重启httpd的handlers,handlers/main.yml
[root@ansible httpd]$ vim handlers/main.yml
# 这里的名字需要和task中的notify保持一致
- name: Restart Httpd
service: name=httpd state=restarted
编写主的httpd_roles.yml文件调用httpd角色
[root@ansible httpd]$ cd ..
[root@ansible roles]$ vim httpd_roles.yml
---
- hosts: all
remote_user: root
roles:
- role: httpd #指定角色名称
整体的一个目录结构查看
[root@ansible roles]$ tree .
.
├── httpd
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ ├── config.yml
│ │ ├── group.yml
│ │ ├── install.yml
│ │ ├── main.yml
│ │ ├── start.yml
│ │ └── user.yml
│ ├── templates
│ │ └── httpd.conf.j2
│ └── vars
│ └── main.yml
└── httpd_roles.yml
5 directories, 10 files
测试playbook语法是否正确
[root@ansible roles]$ ansible-playbook -C httpd_roles.ym
上面的测试没有问题,正式执行playbook
[root@ansible roles]$ ansible-playbook httpd_roles.ym
Ansible性能调优
相比于其他的自动化配置工具,Ansible的一个突出特性就是它是基于SSH链接对下游设备进行控制的,这样做的突出好处就是方便,下游设备不需要安装客户端软件。但是这也不可避免的带来一个问题,即Ansible的执行速度较慢。并且,随着Ansible控制设备的增多,Ansible的执行速度会越来越慢。
关于Ansible执行速度的问题,尽管是Ansible的硬伤,但是我们还是可以对其进行部分的优化,尽量的加快Ansible的执行速度。对Ansible的优化可以有两个思路,一个是优化SSH链接,使得SSH的传输速度变快。
另一个如下图所示:
每次Ansible Playbook在执行时,都会收集下游设备的信息,这个过程通常要耗费较长的时间。因此,我们可以考虑使用Redis对这些信息进行缓存,从而加快收集信息的速度,如果业务环境允许,我们也可以直接控制Ansible设备跳过该步骤。
Ansible SSH链接调优
SSH关闭密钥检测
在默认情况下,以SSH登录远程设备时,该设备会检查远程主机的公钥,并且将该公钥记录在~/.ssh/known_hosts文件中,当下次该主机访问时,OpenSSH会核对公钥。如果公钥不同,则OpenSSH会发出警告,如果公钥相同,则OpenSSH则会提示输入密码。
SSH对主机公钥的检查是根据StrictHostKeyChecking变量来设定的,StrictHostKeyChecking的检查级别包括:no(不检查),ask(是否检查要询问),yes(每次都检查),False(关闭检查)。
我们可以在Ansible的配置文件中defaults模块下加入如下代码:
host_key_checking = False
加入后,配置文件如下所示:
这样,Ansible就可以关闭密钥检测了。
OpenSSH链接优化
在使用OpenSSH服务时,默认情况下服务器端会根据客户端的IP地址进行DNS反向解析,得到客户端的主机名,然后根据获取到的主机名再次进行DNS查询得到IP地址,比较这两个IP地址是否一样。这样做可以在一定程度上提高安全性,但是却会浪费时间,因此,我们可以通过关闭这一特性,来实现加速SSH链接速度的目的。 关闭该特新需要进入到/etc/ssh/sshd_config目录下,找到UseDNS的参数,将其修改为no,修改后的配置文件如下所示:
之后,重启SSHD服务即可生效。
SSH pipelining链接加速
SSH的pipelining是另一个加速Ansible执行速度的方法。在Ansible的设置中,SSH的pipelining功能时默认关闭的,这是为了兼容不同的sudo配置,主要是requieretty选项。因此,如果我们不需要在Ansible的控制中使用sudo选项,可以关闭这一选项以加快SSH链接速度。
如果要关闭这一项,可以打开Ansible的配置文件/etc/ansible/ansbile.cfg,将pipelining = False改为True即可,修改后的配置文件如下所示:
Ansible Facts调优
关闭Gather Facts
为了减少Ansible在收集客户端信息时的时间,我们首先想到的就是直接删除这一选项。要删除这一步骤,我们可以在palybook文件中添加一行:
gather_facts: no
添加后的Playbook文件如下所示:
这样,我们在执行该Playbook时,就不会再次进行gather_facts的步骤了,结果如下所示:
将Facts信息存入Redis缓存
除了删除这一步之外,我们还可以考虑将客户端信息写入内存,以加快信息查询。