自动化运维工具:ansible
1、运维自动化发展历程及技术应用
本地部署:On-Premises
基础设施即服务:IaaS(Infrastructure as a Service)
平台即服务:PaaS(Platform as a Service)
软件即服务:SaaS(Software as a Service)
2、自动化运维应用场景
1>文件传输
2>命令执行
应用部署
配置管理
任务流编排
3>程序发布
预发布:新版本的代码先发布到服务器(跟线上环境完全相同,只是未接入调度器)
程序发布:不能导致系统故障或造成系统完全不可用;不能影响用户体验
灰度发布:先发布1/10的服务器,面向少量精准用户,在逐渐增多
发布路径:
/nginx1.1
nginx(软链接)
/nginx1.2
在调度机上将一台real-server置为down,停止旧版本的服务,删除旧的软链接,启动新的服务,创建新的软链接,在调度器启用这一批服务器
自动化灰度发布:脚本、发布平台
3、ansible特性
1>模块化,有Paramiko,PyYAML,jinja2(模块语言)三个关键模块,支持自定义模块
2>基于Python语言实现,部署简单,基于python和ssh(默认安装),安全、agentless(无代理,不需要配置代理,主控端直接控制被控端)
3>支持playbook编排任务
4>幂等性:一个任务执行1遍和执行n遍效果一样,不因重复执行带来意外情况
5>无需代理不依赖PKI(无需ssl)
6>可使用任何编程语言写模块
7>YAML格式,编排任务,支持丰富的数据结构
8>较强大的多层解决方案(角色)
4、ansible架构及工作原理
5、ansible主要组成部分
ansible playbook:任务剧本(任务集),编排定义Ansible任务集的配置文件,由Ansible顺序依次执行,通常是JSON格式的YML文件
inventory:Ansible管理主机的清单/etc/anaible/hosts
modules:Ansible执行命令的功能模块,多数为内置核心模块,也可自定义
plugins:模块功能的补充,如连接类型插件、循环插件、变量插件、过滤插件等,该功能不常用
API:供第三方程序调用的应用程序编程接口
ANSIBLE:组合INVENTORY、API、 MODULES、PLUGINS的绿框,可以理解为是ansible命令工具,其为核心执行工具
6、Ansible命令执行来源:
USER,普通用户,即SYSTEM ADMINISTRATOR
CMDB (配置管理数据库) API 调用
PUBLIC/PRIVATE CLOUD API调用
USER → Ansible Playbook → Ansibile
7、利用ansible实现管理的方式:
Ad-Hoc即ansible命令,主要用于临时命令使用场景
Ansible-playbook主要用于长期规划好的,大型项目的场景,需要有前提的规划
Ansible-playbook (剧本) 执行过程:
将已有编排好的任务集写入Ansible-Playbook
通过ansible-playbook命令分拆任务集至逐条ansible命令,按预定规则逐条执行
Ansible主要操作对象:
HOSTS主机
NETWORKING网络设备
注意事项:
➢执行ansible的主机一般称为主控端,中控,master或堡垒机
➢主控端Python版本需要2.6或以上
➢被控端Python版本小于2.4需要安装python-simplejson
➢被控端如开启SELinux需要安装libselinux-python
➢windows不能做为主控端
8、安装(本机采用rpm安装,实验)
1>rpm包安装:EPEL源
yum install ansible -y
2>编译安装:
yum -y install python-jinja2 PyYAML python-paramiko python-babel python-crypto tar xf ansible-1.5.4.tar.gz cd ansible-1.5.4 python setup.py build python setup.py install mkdir /etc/ansible cp -r examples/* /etc/ansible
3>Git方式:
git clone git://github.com/ansible/ansible.git --recursive cd ./ansible source ./hacking/env-setup
4>pip安装:pip是安装Python包的管理器,类似yum
yum install python-pip python-devel yum install gcc glibc-devel zibl-devel rpm-bulid openssl-devel pip install --upgrade pip pip install ansible --upgrade
5>确认安装: ansible --version
9、相关文件
1>配置文件
/etc/ansible/ansible.cfg 主配置文件,配置ansible工作特性
/etc/ansible/hosts 主机清单
/etc/ansible/roles/ 存放角色的目录
2>程序
/usr/bin/ansible 主程序,临时命令执行工具
/usr/bin/ansible-doc 查看配置文档,模块功能查看工具
/usr/bin/ansible-galaxy 下载/上传优秀代码或Roles模块的官网平台
/usr/bin/ansible-playbook 定制自动化任务,编排剧本工具/usr/bin/ansible-pull远程执行命令的工具
/usr/bin/ansible-vault 文件加密工具
/usr/bin/ansible-console 基于Console界面与用户交互的执行工具
3>主机清单inventory
Inventory主机清单
ansible的主要功用在于批量主机操作,为了便捷地使用其中的部分主机,可以在inventory file中将其分组命名
默认的inventory file为/etc/ansible/hosts
inventory file可以有多个,且也可以通过Dynamic Inventory来动态生成
4>/etc/ansible/hosts文件格式
inventory文件遵循INI文件风格,中括号中的字符为组名。可以将同一个主机同时归并到多个不同的组中;此外,当如若目标主机使用了非默认的SSH端口,还可以在主机名称之后使用冒号加端口号来标明
[root@ansible ~]#vim /etc/ansible/hosts
也可以配置域名解析,使用域名代替ip,如果ssh默认端口号有改动不是22端口,则 ip/域名:端口
Ansible配置文件
Ansible配置文件/etc/ansible/ansible.cfg (一般保持默认)
[defaults] #inventory = /etc/ansible/hosts #主机列表配置文件 #library = /usr/share/my_modules/ #库文件存放目录 #remote_tmp = $HOME/.ansible/tmp #临时py命令文件存放在远程主机目录 #local_tmp = $HOME/.ansible/tmp #本机的临时命令执行目录 #forks = 5 #默认并发数 #sudo_user = root #默认sudo用户 #ask_sudo_pass = True #每次执行ansible命令是否询问ssh密码 #ask_pass = True #remote_port = 22 #host_key_checking = False #检查对应服务器的host_key,建议取消注释,要不然得先建立连接(/root/.ssh/known_hosts)Ansible才能连接成功 #log_path=/var/log/ansible.log #日志文件
Ansible配置文件保持默认,建议开启:host_key_checking = False,log_path=/var/log/ansible.log
10、Ansible系列命令
ansible ansible-doc ansible-playbook ansible-vault ansible-console ansible-galaxy ansible-pull
ansible-doc 显示模块帮助
ansible-doc [options] [module...]
-a 显示所有模块的文档
-l,--list 列出可用模块
-s,--snippet显示指定模块的playbook片段
示例:
ansible-doc -l 列出所有模块 ansible-doc ping 查看指定模块帮助用法 ansible-doc -s ping 查看指定模块帮助用法(短帮助)
ansible通过ssh实现配置管理、应用部署、任务执行等功能,建议配置ansible端能基于密钥认证的方式联系各被管理节点
ansible <host-pattern> [-m module_name] [-a args]
--version显示版本
-m module指定模块,默认为command
-v详细过程-vv -vvv更详细
--list-hosts显示主机列表,可简写-list
-k, --ask-pass提示输入ssh连接密码,默认Key验证
-K, --ask-become-pass提示输入sudo时的口令
-C, --check检查,并不执行
-T, --timeout=TIMEOUT执行命令的超时时间,默认10s
-u, --user=REMOTE_USER执行远程执行的用户
-b, --become代替旧版的sudo切换
[root@ansible ~]#ansible 192.168.100.10 -m ping -k #-m:指定模块,-k:指定k口令验证 SSH password: 192.168.100.10 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } [root@ansible ~]#ansible all --list #列出主机列表 hosts (2): 192.168.100.10 192.168.100.20 [root@ansible ~]#ansible all -m ping -u yang -k #以yang用户身份执行ping SSH password: 192.168.100.20 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } 192.168.100.10 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } [root@ansible ~]#ansible all -k -u yang -b -K -m command -a 'ls /root' #以yang用户身份执行ls /root,-b即sudo切换身份,-K输入sudo时的口令 SSH password: BECOME password[defaults to SSH password]: 192.168.100.20 | CHANGED | rc=0 >> anaconda-ks.cfg jdk-8u221-linux-x64.tar.gz 192.168.100.10 | CHANGED | rc=0 >> anaconda-ks.cfg 设置基于key验证: [root@ansible ~]#ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): #设置空,为了安全建议进行进一步设置 Enter passphrase (empty for no passphrase): #设置空 Enter same passphrase again: #设置空 Your identification has been saved in /root/.ssh/id_rsa. Your public key has been saved in /root/.ssh/id_rsa.pub. The key fingerprint is: SHA256:nHRFKqBQ/jceNpkOFfoVLKbE+hVchYw25FKG8m3eFeM root@ansible.localdomain The key's randomart image is: +---[RSA 2048]----+ | .....+==o++ | | o..+*O.++o | | o=o==++. o | | ..o*+B E | | .o+S. . | | .*.+. | | o | | | | | +----[SHA256]-----+ [root@ansible ~]#ssh-copy-id 192.168.100.10 #拷贝至192.168.100.10 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub" /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@192.168.100.10's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh '192.168.100.10'" and check to make sure that only the key(s) you wanted were added. [root@ansible ~]#ssh-copy-id 192.168.100.20 #拷贝至192.168.100.20 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub" /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@192.168.100.20's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh '192.168.100.20'" and check to make sure that only the key(s) you wanted were added.
[root@ansible ~]#ansible all -m ping #基于key验证不需要在输入key口令 192.168.100.20 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } 192.168.100.10 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" }
ansible的Host-pattern
匹配主机的列表
All:表示所有Inventory中的所有主机
[root@ansible ~]#ansible all --list hosts (2): 192.168.100.10 192.168.100.20
*:通配符
ansible "*" -m ping
ansible 192.168.100.* -m ping
ansible "*server" -m ping
[root@ansible ~]#ansible *server -m ping 192.168.100.20 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } 192.168.100.10 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" }
或关系
ansible "webserver:dbserver" -m ping
ansible "192.168.100.10:192.168.100.20" -m ping
[root@ansible ~]#ansible "webserver:dbserver" -m ping 192.168.100.20 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } 192.168.100.10 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" }
逻辑与
ansible "webserver:&dbserver" -m ping
在webserver组并且在dbserver组中的主机
逻辑非
ansible 'webserver:!dbserver' -m ping
在webserver组,但不在dbserver组中的主机
注意:此处为单引号
综合逻辑
ansible 'webserver:dbserver:&appserver:!ftpserver' -m ping
正则表达式
ansible "webserver:&dbserver" -m ping
ansible“ ~(web|db).*\.yang\.com”-m ping
11、Ansible命令执行过程
1.加载自己的配置文件默认/etc/ansible/ansible.cfg
2.加载自己对应的模块文件,如command
3.通过ansible将模块或命令生成对应的临时py文件,并将该文件传输至远程服务器的对应执行用户$HOME/.ansible/tmp/ansible-tmp-数字/XXX.PY文件
4.给文件+x执行
5.执行并返回结果
6.删除临时py文件,sleep 0退出
执行状态:
绿色:执行成功并且不需要做改变的操作
黄色:执行成功并且对目标主机做变更
红色:执行失败
12、Ansible常见模块
各种模块的使用,可以ansible-doc -s service先查看帮助,再进行使用
1>command模块:在远程主机执行命令,默认模块,可忽略-m选项
[root@ansible ~]#ansible all -m command -a 'hostname' 192.168.100.20 | CHANGED | rc=0 >> node-2 192.168.100.10 | CHANGED | rc=0 >> node-1 [root@ansible ~]#ansible all -m command -a 'echo yang | passwd --stdin yang' 192.168.100.20 | CHANGED | rc=0 >> yang | passwd --stdin yang #没有成功,当字符串打印出来了 192.168.100.10 | CHANGED | rc=0 >> yang | passwd --stdin yang #没有成功,当字符串打印出来了
此命令不支持$VARNAME < > | ; & 等,需要使用shelI模块实现
2>shell模块:和command相似,用shell执行命令
[root@ansible ~]#ansible all -m shell -a 'echo yang | passwd --stdin yang' 192.168.100.20 | CHANGED | rc=0 >> 更改用户 yang 的密码 。 passwd:所有的身份验证令牌已经成功更新。 192.168.100.10 | CHANGED | rc=0 >> 更改用户 yang 的密码 。 passwd:所有的身份验证令牌已经成功更新。
调用bash执行命令类似cat /tmp/stanley.md | awk -F'|' '{print $1,$2}' &>/tmp/example.txt 这些复杂命令,即使使用shell也可能会失败,解决办法:写到脚本,copy到远程,执行,再把需要的结果拉回执行命令的机器
3>script模块:运行脚本
-a "/PATH/TO/SCRIPT_FILE"
[root@ansible ~]#./hello.sh #当前Ansible机器上的脚本:实现打印hello,输出主机名 hello my hostname is ansible.localdomain [root@ansible ~]#ansible all -m script -a '/root/hello.sh' 192.168.100.20 | CHANGED => { "changed": true, "rc": 0, "stderr": "Shared connection to 192.168.100.20 closed.\r\n", "stderr_lines": [ "Shared connection to 192.168.100.20 closed." ], "stdout": "hello\r\nmy hostname is node2.localdomain\r\n", "stdout_lines": [ "hello", "my hostname is node2.localdomain" ] } 192.168.100.10 | CHANGED => { "changed": true, "rc": 0, "stderr": "Shared connection to 192.168.100.10 closed.\r\n", "stderr_lines": [ "Shared connection to 192.168.100.10 closed." ], "stdout": "hello\r\nmy hostname is node1.localdomain\r\n", "stdout_lines": [ "hello", "my hostname is node1.localdomain" ] }
4>copy模块:从服务器复制文件到客户端
[root@ansible ~]#ansible all -m copy -a 'src=/root/hello.sh dest=/root/' #src:指定源文件 dest:指定目标文件存放目录 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "63ff849c6cde8cd1eef04e0472e832d8407eca00", "dest": "/root/hello.sh", "gid": 0, "group": "root", "md5sum": "c426adca4193658a2183f59d726cc2dc", "mode": "0644", "owner": "root", "size": 58, "src": "/root/.ansible/tmp/ansible-tmp-1584450232.18-80008690840068/source", "state": "file", "uid": 0 } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "63ff849c6cde8cd1eef04e0472e832d8407eca00", "dest": "/root/hello.sh", "gid": 0, "group": "root", "md5sum": "c426adca4193658a2183f59d726cc2dc", "mode": "0644", "owner": "root", "size": 58, "src": "/root/.ansible/tmp/ansible-tmp-1584450232.14-44687748938829/source", "state": "file", "uid": 0 }
node1节点查看:
[root@node1 ~]# ll
total 8
-rw-------. 1 root root 1557 Sep 28 22:49 anaconda-ks.cfg
-rw-r--r-- 1 root root 58 Mar 17 21:03 hello.sh
删除node节点的hello.sh文件,重新创建个同名文件,再使用ansible复制,如目标存在,默认覆盖,此处backup=yes指定先备份(ansible有幂等性特点)
[root@ansible ~]#ansible all -m copy -a 'src=/root/hello.sh dest=/root/ backup=yes mode=+x' #src:指定源文件 dest:指定目标文件存放目录 backup=yes:如果目标文件存在则进行备份
192.168.100.20 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"checksum": "63ff849c6cde8cd1eef04e0472e832d8407eca00",
"dest": "/root/hello.sh",
"gid": 0,
"group": "root",
"mode": "0755",
"owner": "root",
"path": "/root/hello.sh",
"size": 58,
"state": "file",
"uid": 0
}
192.168.100.10 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"checksum": "63ff849c6cde8cd1eef04e0472e832d8407eca00",
"dest": "/root/hello.sh",
"gid": 0,
"group": "root",
"mode": "0755",
"owner": "root",
"path": "/root/hello.sh",
"size": 58,
"state": "file",
"uid": 0
}
[root@ansible ~]#ansible all -a 'ls /root/'
192.168.100.20 | CHANGED | rc=0 >>
anaconda-ks.cfg
hello.sh
hello.sh.4160.2020-03-17@21:14:23~ #原文件存在,被备份
jdk-8u221-linux-x64.tar.gz
192.168.100.10 | CHANGED | rc=0 >>
anaconda-ks.cfg
hello.sh
hello.sh.4604.2020-03-17@21:14:22~ #原文件存在,被备份
5>Fetch模块:从客户端取文件至服务器端,copy相反,目录可先tar
[root@ansible ~]#mkdir /data #先创建一个/data目录,存放抓取过来的文件 [root@ansible ~]#ansible all -m fetch -a 'src=/root/anaconda-ks.cfg dest=/data' 192.168.100.10 | CHANGED => { "changed": true, "checksum": "c324508ffa651d64f4fa6286fb74ec438e283b3b", "dest": "/data/192.168.100.10/root/anaconda-ks.cfg", "md5sum": "b1c0bd6a556fac4e120fddcc7d1124df", "remote_checksum": "c324508ffa651d64f4fa6286fb74ec438e283b3b", "remote_md5sum": null } 192.168.100.20 | CHANGED => { "changed": true, "checksum": "4a2e5b1e34c5496d9dcebbb9a1b7ece8b6b03950", "dest": "/data/192.168.100.20/root/anaconda-ks.cfg", "md5sum": "32435d070271e8018672e21ae7c2acbb", "remote_checksum": "4a2e5b1e34c5496d9dcebbb9a1b7ece8b6b03950", "remote_md5sum": null } [root@ansible ~]#tree /data/ /data/ ├── 192.168.100.10 │ └── root │ └── anaconda-ks.cfg └── 192.168.100.20 └── root └── anaconda-ks.cfg 4 directories, 2 files
6>File模块:设置文件属性
[root@ansible ~]#ansible all -a 'ls /data' 192.168.100.20 | FAILED | rc=2 >> ls: 无法访问/data: 没有那个文件或目录non-zero return code 192.168.100.10 | FAILED | rc=2 >> ls: 无法访问/data: 没有那个文件或目录non-zero return code [root@ansible ~]#ansible all -m shell -a 'mkdir /data' #可以看到我们使用shell,创建目录,建议我们使用file模块创建目录 [WARNING]: Consider using the file module with state=directory rather than running 'mkdir'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.100.10 | CHANGED | rc=0 >> 192.168.100.20 | CHANGED | rc=0 >> [root@ansible ~]#ansible all -a 'ls /data' 192.168.100.20 | CHANGED | rc=0 >> 192.168.100.10 | CHANGED | rc=0 >> [root@ansible ~]#ansible all -m file -a 'name=/data/f1 state=touch' #在创建的/data目录下,使用file模块创建文件,state=touch:状态=touch 创建 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "dest": "/data/f1", "gid": 0, "group": "root", "mode": "0644", "owner": "root", "size": 0, "state": "file", "uid": 0 } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "dest": "/data/f1", "gid": 0, "group": "root", "mode": "0644", "owner": "root", "size": 0, "state": "file", "uid": 0 } [root@ansible ~]#ansible all -a 'ls /data' 192.168.100.20 | CHANGED | rc=0 >> f1 192.168.100.10 | CHANGED | rc=0 >> f1 [root@ansible ~]#ansible all -m file -a 'name=/data/f1 state=absent' #absent删除文件 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "path": "/data/f1", "state": "absent" } 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "path": "/data/f1", "state": "absent" } [root@ansible ~]#ansible all -a 'ls /data' 192.168.100.20 | CHANGED | rc=0 >> 192.168.100.10 | CHANGED | rc=0 >> [root@ansible ~]#ansible all -m file -a 'name=/data/dir1 state=directory' #directory创建目录 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/data/dir1", "size": 6, "state": "directory", "uid": 0 } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/data/dir1", "size": 6, "state": "directory", "uid": 0 } [root@ansible ~]#ansible all -a 'ls -l /data' 192.168.100.20 | CHANGED | rc=0 >> 总用量 0 drwxr-xr-x 2 root root 6 3月 17 22:29 dir1 192.168.100.10 | CHANGED | rc=0 >> 总用量 0 drwxr-xr-x 2 root root 6 3月 17 22:29 dir1 [root@ansible ~]#ansible all -m file -a 'name=/data/dir1 state=absent' #删除目录 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "path": "/data/dir1", "state": "absent" } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "path": "/data/dir1", "state": "absent" } [root@ansible ~]#ansible all -a 'ls /data' 192.168.100.20 | CHANGED | rc=0 >> 192.168.100.10 | CHANGED | rc=0 >> [root@ansible ~]#ansible all -m file -a 'src=/etc/fstab dest=/data/fstab.link state=link' #src,dest指定源文件,目标文件,link:创建软链接 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "dest": "/data/fstab.link", "gid": 0, "group": "root", "mode": "0777", "owner": "root", "size": 10, "src": "/etc/fstab", "state": "link", "uid": 0 } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "dest": "/data/fstab.link", "gid": 0, "group": "root", "mode": "0777", "owner": "root", "size": 10, "src": "/etc/fstab", "state": "link", "uid": 0 } [root@ansible ~]#ansible all -a 'ls -l /data' 192.168.100.20 | CHANGED | rc=0 >> 总用量 0 lrwxrwxrwx 1 root root 10 3月 17 22:31 fstab.link -> /etc/fstab 192.168.100.10 | CHANGED | rc=0 >> 总用量 0 lrwxrwxrwx 1 root root 10 3月 17 22:31 fstab.link -> /etc/fstab [root@ansible ~]#ansible all -m file -a 'dest=/data/fstab.link state=absent' #删除软链接 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "path": "/data/fstab.link", "state": "absent" } 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "path": "/data/fstab.link", "state": "absent" } [root@ansible ~]#ansible all -a 'ls -l /data' 192.168.100.20 | CHANGED | rc=0 >> 总用量 0 192.168.100.10 | CHANGED | rc=0 >> 总用量 0
7>Hostname模块:管理主机名
node-1节点先查看主机名: [root@node-1 ~]# hostname node-1 [root@ansible ~]#ansible 192.168.100.10 -m hostname -a 'name=node1' 192.168.100.10 | CHANGED => { "ansible_facts": { "ansible_domain": "", "ansible_fqdn": "node1", "ansible_hostname": "node1", "ansible_nodename": "node1", "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "name": "node1" } node-1节点再查看主机名:exit退出生效 [root@node-1 ~]# hostname node1
8>Cron模块:计划任务
支持时间:minute , hour , day , month , weekday:分钟,小时,日,月,周
[root@ansible ~]#ansible all -m cron -a 'minute=* weekday=1,2,3 job="/usr/bin/wall FBI warning" name=warning' #创建任务 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "envs": [], "jobs": [ "warning" ] } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "envs": [], "jobs": [ "warning" ] }
node-1节点查看:node-2节点类似
[root@node-1 ~]#
Broadcast message from root@node-1 (Tue Mar 17 22:48:01 2020):
FBI warning
Broadcast message from root@node-1 (Tue Mar 17 22:49:01 2020):
FBI warning
[root@ansible ~]#ansible all -m cron -a 'disabled=true job="/usr/bin/wall FBI warning" name=warning' #disabled=true关闭任务
192.168.100.20 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"envs": [],
"jobs": [
"warning"
]
}
192.168.100.10 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"envs": [],
"jobs": [
"warning"
]
}
[root@ansible ~]#ansible all -m cron -a 'job="/usr/bin/wall FBI warning" name=warning state=absent' #absent删除任务
192.168.100.20 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"envs": [],
"jobs": []
}
192.168.100.10 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"envs": [],
"jobs": []
}
9>Yum:管理包
ansible server -m yum -a 'name=httpd state=latest'安装
ansible server -m yum -a 'name=httpd state=absent'删除
[root@ansible ~]#ansible all -m yum -a 'name=httpd state=latest' #latest:安装 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "changes": { "installed": [ "httpd" ], "updated": [] }, "msg": "", "rc": 0, "results": [ "Loaded plugins: fastestmirror\nLoading mirror speeds from cached hostfile\nResolving Dependencies\n--> Running transaction check\n---> Package httpd.x86_64 0:2.4.6-67.el7.centos will be installed\n--> Processing Dependency: httpd-tools = 2.4.6-67.el7.centos for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Running transaction check\n---> Package apr.x86_64 0:1.4.8-3.el7 will be installed\n---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed\n---> Package httpd-tools.x86_64 0:2.4.6-67.el7.centos will be installed\n---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package Arch Version Repository Size\n================================================================================\nInstalling:\n httpd x86_64 2.4.6-67.el7.centos base 2.7 M\nInstalling for dependencies:\n apr x86_64 1.4.8-3.el7 base 103 k\n apr-util x86_64 1.5.2-6.el7 base 92 k\n httpd-tools x86_64 2.4.6-67.el7.centos base 87 k\n mailcap noarch 2.1.41-2.el7 base 31 k\n\nTransaction Summary\n================================================================================\nInstall 1 Package (+4 Dependent packages)\n\nTotal download size: 3.0 M\nInstalled size: 10 M\nDownloading packages:\n--------------------------------------------------------------------------------\nTotal 8.4 MB/s | 3.0 MB 00:00 \nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n Installing : apr-1.4.8-3.el7.x86_64 1/5 \n Installing : apr-util-1.5.2-6.el7.x86_64 2/5 \n Installing : httpd-tools-2.4.6-67.el7.centos.x86_64 3/5 \n Installing : mailcap-2.1.41-2.el7.noarch 4/5 \n Installing : httpd-2.4.6-67.el7.centos.x86_64 5/5 \n Verifying : httpd-2.4.6-67.el7.centos.x86_64 1/5 \n Verifying : mailcap-2.1.41-2.el7.noarch 2/5 \n Verifying : apr-1.4.8-3.el7.x86_64 3/5 \n Verifying : httpd-tools-2.4.6-67.el7.centos.x86_64 4/5 \n Verifying : apr-util-1.5.2-6.el7.x86_64 5/5 \n\nInstalled:\n httpd.x86_64 0:2.4.6-67.el7.centos \n\nDependency Installed:\n apr.x86_64 0:1.4.8-3.el7 apr-util.x86_64 0:1.5.2-6.el7 \n httpd-tools.x86_64 0:2.4.6-67.el7.centos mailcap.noarch 0:2.1.41-2.el7 \n\nComplete!\n" ] } 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "changes": { "installed": [ "httpd" ], "updated": [] }, "msg": "", "rc": 0, "results": [ "Loaded plugins: fastestmirror\nLoading mirror speeds from cached hostfile\nResolving Dependencies\n--> Running transaction check\n---> Package httpd.x86_64 0:2.4.6-67.el7.centos will be installed\n--> Processing Dependency: httpd-tools = 2.4.6-67.el7.centos for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-67.el7.centos.x86_64\n--> Running transaction check\n---> Package apr.x86_64 0:1.4.8-3.el7 will be installed\n---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed\n---> Package httpd-tools.x86_64 0:2.4.6-67.el7.centos will be installed\n---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package Arch Version Repository Size\n================================================================================\nInstalling:\n httpd x86_64 2.4.6-67.el7.centos base 2.7 M\nInstalling for dependencies:\n apr x86_64 1.4.8-3.el7 base 103 k\n apr-util x86_64 1.5.2-6.el7 base 92 k\n httpd-tools x86_64 2.4.6-67.el7.centos base 87 k\n mailcap noarch 2.1.41-2.el7 base 31 k\n\nTransaction Summary\n================================================================================\nInstall 1 Package (+4 Dependent packages)\n\nTotal download size: 3.0 M\nInstalled size: 10 M\nDownloading packages:\n--------------------------------------------------------------------------------\nTotal 8.3 MB/s | 3.0 MB 00:00 \nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n Installing : apr-1.4.8-3.el7.x86_64 1/5 \n Installing : apr-util-1.5.2-6.el7.x86_64 2/5 \n Installing : httpd-tools-2.4.6-67.el7.centos.x86_64 3/5 \n Installing : mailcap-2.1.41-2.el7.noarch 4/5 \n Installing : httpd-2.4.6-67.el7.centos.x86_64 5/5 \n Verifying : httpd-2.4.6-67.el7.centos.x86_64 1/5 \n Verifying : mailcap-2.1.41-2.el7.noarch 2/5 \n Verifying : apr-1.4.8-3.el7.x86_64 3/5 \n Verifying : httpd-tools-2.4.6-67.el7.centos.x86_64 4/5 \n Verifying : apr-util-1.5.2-6.el7.x86_64 5/5 \n\nInstalled:\n httpd.x86_64 0:2.4.6-67.el7.centos \n\nDependency Installed:\n apr.x86_64 0:1.4.8-3.el7 apr-util.x86_64 0:1.5.2-6.el7 \n httpd-tools.x86_64 0:2.4.6-67.el7.centos mailcap.noarch 0:2.1.41-2.el7 \n\nComplete!\n" ] } [root@ansible ~]#ansible all -m shell -a 'rpm -q httpd' #查看是否安装 [WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'. If you need to use command because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.100.20 | CHANGED | rc=0 >> httpd-2.4.6-67.el7.centos.x86_64 192.168.100.10 | CHANGED | rc=0 >> httpd-2.4.6-67.el7.centos.x86_64 [root@ansible ~]#ansible all -m yum -a 'name=httpd state=absent' #absent:卸载 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "changes": { "removed": [ "httpd" ] }, "msg": "", "rc": 0, "results": [ "已加载插件:fastestmirror\n正在解决依赖关系\n--> 正在检查事务\n---> 软件包 httpd.x86_64.0.2.4.6-67.el7.centos 将被 删除\n--> 解决依赖关系完成\n\n依赖关系解决\n\n================================================================================\n Package 架构 版本 源 大小\n================================================================================\n正在删除:\n httpd x86_64 2.4.6-67.el7.centos @base 9.4 M\n\n事务概要\n================================================================================\n移除 1 软件包\n\n安装大小:9.4 M\nDownloading packages:\nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n 正在删除 : httpd-2.4.6-67.el7.centos.x86_64 1/1 \n 验证中 : httpd-2.4.6-67.el7.centos.x86_64 1/1 \n\n删除:\n httpd.x86_64 0:2.4.6-67.el7.centos \n\n完毕!\n" ] } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "changes": { "removed": [ "httpd" ] }, "msg": "", "rc": 0, "results": [ "已加载插件:fastestmirror\n正在解决依赖关系\n--> 正在检查事务\n---> 软件包 httpd.x86_64.0.2.4.6-67.el7.centos 将被 删除\n--> 解决依赖关系完成\n\n依赖关系解决\n\n================================================================================\n Package 架构 版本 源 大小\n================================================================================\n正在删除:\n httpd x86_64 2.4.6-67.el7.centos @base 9.4 M\n\n事务概要\n================================================================================\n移除 1 软件包\n\n安装大小:9.4 M\nDownloading packages:\nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n 正在删除 : httpd-2.4.6-67.el7.centos.x86_64 1/1 \n 验证中 : httpd-2.4.6-67.el7.centos.x86_64 1/1 \n\n删除:\n httpd.x86_64 0:2.4.6-67.el7.centos \n\n完毕!\n" ] } [root@ansible ~]#ansible all -m shell -a 'rpm -q httpd' #卸载后查看 [WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'. If you need to use command because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.100.20 | FAILED | rc=1 >> 未安装软件包 httpd non-zero return code 192.168.100.10 | FAILED | rc=1 >> 未安装软件包 httpd non-zero return code
10>Service模块:管理服务
ansible server -m service -a 'name=httpd state=stopped'
ansible server -m service -a 'name=httpd state=started'
ansible server -m service -a 'name=httpd state=reloaded'
ansible server -m service -a 'name=httpd state=restarted'
[root@ansible ~]#ansible all -m yum -a 'name=httpd state=latest' #安装httpd [root@ansible ~]#ansible all -m shell -a 'ss -ntl' 192.168.100.20 | CHANGED | rc=0 >> State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:22 *:* LISTEN 0 100 127.0.0.1:25 *:* LISTEN 0 128 :::22 :::* LISTEN 0 100 ::1:25 :::* 192.168.100.10 | CHANGED | rc=0 >> State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:22 *:* LISTEN 0 100 127.0.0.1:25 *:* LISTEN 0 128 :::22 :::* LISTEN 0 100 ::1:25 :::* [root@ansible ~]#ansible all -m service -a 'name=httpd state=started enabled=yes' #启动httpd,并设为开机自启动 [root@ansible ~]#ansible all -m shell -a 'ss -ntl' 192.168.100.20 | CHANGED | rc=0 >> State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:22 *:* LISTEN 0 100 127.0.0.1:25 *:* LISTEN 0 128 :::80 :::* LISTEN 0 128 :::22 :::* LISTEN 0 100 ::1:25 :::* 192.168.100.10 | CHANGED | rc=0 >> State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:22 *:* LISTEN 0 100 127.0.0.1:25 *:* LISTEN 0 128 :::80 :::* LISTEN 0 128 :::22 :::* LISTEN 0 100 ::1:25 :::*[root@ansible ~]#ansible all -m service -a 'name=httpd state=stopped' #关闭httpd
11>User模块:管理用户
[root@ansible ~]#ansible all -m user -a 'name=nginx shell=/sbin/nologin system=yes home=/var/nginx groups=root,bin uid=77 comment="nginx service"' #创建用户指定shell类型,系统账号,家目录,附加组,uid,描述信息 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "comment": "nginx service", "create_home": true, "group": 77, "groups": "root,bin", "home": "/var/nginx", "name": "nginx", "shell": "/sbin/nologin", "state": "present", "system": true, "uid": 77 } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "comment": "nginx service", "create_home": true, "group": 77, "groups": "root,bin", "home": "/var/nginx", "name": "nginx", "shell": "/sbin/nologin", "state": "present", "system": true, "uid": 77 } [root@ansible ~]#ansible all -a 'getent passwd nginx' 192.168.100.20 | CHANGED | rc=0 >> nginx:x:77:77:nginx service:/var/nginx:/sbin/nologin 192.168.100.10 | CHANGED | rc=0 >> nginx:x:77:77:nginx service:/var/nginx:/sbin/nologin [root@ansible ~]#ansible all -m user -a 'name=nginx state=absent remove=yes' #删除用户及其家目录信息 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "force": false, "name": "nginx", "remove": true, "state": "absent", "stderr": "userdel: nginx 邮件池 (/var/spool/mail/nginx) 未找到\n", "stderr_lines": [ "userdel: nginx 邮件池 (/var/spool/mail/nginx) 未找到" ] } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "force": false, "name": "nginx", "remove": true, "state": "absent", "stderr": "userdel: nginx 邮件池 (/var/spool/mail/nginx) 未找到\n", "stderr_lines": [ "userdel: nginx 邮件池 (/var/spool/mail/nginx) 未找到" ] } [root@ansible ~]#ansible all -a 'getent passwd nginx' 192.168.100.20 | FAILED | rc=2 >> non-zero return code 192.168.100.10 | FAILED | rc=2 >> non-zero return code
12>Group:管理组
[root@ansible ~]#ansible all -m group -a 'name=nginx system=yes gid=88' 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "gid": 88, "name": "nginx", "state": "present", "system": true } 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "gid": 88, "name": "nginx", "state": "present", "system": true } [root@ansible ~]#ansible all -a 'getent group nginx' 192.168.100.20 | CHANGED | rc=0 >> nginx:x:88: 192.168.100.10 | CHANGED | rc=0 >> nginx:x:88: [root@ansible ~]#ansible all -m group -a 'name=nginx state=absent' 192.168.100.20 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "name": "nginx", "state": "absent" } 192.168.100.10 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "name": "nginx", "state": "absent" } [root@ansible ~]#ansible all -a 'getent group nginx' 192.168.100.20 | FAILED | rc=2 >> non-zero return code 192.168.100.10 | FAILED | rc=2 >> non-zero return code
模块用法不清楚可以查看帮助:
[root@ansible ~]#ansible-doc -s cron #例:查看cron模块
13、ansible系列命令
1>ansible-galaxy
连接https://galaxy.ansible.com下载相应的roles(角色)
列出所有已安装的galaxy
ansible-galaxy list
安装galaxy
ansible-galaxy install geerlingguy.redis
删除galaxy
ansible-galaxy remove geerlingguy.redis
2>ansible-pull
推送命令至远程,效率无限提升,对运维要求较高
3>Ansible-playbook
(1)playbook是由一个或多个 "play" 组成的列表,采用YAML语言编写。
play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联同起来按事先编排的机制同唱一台大戏。
(2)YAML是一一个可读性高的用来表达资料序列的格式。YAML参考了其他多种语言,包括:XML、C语言、Python、 Perl以及电子邮件格式RFC2822等。
YAML Ain't Markup Language,即YAML不是XML。不过,在开发的这种语言时,YAML的意思其实是:"Yet Another Markup Language" (仍是一种标记语言)
特性:
YAML的可读性好
YAML和脚本语言的交互性好
YAML使用实现语言的数据类型
YAML有一个一致的信息模型
YAML易于实现
YAML可以基于流来处理
YAML表达能力强,扩展性好
更多的内容及规范参见http://www.yaml.org
(3)在单一档案中,可用连续三个连字号(一)区分多个档案。另外,还有选择性的连续三个点号(...)用来表示档案结尾
次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
使用#号注释代码
缩进必须是统一的,不能空格和tab混用
缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的
YAML文件内容和Linux系统大小写判断方式保持一致,是区别大小写的,k/v的值均需大小写敏感
k/v的值可同行写也可换行写。同行使用:分隔
v可是个字符串,也可是另一个列表
一个完整的代码块功能需最少元素需包括name: task
一个name只能包括一个task
YAML文件扩展名通常为yml或yaml
(4)List:列表,其所有元素均使用"-"打头
示例: [root@ansible ansible]#cat file.yml --- - hosts: webserver #一个hosts列表,定义针对的列表主机 remote_user: root tasks: #任务 - name: create new file #任务列表1 file: name=/root/newfile state=touch - name: create new user #任务列表2 user: name=test system=yes shell=/sbin/nologin
(5)Dictionary:字典,通常由多个key与value构成
示例:
# An employee record
name: Example Developer
job: Developer
skill: Elite
也可以将key:value放置于{}中进行表示,用","分隔多个key:value
{name: Example Developer, job: Developer, skill: Elite}
(6)Hosts:执行的远程主机列表
playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机,须事先定义在主机清单中。
可以是如下形式:
one.example.com
one.example.com:two.example.com
192.168.100.10
192.168.100.*
webserver:dbserver 两个组的并集
webserver:&dbserver 两个组的交集
webservers:!dbserver 在webserver组,但不在dbserver组
all /etc/ansible/hosts主机清单中的所有主机
---
- hosts: all #主机清单中的所有主机
remote_user: root
tasks: #任务
......
(7)task列表和action
➢play的主体部分是task list,task list中的各任务按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个任务后再开始第二个。在运行自下而下某playbook时,如果中途发生错误,所有已执行任务都将回滚,因此,在更正playbook后重新执行一次即可。
➢task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致。
➢每个task都应该有其name,用于playbook的执行结果输出,建议其内容尽可能清晰地描述任务执行步骤,如果未提供name,则action的结果将用于输出。
tasks:任务列表
格式:
action: module arguments
module: arguments #建议使用
➢注意:shell和command模块后面跟命令,而非key=value
某任务的状态在运行后为changed时,可通过"notify"通知给相应的 handlers
handlers(触发器)和notify(通知)结合使用触发条件
Handlers是task列表,这些task与前述的task并没有本质上的不同,用于当关注的资源发生变化时,才会采取一定的操作
Notify此action可用于在每个play的最后被触发,这样可避免多次有改变发生时每次都执行指定的操作,仅在所有的变化发生完成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的操作。
#编辑httpd.yml,实现httpd的安装,配置文件更改,服务启动 #问题:第一次启动后,更改httpd的配置文件,第二次再执行,会因为之前启动了,而无法重启服务 #解决:通过设置"notify",当某任务的状态在运行后为changed时,通知给相应的 handlers ,触发相应的命令
[root@ansible ansible]#vim httpd.yml
---
- hosts: all
remote_user: root
tasks:
- name: install httpd package
yum: name=httpd
- name: copy conf file
copy: src=/etc/httpd/conf/httpd.conf dest=/etc/httpd/conf/ backup=yes
notify: restart service
- name: start service
service: name=httpd state=started enabled=yes
handlers:
- name: restart service
service: name=httpd state=restarted
➢任务可以通过"tags"打标签,而后可在ansible-playbook命令上使用-t指定进行调用
[root@ansible ansible]#ansible all -m yum -a 'name=httpd state=absent' [root@ansible ansible]#ansible all -m shell -a 'rpm -q httpd' [WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'. If you need to use command because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.100.20 | FAILED | rc=1 >> 未安装软件包 httpd non-zero return code 192.168.100.10 | FAILED | rc=1 >> 未安装软件包 httpd non-zero return code
通过ansible卸载httpd,在通过tags标签指定只下载httpd
[root@ansible ansible]#ansible-playbook -t install -C httpd.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.20] ok: [192.168.100.10] TASK [install httpd package] ******************************************************* changed: [192.168.100.20] changed: [192.168.100.10] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@ansible ansible]#ansible-playbook -t install httpd.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.20] ok: [192.168.100.10] TASK [install httpd package] ******************************************************* changed: [192.168.100.10] changed: [192.168.100.20] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@ansible ansible]#ansible all -m shell -a 'rpm -q httpd' [WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'. If you need to use command because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.100.20 | CHANGED | rc=0 >> httpd-2.4.6-67.el7.centos.x86_64 192.168.100.10 | CHANGED | rc=0 >> httpd-2.4.6-67.el7.centos.x86_64 [root@ansible ansible]#ansible all -m shell -a 'ss -ntl | grep 80' 192.168.100.20 | FAILED | rc=1 >> non-zero return code 192.168.100.10 | FAILED | rc=1 >> non-zero return code
(8)运行playbook
如果命令或脚本的退出码不为零,可以使用如下方式替代
tasks: - name: run this command and ignore the result shell: /usr/bin/somecommand || /bin/true
或者使用ignore_errors来忽略错误信息:
tasks: - name: run this command and ignore the result shell: /usr/bin/somecommand ignore_errors: True
运行playbook的方式
ansible-playbook <filename.yml> ... [options]
常见选项:
--check只检测可能会发生的改变,但不真正执行操作
--list-hosts 列出运行任务的主机
--limit主机列表只针对主机列表中的主机执行
-v显示过程-vv -vvv 更详细
示例: ansible-playbook file.yml --check #只检测 ansible-playbook file.yml #执行 ansible-playbook file.yml --limit webserver
(9)ansible-playbook支持变量:
变量名:仅能由字母、数字和下划线组成,且只能以字母开头
变量来源:
1 ansible setup facts远程主机的所有变量都可直接调用
2 在/etc/ansible/hosts中定义
普通变量:主机组中主机单独定义,优先级高于公共变量
公共(组)变量:针对主机组中所有主机定义统一变量
3 通过命令行指定变量,优先级最高
ansible-playbook -e varname=value
4 在playbook中定义
[root@ansible ansible]#vim apps.yml --- - hosts: all remote_user: root tasks: - name: install package yum: name={{ pkname }} #定义变量 - name: start service service: name={{ pkname }} state=started enabled=yes [root@ansible ansible]#ansible-playbook -e 'pkname=vsftpd' -C apps.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.20] ok: [192.168.100.10] TASK [install package] ************************************************************* changed: [192.168.100.20] changed: [192.168.100.10] TASK [start service] *************************************************************** changed: [192.168.100.10] changed: [192.168.100.20] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@ansible ansible]#ansible-playbook -e 'pkname=vsftpd' apps.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.10] ok: [192.168.100.20] TASK [install package] ************************************************************* changed: [192.168.100.20] changed: [192.168.100.10] TASK [start service] *************************************************************** changed: [192.168.100.10] changed: [192.168.100.20] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@ansible ansible]#ansible all -m shell -a 'ss -ntl|grep 21' 192.168.100.20 | CHANGED | rc=0 >> LISTEN 0 32 :::21 :::* 192.168.100.10 | CHANGED | rc=0 >> LISTEN 0 32 :::21 :::*
也可以在playbook中,定义vars
也可以使用变量文件:
[root@ansible ansible]#vim vars.yml var1: httpd var2: vsftpd [root@ansible ansible]#vim testvar.yml --- - hosts: all remote_user: root vars_files: - vars.yml tasks: - name: install package yum: name={{ var1 }} - name: create file file: name=/root/{{ var2 }}.log state=touch [root@ansible ansible]#ansible-playbook -C testvar.yml ...... [root@ansible ansible]#ansible-playbook testvar.yml
...... [root@ansible ansible]#ansible all -a 'ls /root' 192.168.100.20 | CHANGED | rc=0 >> ...... vsftpd.log 192.168.100.10 | CHANGED | rc=0 >> ...... vsftpd.log [root@ansible ansible]#ansible all -m shell -a 'rpm -q httpd' [WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'. If you need to use command because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.100.20 | CHANGED | rc=0 >> httpd-2.4.6-67.el7.centos.x86_64 192.168.100.10 | CHANGED | rc=0 >> httpd-2.4.6-67.el7.centos.x86_64
5 在role中定义
变量定义:key=value
示例:http_port=80
变量调用方式:
通过{{ variable_name }}调用变量,且变量名前后必须有空格,有时用"{{ variable_ name}}" 才生效
ansible playbook -e选项指定
4>Ansible-vault
功能:管理加密解密yml文件
ansible-vault [create |decrypt| edit encrypt|rekey |view] ansible-vault encrypt hello.yml 加密 ansible-vault decrypt hello.yml 解密 ansible-vault view hello.yml 查看 ansible-vault edit hello.yml 编辑加密文件 ansible-vault rekey hello.yml 修改口令 ansible-vault create new.yml 创建新文件
5>Ansible-console:2.0+新增,可交互执行命令,支持tab
root@test (2)[f:10] $
执行用户@当前操作的主机组(当前组的主机数量)[f:并发数]$
➢设置并发数:forksn 例如:forks 10
➢切换组:cd主机组 例如:cd web
➢列出当前组主机列表:list
➢列出所有的内置命令:?或help
➢示例:
[root@ansible ~]#ansible-console Welcome to the ansible console. Type help or ? to list commands. root@all (2)[f:5]$ cd webserver root@webserver (1)[f:5]$ forks 10 root@webserver (1)[f:10]$ command hostname 192.168.100.10 | CHANGED | rc=0 >> node1 root@webserver (1)[f:10]$ shell ls /root 192.168.100.10 | CHANGED | rc=0 >> anaconda-ks.cfg hello.sh hello.sh.4604.2020-03-17@21:14:22~
14、模板templates
文本文件,嵌套有脚本(使用模板编程语言编写)
Jinja2语言,使用字面量,有下面形式
字符串:使用单引号或双引号
数字:整数,浮点数
列表: [item1, item2, ...]
元组: (item1, item2, ...)
字典: {key1:value1, key2:value2, ...}
布尔型: true/false
算术运算: +,-, *,/.// %,**
比较操作: ==,!=,>,>=, <, <=
逻辑运算: and, or, not
流表达式: For If When
templates功能:根据模块文件动态生成对应的配置文件
templates文件必须存放于templates目录下,且命名为.j2结尾
yamI/yml文件需和templates目录平级,目录结构如下:
.
├── ansible
└── templates
#实例:配置nginxtemplates模板文件,使不同主机的worker进程数量=CPU*2 [root@ansible ~]#mkdir ansiable/templates [root@ansible ~]#tree -d . ├── ansible │ └── templates └── bin [root@ansible ansible]#ansible webserver -m setup | grep "processor" #查看CPU变量名 "ansible_processor": [ "ansible_processor_cores": 1, "ansible_processor_count": 1, "ansible_processor_threads_per_core": 1, "ansible_processor_vcpus": 1, [root@ansible ansible]#cp /etc/nginx/nginx.conf ./templates/nginx.conf.j2 [root@ansible ansible]#vim templates/nginx.conf.j2
[root@ansible ansible]#vim testtempl.yml
---
- hosts: webserver
remote_user: root
tasks:
- name: install paskage
yum: name=nginx
- name: copy template
tempate: src=/root/ansible/templates/nginx.conf.j2 dest=/etc/nginx/nginx.conf
- name: start service
service: name=nginx state=started enabled=yes
[root@ansible ansible]#ansible-playbook testtempl.yml
when语句:
条件测试:如果需要根据变量、facts或此前任务的执行结果来做为某task执行与否的前提时要用到条件测试,通过when语句实现,在task中使用,jinja2的语法
格式:
在task后添加when子句即可使用条件测试;when语句支持Jinja2表达式语法
对不同centos版本针对配置
[root@ansible ansible]#ansible all -m setup -a 'filter="*distribution*"' 192.168.100.20 | SUCCESS => { "ansible_facts": { "ansible_distribution": "CentOS", "ansible_distribution_file_parsed": true, "ansible_distribution_file_path": "/etc/redhat-release", "ansible_distribution_file_variety": "RedHat", "ansible_distribution_major_version": "7", "ansible_distribution_release": "Core", "ansible_distribution_version": "7.4", "discovered_interpreter_python": "/usr/bin/python" }, "changed": false } 192.168.100.10 | SUCCESS => { "ansible_facts": { "ansible_distribution": "CentOS", "ansible_distribution_file_parsed": true, "ansible_distribution_file_path": "/etc/redhat-release", "ansible_distribution_file_variety": "RedHat", "ansible_distribution_major_version": "7", "ansible_distribution_release": "Core", "ansible_distribution_version": "7.4", "discovered_interpreter_python": "/usr/bin/python" }, "changed": false }
迭代:当有需要重复性执行的任务时,可以使用迭代机制
对迭代项的引用,固定变量名为"item"
要在task中使用with_items给定要迭代的元素列表
列表格式:
字符串
字典
迭代嵌套子变量
[root@ansible ansible]#cat testitem.yml --- - hosts: all remote_user: root tasks: - name: create some groups group: name={{ item }} with_items: - g1 - g2 - g3 - name: create some users user: name={{ item.name }} group={{ item.group }} with_items: - { name: 'user1', group: 'g1' } - { name: 'user2', group: 'g2' } - { name: 'user3', group: 'g3' }
playbook中的template for if:
[root@ansible ansible]#cat templates/for1.conf.j2 {% for port in ports %} server{ listen {{ port }} } {% endfor %} [root@ansible ansible]#cat testfor.yml --- - hosts: all remote_user: root vars: ports: - 81 - 82 - 83 tasks: - name: copy conf template: src=/root/ansible/templates/for1.conf.j2 dest=/root/for1.conf [root@ansible ansible]#ansible-playbook -C testfor.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.20] ok: [192.168.100.10] TASK [copy conf] ******************************************************************* changed: [192.168.100.10] changed: [192.168.100.20] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@ansible ansible]#ansible-playbook testfor.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.20] ok: [192.168.100.10] TASK [copy conf] ******************************************************************* changed: [192.168.100.10] changed: [192.168.100.20] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node1节点查看:
[root@node1 ~]# cat for1.conf
server{
listen 81
}
server{
listen 82
}
server{
listen 83
}
[root@ansible ansible]#cat testfor2.yml --- - hosts: all remote_user: root vars: ports: - listen_port: 80 - listen_port: 82 - listen_port: 83 tasks: - name: copy conf template: src=/root/ansible/templates/for2.conf.j2 dest=/root/for2.conf [root@ansible ansible]#cat templates/for2.conf.j2 {% for port in ports %} server{ listen {{ port.listen_port }} } {% endfor %} [root@ansible ansible]#ansible-playbook testfor2.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.20] ok: [192.168.100.10] TASK [copy conf] ******************************************************************* changed: [192.168.100.10] changed: [192.168.100.20] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node1节点查看: [root@node1 ~]# cat for2.conf server{ listen 80 } server{ listen 82 } server{ listen 83 }
[root@ansible ansible]#cat testfor3.yml --- - hosts: all remote_user: root vars: ports: - web1: port: 81 name: web1.yang.com rootdir: /root/website1 - web1: port: 82 name: web2.yang.com rootdir: /root/website2 - web1: port: 83 name: web3.yang.com rootdir: /root/website3 tasks: - name: copy conf template: src=/root/ansible/templates/for3.conf.j2 dest=/root/for3.conf [root@ansible ansible]#cat templates/for3.conf.j2 {% for p in ports %} server{ listen {{ p.port }} servername {{ p.name }} documentroot {{ p.rootdir }} } {% endfor %} [root@ansible ansible]#ansible-playbook testfor3.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.20] ok: [192.168.100.10] TASK [copy conf] ******************************************************************* changed: [192.168.100.20] changed: [192.168.100.10] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node1节点查看: [root@node1 ~]# cat for3.conf server{ listen 81 servername web1.yang.com documentroot /root/website1 } server{ listen 82 servername web2.yang.com documentroot /root/website2 } server{ listen 83 servername web3.yang.com documentroot /root/website3 }
[root@ansible ansible]#cat testfor4.yml --- - hosts: all remote_user: root vars: ports: - web1: port: 81 #name: web1.yang.com rootdir: /root/website1 - web1: port: 82 name: web2.yang.com rootdir: /root/website2 - web1: port: 83 #name: web3.yang.com rootdir: /root/website3 tasks: - name: copy conf template: src=/root/ansible/templates/for4.conf.j2 dest=/root/for4.conf [root@ansible ansible]#cat templates/for4.conf.j2 {% for p in ports %} server{ listen {{ p.port }} {% if p.name is defined %} #如果被定义了则生成,没有定义则不生成 servername {{ p.name }} {% endif %} documentroot {{ p.rootdir }} } {% endfor %} [root@ansible ansible]#ansible-playbook testfor4.yml PLAY [all] ************************************************************************* TASK [Gathering Facts] ************************************************************* ok: [192.168.100.10] ok: [192.168.100.20] TASK [copy conf] ******************************************************************* changed: [192.168.100.20] changed: [192.168.100.10] PLAY RECAP ************************************************************************* 192.168.100.10 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.100.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node1节点查看: [root@node1 ~]# cat for4.conf server{ listen 81 documentroot /root/website1 } server{ listen 82 servername web2.yang.com documentroot /root/website2 } server{ listen 83 documentroot /root/website3 }
15、roles
1>定义:ansilbe自1.2版本引入的新特性,用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。
复杂场景:建议使用roles,代码复用度高
变更指定主机或主机组
如命名不规范维护和传承成本大
某些功能需多个Playbook,通过Includes即可实现
2>roles目录结构
每个角色,以特定的层级目录结构进行组织
playbook.yml roles/ project/ tasks/ files/ vars/ 不常用 default/ 不常用 templates/ handlers/ meta/ 不常用
/roles/project/:项目名称,有以下子目录
files/ : 存放由copy或script模块等调用的文件
templates/ : template模块查找所需要模板文件的目录
tasks/ : 定义task,role的基本元素,至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含
handlers/ : 至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含
vars/ : 定义变量,至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含
meta/ : 定义当前角色的特殊设定及其依赖关系,至少应该包含一个名为main.ymI的文件,其它文件需在此文件中通过include进行包含
default/ : 设定默认变量时使用此目录中的main.yml文件
3>使用:
角色(roles):角色集合
roles/(官方建议目录/etc/ansible/roles,不过也可以自定义)
mysql/
httpd/
nginx/
memcached/
[root@ansible ansible]#tree #构建目录结构,如果没有可以mkdir创建目录 . └── roles ├── httpd ├── memcache ├── mysql └── nginx 5 directories, 0 files [root@ansible ansible]#cd roles/nginx/ [root@ansible nginx]#pwd /root/ansible/roles/nginx [root@ansible nginx]#mkdir tasks templates #在roles/nginx创建一个任务目录和模板目录 [root@ansible nginx]#ls tasks templates [root@ansible nginx]#cd tasks/ #进入任务tasks目录 [root@ansible tasks]#vim group.yml #先创建组文件 [root@ansible tasks]#cat group.yml - name: create group group: name=nginx gid=80 [root@ansible tasks]#vim user.yml #再创建用户文件 [root@ansible tasks]#cat user.yml - name: create user user: name=nginx uid=80 group=nginx system=yes shell=/sbin/nologin [root@ansible tasks]#vim yum.yml #再创建安装文件 [root@ansible tasks]#cat yum.yml - name: install package yum: name=nginx [root@ansible tasks]#vim start.yml #进而创建启动文件 [root@ansible tasks]#cat start.yml - name: start service service: name=nginx state=started enabled=yes [root@ansible tasks]#cp start.yml restart.yml [root@ansible tasks]#vim restart.yml #重启服务文件 [root@ansible tasks]#cat restart.yml - name: restart service service: name=nginx state=restarted [root@ansible templates]#cd ../templates/ #进入模板templates目录 [root@ansible templates]#cp /etc/nginx/nginx.conf nginx.conf.j2 #拷贝本机的nginx文件成为模板文件 [root@ansible templates]#vim nginx.conf.j2
[root@ansible templates]#cd ../tasks/ [root@ansible tasks]#ls group.yml restart.yml start.yml user.yml yum.yml [root@ansible tasks]#vim templ.yml [root@ansible tasks]#cat templ.yml #回到任务tasks目录,创建templ.yml文件 - name: copy conf template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf [root@ansible tasks]#ls group.yml restart.yml start.yml templ.yml user.yml yum.yml [root@ansible tasks]#vim main.yml #创建全局文件,定义调用各个板块文件的顺序 [root@ansible tasks]#cat main.yml - include: group.yml - include: user.yml - include: yum.yml - include: templ.yml - include: start.yml [root@ansible tasks]#cd ../../ [root@ansible roles]#ls httpd memcache mysql nginx [root@ansible roles]#cd ../ [root@ansible ansible]#ls roles [root@ansible ansible]#vim nginx_role.yml #创建nginx role文件,注意必须和roles同级 [root@ansible ansible]#cat nginx_role.yml - hosts: all remote_user: root roles: - role: nginx [root@ansible ansible]#ansible-playbook -C nginx_role.yml #检查语法 [root@ansible ansible]#ansible-playbook nginx_role.yml #执行
总结:构建目录树结构如下 [root@ansible ansible]#tree . ├── nginx_role.yml └── roles ├── httpd ├── memcache ├── mysql └── nginx ├── tasks │ ├── group.yml │ ├── main.yml │ ├── restart.yml │ ├── start.yml │ ├── templ.yml │ ├── user.yml │ └── yum.yml └── templates └── nginx.conf.j2 7 directories, 9 files