Ansible的使用入门 | Word Count: 11.7k | Reading Time: 58mins | Post Views:
简介 Ansible 的名字来源于小说《安德的游戏》中超时空的即时通信工具,可以使用它远程实时控制前线舰队的战斗。2012年3月发布0.0.1版,2015年为Redhat收购。
宗旨
Keep Things Simple
Stay Organized
Test Often
优点
具有数千个功能丰富的模块
使用和部署简单
安全
幂等性
支持使用YAML格式进行playbook的任务编排
具有较强大的多层解决方案
缺点
不支持一般性事务回滚
对于大量主机执行效率差,如不saltstack效率高
组成
Inventory:主机清单文件,提供主机信息和主机分组信息
Modules:执行命令的功能模块
Plugins:功能模块的补充
API:供第三方程序调用的编程接口
安装 部署 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # Rocklinux 需要BaseOS、APPstream、EPEL三个软件源的支持 [sujx@master ~]$ yum info ansible Name : ansible Version : 7.2.0 Release : 1.el8 Architecture : noarch Size : 329 M Source : ansible-7.2.0-1.el8.src.rpm Repository : @System From repo : epel Summary : Curated set of Ansible collections included in addition to ansible-core URL : https://ansible.com License : GPL-3.0-or-later AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND MIT AND MPL-2.0 : AND PSF-2.0 Description : Ansible is a radically simple model-driven configuration management, : multi-node deployment, and remote task execution system. Ansible works : over SSH and does not require any software or daemons to be installed : on remote nodes. Extension modules can be written in any language and : are transferred to managed machines automatically. : : This package provides a curated set of Ansible collections included in addition : to ansible-core. [sujx@master ~]$ sudo yum install -y ansible
配置文件
ansible.cfg1 2 3 4 5 6 7 8 9 # 指定资产管理清单,忽略命令告警 [defaults] inventory = ./hosts # 配置sudo 用户,默认使用管理员账号登录,然后sudo 为root执行操作 [privilege_escalation] become = True become_method = sudo become_user = root
inventory‘1 2 3 4 5 6 master node[1:2] [Node] node1 node2
多个配置文件可以嵌套1 2 3 4 5 6 7 8 9 10 11 12 [sujx@master ~]$ [sujx@master ~]$ ansible --version ansible [core 2.14.2] # 配置文件为用户家目录下 config file = /home/sujx/ansible.cfg configured module search path = ['/home/sujx/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3.11/site-packages/ansible ansible collection location = /home/sujx/.ansible/collections:/usr/share/ansible/collections executable location = /usr/bin/ansible python version = 3.11.2 (main, Feb 18 2023, 08:12:16) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] (/usr/bin/python3.11) jinja version = 3.1.2 libyaml = True
相关工具
/usr/bin/ansible 主程序,临时命令执行工具
/usr/bin/ansible-doc 查看配置文档,模块功能查看工具,相当于man
/usr/bin/ansible-playbook 定制自动化任务,编排剧本工具,相当于脚本
/usr/bin/ansible-pull 远程执行命令的工具
/usr/bin/ansible-vault 文件加密工具
/usr/bin/ansible-console 基于Console界面与用户交互的执行工具
/usr/bin/ansible-galaxy 下载/上传优秀代码或Roles模块的官网平台
基于密钥对的批量添加脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # !/bin/bash set -xn # 设置SSH连接超时时间(秒) TIMEOUT=5 # 待连接主机列表(IP地址或域名) HOSTS=("192.168.1.1" "192.168.1.2" "192.168.1.3") # SSH登录用户名和密码 USERNAME="your_username" PASSWORD="your_password" # 判断主机发行版是否为Redhat系 if [ -f /etc/redhat-release ]; then # 判断sshpass是否已安装 if ! command -v sshpass &> /dev/null; then # 安装sshpass sudo yum install -y sshpass fi else # 判断coreutils是否已安装 if ! command -v timeout &> /dev/null; then # 安装coreutils sudo apt-get install -y coreutils fi fi for HOST in ${HOSTS[@]} do # 使用ssh-keygen生成密钥对 ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa &> /dev/null # 把本地公钥复制到远程主机的授权文件中 sshpass -p "${PASSWORD}" ssh-copy-id -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa.pub ${USERNAME}@${HOST} &> /dev/null # 测试是否可以无密码登录 if timeout ${TIMEOUT} ssh -o ConnectTimeout=${TIMEOUT} -o PasswordAuthentication=no ${USERNAME}@${HOST} exit &> /dev/null; then echo "${HOST}: 登录成功!" else echo "${HOST}: 登录失败!" fi done exit 0
基本用法
ansible 机器名 -m 模块名 -a “模块的参数”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 以Shell模块为例 [sujx@master ~]$ ansible all -m shell -a "hostname" # 当返回信息为绿色时,”changed”为false ,表示ansible没有进行任何操作,没有”改变什么”。 # 当返回信息为黄色时,”changed”为true ,表示ansible执行了操作,”当前状态”已经被ansible改变成了”目标状态”。 # 主机名 |命令执行状态|return code 为0 表示执行成功 node2 | CHANGED | rc=0 >> node2 master | CHANGED | rc=0 >> master node1 | CHANGED | rc=0 >> node1 # 执行失败的状态,返回非零值 [sujx@master ~]$ ansible all -m shell -a "hostnamexx" master | FAILED | rc=127 >> /bin/sh: hostnamexx: command not foundnon-zero return code node2 | FAILED | rc=127 >> /bin/sh: hostnamexx: command not foundnon-zero return code node1 | FAILED | rc=127 >> /bin/sh: hostnamexx: command not foundnon-zero return code
相关模块 file模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 # 创建文件 [sujx@master ~]$ ansible node2 -m file -a "path=/home/sujx/target owner=sujx group=sujx mode=444 state=touch" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "dest": "/home/sujx/target", "gid": 1000, "group": "sujx", "mode": "0444", "owner": "sujx", "size": 0, "state": "file", "uid": 1000 } # 创建目录 [sujx@master ~]$ ansible node2 -m file -a "path=/home/sujx/targetdir owner=sujx group=sujx mode=444 state=directory" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "gid": 1000, "group": "sujx", "mode": "0444", "owner": "sujx", "path": "/home/sujx/targetdir", "size": 6, "state": "directory", "uid": 1000 } # 查看文件状态 [sujx@master ~]$ ansible node2 -m shell -a "ls -l" node2 | CHANGED | rc=0 >> total 4 dr--r--r-- 2 sujx sujx 6 Jun 16 15:44 targetdir -r--r--r-- 1 sujx sujx 0 Jun 16 15:44 target -rw-r--r-- 1 root root 134 Jun 13 15:55 ansible.cfg # 删除目录 [sujx@master ~]$ ansible node2 -m file -a "path=/home/sujx/targetdir owner=sujx group=sujx mode=444 state=absent" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "path": "/home/sujx/targetdir", "state": "absent" } # 删除文件 [sujx@master ~]$ ansible node2 -m file -a "path=/home/sujx/target owner=sujx group=sujx mode=444 state=absent" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "path": "/home/sujx/target", "state": "absent" } # 查看 [sujx@master ~]$ ansible node2 -m shell -a "ls -lr" node2 | CHANGED | rc=0 >> total 4 -rw-r--r-- 1 root root 134 Jun 13 15:55 ansible.cfg
copy和fetch模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 # 向Node1、Node2两台主机复制hosts文件 [sujx@master ~]$ ansible Node -m copy -a "src=/home/sujx/hosts dest=/home/sujx/" node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "checksum": "f0d83ba599f75cda5a4658de9503d0ec62954d76", "dest": "/home/sujx/hosts", "gid": 0, "group": "root", "md5sum": "f986a6c7d575b0c17440d229e7d003a5", "mode": "0644", "owner": "root", "size": 40, "src": "/home/sujx/.ansible/tmp/ansible-tmp-1686901933.678302-10766-60719225663651/source", "state": "file", "uid": 0 } node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "checksum": "f0d83ba599f75cda5a4658de9503d0ec62954d76", "dest": "/home/sujx/hosts", "gid": 0, "group": "root", "md5sum": "f986a6c7d575b0c17440d229e7d003a5", "mode": "0644", "owner": "root", "size": 40, "src": "/home/sujx/.ansible/tmp/ansible-tmp-1686901933.7078812-10767-77112866750173/source", "state": "file", "uid": 0 } # 使用copy模块向某个文件写入内容 [sujx@master ~]$ ansible node2 -m copy -a 'content="Hello world!" dest=/home/sujx/target' node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "checksum": "d3486ae9136e7856bc42212385ea797094475802", "dest": "/home/sujx/target", "gid": 1000, "group": "sujx", "md5sum": "86fb269d190d2c85f6e0468ceca42a20", "mode": "0444", "owner": "sujx", "size": 12, "src": "/home/sujx/.ansible/tmp/ansible-tmp-1686902375.0828805-11299-205069408367467/source", "state": "file", "uid": 1000 } [sujx@master ~]$ ansible node2 -m shell -a "cat /home/sujx/target" node2 | CHANGED | rc=0 >> Hello world! # 使用fetch模块取回文件 [sujx@master ~]$ ansible node2 -m fetch -a "src=/home/sujx/target dest=." node2 | CHANGED => { "changed": true, "checksum": "d3486ae9136e7856bc42212385ea797094475802", "dest": "/home/sujx/node2/home/sujx/target", "md5sum": "86fb269d190d2c85f6e0468ceca42a20", "remote_checksum": "d3486ae9136e7856bc42212385ea797094475802", "remote_md5sum": null } # 取回的文件会在本地创建一个和远端主机同名的目录用于存储文件 [sujx@master ~]$ tree node2 node2 └── home └── sujx └── target 2 directories, 1 file
yum_repository模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 一个标准的yum repository文件如下: [nodesource] name=Node.js Packages for Enterprise Linux 8 - $basearch baseurl=https://rpm.nodesource.com/pub_14.x/el/8/$basearch failovermethod=priority enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/NODESOURCE-GPG-SIGNING-KEY-EL # 使用ansible创建yum_repo文件 [sujx@master ~]$ ansible node2 -m yum_repository -a "name=NodeJS description='Node.js' \ baseurl=https://rpm.nodesource.com/pub_14.x/el/8/x86_64/ gpgcheck=no enabled=yes" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "repo": "NodeJS", "state": "present" } # 查看结果 [sujx@node2 ~]$ cat /etc/yum.repos.d/NodeJS.repo [NodeJS] baseurl = https://rpm.nodesource.com/pub_14.x/el/8/x86_64/ enabled = 1 gpgcheck = 0 name = Node.js
yum模块管理软件包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # 模块参数主要为name和state,state的参数为present、absent和latest # 在指定Node2主机上安装nmap [sujx@master ~]$ ansible Node2 -m yum -a "name=nmap state=present" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "msg": "", "rc": 0, "results": [ "Installed: nmap-2:7.70-8.el8.x86_64" ] } # 删除NMAP [sujx@master ~]$ ansible node12 -m yum -a "name=nmap state=absent" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "msg": "", "rc": 0, "results": [ "Removed: nmap-2:7.70-8.el8.x86_64" ] } # 检查系统更新 [sujx@master ~]$ ansible node2 -m yum -a "name=* state=latest" node2 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "msg": "Nothing to do", "rc": 0, "results": [] }
service模块管理服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 主要参数name enabled(yes |no) state(started|stopped|restarted) [sujx@master ~]$ ansible Node -m service -a "name=cockpit.socket enabled=yes state=started" # 查看服务是否启动 [sujx@master ~]$ ansible Node -m shell -a "systemctl is-active cockpit.socket" node1 | CHANGED | rc=0 >> active node2 | CHANGED | rc=0 >> active # 查看服务是否自启动 [sujx@master ~]$ ansible Node -m shell -a "systemctl is-enabled cockpit.socket" node2 | CHANGED | rc=0 >> enabled node1 | CHANGED | rc=0 >> enabled
parted模块对硬盘分区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 常用参数 device指那个硬盘 number第几个分区 part_start从什么位置开始,默认从头开始 part_end分区结束位置 state(present创建|absent删除) # 在主机上创建一个2GB分区 [sujx@master ~]$ ansible Node -m parted -a "device=/dev/sda number=1 part_end=2GiB state=present" # 在接着创建一个2GB分区 [sujx@master ~]$ ansible Node -m parted -a "device=/dev/sda number=2 part_start=2GiB part_end=4GiB state=present" [sujx@master ~]$ ansible Node -m shell -a 'lsblk' node1 | CHANGED | rc=0 >> NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 10G 0 disk ├─sda1 8:1 0 2G 0 part └─sda2 8:2 0 2G 0 part node2 | CHANGED | rc=0 >> NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 10G 0 disk ├─sda1 8:1 0 2G 0 part └─sda2 8:2 0 2G 0 part # 删除已创建分区 [sujx@master ~]$ ansible Node -m parted -a "device=/dev/sda number=2 state=absent" [sujx@master ~]$ ansible Node -m parted -a "device=/dev/sda number=1 state=absent" # 检查 [sujx@master ~]$ ansible Node -m shell -a 'lsblk' node1 | CHANGED | rc=0 >> NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 10G 0 disk node2 | CHANGED | rc=0 >> NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 10G 0 disk
filesystem模块格式化 1 2 3 4 5 6 7 8 9 10 11 12 # 将sda2格式化为xfs格式 [sujx@master ~]$ ansible Node -m filesystem -a "device=/dev/sda2 fstype=xfs" # 将sda1原有的xfs格式强制格式化为ext4 [sujx@master ~]$ ansible Node -m filesystem -a "device=/dev/sda1 fstype=ext4 force=yes" # 检查 [sujx@master ~]$ ansible Node -m shell -a 'blkid' node1 | CHANGED | rc=0 >> /dev/sda1: UUID="671ca843-cd6c-4184-9d97-095dde88e54b" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="4dfa6783-01" /dev/sda2: UUID="955fb028-713f-407b-89a0-0aca4b7a6d01" BLOCK_SIZE="512" TYPE="xfs" PARTUUID="4dfa6783-02" node2 | CHANGED | rc=0 >> /dev/sda1: UUID="464f8844-1889-4d53-b66d-64f3b3832dfe" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="6e712d96-01" /dev/sda2: UUID="8e040e37-a9d8-4e52-bc1e-cec548686960" BLOCK_SIZE="512" TYPE="xfs" PARTUUID="6e712d96-02"
mount模块挂载文件系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # 常用参数 src待挂载设备 path挂载点 fstype文件系统 opts挂载选项 state(mounted挂载的同时写入fstab|present只写入fstab|unmounted卸载但是不从fstab中删除|absent卸载并清理fstab) # 建立挂载点 [sujx@master ~]$ ansible Node -m file -a 'path=/data state=directory owner=root group=root mode=444' [sujx@master ~]$ ansible Node -m file -a 'path=/data/sda1 state=directory owner=root group=root mode=444' [sujx@master ~]$ ansible Node -m file -a 'path=/data/sda2 state=directory owner=root group=root mode=444' [sujx@master ~]$ ansible Node -m shell -a 'tree /data' node2 | CHANGED | rc=0 >> /data ├── sda1 └── sda2 2 directories, 0 files node1 | CHANGED | rc=0 >> /data ├── sda1 └── sda2 2 directories, 0 files # sda1 sda2分别挂载 [sujx@master ~]$ ansible Node -m mount -a 'src=/dev/sda1 path=/data/sda1 fstype=ext4 state=mounted' [sujx@master ~]$ ansible Node -m mount -a 'src=/dev/sda2 path=/data/sda2 fstype=xfs state=present' # 检查Node2主机的fstab,可见fstab均以写入 [sujx@node2 ~]$ cat /etc/fstab /dev/sda1 /data/sda1 ext4 defaults 0 0 /dev/sda2 /data/sda2 xfs defaults 0 0 # 检查实际挂载,可见只挂载了sda1 [sujx@node2 ~]$ sudo df -Th Filesystem Type Size Used Avail Use% Mounted on /dev/sda1 ext4 2.0G 24K 1.9G 1% /data/sda1 # 卸载挂载,可以只写挂载点和状态参数 [sujx@master ~]$ ansible Node -m mount -a 'path=/data/sda1 state=absent' [sujx@master ~]$ ansible Node -m mount -a 'path=/data/sda2 state=absent'
lvg模块管理卷组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # 常用参数 pvs指定逻辑卷,不需要提前创建会自动创建 vg指定卷组名称 pesize指定PE大小 state(present创建|absent删除) # 检查现有主机状态 [sujx@master ~]$ ansible Node -m shell -a 'vgs' node2 | CHANGED | rc=0 >> VG #PV #LV #SN Attr VSize VFree rl_rocky 1 2 0 wz--n- 18.41g 0 node1 | CHANGED | rc=0 >> VG #PV #LV #SN Attr VSize VFree rl_rocky 1 2 0 wz--n- 18.41g 0 # 将/dev/sda1 /dev/sda2建立PV并加入vg_data卷组 [sujx@master ~]$ ansible Node -m lvg -a "pvs=/dev/sda1,/dev/sda2 vg=vg_data state=present" node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true } node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true } # 检查结果,可见创建了名为vg_data,数量为2个PV,容量为4G的卷组 [sujx@master ~]$ ansible Node -m shell -a 'vgs' node2 | CHANGED | rc=0 >> VG #PV #LV #SN Attr VSize VFree rl_rocky 1 2 0 wz--n- 18.41g 0 vg_data 2 0 0 wz--n- 3.99g 3.99g node1 | CHANGED | rc=0 >> VG #PV #LV #SN Attr VSize VFree rl_rocky 1 2 0 wz--n- 18.41g 0 vg_data 2 0 0 wz--n- 3.99g 3.99g
lvol模块管理逻辑卷 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 # 常用参数 vg指定卷组创建逻辑卷 lv指定逻辑卷名称 size指定逻辑卷大小 state(present创建|absent删除) # 在vg_data上创建1GB大小的逻辑卷lv_data [sujx@master ~]$ ansible Node -m lvol -a "vg=vg_data lv=lv_data size=1G state=present" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "msg": "" } node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "msg": "" } [sujx@master ~]$ ansible Node -m shell -a "lvs" node2 | CHANGED | rc=0 >> LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root rl_rocky -wi-ao---- 16.41g swap rl_rocky -wi-ao---- 2.00g lv_data vg_data -wi-a----- 1.00g node1 | CHANGED | rc=0 >> LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root rl_rocky -wi-ao---- 16.41g swap rl_rocky -wi-ao---- 2.00g lv_data vg_data -wi-a----- 1.00g # 将lv_data扩容1GB [sujx@master ~]$ ansible Node -m lvol -a "vg=vg_data lv=lv_data size=+1G" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "lv": "lv_data", "size": 1.0, "vg": "vg_data" } node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "lv": "lv_data", "size": 1.0, "vg": "vg_data" } [sujx@master ~]$ ansible Node -m shell -a "lvs" node2 | CHANGED | rc=0 >> LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root rl_rocky -wi-ao---- 16.41g swap rl_rocky -wi-ao---- 2.00g lv_data vg_data -wi-a----- 2.00g node1 | CHANGED | rc=0 >> LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root rl_rocky -wi-ao---- 16.41g swap rl_rocky -wi-ao---- 2.00g lv_data vg_data -wi-a----- 2.00g # 将VG的所有空间分配给lv_data,同时还有100%PVS将所有的PV分配给逻辑卷,100%FREE将剩余空间都分配给逻辑卷 [sujx@master ~]$ ansible Node -m lvol -a "vg=vg_data lv=lv_data size=100%VG" node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "lv": "lv_data", "size": 2048.0, "vg": "vg_data" } node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "lv": "lv_data", "size": 2048.0, "vg": "vg_data" } [sujx@master ~]$ ansible Node -m shell -a "lvs" node2 | CHANGED | rc=0 >> LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root rl_rocky -wi-ao---- 16.41g swap rl_rocky -wi-ao---- 2.00g lv_data vg_data -wi-a----- 3.99g node1 | CHANGED | rc=0 >> LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root rl_rocky -wi-ao---- 16.41g swap rl_rocky -wi-ao---- 2.00g lv_data vg_data -wi-a----- 3.99g
firewad模块管理防火墙 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 常用参数 service开放服务 ports开放端口 premanent(yes |no)设置永久生效 immediate(yes |no)是否立即生效 state(enabled|disabled) rich_rule富规则 [sujx@master ~]$ ansible Node -m firewalld -a "service=https immediate=yes permanent=yes state=enabled" [sujx@master ~]$ ansible Node -m firewalld -a "port=3306/tcp immediate=yes permanent=yes state=enabled" [sujx@master ~]$ ansible Node -m shell -a "firewall-cmd --list-all" node1 | CHANGED | rc=0 >> trusted (active) target: ACCEPT icmp-block-inversion: no interfaces: ens160 sources: services: https ports: 3306/tcp node2 | CHANGED | rc=0 >> trusted (active) target: ACCEPT icmp-block-inversion: no interfaces: ens160 sources: services: https ports: 3306/tcp
替换模块replace 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 # 常用参数 path待编辑文件 regexp正则表达式 replace替换后的字符 # 准备文件 [sujx@master ~]$ cat target aa = 111 bb = 222 cc = 333 # 复制文件 [sujx@master ~]$ ansible Node -m copy -a "src=/home/sujx/target dest=/home/sujx/" [sujx@master ~]$ ansible Node -m replace -a 'path=/home/sujx/target regexp=^aa replace="XX = 666"' # 将aa替换为 XX = 666 [sujx@node2 ~]$ cat target XX = 666 = 111 bb = 222 cc = 333 # 将bb之后的整行进行替换 [sujx@master ~]$ ansible Node -m replace -a 'path=/home/sujx/target regexp=^bb.+ replace="YY = 999"' node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "msg": "1 replacements made", "rc": 0 } node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "msg": "1 replacements made", "rc": 0 } [sujx@master ~]$ ansible Node -m shell -a "cat /home/sujx/target" node1 | CHANGED | rc=0 >> XX = 666 YY = 999 cc = 333 node2 | CHANGED | rc=0 >> XX = 666 YY = 999 cc = 333
替换模块lineinfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 与replace类似功能,但replace进行字符替换,lineinfile进行整行替换 [sujx@master ~]$ ansible Node -m lineinfile -a 'path=/home/sujx/target regexp=^cc line="ZZ = 000"' node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "backup": "", "changed": true, "msg": "line replaced" } node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "backup": "", "changed": true, "msg": "line replaced" } [sujx@master ~]$ ansible Node -m shell -a "cat /home/sujx/target" node1 | CHANGED | rc=0 >> XX = 666 YY = 999 ZZ = 000 node2 | CHANGED | rc=0 >> XX = 666 YY = 999 ZZ = 000
打印模块debug 1 2 3 4 5 6 7 8 # 用于打印提示信息,类似echo ,常用参数为msg表示具体信息,var表示变量 [sujx@master ~]$ ansible Node -m debug -a "msg='Hello World.'" node1 | SUCCESS => { "msg": "Hello World." } node2 | SUCCESS => { "msg": "Hello World." }
script模块在远端执行脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # 远程主机直接调用本地脚本,而无须先复制到远程再执行 [sujx@master ~]$ vim hello.sh # !/bin/bash echo "Hello World" [sujx@master ~]$ chmod +x hello.sh [sujx@master ~]$ ./hello.sh Hello World [sujx@master ~]$ ansible Node -m script -a "./hello.sh" node1 | CHANGED => { "changed": true, "rc": 0, "stderr": "Shared connection to node1 closed.\r\n", "stderr_lines": [ "Shared connection to node1 closed." ], "stdout": "Hello World\r\n", "stdout_lines": [ "Hello World" ] } node2 | CHANGED => { "changed": true, "rc": 0, "stderr": "Shared connection to node2 closed.\r\n", "stderr_lines": [ "Shared connection to node2 closed." ], "stdout": "Hello World\r\n", "stdout_lines": [ "Hello World" ] }
group模块对用户组进行管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 常用参数有 name组名 state(present|absent) # 创建node组 [sujx@master ~]$ ansible Node -m group -a "name=node state=present" node2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "gid": 1001, "name": "node", "state": "present", "system": false } node1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "gid": 1001, "name": "node", "state": "present", "system": false } # 检查组的存续 [sujx@master ~]$ ansible Node -m shell -a "grep node /etc/group" node1 | CHANGED | rc=0 >> node:x:1001: node2 | CHANGED | rc=0 >> node:x:1001:
user模块对用户进行管理 1 2 3 4 5 6 7 8 9 10 # 常用参数 name用户名 comment备注信息 group用户组 groups 附属组 password用户密码 state(present|absent) # 执行加密需要先安装加密套件 [sujx@master ~]$ sudo yum install -y python3-passlib [sujx@master ~]$ ansible Node -m user -a "name=node group=node groups=wheel comment='extra admin user' password={{ 'haha001' | password_hash('sha512') }} state=present " [sujx@master ~]$ ssh node@node2 node@node2's password: [node@node2 ~]$ whoami node # 删除用户,同时也会删除用户的组 [sujx@master ~]$ ansible Node -m user -a "name=node state=absent "
get_url模块下载文件 1 2 # 常用参数 url文件的链接 dest目标路径 [sujx@master ~]$ ansible Node -m get_url -a "url=https://repo.huaweicloud.com/repository/conf/CentOS-8-reg.repo dest=/etc/yum.repos.d/"
setup模块获取被管理主机 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 # 用来获取主机信息 # 获取主机网卡地址 [sujx@master ~]$ ansible Node -m setup -a "filter=ansible_default_ipv4" node2 | SUCCESS => { "ansible_facts": { "ansible_default_ipv4": { "address": "192.168.10.102", "alias": "ens160", "broadcast": "192.168.10.255", "gateway": "192.168.10.2", "interface": "ens160", "macaddress": "00:0c:29:b6:0a:a1", "mtu": 1500, "netmask": "255.255.255.0", "network": "192.168.10.0", "prefix": "24", "type": "ether" }, "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false } node1 | SUCCESS => { "ansible_facts": { "ansible_default_ipv4": { "address": "192.168.10.101", "alias": "ens160", "broadcast": "192.168.10.255", "gateway": "192.168.10.2", "interface": "ens160", "macaddress": "00:0c:29:1d:aa:5f", "mtu": 1500, "netmask": "255.255.255.0", "network": "192.168.10.0", "prefix": "24", "type": "ether" }, "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false } # 获取主机BIOS版本 [sujx@master ~]$ ansible Node -m setup -a "filter=ansible_bios_version" node1 | SUCCESS => { "ansible_facts": { "ansible_bios_version": "VMW71.00V.18452719.B64.2108091906", "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false } node2 | SUCCESS => { "ansible_facts": { "ansible_bios_version": "VMW71.00V.18452719.B64.2108091906", "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false } # 如果filter包含子健,在ad-hoc命令中不显示,只有写入playbook才会显示。
剧本 简介 Ansible的模块都是在命令中执行,每次只能执行一个模块。如果需要 执行多个模块,且要对执行是否成功进行判断以及后续如何处理。这就需要编写脚本,Ansible中的脚本叫做playbook,即英文剧本的意思。其中,一个playbook可以包含多个playbook。
YAML语言 YAML 的语法比较简洁直观,特点是使用空格来表达层次结构,其最大优势在于数据结构 方面的表达,所以 YAML 更多应用于编写配置文件 ,其文件一般以 .yml 为后缀。
YAML 目前的官方全称为 “YAML Ain’t Markup Language (YAML 不是标记语言)”,但有意思的是,其实 YAML 最初的含义是 “Yet Another Markup Language (还是一种标记语言)”。
基本语法:一文看懂YAML
写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - name: play的名称 hosts: 主机组1, 主机组2, 主机组3, …… tasks: - name: 提示信息1 模块1: argx1=vx1 argx2=vx2 - name: 提示信息2 模块2: rgx1=vx1 argx2=vx2 - name: 第二个play的名称 hosts: 主机组4, 主机组5, …… gather_facts: false tasks: - name: 提示信息3 模块1: argx1=vx1 argx2=vx2 - name: 提示信息x 模块x: rgx1=vx1 rgx2=vx2
参数写在模块之后
一定要先写好框架,然后再往框架里面写内容
多个主机执行相同内容,可以放到同一个playbook之中;
gather_facts: false 避免使用setup模块收集主机信息,以提高执行速度
命令
编写一个playbook,在node1和node2上打印主机名和IP
1 2 3 4 5 6 7 --- - hosts: node1,node2 tasks: - name: Print Host Name debug: msg={{ ansible_fqdn }} - name: Print IP Address debug: msg={{ ansible_default_ipv4.address }}
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [sujx@master ~]$ ansible-playbook playbook/printhostname.yaml PLAY [node1,node2] ************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************** ok: [node1] ok: [node2] TASK [Print Host Name] ************************************************************************************************** ok: [node1] => { "msg": "node1" } ok: [node2] => { "msg": "node2" } TASK [Print IP Address] ************************************************************************************************** ok: [node1] => { "msg": "192.168.10.101" } ok: [node2] => { "msg": "192.168.10.102" } PLAY RECAP ************************************************************************************************** node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
写一个palybook,完成下面的任务
在node2上安装nfs-utils包,启动nfs-server服务,并设置开机启动
在node2上配置防火墙,要求开放nfs、rpc-bind、mountd三个服务,重启系统也能生效
在node2上创建目录/share
在node2上的/etc/exports中写入 /share *(rw,no_root_squash)
在node2上执行系统命令 exportfs -arv
在node3上将node2:/share挂载到/nfs目录上,文件系统为nfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 --- - name: Node2开启NFS服务 hosts: node2 gather_facts: false tasks: - name: 安装NFS软件包 yum: name=nfs-utils state=installed - name: 启动NFS服务 service: name=nfs-server state=started enabled=yes - name: 开启防火墙 firewalld: service=nfs service=mountd service=rpc-bind immediate=yes permanent=yes state=enabled - name: 创建目录 file: path=/share state=directory - name: 修改配置 copy: content="/share *(rw,no_root_squash)" dest=/etc/exports - name: 执行配置 shell: exportfs -arv - name: Node1挂载NFS共享磁盘 hosts: node1 gather_facts: false tasks: - name: 安装NFS软件包 yum: name=nfs-utils state=installed - name: 开启防火墙 firewalld: service=nfs service=mountd service=rpc-bind immediate=yes permanent=yes state=enabled - name: 创建目录 file: path=/nfs state=directory - name: 挂载目录 mount: src=node2:/share path=/nfs fstype=nfs state=mounted opts=_netdev,rw
执行结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 [sujx@master ~]$ ansible-playbook playbook/nfsmount.yaml PLAY [Node2开启NFS服务] *********************************************************** TASK [安装NFS软件包] ************************************************************** changed: [node2] TASK [启动NFS服务] **************************************************************** changed: [node2] TASK [开启防火墙] ***************************************************************** changed: [node2] TASK [创建目录] ******************************************************************* changed: [node2] TASK [修改配置] ******************************************************************* changed: [node2] TASK [执行配置] ******************************************************************* changed: [node2] PLAY [Node1挂载NFS共享磁盘] ********************************************************************************* TASK [安装NFS软件包] ************************************************************************************************** changed: [node1] TASK [开启防火墙] ************************************************************************************************** changed: [node1] TASK [创建目录] ************************************************************************************************** changed: [node1] TASK [挂载目录] ************************************************************************************************** changed: [node1] PLAY RECAP ************************************************************************************************** node1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node2 : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@node1 ~]# df -Th Filesystem Type Size Used Avail Use% Mounted on node2:/share nfs4 17G 2.7G 14G 17% /nfs [root@node1 ~]# vim /etc/fstab /dev/mapper/rl_rocky-root / xfs defaults 0 0 UUID=51ea77c3-fcaa-4985-98d6-1d80de9fdbe9 /boot xfs defaults 0 0 UUID=F2E1-A827 /boot/efi vfat umask=0077,shortname=winnt 0 2 /dev/mapper/rl_rocky-swap none swap defaults 0 0 node2:/share /nfs nfs _netdev,rw 0 0
变量 为了能够写出更实用的playbook,就需要在playbook中加入变量。
手动变量
通过vars来定义变量,不可以有重复的变量,否则后面定义的变量会覆盖前面定义的变量值。
引用变量时用, 大括号内侧两边是否有空格时无所谓。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- - name: 打印变量名 hosts: node1 gather_facts: false vars: myname1: 北京 myname2: 天津 myname3: 南京 myname3: 上海 tasks: - name: 打印myname1变量 debug: msg="变量 myname1 的值是 {{ myname1 }}" - name: 打印myname2变量 debug: msg=" 变量 myname2 的值是 {{ myname2 }}" - name: 打印myname3变量 debug: msg=" 变量 myname3 的值是 {{ myname3 }}"
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [sujx@master ~]$ ansible-playbook playbook/printvars.yaml [WARNING]: While constructing a mapping from /home/sujx/playbook/printvars.yaml, line 6, column 5, found a duplicate dict key (myname3). Using last defined value only. PLAY [打印变量名] ******************************************************************************************* TASK [打印myname1变量] ************************************************************************************* ok: [node1] => { "msg": "变量 myname1 的值是 北京" } TASK [打印myname2变量] ************************************************************************************** ok: [node1] => { "msg": "变量 myname2 的值是 天津" } TASK [打印myname3变量] ************************************************************************************** ok: [node1] => { "msg": "变量 myname3 的值是 上海" } PLAY RECAP ************************************************************************************************** node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
变量myname3定义两次,输出最后一次定义的值。
变量文件 如果定义变量太多,则可以将其单独存放到一个文件之中,然后在playbook中通过vars_files参数调用。 编写vars.yaml
1 2 3 myname1: 北京 myname2: 天津 myname3: 南京
playbook调用变量文件
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: 打印变量名 hosts: node1 gather_facts: false vars_files: - vars.yaml tasks: - name: 打印myname1变量 debug: msg="变量 myname1 的值是 {{ myname1 }}" - name: 打印myname2变量 debug: msg=" 变量 myname2 的值是 {{ myname2 }}" - name: 打印myname3变量 debug: msg=" 变量 myname3 的值是 {{ myname3 }}"
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [sujx@master ~]$ ansible-playbook playbook/printvars.yaml PLAY [打印变量名] ******************************************************************************************* TASK [打印myname1变量] ************************************************************************************** ok: [node1] => { "msg": "变量 myname1 的值是 北京" } TASK [打印myname2变量] ************************************************************************************** ok: [node1] => { "msg": "变量 myname2 的值是 天津" } TASK [打印myname3变量] ************************************************************************************** ok: [node1] => { "msg": "变量 myname3 的值是 南京" } PLAY RECAP ************************************************************************************************** node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
字典变量 字典就是存储变量的容器,一个字典中可以有多个变量。同一个字典中定义的多个变量不可有重复值。要引用字典中的变量,必须指定是那个字典。
1 2 3 4 5 6 7 vars: 字典1: var1: value1 var2: value2 字典2: var1: value1 var2: value2
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 --- - name: 打印变量名 hosts: node1 gather_facts: false vars: dict1: city1: 北京 city2: 广州 city3: 上海 dict2: city1: 乌鲁木齐 city2: 呼和浩特 city3: 巴彦淖尔 tasks: - name: 打印城市名称 debug: msg="有个城市,她的名字是 {{ dict1.city1 }}" - name: 打印城市名称 debug: msg=" 某个城市,名字是四个字 {{dict2.city1 }}"
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 [sujx@master ~]$ ansible-playbook playbook/printvars.yaml PLAY [打印变量名] ******************************************************************************************* TASK [打印城市名称] ***************************************************************************************** ok: [node1] => { "msg": "有个城市,她的名字是 北京" } TASK [打印城市名称] ***************************************************************************************** ok: [node1] => { "msg": "某个城市,名字是四个字 乌鲁木齐" } PLAY RECAP ************************************************************************************************* node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
列表变量 列表变量与字典变量类似,都可以输出容器中的元素。区别是列表变量可以输出整个列表,并且列表变量前面有-,而字典列表没有。列表用角标[]来确定元素位置,并从0开始排序。 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 --- - name: 打印变量名 hosts: node1 gather_facts: false vars: city: - name: 北京 location: 北方 population: 2150 万 - name: 上海 location: 中部 population: 2480 万 - name: 广州 location: 南方 population: 1530 万 tasks: - name: 打印城市信息 debug: msg="{{ city[2] }}" - name: 打印城市地理 debug: msg="{{ city[1].location }}" - name: 打印城市名称 debug: msg="{{ city[0].name }}"
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [sujx@master ~]$ ansible-playbook playbook/printlistvar.yaml PLAY [打印变量名] ************************************************************************************************* TASK [打印城市信息] ************************************************************************************************* ok: [node1] => { "msg": { "location": "南方", "name": "广州", "population": "1530万" } } TASK [打印城市地理] ************************************************************************************************* ok: [node1] => { "msg": "中部" } TASK [打印城市名称] ************************************************************************************************* ok: [node1] => { "msg": "北京" } PLAY RECAP ************************************************************************************************* node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
数字变量 在YAML文件中的变量,其值如果是数字,可以进行数字运算,包括加减乘除和次方。
1 2 3 4 5 6 7 8 9 10 11 --- - name: 使用Ansible进行数学计算 hosts: node1 gather_facts: false vars: num: 256 tasks: - name: 乘法计算 debug: msg="{{num*3}}" - name: 次方计算 debug: msg="{{num**3}}"
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [sujx@master ~]$ ansible-playbook playbook/printnum.yaml PLAY [使用Ansible进行数学计算] ************************************************************************************************** TASK [乘法计算] ************************************************************************************************** ok: [node1] => { "msg": "768" } TASK [次方计算] ************************************************************************************************** ok: [node1] => { "msg": "16777216" } PLAY RECAP ************************************************************************************************** node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
注册变量 playbook用shell模块执行系统命令在结果中不显示命令结果。如果需要查看命令结果,就需要临时将结果保存在一个变量中,这个变量就是注册变量。
1 2 3 4 5 6 7 8 9 10 --- - name: 注册变量 hosts: node1 gather_facts: false tasks: - name: 将shell命令结果注册为变量 shell: "hostname" register: cmd - name: 打印注册变量 debug: msg={{cmd}}
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [sujx@master ~]$ ansible-playbook playbook/printregister.yaml PLAY [注册变量] ************************************************************************************************** TASK [将shell命令结果注册为变量] ************************************************************************************************** changed: [node1] TASK [打印注册变量] ************************************************************************************************** ok: [node1] => { "msg": { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "cmd": "hostname", "delta": "0:00:00.005883", "end": "2023-06-20 19:57:15.575235", "failed": false, "msg": "", "rc": 0, "start": "2023-06-20 19:57:15.569352", "stderr": "", "stderr_lines": [], "stdout": "node1", "stdout_lines": [ "node1" ] } } PLAY RECAP ************************************************************************************************** node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stdout 标准输出,表示命令的执行结果。rc表示执行命令的状态返回值。如果只需要命令结果,就可以将运行结果当作字典,debug=
1 2 3 4 5 TASK [打印注册变量] **** ok: [node1] => { "msg": "node1" }
facts 变量 Ansible通过setup模块收集被管理主机的信息,这些信息以变量的形式存在,统称为facts。
1 2 3 4 5 6 7 8 --- - name: Facts变量 hosts: node1 tasks: - name: 打印IP地址 debug: msg="主机IP地址为:{{ansible_default_ipv4.address}}" - name: 主机运行时间 debug: msg="主机运行时长为:{{ansible_uptime_seconds / 3600 }}小时"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [sujx@master ~]$ ansible-playbook playbook/printfacts.yaml PLAY [Facts变量] ************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************** ok: [node1] TASK [打印IP地址] ************************************************************************************************** ok: [node1] => { "msg": "主机IP地址为:192.168.10.101" } TASK [主机运行时间] ************************************************************************************************** ok: [node1] => { "msg": "主机运行时长为:5.427222222222222小时" } PLAY RECAP ************************************************************************************************** node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
内置变量 groups 用于列出inventory文件中定义的主机组和里面的主机
1 2 3 4 5 6 --- - name: Group变量 hosts: node1 tasks: - name: 打印主机组 debug: msg="{{groups}}"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [sujx@master ~]$ ansible-playbook playbook/printgroups.yaml TASK [打印主机组] ******************************************************************************************* ok: [node1] => { "msg": { "Node": [ "node1", "node2" ], "all": [ "master", "node1", "node2" ], "ungrouped": [ "master" ] } }
变量的过滤器 变量的过滤器,实际上就是对变量的值进行操作,例如类型转化、截取、加密等。
1 2 3 4 5 6 7 8 --- - name: 大小写转换 hosts: node1 vars: bb: BOB tasks: - name: 执行转换 debug: msg={{bb|lower}}
执行结果
1 2 3 4 5 6 7 8 9 PLAY [大小写转换] ************************************************************************************************************************************************************************************************ TASK [Gathering Facts] ******************************************************************************************************************************************************************************************* ok: [node1] TASK [执行转换] ************************************************************************************************************************************************************************************************** ok: [node1] => { "msg": "bob" }
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: 列表 hosts: node1 gather_facts: false vars: list1: [0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,10 ] tasks: - name: 求列表的长度 debug: msg="{{ list1 | length}}" - name: 求列表中元素的最大值 debug: msg="{{ list1 | max}}" - name: 求列表中元素的最小值 debug: msg="{{ list1 | min}}"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 TASK [求列表的长度] ********************************************************************************************************************************************************************************************** ok: [node1] => { "msg": "9" } TASK [求列表中元素的最大值] ************************************************************************************************************************************************************************************** ok: [node1] => { "msg": "10" } TASK [求列表中元素的最小值] ************************************************************************************************************************************************************************************** ok: [node1] => { "msg": "0" }
1 2 3 4 5 6 7 8 9 --- - name: 新建用户并修改密码 hosts: node1 gather_facts: false vars: passcontent: qwe123!! tasks: - name: 创建用户Test user: user=test comment="Test User,Don't Remove." group=sujx password={{passcontent|password_hash('sha512')}}
1 2 3 4 5 PLAY [新建用户并修改密码] ********************************************************************************** TASK [创建用户Test] ****************************************************************************************************** changed: [node1] PLAY RECAP ********************************************************************************** node1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
控制语句 一个playbook中可以包含多个tasks,可以设置满足某个条件时执行某个tasks。
判断语句 when 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 --- - name: 条件判断 hosts: node1 tasks: - name: 使用>判断 debug: msg="1大于2" when: 1 >2 - name: 使用<判断 debug: msg="1小于2" when: 1 <2 - name: 使用!=判断 debug: msg="1不等于2" when: 1 !=2 - name: 使用==判断 debug: msg="1等于2" when: 1 ==2 - name: 使用or连接 debug: msg="或判断" when: 1 >2 or 2 <3 - name: 使用and连接 debug: msg="与判断" when: 1 >2 and 2 <3 - name: 变量判断 debug: msg="操作系统的版本是8" when: ansible_distribution_major_version == "8"
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 PLAY [条件判断] ********************************************************************************************* TASK [Gathering Facts] ********************************************************************************************* ok: [node1] TASK [使用>判断] ********************************************************************************************* skipping: [node1] TASK [使用<判断] ********************************************************************************************* ok: [node1] => { "msg": "1小于2" } TASK [使用!=判断] ********************************************************************************************* ok: [node1] => { "msg": "1不等于2" } TASK [使用==判断] ********************************************************************************************* skipping: [node1] TASK [使用or连接] ********************************************************************************************* ok: [node1] => { "msg": "或判断" } TASK [使用and连接] ********************************************************************************************* skipping: [node1] TASK [变量判断] ********************************************************************************************* ok: [node1] => { "msg": "操作系统的版本是8" }
判断语句 block-rescue 对于when来说,只能做一个判断,成立就执行,反之则不执行。block和rescue一般同用,类似if-else语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 --- - name: 条件判断 hosts: node1 tasks: - name: ifelse判断 block: - name: 使用>判断 debug: msg="1大于2不成立" when: 2 >1 - name: 使用<判断 debug: msg="1小于2" when: 1 >2 - name: 使用!=判断 debug: msg="1不等于2" when: 1 ==2 rescue: - name: 使用==判断 debug: msg="1等于2" when: 1 ==2 - name: 使用or连接 debug: msg="或判断" when: 1 >2 or 2 <3 - name: 使用and连接 debug: msg="与判断" when: 1 >2 and 2 <3 - name: 变量判断 debug: msg="操作系统的版本是8" when: ansible_distribution_major_version == "8"
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PLAY [条件判断] ************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************** ok: [node1] TASK [使用>判断] ************************************************************************************************** ok: [node1] => { "msg": "1大于2不成立" } TASK [使用<判断] ************************************************************************************************** skipping: [node1] TASK [使用!=判断] ************************************************************************************************** skipping: [node1] TASK [变量判断] ************************************************************************************************** ok: [node1] => { "msg": "操作系统的版本是8" } PLAY RECAP ************************************************************************************************** node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
循环语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 --- - name: 循环列表 hosts: node1 gather_facts: false vars: city: - name: 北京 location: 北方 population: 2150 万 provincelevel: 直辖市 - name: 上海 location: 中部 population: 2480 万 provincelevel: 直辖市 - name: 广州 location: 南方 population: 1530 万 provincelevel: 省会 tasks: - name: 打印城市信息 debug: msg={{ item.name }} when: item.provincelevel == "直辖市" loop: "{{ city }} "
循环打印城市列表内容,并输出所有直辖市
1 2 3 4 5 6 7 8 9 10 11 12 PLAY [循环列表] ********************************************************************************************* TASK [打印城市信息] ********************************************************************************************* ok: [node1] => (item={'name': '北京', 'location': '北方', 'population': '2150万', 'provincelevel': '直辖市'}) => { "msg": "北京" } ok: [node1] => (item={'name': '上海', 'location': '中部', 'population': '2480万', 'provincelevel': '直辖市'}) => { "msg": "上海" } skipping: [node1] => (item={'name': '广州', 'location': '南方', 'population': '1530万', 'provincelevel': '省会'}) PLAY RECAP ********************************************************************************************* node1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Template模板 通过playbook中的template模块复制含有变量的文件,在复制到远端主机之后,文件中包含的变量会打印为该主机的实际值。这个通过template模块复制、包含变量的文件称为jinja2模板,其后缀为j2.
示例
1 2 3 4 5 6 7 8 9 10 11 --- - name: Copy JinJa2 hosts: Node tasks: - name: Copy IPaddress template: src=hostinfo.j2 dest=/opt/hostinfo - name: Print HostInfo shell: cat /opt/hostinfo register: shellresult - name: PrintShell debug: msg={{shellresult.stdout}}
模板
1 2 3 4 +-------------------------------------------+ 主机IP地址为:{{ ansible_default_ipv4.address }} 主机IP名为:{{ ansible_fqdn }} +-------------------------------------------+
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 [sujx@master ~]$ ansible-playbook playbook/copy.yaml PLAY [Copy JinJa2] ************************************************************************************************ TASK [Gathering Facts] ************************************************************************************************ ok: [node1] ok: [node2] TASK [Copy IPaddress] ************************************************************************************************ changed: [node1] changed: [node2] TASK [Print HostInfo] ************************************************************************************************ changed: [node2] changed: [node1] TASK [PrintShell] ************************************************************************************************ ok: [node1] => { "msg": "+-------------------------------------------+\n 主机IP地址为:192.168.10.101\n 主机IP名为:node1\n+-------------------------------------------+" } ok: [node2] => { "msg": "+-------------------------------------------+\n 主机IP地址为:192.168.10.102\n 主机IP名为:node2\n+-------------------------------------------+" } PLAY RECAP ************************************************************************************************ node1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [sujx@master ~]$ ansible Node -m shell -a 'cat /opt/hostinfo' node2 | CHANGED | rc=0 >> +-------------------------------------------+ 主机IP地址为:192.168.10.102 主机IP名为:node2 +-------------------------------------------+ node1 | CHANGED | rc=0 >> +-------------------------------------------+ 主机IP地址为:192.168.10.101 主机IP名为:node1 +-------------------------------------------+
在jinja2模板中也可以使用if判断语句,语法格式如下:
1 2 3 4 5 6 7 {% if 判断1 % } 内容1 {% elif 判断2 % } 内容2 {% else % } 内容3 {% endif % }
示例
模板
1 2 3 4 5 6 7 8 9 +-------------------------------------------+ 主机IP地址为:{{ ansible_default_ipv4.address }} {% if ansible_fqdn=="master" % } {{ ansible_fqdn }}为控制节点 {% else % } {{ ansible_fqdn }}为计算节点 {% endif % } +-------------------------------------------+
playbook
1 2 3 4 5 6 7 8 9 10 11 --- - name: Copy JinJa2 hosts: all tasks: - name: Copy IPaddress template: src=hostinfo.j2 dest=/opt/hostinfo - name: Print HostInfo shell: cat /opt/hostinfo register: shellresult - name: PrintShell debug: msg={{shellresult.stdout}}
执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [sujx@master ~]$ ansible-playbook playbook/copy.yaml [sujx@master ~]$ ansible all -m shell -a 'cat /opt/hostinfo' node1 | CHANGED | rc=0 >> +-------------------------------------------+ 主机IP地址为:192.168.10.101 node1为计算节点 +-------------------------------------------+ node2 | CHANGED | rc=0 >> +-------------------------------------------+ 主机IP地址为:192.168.10.102 node2为计算节点 +-------------------------------------------+ master | CHANGED | rc=0 >> +-------------------------------------------+ 主机IP地址为:192.168.10.100 master为控制节点 +-------------------------------------------+
角色 配置linux主机服务时,需要进行一系列的操作,比如安装、配置、启动服务等,为了避免重复劳动,将所有操作打包成为一个整体,这个整体就是角色。角色本质上就是一个文件夹,文件夹名就是角色名,其中包含多个文件,比如执行各个模块的文件、jinja模板、变量文件等等。为保持配置的整洁,对于不同的文件需要放置到不同的文件夹下。
名称
作用
tasks
执行的任务
vars
定义变量
files
需要拷贝的文件
templates
需要拷贝的jinja2模板
defaults
默认变量
handlers
Handler操作
mate
注释信息
所有的角色都放在一个目录中等待调用,默认目录为ansible.cfg所在目录的roles目录,如要修改路径可通过ansible.cfg中roles_path来指定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # 在配置文件中指定roles位置 [sujx@master demo]$ cat ansible.cfg [defaults] inventory = ./hosts roles_path = ./roles # 使用ansible-galaxy 创建role文件夹 [sujx@master demo]$ ansible-galaxy init roles/apache - Role roles/apache was created successfully [sujx@master roles]$ tree apache/ apache/ ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── README.md ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml 8 directories, 8 files # 将httpd.conf文件复制为jinja2模板,并将80端口修改为{{myport}} [sujx@master ~]$ egrep -V '#|^#' /etc/httpd/conf/httpd.conf > /home/sujx/deamo/roles/apache/templates/httpd.conf.j2
原始的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 --- - name: 安装HTTP服务 hosts: master vars: myport: 80 tasks: - name: 软件安装 yum: name=httpd state=installed - name: 开启防火墙 firewalld: port=8080/tcp immediate=yes permanent=yes state=enabled - name: 复制配置文件 template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf notify: restart httpd - name: 启动服务 service: name=httpd state=started enabled=yes handlers: - name: restart httpd service: name=httpd state=restarted
将任务文件写入roles/apache/tasks/main.yml中
1 2 3 4 5 6 7 8 9 10 11 12 [sujx@master demo ]$ cat roles/apache/tasks/main.yml --- - name: 软件安装 yum: name=httpd state=installed - name: 开启防火墙 firewalld: port=8080/tcp immediate=yes permanent=yes state=enabled - name: 复制配置文件 template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf notify: restart httpd - name: 启动服务 service: name=httpd state=started enabled=yes
将handles文件内容写入roles/apache/handles/main.yml中
1 2 3 4 5 [sujx@master demo ]$ cat roles/apache/handlers/main.yml --- - name: restart httpd service: name=httpd state=restarted
将变量名写入roles/apache/vars/main.yml中
1 2 3 4 [sujx@master demo ]$ cat roles/apache/vars/main.yml --- myport: 8080
编写调用role的playbook
1 2 3 4 5 6 [sujx@master demo ]$ cat rolehttpd.yaml --- - name: Ansible Roles安装httpd hosts: master roles: - role: apache
执行部署
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 [sujx@master demo]$ ansible-playbook rolehttpd.yaml PLAY [Ansible Roles安装httpd] ************************************************************************* TASK [Gathering Facts] ******************************************************************************** ok: [master] TASK [apache : 软件安装] ****************************************************************************** ok: [master] TASK [apache : 开启防火墙] **************************************************************************** ok: [master] TASK [apache : 复制配置文件] ************************************************************************** changed: [master] TASK [apache : 启动服务] ****************************************************************************** ok: [master] RUNNING HANDLER [apache : restart httpd] ************************************************************** changed: [master] PLAY RECAP ******************************************************************************************** master : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 [sujx@master demo]$ netstat -ltnp (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:44321 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:4330 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp6 0 0 :::8080 :::* LISTEN - tcp6 0 0 :::22 :::* LISTEN -
加密 Ansilbe playbook是以明文形式存在,可以使用ansible-vault命令来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 # 执行加密 [sujx@master ~]$ ansible-vault encrypt playbook/printnum.yaml New Vault password: Confirm New Vault password: Encryption successful # 直接查看文本内容 [sujx@master ~]$ cat playbook/printnum.yaml $ ANSIBLE_VAULT;1.1;AES256 62353932373763343836636637366664623362353632646234343662643531316662333661333730 6630333464373263633630306137343761666533626536650a303331343461626662373866346330 36346335303866366637306139646264373363393465633763313133626266306639396231623335 3863356334383732650a653332363239616236356332346632363730383066376336316234616134 66313738333636376663303766626333653938313863633935336364373164323832646536363334 30343461343064646538316463636537633832316665613164333435346437646434356339653365 31323730336464353866323030306233343665336439346535343464326536313431656561653531 30393230396332336538616461306663393434623164376330366336626661643231383233663135 61373430353736396239323566653861393961653432626432386634663139633864343865623964 65336234653339373261623931356132326362653361356561343731326361656537303562663563 37373366336239636662373632613762313835393664383263653631386162636363336438316261 61316165376334313239303931383838643031343064643939646166333836356361353735646466 31346365633236343937353462653163626536343662613666323865346430653065333431336166 35363861636561313537383364613932343561343937373634653565313762396662653234633830 353432626638653466303366396332323738 # 解密查看 [sujx@master ~]$ ansible-vault view playbook/printnum.yaml Vault password: --- - name: 使用Ansible进行数学计算 hosts: node1 gather_facts: false vars: num: 256 tasks: - name: 乘法计算 debug: msg="{{num*3}}" - name: 次方计算 debug: msg="{{num**3}}" # 直接执行报错 [sujx@master ~]$ ansible-playbook playbook/printnum.yaml ERROR! Attempting to decrypt but no vault secrets found # 输入密码执行 [sujx@master ~]$ ansible-playbook --ask-vault-pass playbook/printnum.yaml Vault password: PLAY [使用Ansible进行数学计算] ************************************************************************ TASK [乘法计算] *************************************************************************************** ok: [node1] => { "msg": "768" } TASK [次方计算] *************************************************************************************** ok: [node1] => { "msg": "16777216" } # 执行解密 [sujx@master ~]$ ansible-vault decrypt playbook/printnum.yaml Vault password: Decryption successful # 直接执行 [sujx@master ~]$ ansible-playbook playbook/printnum.yaml PLAY [使用Ansible进行数学计算] ************************************************************************ TASK [乘法计算] *************************************************************************************** ok: [node1] => { "msg": "768" } TASK [次方计算] *************************************************************************************** ok: [node1] => { "msg": "16777216"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 # 查看密码文件 [sujx@master ~]$ cat pass qwe123!! # 以密码文件加密 [sujx@master ~]$ ansible-vault encrypt --vault-id pass playbook/printnum.yaml Encryption successful # 使用密码查看文件 [sujx@master ~]$ ansible-vault view --vault-id pass playbook/printnum.yaml --- - name: 使用Ansible进行数学计算 hosts: node1 gather_facts: false vars: num: 256 tasks: - name: 乘法计算 debug: msg="{{num*3}}" - name: 次方计算 debug: msg="{{num**3}}" # 使用密码文件执行剧本 [sujx@master ~]$ ansible-playbook --vault-id pass playbook/printnum.yaml PLAY [使用Ansible进行数学计算] ************************************************************************ TASK [乘法计算] *************************************************************************************** ok: [node1] => { "msg": "768" } TASK [次方计算] *************************************************************************************** ok: [node1] => { "msg": "16777216" } PLAY RECAP ******************************************************************************************** node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 # 解密剧本 [sujx@master ~]$ ansible-vault decrypt --vault-id pass playbook/printnum.yaml Decryption successful
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 # 原文 [sujx@master ~]$ cat playbook/cat.yaml --- - hosts: node1 gather_facts: false vars: printf: test tasks: - name: 打印变量 debug: msg="{{printf}}" # 使用前面的pass文本对字符串test 进行加密,生成密文 [sujx@master ~]$ ansible-vault encrypt_string --vault-id pass test Encryption successful !vault | $ANSIBLE_VAULT;1.1;AES256 37613637323338326563306562653533646330336365366566613838326337323637613065373465 3765373233376331613661316663616530663239366633320a323562653631373931613861613037 30363263636366346232316566653032373564643337383761363432363330646162646436303465 3638653064376464330a646135336661623637656530353436636666653433643935666531613239 3330 # 替换文本 [sujx@master ~]$ cat playbook/cat.yaml --- - hosts: node1 gather_facts: false vars: printf: !vault | $ANSIBLE_VAULT;1.1;AES256 37613637323338326563306562653533646330336365366566613838326337323637613065373465 3765373233376331613661316663616530663239366633320a323562653631373931613861613037 30363263636366346232316566653032373564643337383761363432363330646162646436303465 3638653064376464330a646135336661623637656530353436636666653433643935666531613239 3330 tasks: - name: 打印变量 debug: msg="{{printf}}" # 使用密码文件解密并执行 [sujx@master ~]$ ansible-playbook --vault-id pass playbook/cat.yaml PLAY [node1] ****************************************************************************************** TASK [打印变量] *************************************************************************************** ok: [node1] => { "msg": "test" }