ansible比如playbook要写的多,你没写,等于没学,记不住的,频繁报错
ansible架构
ansible的架构主要有下面这几部分组成:
- Ansible:ansible核心程序
- Host Inventory:主机清单,可以定义主机组和主机
- 模块:ansible执行任务是由模块执行的,不是ansible它自己执行的
- Playbook:剧本,Yaml定义的文件,类似于shell脚本
- Connect Plugin:连接插件
以搭建LAMP架构为例,我们的控制端一般拥有多个被控端,并不需要每个被控端都安装完整的LAMP架构,一般是一些主机安装Apache,一些安装MySQL等等,这时候我们可以通过inventory定义主机组和主机,安装的时候,我们可以编写playbook或者AD-HOC,然后把模块等推给被控端,然后被控端再执行playbook或者AH-HOC,完成任务,这个推送过程是使用ssh的方式推送的,确保安全,并且客户端必须有Python2,确保能够执行playbook或者AD-HOC。这个就是他的整个架构原理
ansible命令执行过程
- 加载自己的配置文件,默认/etc/ansible/ansible.cfg;
- 查找对应的主机配置文件,找到要执行的主机或者组;
- 加载自己对应的模块文件,如 command;
- 通过ansible将模块或命令生成对应的临时py文件(python脚本), 并将该文件传输至远程服务器;
- 对应执行用户的家目录的.ansible/tmp/XXX/XXX.PY文件;
- 给文件 +x 执行权限;
- 执行并返回结果;
- 删除临时py文件,sleep 0退出;
ansible配置文件
ansible的配置文件是有优先级的:
我们可以进入/etc/ansible/ansible.cfg(默认)的注释查看
# config file for ansible -- https://ansible.com/
# ===============================================
# nearly all parameters can be overridden in ansible-playbook
# or with command line flags. ansible will read ANSIBLE_CONFIG,
# ansible.cfg in the current working directory, .ansible.cfg in
# the home directory or /etc/ansible/ansible.cfg, whichever it
# finds first
可以看到ansible配置文件存在优先级的问题
ANSIBLE_CONFIG
ansible.cfg
项目目录.ansible.cfg
当前用户的家目录/etc/ansible/ansible.cfg
默认就有的
ansible主要配置文件都在注释说明这里,可以修改,但是一般不用改
[root@manager ~]# cat /etc/ansible/ansible.cfg
#inventory = /etc/ansible/hosts #主机列表配置文件
#library = /usr/share/my_modules/ #库文件存放目录
#remote_tmp = ~/.ansible/tmp #临时py文件存放在远程主机目录
#local_tmp = ~/.ansible/tmp #本机的临时执行目录
#forks = 5 #默认并发数
#sudo_user = root #默认sudo用户
#ask_sudo_pass = True #每次执行是否询问sudo的ssh密码
#ask_pass = True #每次执行是否询问ssh密码
#remote_port = 22 #远程主机端口
host_key_checking = False #跳过检查主机指纹
log_path = /var/log/ansible.log #ansible日志
密码连接的方式不常用,不够安全
一般使用ssh-keygen先在服务端生成密钥对,然后把公钥发给客户端。实现免密码登录,这里我先生成公钥,然后发给我的阿里云主机
[root@lvs ~]# ssh-copy-id root@47.107.65.32
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '47.107.65.32 (47.107.65.32)' can't be established.
ECDSA key fingerprint is SHA256:3bZrAWNEKPd87ymeRW56QtvQV7AHzDG52LgBvf7KaRk.
ECDSA key fingerprint is MD5:94:42:fe:2d:ce:5a:c2:2f:79:dc:a4:b4:74:90:09:cb.
Are you sure you want to continue connecting (yes/no)? yu^He
Please type 'yes' or 'no': yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@47.107.65.32's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'root@47.107.65.32'"
and check to make sure that only the key(s) you wanted were added.
[root@lvs ~]# ssh root@47.107.65.32
Last login: Wed May 6 14:01:03 2020 from 120.231.158.10
Welcome to Alibaba Cloud Elastic Compute Service !
[root@iZwz9hcv43i3gmo6zod6srZ ~]# exit
然后在/etc/ansible/host里面添加主机组,使用ping模块测试。
加入这两行:
[web]
47.107.65.32
使用ping测试
[root@lvs ~]# ansible -m ping web
47.107.65.32 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
可以看到已经ping通。
生产案例1、如果控制端和被控制端第一次通讯,需要确认指纹信息,如果机器特别多的情况下怎么办?
将 Ansible 配置文件中的 host_key_checking = False
参数注释打开即可。但要注意ansible.cfg文件的读取顺序。
ansible模块
get_url模块,command,get_url,
ansible playbook基本介绍
playbook
是由yaml
语法书写,结构清晰,可读性强,所以写playbook必须掌握yaml基础语法。它有点类似于shell脚本,playbook是把多个要执行的Ad-Hoc写在一个文件里边
编写playbook的语法
(这里大家要多写,只看规则记不住的)
- 缩进 YAML使用固定的缩进风格表示层级结构,每个缩进由两个空格组成, 不能使用tabs
- 冒号 以冒号结尾的除外,其他所有冒号后面所有必须有空格。
- 短横线 表示列表项,使用一个短横杠加一个空格。多个项使用同样的缩进级别作为同一列表。
错误排查
博主在第一次编写playbook里面也遇到粗心的情况,比如
- 在同一个列表中加了多个
-
,同一个列表中只要第一个元素加-
- 忘记加
:
- hosts与tasks没有对齐
- hosts或者tasks漏加了s
- 空格的问题
- 注意双引号的问题
第一种写法:
- hosts: oldboy
tasks:
- name: Installed Httpd Server
yum: name=httpd state=present
第二种写法
- hosts: web
tasks:
- name: Install httpd
yum:
name:
- httpd
- nginx
state: present
playbook实战
案例:使用ansible配置安装httpd服务
[root@m01 project1]# cat http.yaml
- hosts: web
tasks:
- name: Installed Httpd Server
yum: name=httpd state=present
- name: Configure Httpd Server
copy: src=./httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf backup=yes
- name: Configure Httpd WebSite
copy: src=./tt.j2 dest=/var/www/html/tt.html owner=http group=http mode=644
- name: Service Httpd Server
service: name=httpd state=started enabled=yes
- name: Service Firewalld Server
service: name=firewalld state=started
- name: Configure Firewalld Server
firewalld: zone=public port=9988/tcp permanent=yes immediate=yes state=enabled
playbook使用变量
为什么要使用变量:使用变量更容易的维护,比如我们使用playbook安装nginx的时候,下次如果想安装新的版本,可以实现不修改playbook的情况下去安装,只需要修改存放playbook变量的文件就行
变量怎么定义
1.通过playbook中的play进行定义变量
-
通过
vars关键字
来进行定义变量,使用{{ }}
调用[root@m01 project1]# cat vars_1.yml - hosts: oldboy vars: - web_packages: httpd-2.4.6 - ftp_packages: vsftpd-3.0.2 tasks: - name: Installed {{ web_packages }} {{ ftp_packages }} yum: name: - "{{ web_packages }}" - "{{ ftp_packages }}" state: present
-
通过定义一个yaml变量文件,然后在playbook中使用
vars_file
指定对应的变量文件,然后{{ }}
进行调用
2.通过inventory主机清单进行变量定义
进入/etc/ansible/目录下面分别新建一个group_vars
和host_vars
文件,(因为/etc/ansible/目录里面有对应的host等文件,这两个目录才能生效,随便进一个目录创建这两个目录是读取不到变量的)
- 进入
group_vars
目录创建一个和主机组名字的文件,记得啊要同名,比如我的主机组名字叫web,那么我创建的存储变量的文件就叫web,这个文件也是用yaml的语法去写
[root@lvs group_vars]# cat web
package: tree
[root@lvs group_vars]# cat tree_install.yaml
- hosts: web
tasks:
- name: Install "{{ package }}"
yum: name= "{{ package }}" state=present
[root@lvs group_vars]# ansible-playbook tree_install.yaml
PLAY [web] ****************************************************************************************************************************************************************
TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [10.10.31.26]
ok: [10.10.31.28]
TASK [Install "tree"] *****************************************************************************************************************************************************
ok: [10.10.31.26]
ok: [10.10.31.28]
PLAY RECAP ****************************************************************************************************************************************************************
10.10.31.26 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.10.31.28 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看到执行成功了
ansible变量优先级
定义相同的变量不同的值,来测试变量的优先级。操作步骤如下
1)在plabook中定义vars变量
2)在playbook中定义vars_files变量
3)在host_vars中定义变量
4)在group_vars中定义变量
5)通过执行命令传递变量
[root@m01 project1]# cat vars_8.yml
- hosts: oldboy
vars:
file_name: play_vars
vars_files: ./vars_public.yml
tasks:
- name: Create Variables {{ file_name }}
file: path=/tmp/{{ file_name }} state=touch
[root@m01 project1]# vim vars_public.yml
[root@m01 project1]# vim host_vars/172.16.1.7
[root@m01 project1]# vim group_vars/oldboy
[root@m01 project1]# vim group_vars/all
变量的优先级
外置传参—>playbook(vars_files—>vars)—>inventory(host_vars–>group_vars/group_name—>group_vars-all)
register变量注册
如果我们想在跑playbook中输出某个命令的内容,必须使用register关键字进行变量注册,然后命令结果就会保存在这个变量里面
facts变量
facts变量用来采集被控端的状态指标,是默认就有的。, 比如: IP地址 主机名称 cpu信息 内存 等等
默认情况的facts变量名都已经预先定义好了, 只需要采集被控端的信息,然后传递至facts变量即.可
facts变量是非常有用的,因为它可以获取我们被控端的IP,版本等信息,根据这些信息,我们可以对不同的机器做不同的操作,不如使用fact变量,获取到系统版本,根据不同的系统版本做不同的操作。
playbook在执行的时候,会采集Facts变量,这个过程比较慢,如果我们的playbook没有用到这个facts变量,可以吧它关掉,这样不去采集Facts变量,就会快很多。如果需要在 Play 中关闭 Facts 可以在 Play 这一等级上配置 gather_facts: no
Ansible facts批量修改主机名称
Linux上生成随机数的方法,除了使用$RANDOM变量,还可以使用cat /etc/urandom (但是要记得替换掉)
使用ansible批量修改主机名称有三个思路
一:通过shell模块执行命令生成随机数。把这个随机数作为主机名
二:可以使用facts变量里面的一些随机的东西来生成随机值
[root@m01 project1]# cat vars_14.yml
- hosts: web
tasks:
- name: SHell
shell: echo $RANDOM|md5sum |cut -c 5-10
register: get_random
- name: Get Facts
debug:
msg: "{{ ansible_date_time.epoch }}"
- name: Hostname
hostname: name={{ get_random.stdout }}_{{ ansible_date_time.epoch }}
ansible–tasks任务控制
ansible条件语句when
使用when关键字进行判断,条件判断语句经常会和facts变量一起用,因为很多时候是根据facts变量进行判断,常用的facts变量:
实践案例一:使用when判断操作系统,Ubuntu安装httpd2,CentOS安装httpd
- hosts: web
tasks:
- name: Installed {{ ansible_distribution }} Httpd Server
yum: name=httpd state=present
when: ( ansible_distribution == "CentOS" )
- name: Installed {{ ansible_distribution }} Httpd2 Server
yum: name=httpd2 state=present
when: ( ansible_distribution == "Ubuntu" )
~
执行结果可以看出它跳过了安装httpd2,因为操作系统不是Ubuntu,这里因为其中一台主机已经安装过httpd了,所以没有显示change
[root@lvs ~]# ansible-playbook install_httpd.yaml --syntax
playbook: install_httpd.yaml
[root@lvs ~]# ansible-playbook install_httpd.yaml
PLAY [web] ****************************************************************************************************************************************************************
TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [10.10.31.26]
ok: [10.10.31.28]
TASK [Installed CentOS Httpd Server] **************************************************************************************************************************************
ok: [10.10.31.28]
changed: [10.10.31.26]
TASK [Installed CentOS Httpd2 Server] *************************************************************************************************************************************
skipping: [10.10.31.28]
skipping: [10.10.31.26]
PLAY RECAP ****************************************************************************************************************************************************************
10.10.31.26 : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
10.10.31.28 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 igno
red=0
实践案例二:为web的主机组安装yum源,其他主机组不安装
这里ansible_fqdn
是facts变量,表示主机组的名字
[root@m01 project1]# cat tasks_2.yml
- hosts: all
tasks:
- name: Create YUM Repo
yum_repository:
name: ansible_nginx
description: ansible_test
baseurl: https://mirrors.aliyun.com
gpgcheck: no
enabled: no
when: ( ansible_fqdn is match ("web*"))
实践案例三:根据命令执行的结果进行判断
下面代码中rc表示命令的返回值
[root@m01 project1]# cat tasks_3.yml
- hosts: all
tasks:
#检查httpd服务是否是活动的
- name: Check Httpd Server
command: systemctl is-active httpd
ignore_errors: yes
register: check_httpd
#如果check_httpd变量中的rc结果等于0,则执行重启httpd,否则跳过
- name: Httpd Restart
service: name=httpd state=restarted
when: check_httpd.rc == 0
ansible循环语句
循环语句使用with_items关键字,然后调用变量时使用{{ item }}
调用,with_items下面都要有-
案例一:使用循环启动多个服务
[root@m01 project1]# cat tasks_4.yml
- hosts: webserver
tasks:
- name: Service Nginx Server
service: name={{ item }} state=restarted
with_items:
- nginx
- php-fpm
案例二:循环安装多个软件包
[root@m01 playbook]# cat loop-service-v2.yml
- hosts: web
tasks:
- name: Installed Httpd Mariadb Package
yum: name={{ pack }} state=latest
vars:
pack:
- httpd
- mariadb-server
实践案例三、使用变量字典循环方式批量创建用户
[root@m01 project1]# cat tasks_6.yml
- hosts: web
tasks:
- name: Create User
user: name={{ item.name }} groups={{ item.groups }} state=present
with_items:
- { name: 'www', groups: 'bin'}
- { name: 'test', groups: 'root'}
使用变量字典循环方式批量拷贝文件
[root@m01 project1]# cat tasks_7.yml
- hosts: webserver
tasks:
- name: Configure Rsyncd Server
copy: src={{ item.src }} dest={{ item.dest }} mode={{ item.mode }}
with_items:
- { src: './rsyncd.conf.j2', dest: '/tmp/rsyncd.conf', mode: '0644' }
- { src: './rsync.pass.j2', dest: '/tmp/rsync.pass', mode: '0600' }
handlers触发器
handlers 触发器 notify监控 —>通知 —> Handlers触发
- 无论多少个task通知了相同的handlers,handlers仅会在所有tasks结束后运行一次。
- 只有task发生改变了才会通知handlers,没有改变则不会触发handlers
- 不能使用handlers替代tasks、因为handlers是一个特殊的tasks。
例子
安装nginx服务,要求能够实现配置变更,服务自动重载 (万一配置修改错误.怎么办?,后面再讲错误忽略ignore_errors
与错误处理changed_when
)
[root@m01 ~]# cat webserver.yml
- hosts: webserver
#1.定义变量,在配置文件中调用
vars:
http_port: 8881
#2.安装httpd服务
tasks:
- name: Install Httpd Server
yum: name=httpd state=present
#3.使用template模板,引用上面vars定义的变量至配置文件中
- name: Configure Httpd Server
template: src=./httpd.conf dest=/etc/httpd/conf/httpd.conf
notify: #调用名称为Restart Httpd Server的handlers(可以写多个)
- Restart Httpd Server
#4.启动Httpd服务
- name: Start Httpd Server
service: name=httpd state=started enabled=yes
#5.如果配置文件发生变化会调用该handlers下面的对应名称的task
handlers:
- name: Restart Httpd Server
service: name=httpd state=restarted
tags标签
为tasks打标签,可以执行playbook中特定的某个或者某几个任务,根据指定的标签执行 调试
指定执行某个tags标签
[root@m01 docs1]# ansible-playbook -i hosts nginx_php.yml -t "test_user"
忽略执行某个tags标签
[root@m01 docs1]# ansible-playbook -i hosts nginx_php.yml --skip-tags "test_user"
[root@m01 project1]# cat tasks_8.yml
- hosts: webserver
tasks:
- name: Install Nfs Server
yum: name=nfs-utils state=present
tags: install_nfs
- name: Service Nfs Server
service: name=nfs-server state=started enabled=yes
tags: start_nfs-server
include包含
在ansible的playbook中可以使用include
关键字,把另一个yaml文件的tasks任务导入进来,使用import_playbook
关键字还可以吧整个完整的playbook文件导入进来
1)编写restart_httpd.yml文件
[root@ansible project1]# cat restart_httpd.yml #注意这是一个tasks所有没有play的任何信息
- name: Restart Httpd Server
service: name=httpd state=restarted
2)A Project的playbook如下
[root@ansible project1]# cat a_project.yml
- hosts: webserver
tasks:
- name: A Project command
command: echo "A"
- name: Restart httpd
include: restart_httpd.yml
3)B Project的playbook如下
[root@ansible project1]# cat b_project.yml
- hosts: webserver
tasks:
- name: B Project command
command: echo "B"
- name: Restart httpd
include_tasks: restart_httpd.ym
导入一个完整的playbook文件 (play task)
[root@m01 project1]# cat tasks_total.yml
- import_playbook: ./tasks_1.yml
- import_playbook: ./tasks_2.yml
错误忽略与错误处理
错误忽略ignore_errors
,错误处理changed_when
,使用ignore_errors
这个关键字,如果这个任务执行出错,会跳过错误,继续执行下一个任务。
change_when
:
强制调用handlers
:force_handlers: yes(加上这个)
[root@m01 project1]# cat tasks_10.yml
- hosts: webserver
force_handlers: yes #强制调用handlers
tasks:
- name: Touch File
file: path=/tmp/bgx_handlers state=touch
notify: Restart Httpd Server
- name: Installed Packages
yum: name=sb state=latest
handlers:
- name: Restart Httpd Server
service: name=httpd state=restarted
2.关闭changed的状态(确定该tasks不会对被控端做任何的修改和变更.,比如执行一些不修改客户端的命令)
[root@m01 project1]# cat tasks_11.yml
- hosts: webserver
tasks:
- name: Installed Httpd Server
yum: name=httpd state=present
- name: Service Httpd Server
service: name=httpd state=started
- name: Check Httpd Server
shell: ps aux|grep httpd
register: check_httpd
changed_when: false
- name: OutPut Variables
debug:
msg: "{{ check_httpd.stdout_lines }}"
案例三、使用changed_when检查tasks任务返回的结果,报错了就不执行handlers,防止影响网站正常运行
[root@m01 project1]# cat tasks_12.yml
- hosts: webserver
tasks:
- name: Installed Nginx Server
yum: name=nginx state=present
- name: Configure Nginx Server
copy: src=./nginx.conf.j2 dest=/etc/nginx/nginx.conf
notify: Restart Nginx Server
- name: Check Nginx Configure Status
command: /usr/sbin/nginx -t
register: check_nginx
changed_when:
# 这里的意思是如果找到有successful就往下执行,如果没有,就不往下执行了
- ( check_nginx.stdout.find('successful'))
- false
- name: Service Nginx Server
service: name=nginx state=started
handlers:
- name: Restart Nginx Server
service: name=nginx state=restarted
Jinja2模板引擎
Jinja2跟我们的Django的template模板语法很像,比如我们想要装一个nginx,配置nginx服务,并且让他们监听在不同的端口,那么我们就可以使用Jinja2模板引擎进行渲染,就不用相同的地方写多遍,但是其实有它没它,一样可以使用,真的是这样,但使用它可以让我们提高效率,不过维护成本也比较高,但是去下别人的playbook,别人用了Jinja2,要看得懂
galaxy
下到这里面去了
ansible roles
ansible自定义采集方式
如果我们要自定义某台机器的采集方式,需要在那台目标机器上创建/etc/ansible/facts/
目录,然后再这个目录下创建以.fact
结尾的文件,然后我们的setup模块就可以采集到这个信息
在10.10.31.28这台机器上,也就是被控端
[root@web02 ~]# mkdir /etc/ansible/facts.d/ -p
[root@web02 ~]# cd /etc/ansible/facts.d/
[root@web02 facts.d]# ls
[root@web02 facts.d]# vim cpu.fact
[root@web02 facts.d]# cat cpu.fact
[cpu]
cpunum=4
cpuname=i7
在ansible控制端,使用setup模块采集并过滤,filter='ansible_local'
,因为我们自定义的采集的信息,放在ansible_local
里面。可以看到下面采集到的信息,ansible_local
是一个字典,键是我们ansible.facts
目录下的文件的文件名去掉后缀.fact,然后是参数组(中括号括起来的),然后是变量
[root@lvs cmdb_client]# ansible 10.10.31.28 -m setup -a "filter='ansible_local'"
10.10.31.28 | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"cpu": {
"cpu": {
"cpuname": "i7",
"cpunum": "4"
}
}
}
},
"changed": false,
"failed": false
}
使用Python脚本来自定义采集
注意文件不能以.py为后缀,要以.fact为后缀,可以先以.py为后缀写好(有语法高亮),然后再改文件名,注意要用print,不要用return
这里通过Python执行ulimit -n
获取最大的文件描述符的个数,默认是1024
[root@web02 facts.d]# vim get_process.fact
[root@web02 facts.d]# cat get_process.fact
#!/usr/bin/env python
import os,json
def get_process_num():
dic={}
file = os.popen('ulimit -n').read().strip()
dic['pnum'] = file
print json.dumps(dic)
if __name__ == "__main__":
get_process_num()
在ansible控制端采集信息,可以看到采集成功
[root@lvs cmdb_client]# ansible 10.10.31.28 -m setup -a "filter='ansible_local'"
10.10.31.28 | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"cpu": {
"cpu": {
"cpuname": "i7",
"cpunum": "4"
}
},
"get_process": {
"pnum": "1024"
}
}
},
"changed": false,
"failed": false
}
ansible自定义模块
ansible自定义模块指的是在ansible主控端自己写一个模块,ansible自定义采集信息指的是
这里我自定义一个uptime的模块(执行uptime命令)
第一步:首先在/etc/ansible/ansible.cfg这里,把library打开,它默认是注释的
第二步:进入library定义的目录,编辑我们的自定义模块,然后采集信息测试
[root@lvs share]# mkdir my_modules/
[root@lvs share]# vim /etc/ansible/ansible.cfg
[root@lvs share]# cd my_modules/
[root@lvs my_modules]# vim uptime
[root@lvs my_modules]# cat uptime
#!/usr/bin/env python
import json
import os
up = os.popen('uptime').read()
dic = {"result":up}
print json.dumps(dic)
采集信息,可以看到采集信息成功
[root@lvs my_modules]# ansible -m uptime LB
10.10.31.28 | SUCCESS => {
"changed": false,
"failed": false,
"result": " 17:38:33 up 2 days, 13:45, 3 users, load average: 0.00, 0.01, 0.05\n"
}
10.10.31.26 | SUCCESS => {
"changed": false,
"failed": false,
"result": " 17:38:33 up 4 days, 22:31, 4 users, load average: 0.00, 0.01, 0.05\n"
}