随着服务器数量的增多,管理的难度越来越大,再简单的指令如果需要在成百上千台服务器上执行也意味着巨大的工作量。伴随着节点数量不断上升的量变,原始的节点管理模式在或主动或被动地追求质变,这种质变其实就是传统运维向自动化、智能化的发展方向,而Ansible,就是红帽公司研发的一款开源的IT自动化引擎。
Ansible学习思维导图
认识Ansible
初学者可以简单地将Ansible理解为一款批量部署工具。所谓批量部署,就是将指令在多个节点上批量执行,运维工程师可以借助批量部署工具脱离对大量节点手动、重复进行部署的繁重劳动,因此,掌握一款批量部署工具对运维来说是非常重要的。
Ansible特点
Ansible之所以发展成为最受欢迎的批量部署工具,主要是因为它具有以下特点:
- 使用简单,学习曲线平滑;
- 无需客户端,自动推送,体量十分轻量;
- 配置简单,扩展性强,可以轻松实现分布式扩展;
- 支持API和自定义模块,兼容各种语言编写的模块;
- 结构框架十分清晰,二次开发难度小;
- 具备幂等性。
Ansible功能组成
Ansible的组成部件大致可以分为以下几块:
- Ansible:核心组件,命令执行工具;
- Modules:Ansible执行的任何任务都不由Ansible自己完成,而是通过调用模块来实现的。模块分为核心模块与扩展模块两种,前者是红帽公司和开发者编写的适用于大多数使用者的功能实现,并且经过验证没有bug或漏洞,因此集成到了Ansible的安装包中,而后者是使用者基于自己的需求而开发的功能实现,个人量身定做,也可以分享给其他使用者;
- Plugins:插件是对模块功能的补充;
- Playbooks:剧本是Ansible专有的任务编排方式,使用YAML语法,帮助使用者调度多个任务;
- Inventory:主机群定义被管理的主机清单。
Ansible架构模型
Ansible与同类产品对比
Ansible VS SaltStack
Ansible和SaltStack都是目前比较流行的自动化运维工具,二者有许多共同点:
- 基于Python开发;
- 兼容不同的系统环境;
- 具有优秀的二次开发特性;
- 既支持AD-Hoc使用模式也支持YAML语法编排的脚本文件;
- 返回结果以JSON形式输出。
虽然Ansible和SaltStack有很多形似之处,但是它们也存在着差异性。
响应速度
Ansible的管理节点和执行节点通过标准SSH进行通信,而SaltStack的master节点和minion节点通过ZeroMQ来进行通信,因此SaltStack的节点响应速度要比Ansible快很多,在性能测试中,这个差距可能会达到十倍以上。
虽然Ansible在响应速度上可以说被SaltStack碾压,但是通过对Ansible的功能配置,新一代的Ansible已经可以将这个差距缩小到2倍左右,基本上不会影响使用者的工作。
安全性
Ansible和SaltStack都需要和执行节点进行连接,它们最大的安全问题就是MITM攻击,通过伪装成管理节点和执行节点进行通信,从而进行攻击。
Ansible使用标准SSH连接传输数据,不需要在远程主机上启动守护进程,并且标准SSH数据传输本身就是加密传输,远程主机不容易在这样的情况下被攻击到。
而SaltStack使用ZeroMQ进行数据传输,ZeroMQ本身的数据传输不支持加密,虽然SaltStack可以通过使用AES数据加密方法来对数据进行加密传输,但是SaltStack的minion节点以守护进程的方式运行导致会远端暴露很多容易被攻击的点。
自身运维
Ansible管理节点和执行节点之间的通信通过标准SSH进行,执行节点上只需要运行SSH进程就可以保证Ansible的正常调用,而SSH是服务器上一般都会启动的进程,所以只需要保证Ansible管理节点的正常运行即可,因此Ansible对运维不会增加更多的成本。
SaltStack需要在Master和Minion主机启动守护进程,一旦哪个节点的守护进程死亡SaltStack的管理就会出现异常,因此SaltStack其实是有一定的运维成本的。
使用语法
语言没有优劣,但是个人感觉Ansible的Playbook语法要比SaltStack的State语法具有更强的可读性。
Ansible VS Puppet
从市场趋势来说Puppet的使用者在逐年减少,主要是因为相比Ansible和SaltStack有着较为明显的缺点:
- 架构模式:与SaltStack相似,都采用了server/agent的架构,即每个执行节点上都必须安装一个agent组件来启动守护进程,但是Puppet与SaltStack有一点不同,Puppet的管理节点不能主动向执行节点发送指令,而是被动地等待agent定期向server拉取需要执行的配置;
- 模块组成:Puppet并没有集成好的自动化模块可以直接使用,而是将一些基本组件(资源、类、定义、文件、模板等)自己整合成模块;
- 语言基础:Puppet使用Ruby编写,语法格式采用基于Ruby的DSL语言,而模板采用Ruby的ERB语言,因此受众不如Ansible广。
Ansible部署
安装Ansible
Ansible的安装比较简单,因为没有agent端,所以安装工作只需要在管理节点完成。
因为Ansible可以很简单地从源码运行,且不必在执行节点上安装任何软件,很多使用者会跟进使用开发版本。Ansible一般每两个月出一个发行版本,小bug一般在下一个发行版本中修复,并在稳定分支中做backports,大bug会在必要时出一个维护版本,不过这种情况非常少见。
在安装Ansible之前要明确一点,Ansible基于Python2构建,因此管理节点上需要有Python 2.6以上版本的Python2开发环境和pip(安装和管理Python包的工具),而Python2.X和Python3.X之间有许多不同之处,现阶段的Ansible还不能支持Python3。
pip安装
pip install ansible
源码安装
使用前需确保环境中包含Git
git clone https://github.com/ansible/ansible
cd ansible/
source hacking/env-setup -q
yum安装
使用前需确认yum源中包含Ansible的rpm包
yum -y install ansible
Ansible配置
Ansible的配置文件主要包括下面两个
/etc/ansible/hosts
/etc/ansible/ansible.cfg
我们对Ansible的配置也基于这两个文件.
主机管理配置
基于密码认证管理主机群
接受Ansible管理的主机信息会集中在/etc/ansible/hosts文件中,可以通过如下方式配置主机群(不推荐):
cat >> /etc/ansible/hosts <<EOF
[team]
192.168.10.102 ansible_ssh_user=root ansible_ssh_pass=123 ansible_ssh_port=22
192.168.10.103 ansible_ssh_user=root ansible_ssh_pass=123 ansible_ssh_port=22
EOF
配置主机群时可以制定几项参数:
- ansible_ssh_host:执行节点的主机地址;
- ansible_ssh_port:连接执行节点的端口,默认22;
- ansible_ssh_user:连接执行节点使用的默认用户;
- ansible_ssh_pass:连接执行节点的默认用户密码;
- ansible_ssh_connection:执行节点连接类型,可以是local/ssh/paramiko;
- ansible_ssh_private_key_file:连接执行节点的ssh私钥;
- ansible_*_interpreter:指定采用非Python的其他脚本语言,如Ruby、Perl等。
基于密钥认证管理主机群
密码认证的方式既繁琐又不安全,所以建议使用主机互信的方式来管理主机群,这样的配置文件就比较简洁了。
cat >> /etc/ansible/hosts <<EOF
[team]
192.168.10.10[2:3]
EOF
如果管理节点与执行节点间没有配置主机互信,就要在管理节点上执行以下命令:
ssh-keygen -t rsa
ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.88.102
ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.88.103
Ansible支持管理多个主机群,但是要注意主机群名称的区分。
易用性配置
Ansible的易用性配置主要针对/etc/ansible/ansible.cfg文件内的参数。
开启SSH长连接
设置开启长连接后,通过SSH连接的设备会在ansible/cp/下生成一个socket文件,通过netstat查看会发现有一个ESTABLISHED状态的TCP连接一直保持着与执行节点的连接。
将
#ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
改为
ssh_args = -C -o ControlMaster=auto -o ControlPersist=5d
开启流水线
Ansible的执行流程中有一步是将本地Python脚本PUT到执行节点上,当开启了pipelining后,这个过程将会在SSH会话中进行。
将
#pipelining = False
改为
pipelining = True
开启缓存
Ansible在执行节点执行命令的第一步默认是收集节点的facts信息,配置好了缓存之后就不必每次执行指令都收集一遍这些信息了。
在
#gathering = implicit
下面追加
gathering = smart
fact_caching_timeout = 86400
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
修改通信的默认并行进程数
将
#forks = 5
改成
forks = 50
使用前测试
全部配置完后检查一下部署节点是否能够与被管理主机连通,Ansible专门有一个模块ping来检测这个。
ansible all -m ping
成功连通的主机会传来pong的反馈,并且如果终端支持彩色显示的话正常的反馈是绿色的,Ansible输出默认有四种颜色:绿色、黄色、红色和紫色,通过颜色就可以判断指令执行结果:
- 绿色:指令执行成功,并没有对目标主机造成实质性改变;
- 黄色:指令执行成功,对目标主机造成实质改变;
- 红色:指令执行失败;
- 紫色:指令能够成功执行,但与Ansible设计者想要其执行的方式有些微妙的差异,因此给出警告信息。
如果这一步的输出信息中所有执行节点都是绿色就可以了。
AD-Hoc命令
Hoc命令行的使用类似bash命令,根据使用的模块不同来执行不同的指令。
使用须知
模块信息
查看模块列表
ansible-doc -l
可以看到Ansible集成的模块非常之多。
查看具体模块使用方法
ansible-doc [module_name]
命令格式
Hoc命令行是一种比较基础的Ansible使用方式,基本格式为:
ansible [主机群] -m [模块] -a '[模块参数指定]' [其他参数]
其中-m参数和-a参数是必不可少的,至于其他参数则根据情况自行选择。
常用模块
command
command模块是Ansible的默认使用模块,可以运行远程权限范围内所有的shell命令,但是不支持管道符。
ansible all -m command -a 'cmd'
shell
shell模块的功能基本与command一致,并且支持管道符。
ansible all -m shell -a 'cat /etc/passwd |wc -l'
script
script模块的作用是在远程主机上执行控制端上的脚本。
ansible all -m script -a '~/test.sh'
file
file模块用于配置文件属性。
常用选项:
- path:定义文件或目录的路径;
- owner:定义文件或目录的属主;
- group:定义文件或路径的属组;
- mode:定义文件或路径的权限;
- state:定义状态。
关于state可以定义的状态,常用的有以下几种:
- absent:删除;
- directory:创建目录;
- touch:创建文件;
- link:创建链接。
ansible all -m file -a 'path=/tmp/fstab state=touch'
ansible all -m file -a 'path=/tmp/fstab state=absent'
ansible all -m file -a 'src=/etc/fstab dest=/tmp/fstab state=link'
yum
yum模块用于yum方式安装应用。
常用选项:
- name:定义要操作的应用,也可以是rpm包的路径;
- state:定义状态,常用的有present安装、absent卸载或latest更新。
ansible all -m yum -a 'name=ansible state=present'
cron
cron模块用于配置计划任务。
常用选项:
- name:定义任务描述信息(不可省略);
- day|hour|minute|month|weekday:定义计划执行的时间;
- job:定义计划任务主体;
- state:定义状态,常用的有present创建或absent删除。
ansible all -m cron -a 'name=give_me_time minute=*/30 job="echo hello|mail -s `date` freedom17803211561@163.com" state=present'
copy
copy模块用于复制文件到远程主机。
常用选项:
- src:本主机的源文件路径;
- dest:远程主机的目标路径;
- backup:覆盖文件之前是否备份;
- owner:定义文件的属主;
- group:定义文件的属组;
- mode:定义文件的权限。
ansible all -m copy -a 'src=/etc/fstab dest=/tmp/fstab backup=yes'
user
user模块用于进行远程主机的用户管理。
常用选项:
- name:定义用户名;
- uid:定义用户uid;
- password:定义用户密码;
- group:定义用户组;
- createhome:是否创建用户家目录;
- home:指定家目录;
- system:是否创建为系统用户;
- state:定义状态;
- shell:指定用户shell类型。
ansible all -m user -a 'name=user1 uid=1050 group=group1 shell=/sbin/nologin'
group
group模块用于进行远程主机的用户组管理,一般与user模块配合使用,用来弥补user模块欠缺的功能。
ansible host1 -m group -a 'name=group1 gid=1100'
service
service模块用于管理远程主机上运行的服务。
常用选项:
- name:指定服务名称;
- state:指出服务将采取的动作,常用的有started、stopped、restarted、reloaded等;
- enabled:指定服务是否采用开机自启。
ansible all -m service -a 'name=nginx enabled=yes state=restarted'
stat
stat模块用于获取远程主机上的某个文件信息,包括atime/ctime/mtime/md5/uid/gid等。
ansible all -m stat -a 'path=/etc/syctl.conf'
Playbook
Playbook就相当于Ansible中用YAML语法写的脚本。
YAML语法
YAML语法和其他高阶语言类似,主要有两个特点:
- YAML语法必须采用严格的语法格式,即空格的占位、连接符号的选择、包含与被包含关系都必须一点不差;
- YAML语法其实就是由一个个的key/value组成,因此YAML语法非常适合存储键值对形式的数据。
简单介绍一下YAML语法的格式:
- 列表用- 表示(-后面一定有一个空格);
- 键值对用: 表示(:后面一定有一个空格);
- 键值对内部可以嵌套更多的键值对,但是嵌套要缩进两个空格;
- YAML文件扩展名通常为.yaml或.yml。
首先来写一个Playbook的开头:
---
- hosts: all
remote_user: root
这个头部声明了Playbook要管理的主机群和管理使用的用户,是每一个完整的Playbook都要有的内容。
Playbook核心组件
Playbook包括五个核心组件。
- tasks:任务
- variables:变量
- templates:模板
- handles:触发器
- roles:角色
tasks
写tasks时要注意以下几点:
- tasks和hosts处于列表中的同级,所以要与hosts对齐;
- tasks下面的多个任务是一个列表,彼此之间是同级,所以要对齐;
- 属于同一个hosts下的每个任务都要有自己唯一的name。
---
- hosts: all
remote_user: root
tasks:
- name: nginx_install
yum: name=nginx state=present
- name: nginx_start
service: name=nginx state=restarted enabled=true
variables
YAML文件中的变量表示为{{variables_name}},常用的定义变量方式有三种:
写入Playbook
将变量写入Playbook。
- 变量属于当前头部,一旦新开头部变量全部失效;
- 写在变量下面的任务可以调用变量,写在变量上面的任务不能调用变量。
---
- hosts: all
remote_user: root
vars:
- f1: /var/www/html/file1
- f2: /var/www/html/file2
tasks:
- name: f1_touch
shell: echo '123' > {{f1}}
- name: f2_touch
shell: echo '456' > {{f2}}
写入/etc/ansible/hosts文件
将变量写入/etc/ansible/hosts文件。
- 变量属于某一主机群,只有调用该主机群才能使用此变量;
- 写入/etc/ansible/hosts文件后只要调用该主机群变量就能生效。
[host1]
192.168.10.102
192.168.10.103
[host1:vars]
f1=/var/www/html/file1
f2=/var/www/html/file2
注册变量
注册变量是将之前任务的输出结果赋予变量。
- 注册变量只在变量注册任务的后面任务中能够调用;
- 调用变量时变量名后面要加上.stdout。
---
- hosts: all
remote_user: root
tasks:
- name: date_obtain
command: date
register: date
- name: date_echo
shell: echo {{date.stdout}} > /var/www/html/index.html
templates
模板要配合变量使用,最终要实现的是不同主机获得内容存在差异的文件。
---
- hosts: all
remote_user: root
tasks:
- name: ip_obtain
shell: ifconfig |grep 'broadcast' |awk '{print $2}'
register: ip
- name: html_with_ip
template: src=/tmp/httpd/index.html dest=/var/www/html/index.html
本地的模板文件直接调用需要的变量。
handlers
触发器是指若前一个任务成功执行则触发后一个任务的执行动作,若前一个任务执行失败则后一个任务也不执行。使用触发器一定要理清任务是否成功执行,关键点在于结合Ansible的幂等性判断。
---
- hosts: all
remote_user: root
tasks:
- name: httpd.conf_copy
copy: src=/tmp/httpd/httpd.conf dest=/etc/httpd/httpd.conf
notify:
- httpd_restart
handlers:
- name: httpd_restart
service: name=httpd state=restarted
roles
角色其实是对复杂结构的Playbook的一种组织方式,它基于一个固定的文件结构来自动加载所有工程中用到的tasks、variables、templates和handlers,这样的组织方式将每个相对独立的动作分割出来,使Playbook可以适用于更复杂的场景,并且降低了二次修改和复用的难度。
一个最基本的roles应当包括这些内容:
ROLES/
├── roles
│ └── OBJECT
│ ├── files
│ ├── handlers
│ │ └── main.yaml
│ ├── tasks
│ │ └── main.yaml
│ ├── templates
│ └── vars
│ └── main.yaml
└── site.yaml
roles中的各种组件以目录的形式存在于roles/下,这样的结构十分清晰
- files:所有可能会复制到远程主机上的文件都在这里;
- handlers:所有等待被触发器触发的任务文件都在这里;
- tasks:所有最基本的执行任务都在这里;
- templates:所有模板文件都在这里;
- vars:所有变量都在这里;
- site.yaml:roles的执行文件。
要注意的有:
- 目录结构绝对不可以改变;
- 大写项可以自由命名,小写项必须严格按照以上名称;
- main.yaml必不可少,可以作为主线任务,同时可以增加其它YAML文件作为功能的补充。
Playbook特殊用法
tags
标签可以方便我们筛选是否想要执行的任务。
---
- hosts: all
remote_user: root
tasks:
- name: file1
shell: echo '123' > /var/www/html/file1
tags:
- tag1
- name: file2
shell: echo '456' > /var/www/html/file2
tags:
- tag2
执行方式
只执行标有tag1的任务
ansible-playbook test.yaml -t tag1
跳过执行标有tag1的任务
ansible-playbook test.yaml --skip-tags tag1
include
当一个Playbook中的内容过多就会显得十分臃肿,这时可以将大量任务拆分成小块写入yaml文件中,通过include调用,这种形式非常类似roles,但是更适用于单一Playbook较为复杂又达不到使用roles的程度的任务。
---
- hosts: all
remote_user: root
tasks:
- include_task: 1.yaml
- include_task: 2.yaml
这些被包含的yaml文件不需要写头部。
when
when表示条件判断,通过when语句可以自动判断是否执行所属的指令。
---
- hosts: all
remote_user: root
tasks:
- name: hello_world
shell: echo 'hello world'
when: ansible_distribution=='CentOS' and ansible_distribution_major_version=='7'
循环
Ansible的循环结构非常死板,不过简单的需求基本可以满足。
单个变量循环
单个循环变量
---
- hosts: all
remote_user: root
tasks:
- name: useradd
user: name={{item}} state=present
with_items:
- user1
- user2
- user3
多个循环变量
---
- hosts: all
remote_user: root
tasks:
- name: useradd
user: name={{item.name}} group={{item.group}} state=present
with_items:
- { name: user1,group: root }
- { name: user2,group: root }
- { name: user3,group: root }
实用技巧
用户权限提升
很多服务器都会禁止root用户远程登录,这就要求Ansible能够获得更高权限。
编辑/etc/ansible/hosts文件
cat >> /etc/ansible/hosts <<EOF
[team]
192.168.10.102 ansible_ssh_user=user1 ansible_ssh_pass=123 ansible_become_pass=123
192.168.10.103 ansible_ssh_user=user1 ansible_ssh_pass=123 ansible_become_pass=123
EOF
执行命令
ansible all -S -m -a 'whoami'
编辑Playbook文件
---
- hosts: all
remote_user: user1
tasks:
- name: nginx_install
yum: name=nginx state=present
become: yes