控制端主机自带很多模块(模块就是脚本);
ansible通过ssh远程被管理主机,将控制端的模块(脚本)或命令传输到被管理主机;
在被管理端主机执行模块(脚本)或命令,执行不同的模块或命令可以实现不同的功能;
最后ansible退出ssh远程。
绝大多数模块(脚本)都需要参数才能执行成功!!!类似于shell脚本的位置变量!
实验拓扑:
Control控制节点
[root@control ~]# vim /etc/hosts #修改文件,手动添加如下内容(不要删除文件原来的内容)
192.168.4.253 control
192.168.4.11 test
192.168.4.12 node2
192.168.4.13 node3
192.168.4.14 node4
192.168.4.15 node5
验证
ping test
配置ssh密钥实现免密码登陆
Ansible是基于SSH远程的原理实现远程控制,如果控制端主机无法免密登录被管理端主机,后续的所有试验都会失败!!
[root@control ~]# ssh-keygen #生成ssh密钥
[root@control ~]# for i in test node2 node3 node4 node5
do
ssh-copy-id $i
done
#拷贝密钥到远程主机
#提示:拷贝密钥到远程主机时需要输入对方电脑的账户密码才可以!!
#拷贝密钥到test就需要输入test对应账户的密码,拷贝密钥到node2就需要输入node2对应的密码
部署Ansible软件
[root@control ~]# tar -xf ansible_soft.tar.gz
[root@control ~]# cd ansible_soft
[root@control ansible_soft]# dnf -y install *
修改主配置文件。
[root@control ~]# mkdir ~/ansible
[root@control ~]# vim ~/ansible/ansible.cfg
[defaults]
inventory = ~/ansible/inventory
#主机清单配置文件(inventory可以是任意文件名),英语词汇:inventory(清单、财产清单)
#forks = 5 #ssh并发数量
#ask_pass = True #使用密钥还是密码远程,True代表使用密码
#host_key_checking = False #是否校验密钥(第一次ssh时是否提示yes/no)
修改主机清单文件(清单文件名必须与主配置文件inventory定义的一致)。
[root@control ~]# vim ~/ansible/inventory
[test] #定义主机组(组名称任意)
test #定义组中的具体主机,组中包括一台主机test
[proxy] #定义主机组(组名称任意),英语词汇:proxy(代理人,委托人)
node2 #proxy组中包括一台主机node2
[webserver]
node[3:4] #这里的node[3:4]等同于node3和node4
[database]
node5
[cluster:children] #嵌套组(children为关键字),不需要也可以不创建嵌套组
webserver #嵌套组可以在组中包含其他组
database
Ansible ad-hoc是一种通过命令行批量管理的方式,命令基本格式如下:
格式:ansible 主机集合 -m 模块名 -a “参数”
查看主机列表
[root@control ~]# cd ~/ansible #非常重要
[root@control ansible]# ansible all --list-hosts #查看所有主机列表
# --list-hosts是ansible这个命令的固定选项,如同ls -a一样(-a是ls命令的固定选项)
#英语词汇:list(列表,清单)、host(主机、主办、主人)
测试远程主机是否能ping通。
当需要远程多个主机或者多个组时,中间使用逗号分隔!!!
[root@control ansible]# ansible test,webserver -m ping #调用ping模块
[root@control ansible]# ansible all -m ping
模块就是脚本(多数为Python脚本),多数脚本都支持参数,默认模块为command。
可以不添加“-m command”默认使用
[root@control ansible]# ansible test -m command -a "uptime" #查看CPU负载
[root@control ansible]# ansible test -m command -a "uname -r" #查看内核版本
[root@control ansible]# ansible test -a "ip a s" #查看网卡信息
[root@control ansible]# ansible all -a "date" #查看时间
[root@control ansible]# ansible-doc -l #列出所有模块
[root@control ansible]# ansible-doc -l | grep yum #在所有模块中过滤关键词
[root@control ansible]# ansible-doc yum #查看模块帮助
command和shell模块的区别,command不支持bash的特性(bash有哪些特性可以参考Shell课程第一天的PPT),如管道和重定向等功能,但是shell模块可以支持。
不可以使用shell模块执行交互命令,如vim、top等。
[root@control ansible]# ansible test -m command -a "ps | wc -l" #报错
[root@control ansible]# ansible test -m command -a "ls &" #报错
[root@control ansible]# ansible test -m shell -a "ps aux | wc -l" #进程数量
[root@control ansible]# ansible test -m shell -a "who" #登陆信息
[root@control ansible]# ansible test -m shell -a "touch /tmp/txt.txt"
#使用shell模块创建文件会有Warning警告提示,正常!!!
script模块会把-a后面的脚本拷贝到被管理端主机,然后执行这个脚本。
[root@control ansible]# vim ~/ansible/test.sh
#!/bin/bash
echo hello
[root@control ansible]# ansible test -m script -a "./test.sh"
#test是主机组的名称,-m调用script模块,-a后面的./test.sh是上面创建脚本的相对路径和文件名
#./是当前目录的意思,在当前目录下有个脚本叫test.sh
输出:
test | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to test closed.\r\n",
"stderr_lines": [
"Shared connection to test closed."
],
"stdout": "hello\r\n",
"stdout_lines": [
"hello"
]
}
很多ansible模块都具有幂等性的特征。
幂等性:任意次执行所产生的影响均与一次执行的影响相同
file模块可以创建文件、目录、链接;修改权限与属性等(ansible-doc file)
#state=touch是创建文件,state=directory是创建目录
# ansible test -m file -a "path=/tmp/file.txt state=touch" #远程test组中所有主机,新建文件,path后面指定要创建的文件或目录的名称
#state=touch是创建文件,state=directory是创建目录
输出结果
node1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": true,
"dest": "/root/testfile.txt",
"gid": 0,
"group": "root",
"mode": "0644",
"owner": "root",
"secontext": "unconfined_u:object_r:admin_home_t:s0",
"size": 0,
"state": "file",
"uid": 0
}
## 验证: 到node1主机,使用ls /tmp/file.txt看看文件是否被创建成功 ##
[root@control ansible]# ansible node1 -a "ls /root/"
node1 | CHANGED | rc=0 >>
anaconda-ks.cfg
testfile.txt
EXAMPLES:
- name: Change file ownership, group and permissions
file:
path: /etc/foo.conf
owner: foo
group: foo
mode: '0644'
- name: Create an insecure file
file:
path: /work
owner: root
group: root
mode: '1777'
- name: Create a symbolic link
file:
src: /file/to/link/to
dest: /path/to/symlink
owner: foo
group: foo
state: link
- name: Create two hard links
file:
src: '/tmp/{{ item.src }}'
dest: '{{ item.dest }}'
state: link
with_items:
- { src: x, dest: y }
- { src: z, dest: k }
- name: Touch a file, using symbolic modes to set the permissions (equivalent t>
file:
path: /etc/foo.conf
state: touch
mode: u=rw,g=r,o=r
- name: Touch the same file, but add/remove some permissions
file:
path: /etc/foo.conf
state: touch
mode: u+rw,g-wx,o-rwx
- name: Touch again the same file, but dont change times this makes the task id>
file:
path: /etc/foo.conf
state: touch
mode: u+rw,g-wx,o-rwx
modification_time: preserve
access_time: preserve
- name: Create a directory if it does not exist
file:
path: /etc/some_directory
state: directory
mode: '0755'
- name: Update modification and access time of given file
file:
path: /etc/some_file
state: file
modification_time: now
- name: Set access time based on seconds from epoch value
file:
path: /etc/another_file
state: file
access_time: '{{ "%Y%m%d%H%M.%S" | strftime(stat_var.stat.atime) }}'
- name: Recursively change ownership of a directory
file:
path: /etc/foo
state: directory
recurse: yes
owner: foo
group: foo
[root@control ansible]# echo AAA > ~/a3.txt #新建测试文件
[root@control ansible]# ansible test -m copy -a "src=~/a3.txt dest=/root/"
#把管理端本机的a3.txt文件,拷贝到test组中所有主机的/root/目录
#src代表源文件,dest代表目标文件
[root@control ansible]# ansible test -m fetch -a "src=/etc/hostname dest=~/"
#将远程test组中所有主机的hostname文件下载到本地家目录
#src代表源文件,dest代表目标文件
[root@control ansible]# ls ~/ #使用ls查看下是否下载成功
#不能下载目录,如果需要下载目录,可以先打包后再下载
在修改单个文件的单行内容时可以使用lineinfile模块(ansible-doc lineinfile)。
[root@control ansible]# ansible test -m lineinfile \
-a "path=/etc/issue line='hello world'"
#在/etc/issue文件中添加一行内容hello world,默认添加到最后,line后面跟的是需要添加的文件内容
## 验证:到node1主机执行命令cat /etc/issue查看文件内容是否正确
[root@control ansible]# ansible test -m lineinfile \
-a "path=/etc/issue line='hello world'"
#基于幂等原则,重复执行,不会创建多行内容
[root@control ansible]# ansible test -m lineinfile \
-a "path=/etc/issue line='insert' insertafter='Kernel'"
#将line后面的内容插入到/etc/issue文件中Kernel行的后面
#英语词汇:insert(插入),after(在…后面)
## 验证:到node1主机执行命令cat /etc/issue查看文件内容是否正确
lineinfile会替换一整行,replace可以替换关键词(ansible-doc replace)。
[root@control ansible]# ansible test -m replace \
-a "path=/etc/issue.net regexp=Kernel replace=Ocean"
#将node1主机中/etc/issue.net文件全文所有的Kernel替换为Ocean
#regexp后面是需要替换的旧内容;replace后面是需要替换的新内容
## 验证:到node1主机执行命令cat /etc/issue.net查看文件内容是否正确
[root@control ansible]# ansible test -m user -a "name=tuser1"
#远程test组中的所有主机并创建系统账户tuser1,默认state的值为present,代表创建用户
## 验证:到node1主机执行命令id tuser1查看是否有该用户
[root@control ansible]# ansible test -m user -a \
"name=tuser2 uid=1010 group=adm groups=daemon,root home=/home/tuser2"
#创建账户并设置对应的账户属性,uid指定用户ID号,group指定用户属于哪个基本组
#groups指定用户属于哪些附加组,home指定用户的家目录
## 验证: 到node1主机执行命令id tuser2查看是否有该用户
[root@control ansible]# ansible test -m user \
-a "name=tuser1 password={{'abc'| password_hash('sha512')}}"
#修改账户密码,用户名是tuser1,密码是abc,密码经过sha512加密
[root@control ansible]# ansible test -m user \
-a "name=tuser1 state=absent"
#删除账户tuser1,state=absent代表删除账户的意思,name指定要删除的用户名是什么
#账户的家目录不会被删除,相当于执行userdel tuser1
[root@control ansible]# ansible test -m user \
-a "name=tuser2 state=absent remove=true"
#删除tuser2账户同时删除家目录、邮箱,相当于执行userdel -r tuser2
[root@control ansible]# ansible test -m yum_repository \
-a "name=myyum description=hello baseurl=ftp://192.168.4.254/centos gpgcheck=no"
#新建一个yum源配置文件/etc/yum.repos.d/myyum.repo
#yum源文件名为myyum,该文件的内容如下:
[myyum]
baseurl = ftp://192.168.4.254/centos
gpgcheck = 0
name = hello
## 验证:到node1主机ls /etc/yum.repos.d/查看该目录下是否有新的yum文件
[root@control ansible]# ansible test -m yum_repository \
-a "name=myyum description=test baseurl=ftp://192.168.4.254/centos gpgcheck=yes gpgkey=…"
#修改yum源文件内容
[root@control ansible]# ansible test -m yum_repository -a "name=myyum state=absent"
#删除yum源文件myyum
使用yum模块可以安装、卸载、升级软件包(ansible-doc yum),
state: present(安装)|absent(卸载)|latest(升级)。
[root@control ansible]# ansible test -m yum -a "name=unzip state=present"
#安装unzip软件包,state默认为present,也可以不写
## 验证:到node1主机执行命令rpm -q unzip查看是否有该软件
[root@control ansible]# ansible test -m yum -a "name=unzip state=latest"
#升级unzip软件包,软件名称可以是*,代表升级所有软件包
[root@control ansible]# ansible test -m yum -a "name=unzip state=absent"
#调用yum模块,卸载unzip软件包,state=absent代表卸载软件
## 验证:到node1主机执行命令rpm -q unzip查看该软件是否已经被卸载
service为服务管理模块(启动、关闭、重启服务等),
state:started|stopped|restarted,
enabled:yes设置开机启动。
[root@control ansible]# ansible test -m yum -a "name=httpd"
#调用yum模块,安装httpd软件包
## 验证:到node1主机执行命令rpm -q httpd查看该软件是否被安装
[root@control ansible]# ansible test -m service -a "name=httpd state=started"
#调用service模块,启动httpd服务
## 验证:到node1主机执行命令systemctl status httpd查看服务状态
[root@control ansible]# ansible test -m service -a "name=httpd state=stopped"
#调用service模块,关闭httpd服务
## 验证:到node1主机执行命令systemctl status httpd查看服务状态
[root@control ansible]# ansible test -m service -a "name=httpd state=restarted"
#调用service模块,重启httpd服务
[root@control ansible]# ansible test -m service -a "name=httpd enabled=yes"
#调用service模块,设置httpd服务开机自启
提示:做实验之前需要给对应的虚拟机添加额外磁盘,并创建磁盘2个分区
提示:可以使用前面学习过的parted或fdisk命令给磁盘创建分区
提示:这里的磁盘名称仅供参考,不要照抄!!!
lvg模块:创建、删除卷组(VG),修改卷组大小,
state:present(创建)|absent(删除)。
[root@control ansible]# ansible test -m yum -a "name=lvm2"
#安装lvm2软件包,安装了lvm2软件后,才有pvcreate、vgcreate、lvcreate等命令
[root@control ansible]# ansible test -m lvg -a "vg=myvg pvs=/dev/sdb1"
#创建名称为myvg的卷组,该卷组由/dev/sdb1组成
#注意:这里的磁盘名称要根据实际情况填写
## 验证:到node1主机执行命令pvs和vgs查看是否有对应的PV和VG
[root@control ansible]# ansible test -m lvg -a "vg=myvg pvs=/dev/sdb1,/dev/sdb2"
#修改卷组大小,往卷组中添加一个设备/dev/sdb2
一些公司会禁用root,所以要用普通用户远程管理
sudo让普通用户可以以超级管理员或其他人身份执行命令。
sudo基本流程:
管理员授权(修改/etc/sudoers)
普通用户以sudo的方式执行命令
修改/etc/sudoers的方法如下
visudo(带语法检查 没有颜色提醒)
vim /etc/sudoers(带颜色提醒,没有语法检查)
/etc/sudoers授权格式:
用户或组 主机列表=(提权身份) [NOPASSWD]:命令列表
案例
创建用户alice,设置密码并提权
#添加用户
ansible all -m user -a "name=alice \
password={{'123456' | password_hash('sha512')}}"
#修改/etc/sudoers文件内容添加权限
ansible all -m lineinfile \
-a "path=/etc/sudoers line='alice ALL=(ALL) NOPASSWD:ALL'"
测试:
ssh alice@node1
sudo xxxxx #不需要输密码
修改主配置文件,配置文件文件的内容可以参考/etc/ansible/ansible.cfg。
[root@control ansible]# vim ~/ansible/ansible.cfg
[defaults]
inventory = ~/ansible/inventory
remote_user = alice #以什么用户远程被管理主机(被管理端主机的用户名)
[privilege_escalation]
become = true #alice没有特权,是否需要切换用户提升权限
become_method = sudo #如何切换用户(比如用su就可以切换用户,这里是sudo)
become_user = root #切换成什么用户(把alice提权为root账户)
become_ask_pass = False #执行sudo命令提权时是否需要输入密码
修改inventory主机清单配置文件针对个别。
如果个别主机的账户不同,该如何处理呢?
如果有些主机需要使用密码远程呢?如果有些主机的SSH端口不是22呢?
[root@control ~]# cat ~/ansible/inventory
[test]
node1 ansible_ssh_port=端口号 #自定义远程SSH端口
[proxy]
node2 ansible_ssh_user=用户名 #自定义远程连接的账户名
[webserver]
node[3:4] ansible_ssh_pass=密码 #自定义远程连接的密码
[database]
node5
[cluster:children]
webserver
database
Ansible ad-hoc可以通过命令行形式远程管理其他主机,适合执行一些临时性简单任务。另外还有一种远程管理的方式叫Playbook,Ansible Playbook中文名称叫剧本,它将经常需要执行的任务写入一个文件,这个文件就叫剧本。
YAML是什么?
YAML的格式要求如下:
Playbook语法格式要求如下:
ansible-doc 模块名
/EXAMPLE 看帮助知道格式
定义多个主机和任务的剧本
hosts由一个或多个组或主机组成,逗号分隔,tasks由一个或多个任务组成,多个任务按顺序执行,执行ansible-playbook命令可以使用-f选项自定义并发量。
- hosts: test,webserver
tasks:
- name: This is my first playbook #name后面的内容可以任意
ping:
- name: Run a shell command
shell: touch ~/shell.txt
#hosts定义需要远程哪些被管理主机,hosts是关键词
#tasks定义需要执行哪些任务,tasks是关键词
#第一个任务调用ping模块,该模块没有参数
#第二个任务调用shell模块在被管理主机创建一个空文件~/shell.txt
[root@control ansible]# ansible-playbook ~/ansible/test.yml -f 5
## 验证:到node1、node3、node4主机分别执行命令ls /root/shell.txt查看是否有该文件
多个play的Playbook文件
#第一个play剧目
---
- hosts: test
tasks:
- name: This is first play
ping:
#第二个play剧目
- hosts: webserver
tasks:
- name: This is second play
ping:
ansible_facts用于采集被管理设备的系统信息,所有收集的信息都被保存在变量中,每次执行playbook默认第一个任务就是Gathering Facts,使用setup模块可以查看收集到的facts信息。
[root@control ansible]# ansible test -m setup
node1 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
… 省略部分内容…
debug模块可以显示变量的值,可以辅助排错,通过msg可以显示变量的值,变量需要使用{{}}扩起来。
# vim ~/ansible/debug.yml
---
- hosts: test
tasks:
- debug:
msg: "主机名是:{{ ansible_hostname }}"
- debug:
msg: "总内存大小:{{ ansible_memtotal_mb }}"
#备注调用debug模块显示某些具体的变量值
#debug模块可以显示变量的值,可以辅助排错
Ansible支持十几种定义变量的方式,这里我们仅介绍其中一部分变量。
下面是根据优先级排序的定义方式:
[root@control ansible]# vim ~/ansible/inventory
[test]
node1 iname="nb"
[proxy]
node2
[webserver]
node[3:4]
[webserver:vars]
iname="dachui"
#备注,在node1主机后面给该主机添加变量iname,值为nb.
#给webserver组定义变量,vars是关键词不可以改变,webserver是上面定义的组
#给这个组定义变量iname="dachui"
下面编写剧本调用刚才的变量:(在剧本中需要调用变量是要使用{{}})
# vim ~/ansible/inventory_var.yml
---
- hosts: node1,webserver #定义需要远程管理的主机是谁
tasks: #剧目要完成哪些任务
- name: create a user with var. #剧目中的第一个任务描述信息
user: #调用user模块创建用户
name: "{{ iname }}" #需要创建的用户名是iname这个变量
#注意事项:
#在ansible剧本中当调用变量时,开始位置就调用变量,就需要在{{}}外面加双引号
#如果是在后面或者中间位置调用变量{{}}外面可以不加双引号
#如:
# "{{ iname }}"
# nihao {{ iname }}
# vim ~/ansible/facts_var.yml
---
- hosts: test
tasks:
- name: create user.
user:
name: "{{ansible_hostname}}"
#定义剧本,远程所有被管理主机,调用user模块,创建用户
#需要创建的用户名ansible_hostname是一个ansible_facts变量
#验证: 到node1主机查看是否有一个与主机名同名的用户
# vim ~/ansible/playbook_var.yml
---
- hosts: test
vars: #vars是关键词,用来定义变量用的
iname: heal #具体变量名是iname,值是heal
ipass: '123456' #再定义一个变量名是ipass,值是123456
#注意密码必须是字符串,需要引号
tasks: #tasks定义需要执行的任务
- name: Use variables create user. #给任务写个描述信息
user: #调用user模块创建用户
name: "{{ iname }}" #用户名的是前面定义的变量
password: "{{ ipass | password_hash('sha512') }}"
#密码是前面定义好的ipass,管道给password_hash把密码加密.
# vim ~/ansible/file_var.yml
---
- hosts: test
vars_files: variables.yml #当变量比较多时,专门定义一个文件用来存变量
tasks:
- name: create user.
user:
name: "{{ iname }}"
password: "{{ ipass | password_hash('sha512') }}"
#调用user模块创建用户
#用户名是变量文件variables.yml中定义的变量iname,密码也是变量文件中定义的变量
[root@control ansible]# vim ~/ansible/variables.yml
---
iname: cloud
ipass: '123456'
使用firewalld模块可以配置防火墙策略。
# vim ~/ansible/firewall.yml
---
- hosts: test #hosts定义需要远程的主机
tasks: #tasks定义需要执行哪些任务
- name: install firewalld. #name为第一个任务定义描述信息
yum: #第一个任务调用yum模块安装软件
name: firewalld #需要安装的软件名称为firewalld
state: present #state等于present代表安装软件
- name: run firewalld. #定义第二个任务的描述信息
service: #第二个任务调用service模块启动服务
name: firewalld #启动的服务名称为firewalld
state: started #state等于started代表启动服务
enabled: yes #enabled等于yes是设置服务为开机自启动
- name: set firewalld rule. #第三个任务的描述信息
firewalld: #第三个任务调用firewalld模块设置防火墙规则
port: 80/tcp #在防火墙规则中添加一个放行tcp,80端口的规则
permanent: yes #permaenent 是设置永久规则
immediate: yes #immediate 是让规则立刻生效
state: enabled #state等于enabled是添加防火墙规则
#最终:在默认zone中添加一条放行80端口的规则
copy模块可以将一个文件拷贝给远程主机,但是如果希望每个拷贝的文件内容都不一样呢?如何给所有web主机拷贝index.html内容是各自的IP地址?
Ansible可以利用Jinja2模板引擎读取变量,之前在playbook中调用变量,也是Jinja2的功能,Jinja2模块的表达式包含在分隔符**"{{ }}"**内。
这里,我们给webserver主机拷贝首页,要求每个主机内容不同。
# mkdir ~/ansible/template
# vim ~/ansible/template/index.html
Welcome to {{ansible_hostname}} on {{ ansible_eth0.ipv4.address }}.
#注意网卡名称根据实际情况填写,不可以完全照抄,不知道网卡名可以通过ip a s查询!
#{{ansible_hostname}}和{{ ansible_eth0.ipv4.address }}是ansible自动的facts变量。
# vim ~/ansible/template.yml
---
- hosts: webserver
tasks:
- name: use template copy index.html to webserver.
template:
src: ~/ansible/template/index.html
dest: /tmp/index.html
#hosts定义需要远程的目标主机是谁;tasks定义需要执行的任务是什么
#- name定义任务的描述信息;任务需要调用的模块是template模块
#template模块需要两个参数,src指定需要拷贝的源文件,dest指定需要拷贝的目标位置
#src: ~/ansible/template/index.html是上面创建的文件,文件中包含变量
#dest: /tmp/index.html拷贝到目标主机放在/tmp目录下
默认ansible在遇到error会立刻停止playbook,使用ignore_errors可以忽略错误,继续后续的任务。
如果一个剧本里面有20个任务,执行到第3个时失败,则不再往下执行。
下面这个这个Playbook在执行时会意外中断。
# vim ~/ansible/error.yml
---
- hosts: test
tasks:
- name: start a service that does not exist.
service:
name: hehe #注意:没有这个服务(启动一个不存在的服务)
state: started
- name: touch a file.
file:
path: /tmp/service.txt
state: touch
下面这个Playbook在执行时因为忽略了错误(针对某一个任务),不会被中断。
# vim ~/ansible/error.yml
---
- hosts: test
tasks:
- name: start a service that does not exist.
service:
name: hehe
state: started
ignore_errors: true #针对某一个任务忽略错误(ignore_errors是关键词)
- name: touch a file.
file:
path: /tmp/service.txt
state: touch
下面这个Playbook在执行时因为忽略了错误,不会被中断。
# cat ~/ansible/error.yml
---
- hosts: test
ignore_errors: true #针对playbook全局忽略错误
tasks:
- name: start a service that does not exist.
service:
name: hehe
state: started
- name: touch a file.
file:
path: /tmp/service.txt
state: touch
在剧本中tasks用来定义任务(一定会执行),handlers也可以定义任务(不一定执行),handlers任务要想执行必须要被别人触发才能执行。
实例草稿:
---
- hosts: test
tasks:
- 任务1
notify:任务5
- 任务2
handlers:
- 任务5
- 任务6
可以通过handlers定义一组任务,仅当某个任务触发(notify)handlers时才执行相应的任务,如果有多个notify触发执行handlers任务,也仅执行一次。
仅当任务的执行状态为changed时handlers任务才执行,handlers任务在所有其他任务都执行后才执行。
下面编写一个通过notify触发执行handlers任务的案例。
# vim ~/ansible/handlers.yml
---
- hosts: test
tasks:
- name: create directory. #多次执行playbook该任务状态不再是changed
file: #调用file模块创建目录
path: /tmp/parents/subdir/ #需要创建的具体目录名称
state: directory #state等于directory代表创建目录
notify: touch file #notify后面名称必须和handlers中的任务名称一致
handlers: #通过handlers再定义一组任务
- name: touch file #给任务写描述信息(任务的名字,名字可以任意)
file: #调用file模块创建文件
path: /tmp/parents/subdir/new.txt #需要创建的文件名
state: touch #state等于touch代表创建文件
#备注:仅当file模块执行成功,
#并且状态为changed时才会通过notify触发执行handlers下面的任务,
#所以多次执行该剧本时,handlers任务不会被重复执行,
#notity后面的名称必须和handlers下面name定义的任务名称一致(名称可以任意)。
when可以定义判断条件,条件为真时才执行某个任务。
常见条件操作符有:==、!=、>、>=、<、<=。
多个条件可以使用and(并且)或or(或者)分割,when表达式中调用变量不要使用{{ }}。
下面编写Playbook,远程主机剩余内存不足700M则关闭NetworkManager服务
# vim ~/ansible/when_1.yml
---
- hosts: test
tasks:
- name: check memory size.
service:
name: NetworkManager
state: stopped
when: ansible_memfree_mb < 700
#被管理端主机剩余内存不足700M则关闭NetworkManager服务(也可以关闭别的不需要的服务)
#ansible_memfree_mb这个是ansible自带的facts变量,代表剩余内存的容量。
判断操作系统是RedHat8则创建测试文件。YAML的语法格式中>支持多行输入,但不保留换行符
# vim ~/ansible/when_2.yml
---
- hosts: test
tasks:
- name: touch a file
file:
path: /tmp/when.txt
state: touch
when: >
ansible_distribution == "RedHat"
and
ansible_distribution_major_version == "8"
#判断操作系统是RedHat8则创建测试文件
#YAML的语法格式中>支持多行输入,但不保留换行符(计算机会认为实际是一行内容)
#ansible_distribution和ansible_distribution_major_version都是自带的facts变量
#可以使用setup模块查看这些变量
如果我们需要当条件满足时执行N个任务,我们可以给N个任务后面都加when判断(但是很麻烦),此时可以使用block定义一个任务块,当条件满足时执行整个任务块.
任务块就是把一组任务合并为一个任务组,使用block语句可以将多个任务合并为一个任务组。
# vim ~/ansible/block_1.yml
---
- hosts: test
tasks:
- name: define a group of tasks.
block: #block是关键词,定义任务组
- name: install httpd #任务组中的第一个任务
yum: #调用yum模块安装httpd软件包
name: httpd
state: present
- name: start httpd #任务组中的第二个任务
service: #调用service模块启动httpd服务
name: httpd
state: started
when: ansible_distribution == "RedHat" #仅当条件满足再执行任务组
#注意:when和block是对齐的,他们在一个级别,当条件满足时要执行的是任务组(不是某一个任务)
#判断条件是看远程的目标主机使用的Linux发行版本是否是RedHat.
对于block任务块,我们可以使用rescue语句定义在block任务执行失败时要执行的其他任务,还可以使用always语句定义无论block任务是否成功,都要执行的任务。
# vim ~/ansible/block_2.yml
---
- hosts: test
tasks:
- block:
- name: touch a file test1.txt
file:
name: /tmp/test1.txt #如果修改为/tmp/xyz/test1.txt就无法创建成功
state: touch
rescue:
- name: touch a file test2.txt
file:
name: /tmp/test2.txt
state: touch
always:
- name: touch a file test3.txt
file:
name: /tmp/test3.txt
state: touch
#默认在/tmp/目录下创建test1.txt会成功,所以不执行rescue(创建test2.txt)
#如果我们把block中的任务改为创建/tmp/xyz/test1.txt(因为没有xyz目录所以会失败)
#当block默认任务失败时就执行rescue任务(创建test2.txt)
#但是不管block任务是否成功都会执行always任务(创建test3.txt)
相同模块需要反复被执行怎么处理?使用loop循环可以避免重复。
编写Playbook,循环创建目录。
# vim ~/ansible/simple_loop.yml
---
- hosts: test
tasks:
- name: mkdir multi directory.
file:
path=/tmp/{{item}} #注意,item是关键字,调用loop循环的值
state=directory
loop: #loop是关键词,定义循环的值,下面是具体的值
- School
- Legend
- Life
#最终在/tmp目录下创建三个子目录.file模块被反复执行了三次。
#mkdir /tmp/School; mkdir /tmp/Legend; mkdir /tmp/Life
编写Playbook,循环创建用户并设置密码。
# vim ~/ansible/complex_loop.yml
---
- hosts: test
tasks:
- name: create multi user.
user:
name: "{{item.iname}}"
password: "{{item.ipass | password_hash('sha512')}}"
loop:
- { iname: 'term', ipass: '123456' }
- { iname: 'amy' , ipass: '654321' }
#loop循环第一次调用user模块创建用户,user模块创建用户会读取loop里面的第一个值.
#loop第一个值里面有两个子值,iname和ipass
#创建用户item.iname就是loop第一个值里面的iname=term
#修改密码item.ipass就是loop第一个值里面的ipass=123456
#loop循环第二次调用user模块创建用户,user模块创建用户会读取loop里面的第二个值.
#loop第二个值里面有两个子值,iname和ipass
#创建用户item.iname就是loop第二个值里面的iname=amy
#修改密码item.ipass就是loop第二个值里面的ipass=654321
encrypt(加密)、decrypt(解密)、view(查看)。
[root@control ansible]# echo 123456 > data.txt #新建测试文件
[root@control ansible]# ansible-vault encrypt data.txt #加密文件
[root@control ansible]# cat data.txt
[root@control ansible]# ansible-vault view data.txt #查看加密文件
[root@control ansible]# echo 123456 > data.txt
[root@control ansible]# ansible-vault encrypt data.txt
New Vault password:
Confirm New Vault password: #123
Encryption successful
[root@control ansible]# cat data.txt
$ANSIBLE_VAULT;1.1;AES256
63663833653364303931613831383939633139623032313362363135326563383363613963323836
6139653934313865343138636665356330383766366539300a333630393061333662376164616437
36386632363464363535633938313430613834633434333561316539333265353661343332313630
3634393039343865370a366563393832626336633937663765353261643130346465336663306131
3564
[root@control ansible]# ansible-vault view data.txt
Vault password: #123
123456
[root@control ansible]# ansible-vault decrypt data.txt
Vault password:
Decryption successful
[root@control ansible]# cat data.txt
123456
[root@control ansible]# ansible-vault rekey data.txt #修改密码
Vault password: <旧密码>
New Vault password: <新密码>
Confirm New Vault password:<确认新密码>
加密、解密每次都输入密码很麻烦,可以将密码写入文件。
[root@control ansible]# echo "I'm secret data" > data.txt #需要加密的敏感数据
[root@control ansible]# echo 123456 > pass.txt #加密的密码
[root@control ansible]# ansible-vault encrypt --vault-id=pass.txt data.txt
[root@control ansible]# cat data.txt
[root@control ansible]# ansible-vault decrypt --vault-id=pass.txt data.txt
[root@control ansible]# cat data.txt
实际生产环境中,为了实现不同的功能,我们会编写大量的playbook文件。而且,每个playbook还可能会调用其他文件(如变量文件),对于海量的、无规律的文件,管理起来非常痛苦!
Ansible从1.2版本开始支持Role(角色),Role(角色)是管理ansible文件的一种规范(目录结构),Role(角色)会按照标准的规范,自动到特定的目录和文件中读取数据。
如果我们创建了一个名称为user.example的Role(角色)
Roles目录结构中主要文件的作用是什么呢?
1.创建Roles
编写一个包含变量的模板文件,编写任务调用template模块,将模板文件拷贝给被管理端主机。
ansible-galaxy命令可以创建、管理自己的roles。
[root@control ansible]# mkdir ~/ansible/roles
[root@control ansible]# ansible-galaxy init ~/ansible/roles/issue
#创建一个Role,该Role的目的是拷贝自己新建的一个模板文件到远程主机的/etc/issue
[root@control ansible]# tree ~/ansible/roles/issue/
#查看目录结构,如果没有tree命令则需要使用yum安装该软件
2.修改Role文件
定义名称为myfile.txt的模板文件(该文件包含变量,因此必须放置templates目录)
[root@control ansible]# vim ~/ansible/roles/issue/templates/myfile.txt
This is the system {{ansible_hostname}}
Today's date is:{{ansible_date_time.date}}
Contact to {{ admin }}
自定义变量文件(前面调用了admin这个变量,这里需要定义admin变量并赋值)
[root@control ansible]# vim ~/ansible/roles/issue/vars/main.yml
---
# vars file for /root/ansible/roles/issue
admin: [email protected]
#变量名为admin,变量的值为[email protected]
文件准备好了,计算机不会自动将文件拷贝给被管理端主机!需要编写任务调用模块实现拷贝的功能。
修改任务文件,任务文件中不需要tasks关键词,Role的各个文件之间相互调用不需要写文件的路径。
[root@control ansible]# vim ~/ansible/roles/issue/tasks/main.yml
---
# tasks file for /root/ansible/roles/issue
- name: delever issue file
template:
src: myfile.txt
dest: /etc/issue
#调用template模块将myfile.txt文件拷贝给被管理端主机.
3)在Playbook中调用Role
Role创建好了,role不会自己运行,需要编写一个剧本调用上面的role。
编写playbook剧本文件,通过roles关键词调用role。
[root@control ansible]# vim ~/ansible/issue.yml
---
- hosts: test
roles:
- issue
# - role2 #支持加载多个role
修改ansible.cfg配置文件,定义roles目录。
[root@control ansible]# vim ~/ansible/ansible.cfg
[defaults]
inventory = ./inventory
roles_path = ./roles #指定到哪个目录下找role
remote_user = alice
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
公共Roles仓库(https://galaxy.ansible.com)管理。
[root@control ansible]# ansible-galaxy search 'httpd'
#联网搜索roles
[root@control ansible]# ansible-galaxy info acandid.httpd
#查看roles基本信息
[root@control ansible]# ansible-galaxy install acandid.httpd -p ~/ansible/roles/
#下载roles到特定的目录,-p可以指定下载到哪个目录
使用ansible-galaxy install可以直接下载Role,也可以编写requirements.yml文件下载Role。
[root@control ansible]# vim ~/ansible/roles/requirements.yml
#格式一:可以直接从Ansible Galaxy官网下载
- src: acandid.httpd
#格式二:可以从某个git服务器下载
- src: http://gitlab.com/xxx/xxx.git
scm: git
version: 56e00a54
name: nginx-acme
#格式三:可以指定位置下载tar包,支持http、https、file
- src: http://example.com/myrole.tar
name: myrole
[root@control ansible]# ansible-galaxy install \
-r ~/ansible/roles/requirements.yml \
-p roles
# -r后面跟文件名,该文件中包含了需要下载哪些role以及他们的链接位置
# -p 指定将下载的role保存到哪个目录
1.创建role角色
# ansible-galaxy init ~/ansible/roles/http
2.修改role
修改role配置文件,准备2台http网站的素材
安装httpd,拷贝一个网页文件。
vim roles/http/tasks/main.yml
---
- name: install httpd
yum:
name: httpd
state: present
- name: create index.html
copy:
content: "{{ansible_hostname}}"
dest: /var/www/html/index.html
- name: set firewalld
firewalld:
service: http
state: enabled
permanent: yes
immediate: yes
- name: start httpd
service:
name: httpd
state: started
enabled: yes
#文件中包含多个任务,每个任务可以设置一个name名字(也可以没有name)
#第一个任务调用yum模块安装httpd软件包
#第二个任务调用copy模块创建一个新的网页文件(index.html)
#调用copy模块时可以在没有源文件的情况下,直接使用content指定文件的内容
#将该内容直接拷贝到被管理主机的某个文件中(/var/www/html/index.html)
#第三个任务调用firewalld模块,设置防火墙规则,允许访问http服务
#第四个任务调用service模块将httpd服务启动,并设置开机自启。
编写Playbook调用role,并执行Playbook。
[root@control ansible]# vim web.yml
---
- hosts: webserver
roles:
- http
[root@control ansible]# ansible-playbook web.yml
1)创建role角色
[root@control ansible]# ansible-galaxy init ~/ansible/roles/proxy
2)准备代理服务器需要的素材
拷贝Nginx源码包,编写一个源码编译安装nginx的shell脚本。
[root@control ansible]# cp lnmp_soft/nginx-1.17.6.tar.gz \
~/ansible/roles/proxy/files/
[root@control ansible]# vim ~/ansible/roles/proxy/files/nginx_install.sh
#!/bin/bash
yum -y install gcc pcre-devel openssl-devel make
cd /tmp
tar -xf /tmp/nginx-1.17.6.tar.gz
cd nginx-1.17.6
./configure --with-http_ssl_module
make
make install
新建一个Nginx代理服务器的配置文件模板。
[root@control ansible]# vim ~/ansible/roles/proxy/files/nginx.conf
worker_processes 2;
#error_log logs/error.log;
events {
worker_connections 65535;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
#gzip on;
upstream webs {
server 192.168.4.13;
server 192.168.4.14;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://webs;
root html;
index index.html index.htm;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
3)修改role配置文件。
[root@control ansible]# vim roles/proxy/tasks/main.yml
---
- name: copy nginx-1.17.6.tar.gz to proxy.
copy:
src: nginx-1.17.6.tar.gz
dest: /tmp/
#拷贝源码包软件
- name: install nginx through shell script.
script: nginx_install.sh
args:
creates: /usr/local/nginx/sbin/nginx
#执行源码编译安装脚本,如果已经安装nginx,则不再执行安装脚本.
#args是关键词,设置script模块的参数,通过creates参数做判断,creates也是关键词
#creates后面跟文件名,如果creates判断文件存在的话就不再执行script模块对应的命令。
- name: copy nginx.conf to destination host.
copy:
src: nginx.conf
dest: /usr/local/nginx/conf/nginx.conf
- name: run nginx service.
shell: /usr/local/nginx/sbin/nginx
args:
creates: /usr/local/nginx/logs/nginx.pid
#nginx.pid存在,说明nginx已经启动。如果该文件存在,则不再启动nginx。
4)编写Playbook调用role,并执行Playbook。
[root@control ansible]# vim proxy.yml
---
- hosts: proxy
roles:
- proxy
[root@control ansible]# ansible-playbook proxy.yml