1 2 3 4 5 作者:李晓辉 微信联系:lxh_chat 联系邮箱: 939958092@qq.com
第一章 介绍 Ansible ⾃动化与 Linux 系统管理 手工运维的时候,存在以下问题:
低效率 :
手工操作需要耗费大量时间和人力,尤其是在需要频繁执行重复任务时,效率明显低于自动化工具。 易出错 :
人为操作容易产生错误,特别是在执行复杂或繁琐的任务时。错误可能导致系统故障、服务中断,甚至造成数据丢失。 难以追踪 :
手工操作通常缺乏详细的记录和审计功能,难以追踪和回溯问题。自动化工具则能够提供操作日志和审计功能,便于问题排查和责任划分。 不一致性 :
不同的运维人员执行相同任务时,方法和步骤可能不同,导致环境配置和服务状态不一致。这种不一致性可能导致难以预料的问题和故障。 可扩展性差 :
手工操作难以在大规模环境中进行有效管理和扩展。在大规模运维场景中,自动化工具能够轻松处理大量节点和复杂操作。 响应时间长 :
手工操作响应时间较长,尤其是在突发事件和紧急故障处理中。而自动化工具能够快速执行预定义的操作和恢复措施,缩短故障恢复时间。 运维成本高 :
手工运维需要投入大量人力,导致运维成本高昂。自动化工具能够显著降低人力需求,减少运维成本。 知识转移困难 :
手工操作依赖于运维人员的经验和技能,新人接手时可能需要较长的培训和适应期。自动化工具则通过脚本和配置文件实现知识的标准化和传递,便于团队协作和人员更替。 使用自动化系统的好处
高效性 一致性 可扩展性 可维护性 基础架构即代码(IaC) 安全性 Ansible 概念与安装 什么是 Ansible?
Ansible 是⼀款开源⾃动化平台。它是⼀种简单的⾃动化语⾔,可在 Ansible Playbook 中完美描述 IT应⽤基础架构。它还是⼀个⾃动化引擎,可运⾏ Ansible Playbook。
Ansible Playbook 提供⼈类可读的⾃动化,且易于书写和阅读,不需要会编程,Playbook 按顺序执⾏任务
Ansible ⽆需代理即可在被管理机器上执行任务,Ansible 通过 OpenSSH 或 WinRM 连接所管理的主机并运⾏任务,⽅法通常是(但不总是)将称为 Ansible 模块的⼩程序推送⾄这些主机。这些程序⽤于将系统置于需要的特定状态。在 Ansible 运⾏完其任务后,推送的所有模块都会被删除。
Ansible 的优点:
跨平台支持:Ansible 提供 Linux、Windows、UNIX 和网络设备的无代理支持,适用于物理、虚拟、云和容器环境。
人类可读的自动化:Ansible Playbook 采用 YAML 文本文件编写,易于阅读,有助于确保所有人都能理解它们的用途。
精准的描述应用:可以通过 Ansible Playbook 进行每一种更改,并描述和记录应用环境的每一个方面。
轻松管理版本控制:Ansible Playbook 和项目是纯文本。它们可以视作源代码,放在您的现有版本控制系统中。
支持动态清单:可以从外部来源动态更新 Ansible 管理的计算机的列表,随时获取所有受管服务器的当前正确列表,不受基础架构或位置的影响。
编排可与其他系统轻松集成:能够利用环境中现有的 HP SA、Puppet、Jenkins、红帽卫星和其他系统,并且集成到您的 Ansible 工作流中。
Ansible 的架构图
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 +---------------------+ +--------------------+ | | | | | 管理主机 | | 被管理主机 | | | | | | +-----------------+ | | +--------------+ | | | | | SSH 连接 | | | | | | Ansible |<----------------------> | 被管理主机 1 | | | | | | | +--------------+ | | +----+------------+ | +--------------------+ | | | +--------------------+ | | Playbook | | | | | | | 被管理主机 | | v | | | | +-----------------+ | | +--------------+ | | | | | SSH 连接 | | | | | | Inventory |<----------------------> | 被管理主机 2 | | | | | | | +--------------+ | | +----+------------+ | +--------------------+ | | | | | Module | | | | | v | | +-----------------+ | | | | | | | Ansible CLI | | | | | | | +-----------------+ | +---------------------+
架构说明:
Ansible 管理主机 :
部署和运行 Ansible 的主机,负责执行 Playbook 和管理被管理机。 Inventory(清单) :
包含被管理机的列表及其分组信息。Ansible 根据 Inventory 中的定义连接和管理这些主机。 Playbook(剧本) :
使用 YAML 编写的任务配置文件,定义了一系列自动化任务和操作步骤。Ansible 根据 Playbook 执行特定的配置和操作。 Module(模块) :
Ansible 中的功能单元,执行具体的操作任务,如安装软件包、管理服务、复制文件等。Playbook 调用这些模块来完成任务。大部分的模块都具有幂等性,但是shell、command、raw这些模块除外。 Ansible CLI(命令行界面) :
Ansible 的命令行工具,用于执行和管理 Playbook、Inventory 和 Module。 被管理主机 :
Ansible 管理和配置的目标主机,通过 SSH 连接进行通信和操作。 Ansible 设计哲学
复杂性会破坏效率 越简单越好。Ansible 的设计宗旨是工具易用,自动化易写易读。
专为易读性优化 Ansible 自动化语言围绕简单易读的声明性文本文件来构建。正确编写的 Ansible Playbook 可以清楚地记录工作流自动化。
声明式思维 Ansible 是一种要求状态引擎。它通过表达希望系统处于何种状态来解决如何自动化 IT 部署的问题。Ansible 的目标在于仅执行必要更改,将系统置于所需状态。不建议将 Ansible 视为脚本语言。
Ansible能干什么
配置管理
安装和配置软件 :自动安装和配置操作系统及应用程序软件。
文件和目录管理 :管理文件和目录,包括创建、修改和删除操作。
用户和权限管理 :管理用户账户和权限设置,确保系统安全。
应用部署
编排工作流
基础架构即代码(IaC)
持续集成和持续部署(CI/CD)
安全和合规
监控和报告
安装 Ansible 先选一个Ansible 获取渠道
Ansible和RHAAP 社区 Ansible
上游 Ansible 社区通过两种⽅式开发 Ansible 和分发其版本,不管你选谁,红帽都不给你支持,你只能寻求社区支持或Google去搜。
Ansible Core。这是⼀个简约组件,由解释 Ansible 内容的核⼼运⾏时和⼀组常⽤ Ansible模块(作为 ansible.builtin Ansible 内容集合提供)组成。此运⾏时采取特殊架构,让控制节点充当 Ansible 代码的执⾏环境。
社区 Ansible。这是 Ansible Core 的发⾏版,外加开源社区选择的其他 Ansible 内容集合,增加了额外的 Ansible 模块和⻆⾊。
红帽版本
这些版本就可以获得支持
红帽以 RPM 软件包(ansible-core)的形式提供 Ansible Core,该软件包随红帽企业 Linux 9⼀起,通过 AppStream 存储库提供。
红帽 Ansible 高级⾃动化平台,简称rhaap,这个需要购买订阅才能安装和使用。
RHAAP介绍
本课程中,用的是RHAAP2.2,RHAAP提供了以下几个组件:
Ansible Core,Ansible Core 提供⽤于运⾏ Ansible Playbook 的基本功能。比如剧本和模块的执行。RHAAP提供了ansible-core软件包以及在ee-minimal-rhel8 和 ee-supported-rhel8 ⾃动化执⾏环境 中提供 Ansible Core 2.13。
Ansible 内容集合,以前ansible和模块是在一起的,安装了anisble就会带有很多模块,好处是安装即可使用,不好的地方在于,模块的开发者发布新的模块和修复bug都受制于ansible软件是否会更新,软件和模块互相制约,现在Ansible和模块分开发展了,Ansible-core提供了Anible的核心功能,而哪些模块和插件什么的,都单独需要下载才能使用。需要注意的是,Ansible-core 自带了一个名为ansible.builtin
的内容集合,这个内容集合提供了一些基本的模块。
ansible-navigator,也称为自动化导航器,这个命令是当前RHAAP版本中的主要命令,用于替换以前的ansible-playbook、ansible-inventory、ansible-config 等,它通过在容器中运⾏ playbook ,将运⾏ Ansible 的控制节点与运⾏它的⾃动化执⾏环境分隔开来。这样⼀来,就可以更轻松地为⾃动化代码提供完整的⼯作环境,以部署到⽣产环境中。
⾃动化执⾏环境,⾃动化执⾏环境是⼀种容器镜像 ,其包含 Ansible Core、Ansible 内容集合,以及运⾏ playbook 所需的任何 Python 库、可执⾏⽂件或其他依赖项。
使⽤ ansible-navigator 运⾏ playbook 时,你可以选择要用哪个容器镜像
自动化控制器,自动化控制器以前称为红帽 Ansible Tower,是红帽 Ansible 自动化平台的一个组件,提供中央控制点来运行企业自动化代码。此外还提供 Web UI 和 REST API 用于配置、运行和评估自动化作业。
自动化中心,可通过 console.redhat.com 上的公共服务访问红帽 Ansible 认证内容集合,可以将这些内容集合下载下来与 ansible-galaxy(适用于 ansible-navigator)和自动化控制器结合使用。
安装控制节点 控制节点需要做以下准备:
安装有podman
安装有Python3.8或更高版本
需要有订阅,不过在我们的课程中,订阅内容已经帮我们弄好了,执行环境什么的,都有了。
生成并分发ssh密钥给所有的被管理机
Windows不能用作控制节点
安装ansible-navigator
1 [student@workstation ~]$ sudo dnf install ansible-navigator -y
看看安装好了没有,有版本号就行
1 2 [student@workstation ~]$ ansible-navigator --version ansible-navigator 2.1.0
看看Ansible-navigator软件的配置文件,因为在这个配置文件中,控制了用哪个执行环境,输出格式等具体的容器行为
1 2 3 4 5 6 7 [student@workstation ~]$ cat .ansible-navigator.yml --- ansible-navigator: execution-environment: image: utility.lab.example.com/ee-supported-rhel8:latest pull: policy: missing
看到了我们的容器镜像之后,我们来下载一下,ansible-navigator images
命令会自动下载镜像,如果已经存在,就会展示镜像
1 2 3 [student@workstation ~]$ podman login -u admin -p redhat utility.lab.example.com Login Succeeded! [student@workstation ~]$ ansible-navigator images
由于我们的课程中已经提前内置了容器镜像,所以它会自动展示现有镜像,不过它交互式的显示了内容,执行了命令后显示如下内容
左侧的0 1 2 3 4是一个一个的内容,按下对应的数字即可查看详细内容,超过9之后,可以用:22
的方式 最下面的一行是你可以用的操作方式,比如page up/down
翻页,上下键
选择,esc
返回,或者输入:help
查看帮助 1 2 3 4 5 6 Image Tag Execution environment Created Size 0│ee-supported-rhel8 latest True 2 years ago 1.45 GB ^b/PgUp page up ^f/PgDn page down ↑↓ scroll esc back [0-9] goto :help help
准备受管主机 Linux 和 UNIX 受管主机需要安装有 Python 3.8 或更⾼版本
如果受管主机上启⽤了 SELinux,则要确保已安装 python3-libselinux 软件包
已经由控制节点分发了ssh密钥
Windows 受管主机安装 PowerShell 3.0 或更⾼版本,安装 .NET Framework 4.0 或更⾼版本。此外,受管主机还需要远程配置 Windows PowerShell。
网络设备通常使⽤ SSH 上的 CLI、SSH 上的 XML 或 HTTP(S) 上的 API
不过这是红帽的课程,不会讨论如何管理Windows和网络设备
第二章 实施 Ansible Playbook playbook的运行是在被管理机上的,所以需要先定义被管理机,也就是主机清单文件
构建 Ansible 清单 清单定义 Ansible 管理的主机集合。这些主机也可以分配到组中,以进⾏集中管理。组可以包含⼦组,主机也可以是多个组的成员。清单还可以设置应⽤到它所定义的主机和组的变量。
系统中,/etc/ansible/hosts
这个文件是ansible的默认清单问题,如果你没有-i
或者在配置文件中指定清单时,默认使用这个清单
清单格式如下:
这里面可以写IP,主机名,FQDN,甚至如果主机名有规则,还可以用[c:d]来表示多个主机
1 2 3 4 5 cat > inventory <<-'EOF' servera server[b:c] .lab.example.com 172.25.250.13 EOF
列出主机看看效果
我们注意到,有两个组不管我们是否定义,他始终都会存在
all 主机组中含有清单中明确列出的每个主机
ungrouped 主机组中含有清单中明确列出、但不属于任何其他组的每个主机
-i
是手工选择主机清单
-m stdout
是以标准输出的方式显示,而不是交互式
--graph和--list
是展示的样式
--graph和--list
后面还可以加组的名称,仅列出组中的主机
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 [student@workstation ~]$ ansible-navigator inventory -i inventory -m stdout --graph @all: |--@ungrouped: | |--172.25.250.13 | |--servera | |--serverb.lab.example.com | |--serverc.lab.example.com [student@workstation ~]$ ansible-navigator inventory -i inventory -m stdout --list { "_meta" : { "hostvars" : {} }, "all" : { "children" : [ "ungrouped" ] }, "ungrouped" : { "hosts" : [ "172.25.250.13" , "servera" , "serverb.lab.example.com" , "serverc.lab.example.com" ] } }
主机组的表达格式如下:
如果要表达不在任何组中的主机,就一定要写在第一个方括号上面
组名用方括号表示
一个主机可以属于多个组
course:children
的表达是指定义了一个course组,此组下面包括其他组中定义的主机,下面的内容必须是前面定义过的组名
course:vars
的表达是指为course组定义了一个变量name,值是lixiaohui
course=rhcsa
的表达是指为serverb这一个主机单独定义了一个course变量,值为rhcsa
注意不要定义冲突的变量,如果冲突,主机优先级高于组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 cat > inventory <<-'EOF' servera [rhcsa] servera serverb course =rhcsa [rhce] serverc serverd [course:children] rhcsa rhce [course:vars] name =lixaiohuiEOF
列出主机看看效果
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 [student@workstation ~]$ ansible-navigator inventory -i inventory -m stdout --graph @all: |--@course: | |--@rhce: | | |--serverc | | |--serverd | |--@rhcsa: | | |--servera | | |--serverb |--@ungrouped: [student@workstation ~]$ ansible-navigator inventory -i inventory -m stdout --list { "_meta" : { "hostvars" : { "servera" : { "name" : "lixaiohui" }, "serverb" : { "course" : "rhcsa" , "name" : "lixaiohui" }, "serverc" : { "name" : "lixaiohui" }, "serverd" : { "name" : "lixaiohui" } } }, "all" : { "children" : [ "course" , "ungrouped" ] }, "course" : { "children" : [ "rhce" , "rhcsa" ] }, "rhce" : { "hosts" : [ "serverc" , "serverd" ] }, "rhcsa" : { "hosts" : [ "servera" , "serverb" ] } }
管理 Ansible 配置⽂件 可以在每个 Ansible 项⽬⽬录中创建和编辑两个⽂件,⽤于配置 Ansible 的⾏为和 ansiblenavigator 命令:
ansible.cfg,⽤于配置多个 Ansible ⼯具的⾏为。 ansible-navigator.yml,⽤于更改 ansible-navigator 命令的默认选项。 在我们的课程中,主要管理ansible.cfg
,在RHCA DO374的课程中,将会讲到nsible-navigator.yml
系统中,有一个优先级最低的配置文件文件:/etc/ansible/ansible.cfg
这个ansible.cfg可以放在不同的地方,以下是从高到低的优先级:
对于Ansible的基础操作,我们可以使用ansible.cfg文件中的以下部分:
[defaults],⽤于设置 Ansible 操作的默认值
[privilege_escalation],⽤于配置 Ansible 如何在受管主机上执⾏特权升级
下面是典型的 ansible.cfg 文件:
1 2 3 4 5 6 7 8 9 10 11 [defaults] inventory = ./inventory # 清单在哪 remote_user = user # 用哪个用户连接被管理机 ask_pass = false # 如果用密钥认证,就是false host_key_checking=False # 不校验被管理机器的ssh主机指纹,不然第一次连接会让你输入yes/no [privilege_escalation] become = true # 指定连接后是否在受管主机上⾃动切换⽤⼾ become_method = sudo # 切换的方法是什么,有sudo和su become_user = root # 切换成谁 become_ask_pass = false # sudo的时候,要不要输入密码,需要配置被管理机器的/etc/sudoers
生成配置文件
从默认的配置文件摘取第一条命令即可在当前路径生成配置文件
1 2 3 4 [student@workstation ~]$ cat /etc/ansible/ansible.cfg
复制粘贴这个命令,就能生成了
1 [student@workstation ~]$ ansible-config init --disabled > ansible.cfg
查询当前配置
ansible-navigator config 命令会显⽰当前 Ansible 配置,还会告诉你,具体某个设置来自哪里,是否是默认配置,当前值是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [student@workstation ~]$ ansible-navigator config Name Default Source Current 0│Action warnings True default True ▒ 1│Agnostic become prompt True default True ▒ 2│Allow world readable tmpfiles True default False ▒ 3│Ansible connection path True default None ▒ 4│Ansible cow acceptlist True default ['bud-frogs' , 'bunny' , 'cheese' , 'daemon' , 'def... ▒ 5│Ansible cow path True default None ▒ 6│Ansible cow selection True default default 7│Ansible force color True default False 8│Ansible nocolor True default False ^b/PgUp page up ^f/PgDn page down ↑↓ scroll esc back [0-9] goto :help help
ansible-navigator配置文件
它也有优先级,以下是从高到低:
ANSIBLE_NAVIGATOR_CONFIG 环境变量 :
如果设置了环境变量 ANSIBLE_NAVIGATOR_CONFIG
,ansible-navigator
将首先使用该路径指定的配置文件。 当前目录下的 ansible-navigator.yml 文件 :
如果没有设置 ANSIBLE_NAVIGATOR_CONFIG
环境变量,ansible-navigator
将查找当前工作目录中的 ansible-navigator.yml
文件。 用户主目录下的 .ansible-navigator.yml 文件 :
如果当前目录下没有找到 ansible-navigator.yml
文件,ansible-navigator
将查找用户主目录中的 .ansible-navigator.yml
文件。 系统范围的 /etc/ansible-navigator/ansible-navigator.yml 文件 :
如果上述位置都没有找到 ansible-navigator.yml
文件,ansible-navigator
将使用系统范围内的 /etc/ansible-navigator/ansible-navigator.yml
文件。 我们来解读一下自己现有的配置文件
1 2 3 4 5 6 7 8 9 10 [student@workstation ~ ]$ cat .ansible-navigator.yml --- ansible-navigator: execution-environment: image: utility.lab.example.com/ee-supported-rhel8:latest pull: policy: missing playbook-artifact: enable: false mode: stdout
ansible-navigator :
顶层键,表示 ansible-navigator
的配置部分。 execution-environment :
image :
pull :
policy :
playbook-artifact :
mode :
编写和运⾏ Playbook Ansible Playbook 是一个用 YAML 格式编写的文件,用于定义一组自动化任务和操作步骤。文件后缀一般来说是yml或者yaml,Playbook 是 Ansible 自动化工作的核心,通过它可以描述和执行复杂的操作流程。下面是对 Ansible Playbook 的详细解释:
主要特点 YAML 格式 :
Playbook 使用 YAML(Yet Another Markup Language)编写,这是一种易于阅读和编写的标记语言。 任务定义 :
Playbook 由一系列任务(Tasks)组成,每个任务定义了要在目标主机上执行的具体操作。 模块调用 :
每个任务通常调用一个 Ansible 模块来执行具体的操作,例如安装软件包、管理服务、复制文件等。 灵活性和可扩展性 :
Playbook 支持变量、条件判断、循环等高级特性,使得自动化工作更具灵活性和可扩展性。 可重用性 :
Playbook 可以引用其他 Playbook 或角色(Roles),实现代码重用和模块化管理。 Playbook案例 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 --- - name: Setup web server hosts: webservers become: true tasks: - name: Install Apache ansible.builtin.yum: name: httpd state: present - name: Start Apache service ansible.builtin.service: name: httpd state: started enabled: true - name: Setup database server hosts: dbservers become: true tasks: - name: Install MySQL ansible.builtin.yum: name: mysql-server state: present - name: Start MySQL service ansible.builtin.service: name: mysqld state: started enabled: true
示例解析
第一个 Play:Setup web server :
第二个 Play:Setup database server :
Playbook 格式 缩进
使用2个空格缩进,保持每级缩进的一致性,避免使用制表符(tab)。 列表
键值对
字符串
字符串包含特殊字符或需保留格式时,使用引号(单引号或双引号)。 顶层结构
顶层包含 name
、hosts
、tasks
等关键字,表示 Play 的结构。 任务定义
每个任务包含 name
和模块调用的参数,任务名称应描述清晰。 模块调用
使用命名空间调用模块,如 ansible.builtin.yum
。 变量定义
变量引用
条件判断
循环
文本多行处理
使用 |
要保留字符串中的换⾏字符。 使用 >
把换⾏字符转换成空格。 …
还有很多,后面我们学到了再聊,如果你觉得使用TAB习惯了,可以考虑定制一下你的vim,每次输入TAB,自动给你替换成2个空格
1 2 3 cd vim .vimrc autocmd FileType yaml setlocal ai ts=2 sw=2 et
以下是每个选项的解释:
autocmd FileType yaml
:当文件类型为 YAML 时执行自动命令。
setlocal
:只在当前缓冲区生效的设置。
ai
(autoindent):自动缩进。
ts=2
(tabstop):设置 Tab 宽度为 2 个空格。
sw=2
(shiftwidth):设置缩进的宽度为 2 个空格。
et
(expandtab):将 Tab 替换为空格。
查找模块帮助 刚才的案例中,我们用到了一些模块,那我怎么知道都有哪些模块以及都怎么使用呢?
ansible-core 软件包可提供名为 ansible.builtin 的单个 Ansible 内容集合,在这个集合中带来的模块,我们除了可以用模块的全名之外,还可以用短名称,比如ansible.builtin.copy
和copy
除了这个builtin集合外,其他所有安装的集合,都必须使用全名(完全限定集合名称 FQCN)
列出所有可用的模块
1 2 3 4 5 6 7 8 9 10 [student@workstation ~]$ ansible-navigator doc -l -m stdout add_host Add a host (and alternatively a group) to the ans... amazon.aws.aws_az_info Gather information about availability zones in AW... amazon.aws.aws_caller_info Get information about the user and account being ... amazon.aws.aws_s3 manage objects in S3 amazon.aws.cloudformation Create or delete an AWS CloudFormation stack amazon.aws.cloudformation_info Obtain information about an AWS CloudFormation st... amazon.aws.ec2 create, terminate, start or stop an instance in e... amazon.aws.ec2_ami Create or destroy an image (AMI) in ec2 ...
有很多模块,初学时,你可以挑选一些内置模块来练手,用以下方法,可以单独列出这些内置模块,默认以短名称显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [student@workstation ~]$ ansible-doc -l add_host Add a host (and alternatively a group) to the ansible-playbook in ... apt Manages apt-packages apt_key Add or remove an apt key apt_repository Add and remove APT repositories assemble Assemble configuration files from fragments assert Asserts given expressions are true async_status Obtain status of asynchronous task blockinfile Insert/update/remove a text block surrounded by marker lines command Execute commands on targetscopy Copy files to remote locations cron Manage cron.d and crontab entries debconf Configure a .deb package debug Print statements during execution dnf Manages packages with the `dnf' package manager ...
查询copy模块的帮助
我们发现左上角告诉我们模块的全名,然后接着是模块的作用描述,后面是每个参数的含义以及可选值,最后的部分有写EXAMPLE样例,可以辅助我们写playbook,我们只需要复制走,稍微改一下就行
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 [student@workstation ~]$ ansible-navigator doc ansible.builtin.copy -m stdout > ANSIBLE.BUILTIN.COPY (/usr/lib/python3.9/site-packages/ansible/modules/copy.py) The `copy' module copies a file from the local or remote machine to a location on the remote machine. Use the [ansible.builtin.fetch] module to copy files from remote locations to the local box. If you need variable interpolation in copied files, use the [ansible.builtin.template] module. Using a variable in the `content' field will result in unpredictable output. For Windows targets, use the [ansible.windows.win_copy] module instead. ADDED IN: historical * note: This module has a corresponding action plugin. OPTIONS (= is mandatory): - attributes The attributes the resulting filesystem object should have. To get supported flags look at the man page for `chattr' on the target system. This string should contain the attributes in the same order as the one displayed by `lsattr' . The `=' operator is assumed as default, otherwise `+' or `-' operators need to be included in the string. (Aliases: attr)[Default: (null)] type: str added in: version 2.3 of ansible-core - backup .... EXAMPLES: - name: Copy file with owner and permissions ansible.builtin.copy: src: /srv/myfiles/foo.conf dest: /etc/foo.conf owner: foo group: foo mode: ' 0644' - name: Copy file with owner and permission, using symbolic representation ansible.builtin.copy: src: /srv/myfiles/foo.conf dest: /etc/foo.conf owner: foo group: foo mode: u=rw,g=r,o=r - name: Another symbolic mode example, adding some permissions and removing others ansible.builtin.copy: src: /srv/myfiles/foo.conf dest: /etc/foo.conf owner: foo group: foo mode: u+rw,g-wx,o-rwx - name: Copy a new "ntp.conf" file into place, backing up the original if it differs from the copied version ansible.builtin.copy: src: /mine/ntp.conf dest: /etc/ntp.conf owner: root group: root mode: ' 0644'
好,我们按照这个EXAMPLE,来写一个,我们把workstaion这个机器上的/etc/hostname文件复制到servera上的/tmp中,以下是我们写的内容
先准备一个清单
1 echo servera > inventory
这些become字段,如果已经在ansible.cfg中申明了,就不再需要写在playbook中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cat > copy.yml <<-'EOF' --- - name: Copy /etc/hostname file from workstation to servera hosts: servera remote_user: student become: true become_method: sudo become_user: root tasks: - name: Copy it by lixiaohui ansible.builtin.copy: src: /etc/hostname dest: /tmp/hostname owner: root group: root mode: '0644' EOF
运行playbook 写好了之后,我们可以用以下方法做一个语法检查,看看格式什么的有没有什么问题
如果没什么问题,就只输出文件名称
1 2 [student@workstation ~]$ ansible-navigator run copy.yml -m stdout -i inventory --syntax-check playbook: /home/student/copy.yml
写好了之后,我们得运行测试一下,不过直接对生产环境修改可不行,Ansible提供了一个check mode模式,就是对生产环境做模拟测试,不对机器做任何修改,只测试在这个环境中代码是否能成功运行
1 2 3 4 5 6 7 8 9 10 [student@workstation ~]$ ansible-navigator run copy.yml -m stdout -i inventory --check PLAY [Copy /etc/hostname file from workstation to servera] ********************* TASK [Gathering Facts] ********************************************************* fatal: [servera]: FAILED! => {"msg" : "Missing sudo password" } PLAY RECAP ********************************************************************* servera : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 Please review the log for errors.
在上面的模拟运行中,我们发现有点不对劲,运行失败了,提示Missing sudo password
根据提示的意思,是连接没问题,在本地sudo成root的时候,需要密码,这种就需要去被管理机上做一下免密的时候NOPASSWD设置
虽然student已经加入wheel组,可以给组设置,但是我们演示一下如何单独授权,实现安全最小化原则
1 2 3 [student@workstation ~]$ ssh root@servera [root@servera ~]# visudo student ALL=(ALL) NOPASSWD: ALL
退到控制节点上,再模拟运行看看
这次一切成功
1 2 3 4 5 6 7 8 9 10 11 12 [student@workstation ~]$ ansible-navigator run copy.yml -m stdout -i inventory --check PLAY [Copy /etc/hostname file from workstation to servera] ********************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Copy it by lixiaohui] **************************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
模拟成功后,去掉–check就是真的运行
运行命令而非模块 需要注意的是,你应该始终运行相关模块,而非命令,除非你的任务本身没有任何模块能支持,因为模块具有幂等性,而shell、command、raw等不具有此特性
ansible.builtin.command 模块⽆法访问 shell 环境变量,也⽆法执⾏输⼊/输出重定向和管道之类的 shell 操作。当需要执⾏ shell 处理时,可以使⽤ ansible.builtin.shell 模块
我们用shell模块举例创建一个用户user1
1 2 3 4 5 6 7 8 9 10 11 12 cat > user1create.yml <<-EOF --- - name: Create user1 on servera hosts: servera remote_user: student become: true become_method: sudo become_user: root tasks: - name: Create user user1 ansible.builtin.shell: "useradd user1" EOF
第一次运行,一切正常,且全部成功
1 2 3 4 5 6 7 8 9 10 11 12 [student@workstation ~]$ ansible-navigator run user1create.yml -m stdout -i inventory PLAY [Create user1 on servera] ************************************************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Create user user1] ******************************************************* changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
第二次开始,就无法运行成功了,因为user1已经存在了,这就是没有幂等性的后果,如果有幂等性,playbook不会导致失败
1 2 3 4 5 6 7 8 9 10 11 12 13 [student@workstation ~]$ ansible-navigator run user1create.yml -m stdout -i inventory PLAY [Create user1 on servera] ************************************************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Create user user1] ******************************************************* fatal: [servera]: FAILED! => {"changed" : true , "cmd" : "useradd user1" , "delta" : "0:00:00.006055" , "end" : "2024-12-25 02:33:02.358870" , "msg" : "non-zero return code" , "rc" : 9, "start" : "2024-12-25 02:33:02.352815" , "stderr" : "useradd: user 'user1' already exists" , "stderr_lines" : ["useradd: user 'user1' already exists" ], "stdout" : "" , "stdout_lines" : []} PLAY RECAP ********************************************************************* servera : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 Please review the log for errors.
第三章 管理变量和事实 Ansible 变量简介 比如上面写的样例playbook,每次安装的软件和复制的文件都是写明的,每次有不同的软件或文件都要改playbook,容易出错,这个时候就可以用变量来表示,以后同样的工作,只需要更新变量文件就行。
变量命名规范
变量的名称必须以字⺟开头,并且只能含有字⺟、数字和下划线。
仔细观察以下表格
无效的变量名称 有效的变量名称 web server
web_server
remote.file
remote_file
1st file
file1
remoteserver$1
remote_server_1
变量优先级
可以在多个地方定义变量,以下是优先级从低到高的排序:
在清单中定义的组变量
在清单或 playbook 所在目录的 group_vars
子目录中定义的组变量
在清单中定义的主机变量
在清单或 playbook 所在目录的 host_vars
子目录中定义的主机变量
在运行时中发现的主机事实
(vars
和 vars_files
)playbook 中的 play 变量
任务变量
在命令行中定义的额外变量
这么看来,主机的优先级要高于组,而playbook的变量又高于主机,虽然说可以在这么多地方定义,但是到处定义变量不是个好习惯,从社会上看,大家用host_group和group_vars,以及playbook变量多些,其他的几乎没有人使用
Playbook 中的变量 vars变量 在servera服务器上安装并启动 Apache Web 服务器
这里解释一下在playbook中的变量引用:
双大括号语法 可以表达变量
大括号左右两次是否有双引号,却决于变量是否作为参数的第一部分,例如下面的name: 和大括号之间没用内容,那就需要双引号,举例来说
需要双引号
不需要双引号
1 name: hello lixiaohui {{ xxx }}
根据变量引用规范,我们来写一个playbook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- - name: Setup web server hosts: servera vars: apache_package: httpd apache_service: httpd tasks: - name: Install Apache ansible.builtin.yum: name: "{{ apache_package }} " state: present - name: Ensure Apache service is running ansible.builtin.service: name: "{{ apache_service }} " state: started enabled: true
解析
定义变量 :
在 vars
部分定义了一些变量,例如 apache_package
、apache_service
。这些变量将用于后续的任务中,以提高 Playbook 的可读性和可维护性。 安装 Apache :
使用 yum
模块安装 Apache。这里引用了 apache_package
变量,以确保包名称可配置。 确保 Apache 服务正在运行 :
使用 service
模块启动并启用 Apache 服务。这里引用了 apache_service
变量。 运行一下并检查看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [student@workstation ~]$ ansible-navigator run apache.yml -m stdout PLAY [Setup web server] ******************************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Install Apache] ********************************************************** changed: [servera] TASK [Ensure Apache service is running] **************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
检查看看是否安装,本次用的是AD-Hoc方法临时查看
1 2 3 4 5 6 7 8 9 [student@workstation ~]$ ansible servera -m shell -a 'rpm -q httpd' servera | CHANGED | rc=0 >> httpd-2.4.51-7.el9_0.x86_64 [student@workstation ~]$ ansible servera -m shell -a 'systemctl is-active httpd' servera | CHANGED | rc=0 >> active [student@workstation ~]$ ansible servera -m shell -a 'systemctl is-enabled httpd' servera | CHANGED | rc=0 >> enabled
vars_files变量 我们将使用外部变量文件 (vars_files) 来管理配置变量,从而实现更灵活和可维护的Playbook。
先把变量文件建出来 1 2 3 4 [student@workstation ~]$ mkdir vars [student@workstation ~]$ vim vars/apache_vars.yml apache_package: httpd apache_service: httpd
根据需求,写一个playbook,并引用变量,我们这次在serverb上运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- - name: Setup web server hosts: serverb become: true vars_files: - vars/apache_vars.yml tasks: - name: Install Apache ansible.builtin.yum: name: "{{ apache_package }} " state: present - name: Ensure Apache service is running ansible.builtin.service: name: "{{ apache_service }} " state: started enabled: true
解析
变量文件 :
在 vars/apache_vars.yml
文件中定义了一些变量,例如 apache_package
和 apache_service
。 Playbook 文件 :
运行并检查结果
看上去运行一切成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [student@workstation ~]$ ansible-navigator run apache.yml -m stdout PLAY [Setup web server] ******************************************************** TASK [Gathering Facts] ********************************************************* ok: [serverb] TASK [Install Apache] ********************************************************** changed: [serverb] TASK [Ensure Apache service is running] **************************************** changed: [serverb] PLAY RECAP ********************************************************************* serverb : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
看看服务怎么样了
1 2 3 4 5 6 7 8 9 [student@workstation ~]$ ansible serverb -m shell -a 'rpm -q httpd' serverb | CHANGED | rc=0 >> httpd-2.4.51-7.el9_0.x86_64 [student@workstation ~]$ ansible serverb -m shell -a 'systemctl is-active httpd' serverb | CHANGED | rc=0 >> active [student@workstation ~]$ ansible serverb -m shell -a 'systemctl is-enabled httpd' serverb | CHANGED | rc=0 >> enabled
主机变量 主机变量又分为清单中定义的主机变量和host_vars变量
主机变量优先于组变量,但 playbook 中定义的变量的优先级⽐这两者更⾼。
清单中的主机变量 不太推荐定义在清单中,不好统一集中管理
首先定制一下清单文件
1 2 3 cat > hostinventory <<-EOF servera my_variable="Hello, Ansible!" EOF
好的,我们来写一个playbook,看看是否能识别到这个变量
1 2 3 4 5 6 7 8 9 --- - name: Display inventory variable hosts: servera become: true tasks: - name: Show variable from inventory ansible.builtin.debug: msg: "The value of my_variable is: {{ my_variable }} "
运行看看结果
结果显示,使用默认清单是不行的,必须用我们定制的清单才可以,说明我们在清单中定义的变量已经生效
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 [student@workstation ~]$ ansible-navigator run host.yml -m stdout PLAY [Display inventory variable] ********************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Show variable from inventory] ******************************************** fatal: [servera]: FAILED! => {"msg" : "The task includes an option with an undefined variable. The error was: 'my_variable' is undefined\n\nThe error appears to be in '/home/student/host.yml': line 7, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: Show variable from inventory\n ^ here\n" } PLAY RECAP ********************************************************************* servera : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 Please review the log for errors. [student@workstation ~]$ [student@workstation ~]$ ansible-navigator run host.yml -m stdout -i hostinventory PLAY [Display inventory variable] ********************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Show variable from inventory] ******************************************** ok: [servera] => { "msg" : "The value of my_variable is: Hello, Ansible!" } PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_vars文件夹变量 在 Ansible 中,host_vars 文件夹用于为特定的主机定义变量。这些变量只适用于定义它们的特定主机,可以用来对每个主机进行细粒度的配置管理。
host_vars
文件夹通常位于你的 Ansible 项目根目录下,或者位于你正在使用的角色目录中。
在 host_vars
文件夹中,每个主机都有一个对应的 YAML 文件,文件名与主机名相同。
每个 YAML 文件中可以定义针对该主机的变量。这些变量在该主机执行 Playbook 时会被引用。
我们先来确定一下主机清单,我们现在有两个主机
1 2 3 4 cat > hostinventory <<-EOF servera serverb EOF
根据刚才说的,如果要给他们定义变量,要在host_vars目录中,新建和每个主机同名的文件,然后把变量放入到文件中,我们来制作变量
servera的变量:
1 2 3 [student@workstation ~]$ mkdir host_vars [student@workstation ~]$ vim host_vars/servera custom_var: "Hello from servera!"
serverb的变量:
1 2 [student@workstation ~]$ vim host_vars/serverb custom_var: "Hello from serverb!"
这样定义好了变量之后,这两个机器上都有一个同名的变量了,但是值是不一样的,所以可以处理不同的事
我们写一个debug看看能不能识别到变量
1 2 3 4 5 6 7 8 9 --- - name: Display custom variable hosts: all become: true tasks: - name: Show custom variable from host_vars ansible.builtin.debug: msg: "The value of custom_var is: {{ custom_var }} "
运行后,果然看到变量已经准确的绑定到了不同的主机上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [student@workstation ~]$ ansible-navigator run host.yml -m stdout -i hostinventory PLAY [Display custom variable] ************************************************* TASK [Gathering Facts] ********************************************************* ok: [serverb] ok: [servera] TASK [Show custom variable from host_vars] ************************************* ok: [servera] => { "msg" : "The value of custom_var is: Hello from servera!" } ok: [serverb] => { "msg" : "The value of custom_var is: Hello from serverb!" } PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
组变量 变量不要和主机的冲突,如果冲突,主机变量优先级更高
清单中的组变量 重新定制一个新的清单
我们单独给webservers组定义了变量,而没有给主机显式的定义
1 2 3 4 5 6 7 8 cat > groupinventory <<-EOF [webservers] servera serverb [webservers:vars] my_group_variable ="Hello, Web Servers!" EOF
我们来做一个在webservers组上运行的playbook
1 2 3 4 5 6 7 8 9 --- - name: Display group variable hosts: webservers become: true tasks: - name: Show group variable from inventory ansible.builtin.debug: msg: "The value of my_group_variable is: {{ my_group_variable }} "
运行看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [student@workstation ~]$ ansible-navigator run group.yml -m stdout -i groupinventory PLAY [Display group variable] ************************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] ok: [serverb] TASK [Show group variable from inventory] ************************************** ok: [servera] => { "msg" : "The value of my_group_variable is: Hello, Web Servers!" } ok: [serverb] => { "msg" : "The value of my_group_variable is: Hello, Web Servers!" } PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
group_vars 文件夹变量 这个和host_vars是同理的,只是创建的文件要和清单中的组同名
我们先来设计一个清单文件,这个文件要包含不同的组,这样才能看到效果
1 2 3 4 5 6 7 8 cat > groupinventory <<-EOF [webservers] servera serverb [dbservers] serverc serverd EOF
设计一个变量文件夹,注意新进的文件要和上面方括号的字符串相同
1 2 3 4 5 [student@workstation ~]$ mkdir group_vars [student@workstation ~]$ vim group_vars/webservers custom_var: "Hello from webservers group!" [student@workstation ~]$ vim group_vars/dbservers custom_var: "Hello from dbservers group!"
变量也准备好了,写一个playbook看看
1 2 3 4 5 6 7 8 9 --- - name: Display group variable hosts: all become: true tasks: - name: Show group variable from group_vars ansible.builtin.debug: msg: "The value of custom_var is: {{ custom_var }} "
运行之前,你需要先删除host_vars文件夹才能看到效果,因为主机的变量优先级高于组变量
以下是不删除和删除的对比
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 [student@workstation ~]$ ansible-navigator run group.yml -m stdout -i groupinventory PLAY [Display group variable] ************************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] ok: [serverd] ok: [serverc] ok: [serverb] TASK [Show group variable from group_vars] ************************************* ok: [servera] => { "msg" : "The value of custom_var is: Hello from servera!" } ok: [serverb] => { "msg" : "The value of custom_var is: Hello from serverb!" } ok: [serverc] => { "msg" : "The value of custom_var is: Hello from dbservers group!" } ok: [serverd] => { "msg" : "The value of custom_var is: Hello from dbservers group!" } PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverc : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverd : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ rm -rf host_vars/ [student@workstation ~]$ ansible-navigator run group.yml -m stdout -i groupinventory PLAY [Display group variable] ************************************************** TASK [Gathering Facts] ********************************************************* ok: [serverd] ok: [servera] ok: [serverc] ok: [serverb] TASK [Show group variable from group_vars] ************************************* ok: [servera] => { "msg" : "The value of custom_var is: Hello from webservers group!" } ok: [serverb] => { "msg" : "The value of custom_var is: Hello from webservers group!" } ok: [serverc] => { "msg" : "The value of custom_var is: Hello from dbservers group!" } ok: [serverd] => { "msg" : "The value of custom_var is: Hello from dbservers group!" } PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverc : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverd : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
命令行变量 在命令⾏上设置的变量称为额外变量,这个变量可以覆盖清单变量、playbook变量
我们还用刚才的yaml运行看看
1 2 3 4 5 6 7 8 9 --- - name: Display group variable hosts: all become: true tasks: - name: Show group variable from group_vars ansible.builtin.debug: msg: "The value of custom_var is: {{ custom_var }} "
我们运行的时候,主动用-e参数给这个变量赋值,而不是让他采用group_vars变量
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 [student@workstation ~]$ ansible-navigator run group.yml -m stdout -i groupinventory -e custom_var=lixiaohui PLAY [Display group variable] ************************************************** TASK [Gathering Facts] ********************************************************* ok: [serverb] ok: [servera] ok: [serverc] ok: [serverd] TASK [Show group variable from group_vars] ************************************* ok: [servera] => { "msg" : "The value of custom_var is: lixiaohui" } ok: [serverb] => { "msg" : "The value of custom_var is: lixiaohui" } ok: [serverc] => { "msg" : "The value of custom_var is: lixiaohui" } ok: [serverd] => { "msg" : "The value of custom_var is: lixiaohui" } PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverc : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverd : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
将字典⽤作变量 这部分的内容是表达了变量层级的关系和引用规范,将普通变量改为字典的好处:
结构化数据 : 使用字典可以更好地组织和结构化相关的数据。例如,上述例子中,用户的名字、姓氏和主目录属于同一个实体(用户),使用字典将这些属性组织在一起,使数据看起来更加有条理。
减少变量数量 : 使用字典可以减少顶层变量的数量,避免变量名冲突,并提高可读性。例如,将多个 user1_*
和 user2_*
变量合并成一个 users
字典,可以显著减少变量的数量。
易于扩展 : 使用字典可以方便地添加或删除数据,而无需修改多个变量。例如,如果需要为用户添加一个新的属性(如电子邮件地址),只需在字典中添加一行,而不需要定义新的变量。
易于迭代 : 字典非常适合在循环中使用。例如,可以轻松地遍历 users
字典来执行批量操作,而不需要手动列出每个用户的变量。
假设我们原来有一些变量如下:
1 2 3 4 5 6 user1_first_name: Bob user1_last_name: Jones user1_home_dir: /users/bjones user2_first_name: Anne user2_last_name: Cook user2_home_dir: /users/acook
以上的变量可以改成成为users的字典:
1 2 3 4 5 6 7 8 9 users: bjones: first_name: Bob last_name: Jones home_dir: /users/bjones acook: first_name: Anne last_name: Cook home_dir: /users/acook
变量改完之后,我们可以用下面的方法引用:
users.acook.first_name
[‘users’][‘acook’][‘first_name’]
从书写来看,用第一种更好写,如果键名称与 Python ⽅法或属性的名称(如 discard、copy、add 等)相同,点表⽰法可能会造成问题。使⽤⽅括号表⽰法有助于避免冲突和错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 --- - name: Create user acook hosts: servera become: true vars: users : bjones: first_name: Bob last_name: Jones home_dir: /users/bjones acook: first_name: Anne last_name: Cook home_dir: /users/acook tasks: - name: Create user acook ansible.builtin.user: name: "{{ users.acook.first_name }}" state: present home: "{{ users.acook.home_dir }}" shell: /bin/bash
运行并检查结果
1 2 3 4 5 6 7 8 9 10 11 12 [student@workstation ~]$ ansible-navigator run user.yml -m stdout PLAY [Create user acook] ******************************************************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Create user acook] ******************************************************* changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
成功创建了此用户
1 2 3 [student@workstation ~]$ ansible servera -m shell -a 'id Anne' servera | CHANGED | rc=0 >> uid=1002(Anne) gid=1002(Anne) groups =1002(Anne)
注册变量 可使⽤ register 语句来捕获命令的输出或有关模块执⾏的其他信息。输出会保存到⼀个变量中,稍后在 playbook 中可⽤于调试⽤途或者达成其他⽬的
安装 Apache 包 :使用 ansible.builtin.dnf
模块安装 Apache,并使用 register
关键字将任务的输出保存到变量 apache_install_status
中。
确保 Apache 服务正在运行 :使用 ansible.builtin.service
模块启动并启用 Apache 服务。
显示 Apache 安装状态 :使用 ansible.builtin.debug
模块显示 apache_install_status
变量的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 --- - name: Install and check Apache service hosts: serverc become: true tasks: - name: Install Apache package ansible.builtin.dnf: name: httpd state: present register: apache_install_status - name: Ensure Apache service is running ansible.builtin.service: name: httpd state: started - name: Display Apache installation status ansible.builtin.debug: msg: "Apache installation status: {{ apache_install_status }} "
运行看看,正常来说,debug会输出在register中保存的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [student@workstation ~]$ ansible-navigator run register.yml -m stdout PLAY [Install and check Apache service] **************************************** TASK [Gathering Facts] ********************************************************* ok: [serverc] TASK [Install Apache package] ************************************************** changed: [serverc] TASK [Ensure Apache service is running] **************************************** changed: [serverc] TASK [Display Apache installation status] ************************************** ok: [serverc] => { "msg" : "Apache installation status: {'msg': '', 'changed': True, 'results': ['Installed: apr-util-1.6.1-20.el9.x86_64', 'Installed: redhat-logos-httpd-90.4-1.el9.noarch', 'Installed: apr-util-bdb-1.6.1-20.el9.x86_64', 'Installed: mod_http2-1.15.19-2.el9.x86_64', 'Installed: apr-util-openssl-1.6.1-20.el9.x86_64', 'Installed: mod_lua-2.4.51-7.el9_0.x86_64', 'Installed: httpd-2.4.51-7.el9_0.x86_64', 'Installed: httpd-filesystem-2.4.51-7.el9_0.noarch', 'Installed: httpd-tools-2.4.51-7.el9_0.x86_64', 'Installed: apr-1.7.0-11.el9.x86_64'], 'rc': 0, 'failed': False}" } PLAY RECAP ********************************************************************* serverc : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
管理机密 使⽤ Ansible Vault 加密敏感变量,Ansible Vault 是 Ansible 提供的一种工具,用于保护和加密敏感数据,例如密码、密钥和其他机密信息。它允许你在代码库中安全地存储这些信息,而无需担心未经授权的访问。以下是 Ansible Vault 的一些关键功能和使用方法:
加密和解密 : Ansible Vault 可以加密和解密整个文件或文件中的特定变量,以确保敏感信息的安全。
密码保护 : 通过使用密码来加密和解密文件,只有持有正确密码的人才能访问这些敏感数据。
与 Playbook 兼容 : Ansible Vault 与 Ansible Playbook 完全兼容,允许在 Playbook 中引用加密的变量和文件。
创建加密文件 创建的文件默认会提示加密,cat是看不到的
这将提示你输入一个密码,然后你可以在编辑器中输入要加密的内容。
1 2 3 4 5 6 7 8 9 10 11 [student@workstation ~]$ ansible-vault create secret.yml New Vault password: Confirm New Vault password: [student@workstation ~]$ cat secret.yml $ANSIBLE_VAULT ;1.1;AES25635333436313332613163323439656130633363306561363763326134306561643862316437366232 3265386163646632653733326332666262356530636430640a303763373038343734326632336239 32386139323534326635393232396366326438306363653930326439646630303438616632363536 3835646631623835660a633933613534626466653861646535336330633233623739633066613734 39633431623633656566653632326436663038613039346139373130663264353366
查看加密文件 看不见可不行,我们来看看里面的内容
1 2 3 [student@workstation ~]$ ansible-vault view secret.yml Vault password: lixiaohui vault test
编辑加密文件 1 2 [student@workstation ~]$ ansible-vault edit secret.yml Vault password:
加密现有文件 先拷贝一个过来,再加密
1 2 3 4 5 6 7 8 9 10 11 12 [student@workstation ~]$ cp /etc/fstab . [student@workstation ~]$ ansible-vault encrypt fstab New Vault password: Confirm New Vault password: Encryption successful [student@workstation ~]$ cat fstab $ANSIBLE_VAULT ;1.1;AES25631343032636665306261303337343133303232373164636233636338303133343539643333363436 6661333635313633396462353635623066363565323636650a393433316135613763613137656130 39333564656362613530336464303034366634643035336636313339393234383834393865353038 3663663266313166310a363066303664636330653238303630373361336261336434313032653666 65373261623339316132333766393463346436646234303834353436346665306232653733396464
重置密码 1 2 3 4 5 [student@workstation ~]$ ansible-vault rekey fstab Vault password: New Vault password: Confirm New Vault password: Rekey successful
解密文件 1 2 3 4 5 6 7 8 9 10 11 12 [student@workstation ~]$ cat fstab $ANSIBLE_VAULT ;1.1;AES25632653239336436353834326634303465616530356561393863363566613936323938623565636136 3562366338643764363235383961623630616234363066310a643933353863653864313262373038 ... [student@workstation ~]$ ansible-vault decrypt fstab Vault password: Decryption successful [student@workstation ~]$ cat fstab UUID=5e75a2b9-1367-4cc8-bb38-4d6abc3964b8 /boot xfs defaults 0 0 UUID=fb535add-9799-4a27-b8bc-e8259f39a767 / xfs defaults 0 0 UUID=7B77-95E7 /boot/efi vfat defaults,uid=0,gid=0,umask =077,shortname=winnt 0 2
自动输入密码 我们发现,不管是创建、查看、编辑、加解密,都需要输入密码,太长了,有没有办法能非交互式的完成工作呢,那就是提前准备好密码文件,每次都指定一下密码文件
先制作一个私密的密码文件
1 2 [student@workstation ~]$ echo woshimima > passfile [student@workstation ~]$ chmod 600 passfile
现在试试自动输入密码
1 [student@workstation ~]$ ansible-vault create newvaultfile --vault-password-file /home/student/passfile
现在就不再需要输入密码了,但是每次输入这么长也挺麻烦的,这个参数可以放到配置文件中,就会自动引用了
1 2 3 4 5 6 [student@workstation ~]$ cat ansible.cfg [defaults] ... vault_password_file=/home/student/passfile [student@workstation ~]$ ansible-vault create passwordfiletest
现在虽然解决了自动输入密码且不需要你输入密码文件,但是有一个新的问题出现了,重置密码的时候,旧密码和新密码都会自动输入,这就无法重置密码了
这种情况又细分为新密码是字符串还是密码文件
密码是临时的字符串,不打算作为长期的密码文件
临时注释ansible.cfg中的密码文件 完成rekey 恢复注释 新的密码是密码文件,用于替换旧密码文件
1 2 3 [student@workstation ~]$ echo newpass > newpassfile [student@workstation ~]$ ansible-vault rekey fstab --vault-password-file /home/student/passfile --new-vault-password-file /home/student/newpassfile Rekey successful
重置后,别忘了在配置文件中,改为新的密码文件
vault与playbook 如果playbook或变量是加密的,且配置文件中有密码文件,会自动解密运行
如果playbook或变量是加密的,且配置文件中没有密码文件,会运行识别,提示无法解密
如果playbook或变量是加密的,且配置文件中没有密码文件,只提供--vault-id @prompt
会卡着不动,需要同时加上--playbook-artifact-enable false
,才会提示你输入密码
管理fact Ansible Facts 是 Ansible 自动收集的远程主机的系统信息,这些信息通过 ansible.builtin.setup
模块收集。默认情况下,每次运行 Playbook 时,Ansible 都会自动收集这些 Facts,并将其存储在变量中。这些变量可以在 Playbook 和模板中使用。
获取facts 我们自己也可以导出servera的fact,看看都会收集远程主机的哪些信息供我们稍后使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [student@workstation ~]$ ansible servera -m ansible.builtin.setup > fact.txt [student@workstation ~]$ head -n 20 fact.txt servera | SUCCESS => { "ansible_facts" : { "ansible_all_ipv4_addresses" : [ "172.25.250.10" ], "ansible_all_ipv6_addresses" : [ "fe80::a973:f7b3:7b00:e503" ], "ansible_apparmor" : { "status" : "disabled" }, "ansible_architecture" : "x86_64" , "ansible_bios_date" : "04/01/2014" , "ansible_bios_vendor" : "SeaBIOS" , "ansible_bios_version" : "1.16.1-1.el9" , "ansible_board_asset_tag" : "NA" , "ansible_board_name" : "RHEL" , "ansible_board_serial" : "NA" , "ansible_board_vendor" : "Red Hat" , "ansible_board_version" : "RHEL-9.2.0 PC (Q35 + ICH9, 2009)" ,
facts的新旧语法区别 在旧语法中(ansible2.5以前),Ansible Facts 的引用是通过直接访问变量的名称实现的,比如 ansible_ 前缀。
在 Ansible 2.5 之前,事实总是作为以 ansible_ 字符串为前缀的单个变量注⼊,⽽不是作为ansible_facts 变量的⼀部分。例如,ansible_facts[‘distribution’] 事实称为ansible_distribution。
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: Example playbook using old facts syntax hosts: all tasks: - name: Display operating system ansible.builtin.debug: msg: "The operating system is: {{ ansible_os_family }} " - name: Display total memory ansible.builtin.debug: msg: "The total memory is: {{ ansible_memtotal_mb }} MB"
在新语法中,Ansible 引入了 ansible_facts 字典,以提供更加一致和模块化的引用方式。
以下方括号也可以用.
连接起来
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: Example playbook using new facts syntax hosts: all tasks: - name: Display operating system ansible.builtin.debug: msg: "The operating system is: {{ ansible_facts['os_family'] }} " - name: Display total memory ansible.builtin.debug: msg: "The total memory is: {{ ansible_facts['memtotal_mb'] }} MB"
关键区别
引用方式 :
一致性 :
可读性 :
关闭fact收集 在 Ansible 中,如果你不需要收集 Facts(主机信息),可以在 Playbook 中禁用它们,这样可以提高执行效率。要关闭 Facts 收集,可以在 Playbook 的顶部使用 gather_facts: no
。
1 2 3 4 5 6 7 8 9 --- - name: Playbook without gathering facts hosts: all gather_facts: no tasks: - name: Display a message ansible.builtin.debug: msg: "This playbook does not gather facts."
创建⾃定义fact 自定义 Facts 允许你在 Ansible 中定义自己的主机信息,以便在 Playbook 中使用。默认情况下,ansible.builtin.setup
模块会从每个受管主机的 /etc/ansible/facts.d
目录中加载自定义 Facts。这些文件和脚本必须以 .fact
结尾,才能被 Ansible 识别和使用,收集到的自定义facts,将保存至ansible_facts.ansible_local
先创建一个custom.fact
自定义变量列表
1 2 3 4 cat > custom.fact <<-EOF [general] hello=Hello from custom fact file! EOF
我们把这个custom.fact放到所有机器上去,不过需要注意的是,只有play运行的时候才会收集fact,所以第一个play用于准备,第二个用于重新收集
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: Create custom facts file hosts: all become: true tasks: - name: Create facts directory ansible.builtin.file: path: /etc/ansible/facts.d state: directory mode: '0755' - name: Copy custom facts file from current path ansible.builtin.copy: src: custom.fact dest: /etc/ansible/facts.d/custom.fact mode: '0644' - name: Use custom facts example hosts: all become: true tasks: - name: Gather facts ansible.builtin.setup: - name: Display custom fact from file ansible.builtin.debug: msg: "Custom fact from file is: {{ ansible_facts.ansible_local.custom.general.hello }} "
运行看看结果
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 [student@workstation ~]$ ansible-navigator run customfact.yml -m stdout PLAY [Create custom facts file] ************************************************ TASK [Gathering Facts] ********************************************************* ok: [servera] ok: [serverb] ok: [serverc] ok: [serverd] TASK [Create facts directory] ************************************************** ok: [serverb] ok: [serverd] ok: [servera] ok: [serverc] TASK [Copy custom facts file from current path] ******************************** changed: [serverb] changed: [serverd] changed: [servera] changed: [serverc] PLAY [Use custom facts example] ************************************************ TASK [Gathering Facts] ********************************************************* ok: [servera] ok: [serverc] ok: [serverd] ok: [serverb] TASK [Gather facts] ************************************************************ ok: [servera] ok: [serverb] ok: [serverd] ok: [serverc] TASK [Display custom fact from file] ******************************************* ok: [servera] => { "msg" : "Custom fact from file is: Hello from custom fact file!" } ok: [serverb] => { "msg" : "Custom fact from file is: Hello from custom fact file!" } ok: [serverc] => { "msg" : "Custom fact from file is: Hello from custom fact file!" } ok: [serverd] => { "msg" : "Custom fact from file is: Hello from custom fact file!" } PLAY RECAP ********************************************************************* servera : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverc : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverd : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
使⽤魔法变量 在 Ansible 中,魔法变量(Magic Variables)是一些预定义的特殊变量,用于提供有关运行环境和主机的信息。这些变量可以在 Playbook 和角色中使用,以实现更灵活和动态的配置。
我们的课程中提到了4个魔法变量
hostvars hostvars 是一个字典,包含所有受管主机的变量和 Facts。可以通过主机名来访问特定主机的变量。
在这个例子中,我们使用 hostvars 获取主机 servera 的默认 IPv4 地址。
1 2 3 - name: Show a variable from another host ansible.builtin.debug: msg: "The IP address of servera is {{ hostvars['servera']['ansible_facts']['default_ipv4']['address'] }} "
group_names group_names 是一个列表,包含当前主机所属的所有组的名称。
在这个例子中,我们显示当前主机所属的所有组。
1 2 3 - name: Show groups this host belongs to ansible.builtin.debug: msg: "This host belongs to groups: {{ group_names }} "
groups groups 是一个字典,包含所有组及其成员主机。可以通过组名来获取该组中的所有主机。
在这个例子中,我们显示 webservers 组中的所有主机。
1 2 3 - name: Show all hosts in the webservers group ansible.builtin.debug: msg: "All hosts in the webservers group: {{ groups['webservers'] }} "
inventory_hostname inventory_hostname 是当前正在执行任务的主机在清单中的名称。
在这个例子中,我们显示当前主机在清单中的名称。
1 2 3 - name: Show the inventory hostname ansible.builtin.debug: msg: "The inventory hostname is {{ inventory_hostname }} "
hostvars
: 访问所有受管主机的变量和 Facts。
group_names
: 当前主机所属的所有组的名称。
groups
: 所有组及其成员主机。
inventory_hostname
: 当前主机在清单中的名称。
第四章 实施任务控制 编写循环和条件任务 loop循环 在 Ansible 中,loop 是一种用来重复执行任务的机制。它允许你在同一个任务中对多个项目进行操作,而无需重复编写任务块。loop 提供了简洁、可读和灵活的方式来处理多个值或项目
多个item可以在loop关键字中用列表来表示,在引用每个loop中的内容时,直接引用{{ item }}
变量即可
安装多个软件,你就不需要写多个task,可以用loop来循环多个item给到单个task
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: Install multiple packages hosts: all become: true tasks: - name: Install packages ansible.builtin.dnf: name: "{{ item }}" state: present loop: - httpd - ftp
运行看看结果先
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 [student@workstation ~]$ ansible-navigator run loop.yml -m stdout PLAY [Install multiple packages] *********************************************** TASK [Gathering Facts] ********************************************************* ok: [serverd] ok: [serverb] ok: [serverc] ok: [servera] TASK [Install packages] ******************************************************** ok: [servera] => (item=httpd) ok: [serverb] => (item=httpd) ok: [serverc] => (item=httpd) changed: [serverb] => (item=ftp) changed: [servera] => (item=ftp) changed: [serverd] => (item=httpd) changed: [serverc] => (item=ftp) changed: [serverd] => (item=ftp) PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverc : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverd : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
loop循环在任何需要表达多个项目时,都可以使用:
创建用户
安装软件
复制文件
等等任何需要多个项目时。。。
when条件判断 在 Ansible 中,when
是一个用于条件判断的关键字,它允许你根据特定条件有选择地执行任务。when 语句
使 Playbook 更加灵活和动态,可以根据运行时环境的不同情况执行不同的操作。
when 关键字
用于在任务、角色或 Playbook 执行时引入条件判断。如果 when 条件表达式
的结果为 true
,则会执行相应的任务;如果为 false
,则跳过该任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- - name: Conditional tasks based on distribution hosts: all tasks: - name: Install Apache on Ubuntu ansible.builtin.apt: name: apache2 state: present when: ansible_facts['distribution'] == "Ubuntu" - name: Install Apache on RedHat ansible.builtin.dnf: name: mysql state: present when: ansible_facts['distribution'] == "RedHat"
运行看看结果
可以看到,不满足条件的任务都跳过了
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 [student@workstation ~]$ ansible-navigator run when.yml -m stdout PLAY [Conditional tasks based on distribution] ********************************* TASK [Gathering Facts] ********************************************************* ok: [serverc] ok: [serverb] ok: [serverd] ok: [servera] TASK [Install Apache on Ubuntu] ************************************************ skipping: [servera] skipping: [serverb] skipping: [serverc] skipping: [serverd] TASK [Install Apache on RedHat] ************************************************ ok: [serverc] ok: [serverd] changed: [servera] changed: [serverb] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 serverb : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 serverc : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 serverd : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
when
条件支持的运算符:
运算符 说明 示例 ==
等于 ansible_facts['distribution'] == "Ubuntu"
!=
不等于 ansible_facts['distribution'] != "CentOS"
<
小于 ansible_facts['memtotal_mb'] < 1024
<=
小于等于 ansible_facts['memtotal_mb'] <= 2048
>
大于 ansible_facts['memtotal_mb'] > 1024
>=
大于等于 ansible_facts['memtotal_mb'] >= 2048
and
逻辑与(所有条件都为真时为真) ansible_facts['distribution'] == "Ubuntu" and ansible_facts['memtotal_mb'] > 1024
or
逻辑或(任一条件为真时为真) ansible_facts['distribution'] == "Ubuntu" or ansible_facts['distribution'] == "CentOS"
not
逻辑非(条件为假时为真) not ansible_facts['distribution'] == "Ubuntu"
in
成员关系(左操作数在右操作数中) "Ubuntu" in ansible_facts['distributions']
not in
非成员关系(左操作数不在右操作数中) "Windows" not in ansible_facts['distributions']
通过使用这些运算符,你可以构建复杂的条件逻辑,以根据不同的条件执行任务。
举个例子来理解一下:
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: Check operating system and perform tasks hosts: all become: true vars: supported_os: - RedHat - CentOS - Fedora tasks: - name: Gather facts ansible.builtin.setup: - name: Check if OS is RedHat based ansible.builtin.debug: msg: "The operating system is RedHat based." when: ansible_facts['distribution'] in supported_os - name: Check if OS is not RedHat based ansible.builtin.debug: msg: "The operating system is not RedHat based." when: ansible_facts['distribution'] not in supported_os
测试多个条件
when 关键字支持逻辑运算符 and、or,可以用于组合多个条件表达式。如果所有条件都需要满足,则使用 and;如果任意一个条件满足即可,则使用 or。
使用 and 运算符 在这个例子中,我们将根据多个条件来决定是否安装 Apache。如果操作系统是 RedHat 且内存大于 500 MB,则安装 Apache:
1 2 3 4 5 6 7 8 9 10 11 --- - name: Install Apache based on multiple conditions hosts: all become: true tasks: - name: Install Apache on RedHat with sufficient memory ansible.builtin.dnf: name: httpd state: present when: ansible_facts['distribution'] == "RedHat" and ansible_facts['memtotal_mb'] > 500
如果条件很多,也可以用列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 --- - name: Perform tasks based on multiple conditions with list and logic hosts: all become: true vars: supported_os: - Ubuntu - Debian - CentOS - RedHat min_memory_mb: 2048 tasks: - name: Check if OS is supported and memory is sufficient ansible.builtin.debug: msg: "This host is supported and has enough memory." when: - ansible_facts['distribution' ] in supported_os - ansible_facts['memtotal_mb' ] >= min_memory_mb
使用 or 运算符 在这个例子中,如果操作系统是 Ubuntu 或者 RedHat,我们将安装 Apache:
1 2 3 4 5 6 7 8 9 10 11 --- - name: Install Apache based on multiple conditions hosts: all become: true tasks: - name: Install Apache on Ubuntu or Debian ansible.builtin.apt: name: apache2 state: present when: ansible_facts['distribution'] == "Ubuntu" or ansible_facts['distribution'] == "RedHat"
组合使用 and 和 or 在这个例子中,我们结合使用 and 和 or 来决定是否执行任务。如果操作系统是 RedHat 且内存大于 500 MB,或者操作系统是 Ubuntu,我们将安装 Apache:
1 2 3 4 5 6 7 8 9 10 11 --- - name: Install Apache based on complex conditions hosts: all become: true tasks: - name: Install Apache on RedHat with sufficient memory or on Ubuntu ansible.builtin.dnf: name: httpd state: present when: (ansible_facts['distribution'] == "RedHat" and ansible_facts['memtotal_mb'] > 1024 ) or ansible_facts['distribution'] == "Ubuntu"
when
只是一个逻辑判断,它可以和任何其他技术和模块使用:
和loop组合实现对每个loop的item检查
和register组合,实现判断register变量的值等
notify和handlers 我们先来看个案例,理解一下notify和handlers
的必要性
在这种情况下,每次运行任务都会重新启动Nginx服务,即使 nginx 已经是最新版本,不需要重新安装。这不仅浪费资源,还可能导致不必要的服务中断。
1 2 3 4 5 6 7 8 9 - name: Install a package ansible.builtin.dnf: name: nginx state: present - name: Restart nginx ansible.builtin.service: name: nginx state: restarted
我们再看一下如果有notify和handlers
会发生什么
在这里,只有当 nginx 实际被安装或更新时,才会触发 restart nginx 这个 handler,重新启动Nginx服务。
1 2 3 4 5 6 7 8 9 10 11 12 - name: Install a package ansible.builtin.dnf: name: nginx state: present notify: - restart nginx handlers: - name: restart nginx ansible.builtin.service: name: nginx state: restarted
总结一下:
简单来说,notify
和 handlers
帮助确保在配置发生变化时,自动执行必要的后续操作,如重启服务或重新加载配置。这样可以使你的自动化流程更加高效和可靠。
总结一下关键点:
处理程序顺序 :处理程序按照 handlers
部分指定的顺序运行,而不是按照 notify
语句或任务通知的顺序。
执行时机 :处理程序通常在相关 play 的所有任务完成后运行。特定任务调用的处理程序,会等到 tasks 部分的所有任务处理完毕后才运行,极少有例外。
命名空间 :处理程序名称存在于每个 play 的命名空间中。同名的处理程序,只会运行一个。
单次运行 :即使多个任务通知处理程序,它也只会运行一次。没有任务通知处理程序时,处理程序不会运行。
任务变化 :只有当任务报告 changed
结果(即状态改变)时,处理程序才会被通知。报告 ok
结果(如软件包已安装)时,不会通知处理程序。
处理任务失败 ignore_errors Ansible 评估各任务的返回代码,从⽽确定任务是成功还是失败。通常⽽⾔,当任务失败 时,Ansible 会⽴即跳过所有后续任务。
这有一个问题,有些任务是成功了更好,不成功也无所谓,这种锦上添花的任务一旦失败,就会取消后续的任务执行,这可不行啊,像这种任务失败,得忽略错误继续执行下面的才行
在这个任务中,Ansible 尝试安装或升级 notapkg 到最新版本。如果任务失败,例如软件包不存在或有其他错误,ignore_errors: yes 将确保任务继续执行后续任务,而不会中断整个 playbook 的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: Test playbook for ignore_errors hosts: servera tasks: - name: Latest version of notapkg is installed ansible.builtin.dnf: name: notapkg state: latest ignore_errors: yes - name: Debug message ansible.builtin.debug: msg: "hello lixiaohui"
我们来运行看看,会不会把第一个任务的错误忽略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [student@workstation ~]$ ansible-navigator run ignore.yml -i inventory -m stdout PLAY [Test playbook for ignore_errors] ***************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Latest version of notapkg is installed] ********************************** fatal: [servera]: FAILED! => {"changed" : false , "failures" : ["No package notapkg available." ], "msg" : "Failed to install some of the specified packages" , "rc" : 1, "results" : []} ...ignoring TASK [Debug message] *********************************************************** ok: [servera] => { "msg" : "hello lixiaohui" } PLAY RECAP ********************************************************************* servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
force_handlers 当 force_handlers: yes
设置在一个play上时,这个play中的所有处理程序一旦被通知到,即使play的某些任务失败,这些处理程序也会在play结束时执行。这意味着即使有任务失败,Ansible仍会运行那些已经被通知的处理程序,确保关键的清理或恢复操作得以执行。
在这个示例中,force_handlers: yes
确保如果 notapkg
任务失败,但 notify
已经通知了 restart nginx
处理程序,那么处理程序仍会在play结束时执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 --- - name: Test playbook for force_handlers hosts: servera force_handlers: yes tasks: - name: Install ftp for test force_handlers ansible.builtin.dnf: name: php state: present notify: - restart psacct - name: Latest version of notapkg is installed ansible.builtin.dnf: name: notapkg state: latest handlers: - name: restart psacct ansible.builtin.service: name: psacct state: restarted
即使设置了 force_handlers: yes
,处理程序 (handlers) 只有在相应任务实际发生变化时(即任务报告 changed
状态)才会被通知和执行。如果任务报告 ok
状态(表示任务运行成功但没有实际变化),即使有 force_handlers: yes
也不会触发处理程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [student@workstation ~]$ ansible-navigator run force_handlers.yml -i inventory -m stdout PLAY [Test playbook for force_handlers] **************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Install ftp for test force_handlers] ************************************* changed: [servera] TASK [Latest version of notapkg is installed] ********************************** fatal: [servera]: FAILED! => {"changed" : false , "failures" : ["No package notapkg available." ], "msg" : "Failed to install some of the specified packages" , "rc" : 1, "results" : []} RUNNING HANDLER [restart psacct] *********************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 Please review the log for errors.
failed_when failed_when 是 Ansible 中用来定义自定义任务失败条件的选项。它允许你根据任务的输出或其他特定条件来决定任务是否失败,而不仅仅依赖于任务的返回码。这对于需要根据复杂逻辑或特定条件来判断任务成功与否的场景非常有用。
使用场景
检查命令输出 :当你运行一个命令并需要根据输出内容来判断任务是否失败时。例如,检查服务状态并根据特定的关键字来判断服务是否正常运行。
复杂逻辑判断 :当任务的失败条件需要依赖于多个变量或复杂的计算结果时。例如,根据多个文件的存在与否来决定任务是否成功。
API 响应处理 :调用外部 API 并根据响应内容来判断任务是否失败。可以解析 API 的响应数据,并基于特定字段的值来决定任务的结果。
在下面这个示例中:
检查Nginx服务状态 :通过 ansible.builtin.command
模块运行 systemctl status nginx
命令,并将结果存储在变量 nginx_status
中。
定义失败条件 :使用 failed_when
选项来定义自定义失败条件。如果 nginx_status.stdout
中包含 inactive
字符串,则任务被认为失败。
调试信息 :如果任务成功(即 nginx
服务处于活动状态),将输出调试信息 “Nginx service is active”。
1 2 3 4 5 6 7 8 9 10 11 --- - name: Test playbook for failed_when hosts: servera tasks: - name: Check if psacct service is active ansible.builtin.command: systemctl status psacct register: psacct_status failed_when: "'active' in psacct_status.stdout" - name: Debug message ansible.builtin.debug: msg: "psacct service is inactive"
根据预期,我们的运行将会失败,下面的失败可以看到我们失败的条件达到了failed_when_result": true
1 2 3 4 5 6 7 8 9 10 11 12 13 [student@workstation ~]$ ansible-navigator run failed_when.yml -i inventory -m stdout PLAY [Test playbook for failed_when] ******************************************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Check if psacct service is active] *************************************** fatal: [servera]: FAILED! => {"changed" : true , "cmd" : ["systemctl" , "status" , "psacct" ], "delta" : "0:00:00.020887" , "end" : "2024-12-26 21:10:47.169839" , "failed_when_result" : true , "msg" : "" , "rc" : 0, "start" : "2024-12-26 21:10:47.148952" , "stderr" : "" , "stderr_lines" : [], "stdout" : "● psacct.service - Kernel process accounting\n Loaded: loaded (/usr/lib/systemd/system/psacct.service; disabled; vendor preset: disabled)\n Active: active (exited) since Thu 2024-12-26 21:06:46 EST; 4min 1s ago\n Process: 28352 ExecStartPre=/usr/libexec/psacct/accton-create (code=exited, status=0/SUCCESS)\n Process: 28353 ExecStart=/usr/sbin/accton /var/account/pacct (code=exited, status=0/SUCCESS)\n Main PID: 28353 (code=exited, status=0/SUCCESS)\n CPU: 9ms\n\nDec 26 21:06:46 servera.lab.example.com systemd[1]: Starting Kernel process accounting...\nDec 26 21:06:46 servera.lab.example.com accton[28353]: Turning on process accounting, file set to '/var/account/pacct'.\nDec 26 21:06:46 servera.lab.example.com systemd[1]: Finished Kernel process accounting." , "stdout_lines" : ["● psacct.service - Kernel process accounting" , " Loaded: loaded (/usr/lib/systemd/system/psacct.service; disabled; vendor preset: disabled)" , " Active: active (exited) since Thu 2024-12-26 21:06:46 EST; 4min 1s ago" , " Process: 28352 ExecStartPre=/usr/libexec/psacct/accton-create (code=exited, status=0/SUCCESS)" , " Process: 28353 ExecStart=/usr/sbin/accton /var/account/pacct (code=exited, status=0/SUCCESS)" , " Main PID: 28353 (code=exited, status=0/SUCCESS)" , " CPU: 9ms" , "" , "Dec 26 21:06:46 servera.lab.example.com systemd[1]: Starting Kernel process accounting..." , "Dec 26 21:06:46 servera.lab.example.com accton[28353]: Turning on process accounting, file set to '/var/account/pacct'." , "Dec 26 21:06:46 servera.lab.example.com systemd[1]: Finished Kernel process accounting." ]} PLAY RECAP ********************************************************************* servera : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 Please review the log for errors.
fail 模块 在Ansible中,fail 模块可以用来故意使任务失败,以便在特定条件下终止playbook的执行或触发错误处理程序。这对于测试、条件检查和确保某些关键步骤不被绕过非常有用。
使用场景
条件检查失败 :确保在某些条件不满足时,强制终止playbook的执行。
测试 :验证错误处理机制是否正确工作,模拟错误场景。
确保关键步骤完成 :在关键任务之前插入检查,确保其前置条件必须满足。
看看我们的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 --- - name: Test playbook for fail hosts: servera tasks: - name: Check if psacct service is active ansible.builtin.command: systemctl status psacct register: psacct_status - name: Fail if psacct service is active ansible.builtin.fail: msg: "psacct service is active!" when: "'active' in psacct_status.stdout" - name: Debug message ansible.builtin.debug: msg: "psacct service is inactive" when: "'active' not in psacct_status.stdout"
检查 psacct
服务状态 :使用 ansible.builtin.command
模块运行 systemctl status psacct
命令,并将结果存储在变量 psacct_status
中。
失败条件 :使用 ansible.builtin.fail
模块,在 psacct
服务处于活动状态时终止playbook的执行,并输出错误信息 “psacct service is active!”。
调试信息 :如果 psacct
服务处于非活动状态,则输出调试信息 “psacct service is inactive”。
我们运行看看结果
发现fail条件达到后,下面的debug任务不再运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [student@workstation ~]$ ansible-navigator run fail.yml -i inventory -m stdout PLAY [Test playbook for fail] ************************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Check if psacct service is active] *************************************** changed: [servera] TASK [Fail if psacct service is active] **************************************** fatal: [servera]: FAILED! => {"changed" : false , "msg" : "psacct service is active!" } PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 Please review the log for errors.
changed_when changed_when
是 Ansible 中用来定义自定义任务变化条件的选项。它允许你根据任务的输出或特定条件来判断任务是否发生变化。这对需要根据复杂逻辑或特定条件来决定任务状态的场景非常有用。使用场景
检查命令输出 :当你运行一个命令并需要根据输出内容来判断任务是否发生变化时。如,检查配置文件内容或服务状态。
复杂逻辑判断 :当任务的变化条件需要依赖于多个变量或复杂的计算结果时。例如,根多个文件的内容或状态来决定任务是否发生变化。
API 响应处理 :调用外部 API 并根据响应内容来判断任务是否发生变化。可以解析API 的响应数据,并基于特定字段的值来决定任务的状态。
我们看个案例先
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: Test playbook for changed_when hosts: servera tasks: - name: Ensure the test file exists ansible.builtin.copy: content: lixiaohui dest: /tmp/testfile.txt - name: Check if file contains lixiaohui string ansible.builtin.shell: cat /tmp/testfile.txt register: file_content changed_when: "'lixiaohui' in file_content.stdout"
看看运行结果,于其上,我们最后的这个check应该会返回change
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [student@workstation ~]$ ansible-navigator run changed_when.yml -i inventory -m stdout PLAY [Test playbook for changed_when] ****************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Ensure the test file exists] ********************************************* changed: [servera] TASK [Check if file contains lixiaohui string] ********************************* changed: [servera] PLAY RECAP ********************************************************************* servera : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
block、rescue 和 always Ansible的 block、rescue 和 always 提供了一种强大的机制来处理任务中可能出现的错误,并确保在特定条件下执行一些清理或补救操作。
block
block
允许你将多个任务分组在一起,以便它们可以作为一个单元进行管理。这可以用于确保一系列任务一起执行,或是为错误处理提供基础。
rescue
rescue
是在 block
中的任务失败时执行的任务。它通常用于执行一些补救操作或记录错误信息。
always
always
是无论 block
中的任务是否失败,都要执行的任务。这通常用于确保某些清理操作始终执行,比如关闭连接或删除临时文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 --- - name: Test playbook for block, rescue, always hosts: servera tasks: - name: Ansible block, rescue, always example block: - name: Task that might fail ansible.builtin.command: /bin/false rescue: - name: Handle the failure ansible.builtin.debug: msg: "Task failed, executing rescue" always: - name: Ensure cleanup ansible.builtin.debug: msg: "This always runs, even if the task fails"
运行看看block失败会怎么样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [student@workstation ~]$ ansible-navigator run block.yml -i inventory -m stdout PLAY [Test playbook for block, rescue, always] ********************************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Task that might fail] **************************************************** fatal: [servera]: FAILED! => {"changed" : true , "cmd" : ["/bin/false" ], "delta" : "0:00:00.004317" , "end" : "2024-12-26 22:42:28.384521" , "msg" : "non-zero return code" , "rc" : 1, "start" : "2024-12-26 22:42:28.380204" , "stderr" : "" , "stderr_lines" : [], "stdout" : "" , "stdout_lines" : []} TASK [Handle the failure] ****************************************************** ok: [servera] => { "msg" : "Task failed, executing rescue" } TASK [Ensure cleanup] ********************************************************** ok: [servera] => { "msg" : "This always runs, even if the task fails" } PLAY RECAP ********************************************************************* servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
第五章 将⽂件部署到受管主机 修改⽂件并将其复制到主机 file file模块要注意state,创建的是文件还是文件夹或链接,都靠state,比如state: absent就是删除文件,state: directory就是创建文件夹,state: touch就是创建文件等
file模块除了能创建出对象外,还可以追加更多的参数修改对象的属性,比如权限,身份,selinux等
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: Example playbook using file module with setype hosts: servera tasks: - name: Ensure /tmp/example_dir exists ansible.builtin.file: path: /tmp/example_dir state: directory owner: root group: root mode: '0755' setype: httpd_sys_content_t
运行看看
1 2 3 4 5 6 7 8 9 10 11 12 [student@workstation ~]$ ansible-navigator run file.yml -i inventory -m stdout PLAY [Example playbook using file module with setype] ************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Ensure /tmp/example_dir exists] ****************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
权限和selinux都ok了
1 2 3 [student@workstation ~]$ ansible servera -m shell -a 'ls -dlZ /tmp/example_dir' -i inventory servera | CHANGED | rc=0 >> drwxr-xr-x. 2 root root unconfined_u:object_r:httpd_sys_content_t:s0 6 Dec 26 22:43 /tmp/example_dir
copy ansible.builtin.copy
模块是 Ansible 中用来将文件或目录从本地主机复制到远程主机的。它在配置文件分发、模板部署、脚本分发等场景中非常有用。
使用场景
在这个案例中,我们把主机名文件复制到servera的tmp
1 2 3 4 5 6 7 8 9 10 11 --- - name: Example playbook using copy module hosts: servera tasks: - name: Copy a file to the remote server ansible.builtin.copy: src: /etc/hostname dest: /tmp/ owner: root group: root mode: '0644'
运行看看结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [student@workstation ~]$ ansible-navigator run ignore.yml -i inventory -m stdout PLAY [Example playbook using copy module] ************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Copy a file to the remote server] **************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'ls -dlZ /tmp/hostname' -i inventory servera | CHANGED | rc=0 >> -rw-r--r--. 1 root root unconfined_u:object_r:admin_home_t:s0 12 Dec 26 23:58 /tmp/hostname
fetch ansible.builtin.fetch
模块用于从远程主机复制文件到控制节点。它在需要从远程系统收集日志文件、备份配置或获取生成的文件时非常有用。
使用场景
日志收集 :从多个远程服务器收集日志文件进行分析。
配置备份 :将远程主机上的配置文件备份到控制节点。
生成文件获取 :从远程主机获取生成的报告或输出文件。
我们可以使用 ansible.builtin.fetch
模块将 servera
主机上的 /etc/hostname
文件复制到控制节点的 /home/student/
目录
1 2 3 4 5 6 7 8 --- - name: Fetch /etc/hostname from servera hosts: servera tasks: - name: Fetch /etc/hostname from servera ansible.builtin.fetch: src: /etc/hostname dest: /home/student/
我们发现,获取过来的文件在本地会建立一个主机名的文件夹,然后从根目录开始表达文件,避免了重名问题,也可以方便你知道文件来自哪个服务器的哪里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [student@workstation ~]$ ansible-navigator run fetch.yml -i inventory -m stdout PLAY [Fetch /etc/hostname from servera] **************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Fetch /etc/hostname from servera] **************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ tree servera/ servera/ └── etc └── hostname 1 directory, 1 file
lineinfile ansible.builtin.lineinfile
模块用于在文件中添加、修改或删除特定行。它在配置文件管理、脚本修改或文本处理方面非常有用。
使用场景
配置文件管理 :在配置文件中添加或修改特定的配置项。
脚本修改 :在脚本文件中添加或修改特定的代码行。
文本处理 :在文本文件中添加或删除特定的行。
假设我们需要在 /etc/hosts
文件中添加一条新的主机名映射。如果该映射已经存在,则更新它;如果不存在,则添加它
1 2 3 4 5 6 7 8 9 10 --- - name: Example playbook using lineinfile module hosts: servera tasks: - name: Add or update entry in /etc/hosts ansible.builtin.lineinfile: path: /etc/hosts state: present regexp: '^192\.168\.1\.100' line: '192.168.1.100 lixiaohui.lab.example.com'
运行看看是否添加了这一条
非常好,我们已经成功添加了这一条
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [student@workstation ~]$ ansible-navigator run lineinfile.yml -i inventory -m stdout PLAY [Example playbook using lineinfile module] ******************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Add or update entry in /etc/hosts] *************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'cat /etc/hosts' -i inventory servera | CHANGED | rc=0 >> 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.1.100 lixiaohui.lab.example.com
blockinfile ansible.builtin.blockinfile
模块用于在文件中插入、更新或删除特定块的内容。它在需要添加多行配置、脚本代码或文本块时非常有用。
使用场景
配置文件管理 :在配置文件中插入或更新多个配置项。
脚本修改 :在脚本文件中添加或更新多行代码。
文本处理 :在文本文件中插入或删除多个行的文本块。
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: Example playbook using blockinfile module hosts: servera tasks: - name: Add text block to /tmp/blocktext ansible.builtin.blockinfile: path: /tmp/blocktext create: true block: | This is an example line 1. This is an example line 2. This is an example line 3. state: present
运行后,看看文件是否主动完成了创建并写入
我们发现blockinfile模块写入的内容会有注释包围起来,提醒我们哪里是ansible管理的,如果不想要注释,可以marker: ""
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [student@workstation ~]$ ansible-navigator run blockinfile.yml -i inventory -m stdout PLAY [Example playbook using blockinfile module] ******************************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Add text block to /tmp/blocktext] **************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'cat /tmp/blocktext' -i inventory servera | CHANGED | rc=0 >> This is an example line 1. This is an example line 2. This is an example line 3.
stat ansible.builtin.stat
模块是 Ansible 用于获取文件或目录的状态的模块。它可以帮助你检查文件是否存在、文件的权限、所有者、大小以及其他属性。stat
模块返回一个详细的状态信息字典,这对于条件判断、文件操作以及调试非常有用。
使用场景
检查文件是否存在 :在执行特定操作之前,确认文件或目录是否存在。
获取文件属性 :检查文件的权限、所有者、大小、修改时间等属性。
调试和条件判断 :根据文件的状态信息执行不同的任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- - name: Example playbook using stat module hosts: servera tasks: - name: Check if /etc/hostname exists ansible.builtin.stat: path: /etc/hostname checksum_algorithm: md5 register: hostname_stat - name: Debug file existence ansible.builtin.debug: msg: "The file /etc/hostname exists." when: hostname_stat.stat.exists - name: Display MD5 checksum of /etc/hostname ansible.builtin.debug: msg: "MD5 checksum of /etc/hostname: {{ hostname_stat.stat.checksum }} " when: hostname_stat.stat.exists
运行后,果然报告了文件存在以及md5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [student@workstation ~]$ ansible-navigator run stat.yml -i inventory -m stdout PLAY [Example playbook using stat module] ************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Check if /etc/hostname exists] ******************************************* ok: [servera] TASK [Debug file existence] **************************************************** ok: [servera] => { "msg" : "The file /etc/hostname exists." } TASK [Display MD5 checksum of /etc/hostname] *********************************** ok: [servera] => { "msg" : "MD5 checksum of /etc/hostname: 06d69ef7dd6544b371c0339640ce6e78" } PLAY RECAP ********************************************************************* servera : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
synchronize synchronize
模块是 Ansible 用于快速、可靠地在本地或远程主机之间同步文件和目录的模块。它基于 rsync
命令,可以高效地执行数据复制和同步任务,特别适合大文件或大量文件的同步场景。
使用场景
文件备份 :将本地文件或目录备份到远程主机,或将远程文件备份到本地主机。
目录同步 :在多个主机之间同步目录内容,确保数据一致性。
定期复制 :定期复制文件或目录,如日志文件、配置文件等。
假设我们需要将本地主机 /var/log/
目录中的日志文件同步到远程主机 servera
的 /mnt
目录。
1 2 3 4 5 6 7 8 --- - name: Example playbook using synchronize module hosts: servera tasks: - name: Synchronize logs to remote server ansible.builtin.synchronize: src: /var/log/ dest: /mnt/
运行后,日志过去了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [student@workstation ~]$ ansible-navigator run synchronize.yml -i inventory -m stdout PLAY [Example playbook using synchronize module] ******************************* TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Synchronize logs to remote server] *************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'ls /mnt' -i inventory servera | CHANGED | rc=0 >> btmp lastlog private receptor wtmp
Jinja2 模板 Jinja2 模板基础 虽然说用lineinfile或者blockinfile可以修改配置文件,但是我们在本地并不拥有文件的的完整最新副本,而且有时候需要将配置文件部署到很多机器上,而每个机器上的监听地址需要使用他们各自独特的地址,这就效率很低了,此时就可以用模板,所有涉及到每个机器的ip时,都用变量表示,在把模板部署到远程的时候,自动把变量用对应的值填充即可
基本语法
变量 :使用 {{ variable }}
来输出变量的值。
表达式 :使用 {% expression %}
来执行控制语句或逻辑。
注释 :使用 `` 来添加注释,这种注释不会最终出现在远程的文件中。
数据 :我们的业务数据,需要原封不动拷过去的内容。
后缀名 :虽然没有强烈要求,但是jinja2模板一般采用.j2
后缀名。
根据语法,我们来写一个模板:
以下的空行也会作为数据在最终文件呈现
1 2 3 4 5 # 这么写注释,这一行会出现在最终文件中 {# 这么写注释,只在模板中出现,最后不会放到远程 #} {{ ansible_facts.default_ipv4.address }}
写好后,用下面的方法部署到远程
1 2 3 4 5 6 7 8 9 --- - name: Deploy Jinja2 template to servera hosts: servera tasks: - name: Deploy template.j2 to /mnt/template.txt ansible.builtin.template: src: template.j2 dest: /mnt/template.txt mode: '0644'
运行后看看文件是否按照我们预期写入了内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [student@workstation ~]$ ansible-navigator run jinja.yml -i inventory -m stdout PLAY [Deploy Jinja2 template to servera] *************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Deploy template.j2 to /mnt/template.txt] ********************************* changed: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'cat /mnt/template.txt' -i inventory servera | CHANGED | rc=0 >> 172.25.250.10
最后发现,我们不会出现在文件中的注释没出现,空的两行也作为数据出现了,变量成功处理
Jinja2 循环 在模板里可以用for循环来加快效率,减少书写的内容
我们的需求是生成一个/etc/hosts文件,并放到servera上,hosts文件中,要包含servera..d的内容
我们在这里用了groups魔法变量来列出所有主机,让host这个变量分别等于每一个主机 用hostvars魔法变量取出host每个主机的特定值 1 2 3 {% for host in groups['all'] %} {{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }} {{ hostvars[host]['ansible_facts']['fqdn'] }} {{ hostvars[host]['ansible_facts']['hostname'] }} {% endfor %}
写好后,用下面的方法部署到远程
需要注意的是,哪怕只生成在一台主机上,你的hosts也必须携程hosts: all
,原因是在模板中,你用到了每个主机的ansible_facts,只有你的play才收集facts
1 2 3 4 5 6 7 8 9 10 --- - name: Deploy Jinja2 template to servera hosts: all tasks: - name: Deploy template.j2 to /mnt/template.txt ansible.builtin.template: src: template.j2 dest: /mnt/template.txt mode: '0644' when: "'servera' in inventory_hostname"
运行后,检查一下效果是否和hosts格式相同
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 [student@workstation ~]$ ansible-navigator run templatefor.yml -i inventory -m stdout PLAY [Deploy Jinja2 template to servera] *************************************** TASK [Gathering Facts] ********************************************************* ok: [serverc.lab.example.com] ok: [serverb.lab.example.com] ok: [172.25.250.13] ok: [servera] TASK [Deploy template.j2 to /mnt/template.txt] ********************************* skipping: [serverb.lab.example.com] skipping: [serverc.lab.example.com] skipping: [172.25.250.13] changed: [servera] PLAY RECAP ********************************************************************* 172.25.250.13 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb.lab.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 serverc.lab.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'cat /mnt/template.txt' -i inventory servera | CHANGED | rc=0 >> 172.25.250.10 servera.lab.example.com servera 172.25.250.11 serverb.lab.example.com serverb 172.25.250.12 serverc.lab.example.com serverc 172.25.250.13 serverd.lab.example.com serverd
Jinja2 判断 Jinja2 使⽤ if 语句来提供条件控制。如果满⾜某些条件,这允许你在已部署的⽂件中放置⼀⾏。
如果1小于2,写入feature_flag = true,不然等于false
1 2 3 4 5 {% if 1 < 2 %} feature_flag = true {% else %} feature_flag = false {% endif %}
写好后,用下面的方法部署到远程
1 2 3 4 5 6 7 8 9 10 --- - name: Deploy Jinja2 template to servera hosts: all tasks: - name: Deploy template.j2 to /mnt/template.txt ansible.builtin.template: src: template.j2 dest: /mnt/template.txt mode: '0644' when: "'servera' in inventory_hostname"
运行后,检查一下效果,1的确小于2,结果因为是true才对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [student@workstation ~]$ ansible-navigator run templateif.yml -i inventory -m stdout PLAY [Deploy Jinja2 template to servera] *************************************** TASK [Gathering Facts] ********************************************************* ok: [172.25.250.13] ok: [serverc.lab.example.com] ok: [servera] ok: [serverb.lab.example.com] TASK [Deploy template.j2 to /mnt/template.txt] ********************************* skipping: [serverb.lab.example.com] skipping: [serverc.lab.example.com] skipping: [172.25.250.13] changed: [servera] PLAY RECAP ********************************************************************* 172.25.250.13 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 servera : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb.lab.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 serverc.lab.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'cat /mnt/template.txt' -i inventory servera | CHANGED | rc=0 >> feature_flag = true
第六章 管理复杂的 Play 和 Playbook 利⽤主机模式选择主机 这里主要是聊在playbook里的play部分,hosts:到底怎么写,在开始之前,我们先做一个清单
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 web.example.com data.example.com [lab] labhost1.example.com labhost2.example.com [test] test1.example.com test2.example.com [datacenter1] labhost1.example.com test1.example.com [datacenter2] labhost2.example.com test2.example.com [datacenter:children] datacenter1 datacenter2 [new] 192.168.2.1 192.168.2.2
以下是可能的hosts:
字段
所有主机: hosts: all或者hosts: “*”
特定主机名: hosts: web.example.com
特定组:hosts: lab
多个主机组:hosts: lab,test
IP地址:hosts: 192.168.2.1
主机组中排除特定主机: hosts: lab:!labhost1.example.com
列表:hosts: 192.168.2.1,lab,web.example.com
同时存在与lab和datacenter1组:hosts: lab,&datacenter1
inclue和import ⽂件 如果 playbook 很长或很复杂,可以将其拆分成较小的文件以便于管理。你可以将多个 playbook 组合到一个主 playbook 中,或者将文件中的任务列表插入到 play 中。这样可以更轻松地在不同项目中重用 play 或任务序列。
Ansible 支持通过包含(include)和导入(import)两种操作将内容放入 playbook。
包含内容(动态操作)
include 操作是动态的。这意味着在 playbook 运行期间,Ansible 会在执行到 include 指令时处理所包含的内容。使用 include 的好处是,它允许你根据运行时的条件决定是否包含某些任务。
导入内容(静态操作)
import 操作是静态的。在 playbook 开始运行之前,Ansible 会在解析 playbook 时预处理导入的内容。这意味着所有导入的内容在运行前已经确定,并不会在运行时受到条件的影响。
include_tasks 假设我们有一个setup_tasks.yml
内容如下:
1 2 3 4 5 --- - name: Setup task - Install package ansible.builtin.package: name: httpd state: present
那么我们的主playbook就这么写
1 2 3 4 5 6 --- - name: Main playbook with include hosts: servera tasks: - name: Include setup tasks include_tasks: setup_tasks.yml
运行后,将显示我们被包括的文件也运行起来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [student@workstation ~]$ ansible-navigator run main.yml -i inventory -m stdout PLAY [Main playbook with include] ********************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Include setup tasks] ***************************************************** included: /home/student/setup_tasks.yml for servera TASK [Setup task - Install package] ******************************************** ok: [servera] PLAY RECAP ********************************************************************* servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
import_tasks 还用刚才的setup_tasks.yml
做为被导入的对象
我们的主playbook应该长这样:
1 2 3 4 5 6 --- - name: Main playbook with import hosts: servera tasks: - name: import setup tasks import_tasks: setup_tasks.yml
运行后,被导入的任务依旧执行成功
1 2 3 4 5 6 7 8 9 10 11 12 [student@workstation ~]$ ansible-navigator run main.yml -i inventory -m stdout PLAY [Main playbook with import] *********************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Setup task - Install package] ******************************************** ok: [servera] PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
import_playbook 我们有一个子playbook长这样 setup_playbook.yml
1 2 3 4 5 6 7 8 --- - name: Setup playbook hosts: servera tasks: - name: Install httpd package ansible.builtin.package: name: httpd state: present
还有一个子playbook长这样 deploy_playbook.yml
1 2 3 4 5 6 7 8 --- - name: restart service playbook hosts: serverb tasks: - name: Restart psacct service ansible.builtin.service: name: psacct state: restarted
我们的主playbook长这样,把这两个playbook导入进来执行
1 2 3 4 5 --- - name: Import setup playbook import_playbook: setup_playbook.yml - name: Import deploy playbook import_playbook: deploy_playbook.yml
运行后,看看是否在两个机器上都运行了预期的任务
完美,两个playbook都被导入了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [student@workstation ~]$ ansible-navigator run main.yml -i inventory -m stdout PLAY [Setup playbook] ********************************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Install httpd package] *************************************************** ok: [servera] PLAY [restart service playbook] ************************************************ TASK [Gathering Facts] ********************************************************* ok: [serverb] TASK [Restart psacct service] ************************************************** changed: [serverb] PLAY RECAP ********************************************************************* servera : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 serverb : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
第七章 使⽤⻆⾊和集合简化playbook 什么是角色? 想象一下你在组织一个烧烤派对。为了确保一切顺利进行,你会定义几个角色,比如厨师、服务员和娱乐人员。每个角色都可以根据不同的变量(如人数、菜单、时间安排等)来调整他们的任务。
举个例子:烧烤派对
厨师角色:
如果你有一个素食菜单和10位客人,那么厨师的任务可能是:
如果你有一个普通菜单和20位客人,那么厨师的任务可能是:
服务员角色:
任务 :布置场地和服务客人
变量 :
table_count
(桌子数量)
drink_options
(饮品选择)
以上内容,对于每场派对来说,厨师和服务员的工作是不变的,他们只需要根据客人点的菜单、客人数量、喜好稍作调整,整个流程又可以走起来了
这对于我们的IT工作也是一样的,我们有大量的工作是重复的,例如软件安装、重启服务、开通防火墙等,每次只是软件的名称、服务名称、端口的不同,所以我们直接写好重复工作的playbook,那些稍有不同之处,我们用变量表示,我们每次只需要给变量赋予不同的值,即可完成相同的工作。
Ansible 角色本身由众多文件夹构成,不过也不见得每个角色都具有这些所有的文件夹,如果角色是你自己创建的,没用到的建议删除
Ansible 角色子目录
子目录 功能 defaults
此目录中的 main.yml
文件包含角色变量的默认值,使用角色时可以覆盖这些默认值。这些变量的优先级较低,应该在 play 中更改和自定义。 files
此目录包含由角色任务引用的静态文件。 handlers
此目录中的 main.yml
文件包含角色的处理程序定义。 meta
此目录中的 main.yml
文件包含与角色相关的信息,如作者、许可证、平台和可选的角色依赖项。 tasks
此目录中的 main.yml
文件包含角色的任务定义。 templates
此目录包含由角色任务引用的 Jinja2 模板。 tests
此目录可以包含清单和 test.yml
playbook,可用于测试角色。 vars
此目录中的 main.yml
文件定义角色的变量值。这些变量通常用于角色内部用途。这些变量的优先级较高,在 playbook 中使用时不应更改。
创建角色 创建一个lixiaohui
角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [student@workstation ~]$ mkdir roles [student@workstation ~]$ cd roles/ [student@workstation roles]$ ansible-galaxy init lixiaohui - Role lixiaohui was created successfully [student@workstation roles]$ tree lixiaohui/ lixiaohui/ ├── 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
定义角色功能 要注意,为了灵活,这里的具体值用变量表示,以后人家用我们角色的时候,只需要提供这些变量的值,即可完成这些工作,简化人家的工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [student@workstation roles ]$ vim lixiaohui/tasks/main.yml --- - name: Install httpd package ansible.builtin.package: name: "{{ httpd_package }} " state: present - name: Start and enable httpd service ansible.builtin.service: name: "{{ httpd_service }} " state: started enabled: true - name: Open port 80 on the firewall ansible.builtin.firewalld: port: "{{ http_port }} /tcp" permanent: true state: enabled immediate: yes
我们给这些变量提供一个较低优先级的默认值
1 2 3 4 5 [student@workstation roles ]$ vim lixiaohui/defaults/main.yml --- httpd_package: "httpd" httpd_service: "httpd" http_port: 80
使用角色 我们来测试一下角色工作是否正常
需要修改ansible.cfg
中的角色路径,这样才能找到我们的角色并引用
冒号隔开,我们在第一个位置加上我们角色所在的路径
1 2 3 vim ansible.cfg [defaults] roles_path=/home/student/roles:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
列出角色看看,确认一下已经可以识别到我们的角色
1 2 3 [student@workstation ~]$ ansible-galaxy list - lixiaohui, (unknown version)
写一个playbook,测试一下功能是否正常
1 2 3 4 5 --- - name: Test lixiaohui role hosts: servera roles: - lixiaohui
运行后,发现工作正常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [student@workstation ~]$ ansible-navigator run roletest.yml -i inventory -m stdout PLAY [Test lixiaohui role] ***************************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [lixiaohui : Install httpd package] *************************************** ok: [servera] TASK [lixiaohui : Start and enable httpd service] ****************************** changed: [servera] TASK [lixiaohui : Open port 80 on the firewall] ******************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
看来变量的默认值已经工作正常,我们来模拟一下别人简化工作的写法,人家想安装一个nginx,并开通防火墙,由于我们的角色提供的是httpd,所以他和我们默认值需求不同时,需要自己提供变量,他提供的变量优先级比defaults高
1 2 3 4 5 6 7 --- - name: Test lixiaohui role hosts: servera vars: - httpd_package: nginx roles: - lixiaohui
运行后,发现他的变量比我们的高,成功用我的代码安装了他想要的功能,他只提供了变量,所有功能都来自我的角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [student@workstation ~]$ ansible-navigator run roletest.yml -i inventory -m stdout PLAY [Test lixiaohui role] ***************************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [lixiaohui : Install httpd package] *************************************** changed: [servera] TASK [lixiaohui : Start and enable httpd service] ****************************** ok: [servera] TASK [lixiaohui : Open port 80 on the firewall] ******************************** ok: [servera] PLAY RECAP ********************************************************************* servera : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [student@workstation ~]$ ansible servera -m shell -a 'rpm -q nginx' -i inventory servera | CHANGED | rc=0 >> nginx-1.20.1-10.el9.x86_64
使用角色的注意事项
1 2 3 4 5 6 7 8 9 --- - name: Test lixiaohui role hosts: servera vars: - httpd_package: nginx roles: - lixiaohui tasks: - name: xxxx
上面这种引用方法,角色中的任务优先级将始终高于tasks下面的内容,如果要在角色之前运行某些东西,得这么做:
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 --- - name: Playbook with pre_tasks and post_tasks hosts: all pre_tasks: - name: Ensure the system is updated ansible.builtin.dnf: name: '*' state: latest - name: Install necessary utilities ansible.builtin.package: name: vim state: present roles: - lixiaohui tasks: - name: Regular task example ansible.builtin.command: cmd: echo "Main tasks running" post_tasks: - name: Clean up temporary files ansible.builtin.file: path: /tmp/tempfile state: absent - name: Notify the admin about completion ansible.builtin.mail: to: 'admin@example.com' subject: 'Playbook execution complete' body: 'The playbook execution has finished successfully.'
所以说,上面这种角色的引用方法非常简单,但是优先级上不好处理,不过在考试中,没有那么多弯弯绕,直接用上面那种更简单,实际工作可能比较复杂,你可以用下面的ansible.builtin.import_role
这种方法消除角色优先级较高的问题,下面的方法就是执行到他的时候,他才运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- - name: Playbook with import_role hosts: servera tasks: - name: Ensure the system is updated ansible.builtin.dnf: name: '*' state: latest - name: Import and execute the lixiaohui role ansible.builtin.import_role: name: lixiaohui - name: Perform additional setup ansible.builtin.command: cmd: echo "Additional setup tasks"
运行看看角色任务是否变成中间运行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [student@workstation ~]$ ansible-navigator run role.yml -i inventory -m stdout PLAY [Playbook with import_role] *********************************************** TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [Ensure the system is updated] ******************************************** ok: [servera] TASK [lixiaohui : Install httpd package] *************************************** ok: [servera] TASK [lixiaohui : Start and enable httpd service] ****************************** ok: [servera] TASK [lixiaohui : Open port 80 on the firewall] ******************************** ok: [servera] TASK [Perform additional setup] ************************************************ changed: [servera] PLAY RECAP ********************************************************************* servera : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
安装外部角色 安装外部Ansible角色可以简化和自动化配置管理流程。通过使用Ansible Galaxy
等平台,用户可以轻松搜索、下载并安装他人分享的Ansible角色
,从而节省开发时间并提高效率。
常用的外部角色托管位置:
https://console.redhat.com
自己搭建的私有Hub
https://galaxy.ansible.com
https://github.com
让我们来一起打开浏览器,看看galaxy吧
使⽤要求⽂件安装⻆⾊
可以在项⽬⽬录中创建⼀个 roles/requirements.yml
⽂件来指定所需的⻆⾊。此⽂件充当 playbook 项⽬的依赖项清单,在ansible-navigator run运行的时候,会自动将角色安装到roles目录中,不过最好手工下载,确保一切正常,提高运行速度
1 2 3 4 5 6 7 8 9 10 [student@workstation ~]$ mkdir roles [student@workstation ~]$ vim roles/requirements.yml --- - src: http://content.lixiaohui.com/role1.tar name: role1_name - src: file:///role2.tar name: role2_name - src: http://content.lixiaohui.com/role1.tar scm: git version: "1.0"
以上requirements.yml中,示意了http、file、git等不同形式的安装,我们从galaxy下载一个角色,实际安装看看
安装包下载后,上传到workstation的根目录
1 https://github.com/geerlingguy/ansible-role-apache/archive/4.0.0.tar.gz
上传后名字为ansible-role-apache-4.0.0.tar.gz
,写一个requirements.yml
1 2 3 4 [student@workstation ~]$ vim roles/requirements.yml --- - src: file:///ansible-role-apache-4.0.0.tar.gz name: apacherole
安装一个看看
安装的时候,如果不指定 -p roles 选项,ansible-galaxy 将使⽤默认 roles_path 设置中的第⼀个⽬录来确定⻆⾊的安装位置
1 2 3 4 5 [student@workstation ~]$ ansible-galaxy role install -r roles/requirements.yml Starting galaxy role install process - downloading role from file:///ansible-role-apache-4.0.0.tar.gz - extracting apacherole to /home/student/roles/apacherole - apacherole was installed successfully
查看一下本地角色中,是否包含此角色,需要注意ansible.cfg
中的roles_path
1 2 3 4 5 [student@workstation ~]$ grep ^roles_path ansible.cfg roles_path=/home/student/roles:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles [student@workstation ~]$ ansible-galaxy list - apacherole, (unknown version)
从内容集合获取⻆⾊和模块 从Ansible内容集合获取角色和模块可以显著扩展Ansible的功能,简化复杂任务的自动化过程。Ansible集合(Collections)是一个打包和分发Ansible角色、模块、插件和其它内容的机制。
借助 Ansible 内容集合,Ansible 代码可以与模块和插件分开更新,Ansible软件包不再自带模块,模块和Ansible不再互相制约,额可以有自己的发布和维护节奏。
集合命名空间 命名空间可以避免不同开发者创建的模块和角色之间的命名冲突。每个开发者或组织可以使用唯一的命名空间,从而确保其集合中的内容与其他集合中的内容不冲突,比如我想让模块叫copy这个名称,你也想,这就冲突了,但是加上不同的命名空间前缀就不会冲突,比如我的模块全称为lixiaohui.copy,你的叫zhangsan.copy,就非常合适。
Ansible中,有一个名为ansible.builtin
的命名空间始终存在,其内包括仅有的几个核心模块,此命名空间下的模块使用时,可以不带前缀,例如在playbook中可以使用ansible.builtin.copy
,也可以用copy
安装 Ansible 内容集合 必须确保该集合在⾃动化执⾏环境中可⽤,然后 playbook 才可以使⽤ Ansible 内容集合中的内容
和角色一样,我们也必须在ansible.cfg
中配置集合路径,需要注意的是,我们需要保留下面这两个路径,不然自动化执行环境中的集合将无法工作
1 collections_path =~/.ansible/collections:/usr/share/ansible/collections
我们可以以冒号隔开的方式,将我们的路径插入到前面
1 collections_path =/home/student/mycollection:~/.ansible/collections:/usr/share/ansible/collections
我们来试试从galaxy中,下载一个集合,将其上传到workstation的根目录,然后再将其安装到我们的系统中
从这里下载它的9.0.0版本
1 https://galaxy.ansible.com/ui/repo/published/community/general/
上传后名为community-general-9.0.0.tar.gz
准备一下我们的集合目录并安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [student@workstation ~]$ mkdir /home/student/mycollection [student@workstation ~]$ ansible-galaxy collection install /community-general-9.0.0.tar.gz Starting galaxy collection install process Process install dependency map Starting collection install process Installing 'community.general:9.0.0' to '/home/student/mycollection/ansible_collections/community/general' community.general:9.0.0 was installed successfully [student@workstation ~]$ ansible-galaxy collection list Collection Version ----------------- ------- community.general 9.0.0 Collection Version ------------------------ ------- redhat.rhel_system_roles 1.16.2
集合的安装也可以用requirements文件
1 2 3 4 5 6 7 --- collections: - name: community.general - name: community.crypto version: 1.2 .0 - name: /community-general-9.0.0.tar.gz - name: http://content.lixiaohui.com/community-general-9.0.0.tar.gz
安装刚才我们的意思,写一个实例
1 [student@workstation ~]$ vim mycollection/requirements.yml
1 2 3 --- collections: - name: /community-general-9.0.0.tar.gz
1 2 3 4 5 6 7 [student@workstation ~]$ rm -rf mycollection/ansible_collections/community/general/ [student@workstation ~]$ ansible-galaxy collection install -r mycollection/requirements.yml Starting galaxy collection install process Process install dependency map Starting collection install process Installing 'community.general:9.0.0' to '/home/student/mycollection/ansible_collections/community/general' community.general:9.0.0 was installed successfully
使用集合来工作 刚才我们安装的集合有lvm相关的模块,我们来试试用vdb创建一个vg,然后创建lv,并格式化挂载
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 --- - name: 基于/dev/vdb创建VG和LV,并格式化挂载 hosts: servera become: yes tasks: - name: 创建卷组vg_data community.general.lvg: vg: vg_data pvs: /dev/vdb - name: 创建逻辑卷lv_data community.general.lvol: lv: lv_data vg: vg_data size: 100 %FREE - name: 格式化逻辑卷lv_data为ext4 community.general.filesystem: fstype: ext4 dev: /dev/vg_data/lv_data - name: 创建挂载点目录 file: path: /mnt/data state: directory - name: 挂载逻辑卷lv_data到/mnt/data ansible.posix.mount: path: /mnt/data src: /dev/vg_data/lv_data fstype: ext4 state: mounted
运行看看
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 [student@workstation ~]$ echo servera > inventory [student@workstation ~]$ ansible-navigator run lv.yml -m stdout -i inventory PLAY [基于/dev/vdb创建VG和LV,并格式化挂载] ************************************ TASK [Gathering Facts] ********************************************************* ok: [servera] TASK [创建卷组vg_data] ********************************************************* changed: [servera] TASK [创建逻辑卷lv_data] ******************************************************* changed: [servera] TASK [格式化逻辑卷lv_data为ext4] *********************************************** changed: [servera] TASK [创建挂载点目录] ********************************************************** changed: [servera] TASK [挂载逻辑卷lv_data到/mnt/data] ******************************************** changed: [servera] PLAY RECAP ********************************************************************* servera : ok=6 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
看看是否真的创建好了lv,挂好了没有
1 2 3 4 5 6 [student@workstation ~]$ ssh root@servera [root@servera ~]# tail -n 1 /etc/fstab /dev/vg_data/lv_data /mnt/data ext4 defaults 0 0 [root@servera ~]# lvs LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert lv_data vg_data -wi-ao---- 1020.00m
通过系统⻆⾊重⽤内容 系统角色是定义和描述操作系统或应用程序中特定功能和行为的一组配置和管理任务。它们主要用于自动化和简化复杂系统管理任务,使系统管理员能够更高效地管理和维护系统。
系统角色是从光盘里安装软件包带来的,软件包名字为rhel-system-roles
安装一下角色包,并查询系统角色和集合
1 2 3 4 5 6 7 8 9 [student@workstation ~]$ ansible-galaxy collection list Collection Version ----------------- ------- community.general 9.0.0 [student@workstation ~]$ sudo dnf install rhel-system-roles -y ... Complete!
软件包安装后,将释放文件到:
/usr/share/ansible/collections/ansible_collections/redhat/rhel_system_roles
/usr/share/ansible/roles # 这是为了向后兼容而已,优先了解上一个
看到安装后,多了一个集合
1 2 3 4 5 6 7 8 9 10 11 [student@workstation ~]$ ansible-galaxy collection list Collection Version ------------------------ ------- redhat.rhel_system_roles 1.16.2 Collection Version ----------------- ------- community.general 9.0.0
再直接看看角色
1 2 3 4 5 6 7 8 9 [student@workstation ~]$ ansible-galaxy list - apacherole, (unknown version) - linux-system-roles.certificate, (unknown version) - linux-system-roles.cockpit, (unknown version) - linux-system-roles.crypto_policies, (unknown version) - linux-system-roles.firewall, (unknown version) ...
软件包也带来了一些playbook样例,我们改改就能用
1 2 3 4 5 6 7 8 9 10 [student@workstation ~]$ rpm -ql rhel-system-roles | grep -i example /usr/share/ansible/collections/ansible_collections/redhat/rhel_system_roles/docs/sshd/example-accept-env.yml /usr/share/ansible/collections/ansible_collections/redhat/rhel_system_roles/docs/sshd/example-root-login.yml /usr/share/ansible/collections/ansible_collections/redhat/rhel_system_roles/docs/vpn/example_cert.yml /usr/share/ansible/collections/ansible_collections/redhat/rhel_system_roles/docs/vpn/example_psk.yml /usr/share/ansible/collections/ansible_collections/redhat/rhel_system_roles/tests/tlog/tests_example.yml /usr/share/ansible/roles/rhel-system-roles.tlog/tests/tests_example.yml /usr/share/doc/rhel-system-roles/certificate/example-basic_self_signed-playbook.yml /usr/share/doc/rhel-system-roles/certificate/example-dns_ip_and_email-playbook.yml ...
系统角色之时间同步 用系统角色来完成不同系统的时间同步
先查一下帮助,看看有没有playbook可用
1 2 3 [student@workstation ~]$ rpm -ql rhel-system-roles | grep -i example | grep -i timesync /usr/share/doc/rhel-system-roles/timesync/example-multiple-ntp-servers-playbook.yml /usr/share/doc/rhel-system-roles/timesync/example-single-pool-playbook.yml
制作自己的yaml文件
1 2 [student@workstation ~]$ cp /usr/share/doc/rhel-system-roles/timesync/example-single-pool-playbook.yml timesync.yml [student@workstation ~]$ vim timesync.yml
从classroom上同步时间
1 2 3 4 5 6 7 8 - hosts: servera vars: timesync_ntp_servers: - hostname: classroom.example.com pool: yes iburst: yes roles: - rhel-system-roles.timesync
运行看看
运行之前,需要将角色复制到我们的角色路径中
1 2 3 [student@workstation ~]$ cp -r /usr/share/ansible/roles/rhel-system-roles.timesync/ roles/ [student@workstation ~]$ ansible-navigator run timesync.yml -m stdout -i inventory ...
第八章 对 Ansible 进⾏故障排除 debug 调试 有时候运行异常需要知道详细情况,而ansible只会返回changed之类的简略内容, 就需要注册变量,然后debug,也在playbook的运行中,可以加更多个-v来提升更多的详细程度
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: 演示register和debug hosts: localhost gather_facts: no tasks: - name: 检查主机可达性 ansible.builtin.ping: register: ping_result - name: 显示ping结果 ansible.builtin.debug: var: ping_result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [student@workstation ~]$ ansible-navigator run ping.yml -m stdout -i inventory PLAY [演示register和debug] ***************************************************** TASK [检查主机可达性] ********************************************************** ok: [localhost] TASK [显示ping结果] ************************************************************ ok: [localhost] => { "ping_result" : { "changed" : false , "failed" : false , "ping" : "pong" } } PLAY RECAP ********************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
syntax-check 在使⽤ playbook 之前或遇到相关问题时,最好验证其语法。有问题就会输出问题,没问题就会输出文件路径。
1 2 [student@workstation ~]$ ansible-navigator run -m stdout ping.yml --syntax-check playbook: /home/student/ping.yml
playbook书写最佳实践 使用 play 或任务的用途的简要描述来命名 play 和任务。在执行 playbook 时,play 名称或任务名称会显示出来。这也有助于记录每个 play 或任务应该达到的目标,以及可能需要它的原因。
使用注释添加与任务相关的其他内嵌文档。
有效利用垂直空白。通常,垂直组织任务属性可以使它们更易于阅读。
一致的水平缩进至关重要。使用空格而不是制表符,以避免缩进错误。将文本编辑器设置为当您按下 Tab
键时插入空格,以简化操作。
尽可能使 playbook 简单。仅使用您需要的功能。
检测工具 有一些检测工具,可以辅助你找出playbook中潜在的问题
ansible-lint 1 [student@workstation ~]$ sudo dnf install ansible-lint -y
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 [student@workstation ~]$ cat ping.yml --- - name: 演示register和debug hosts: localhost gather_facts: no tasks: - name: 检查主机可达性 ansible.builtin.ping: register: ping_result - name: 显示ping结果 ansible.builtin.debug: var: ping_result [student@workstation ~]$ ansible-lint ping.yml WARNING: PATH altered to include /usr/bin WARNING Overriding detected file kind 'yaml' with 'playbook' for given positional argument: ping.yml WARNING Listing 1 violation(s) that are fatal yaml: truthy value should be one of [false , true ] (truthy) ping.yml:4 You can skip specific rules or tags by adding them to your configuration file: warn_list: - yaml Finished with 1 failure(s), 0 warning(s) on 1 files.
从错误信息来看,问题出在ping.yml
文件的第4行。yaml: truthy value should be one of [false, true] (truthy)
,这表明gather_facts
的值应该是布尔值(true
或false
),而不是其他格式。
你可以将gather_facts: no
改为gather_facts: false
,这样就可以解决这个问题。
不过需要注意的是,以上的运行没有用到自动化执行环境,如果要用自动化执行环境,可以这么做
1 2 3 [student@workstation ~]$ ansible-navigator lint ping.yml -m stdout yaml: truthy value should be one of [false , true ] (truthy) ping.yml:4
artifact artifact记录了我们的playbook运行过程中生成的日志,必要时,可以对日志进行replay,重现当时的情况而又不用真的连接服务器
artifact模式保存在playbook运行的当前目录,其文件格式为:
playbook名称-artifact单词-时间戳.json
1 timesync-artifact-2025-01-07T07:30:15.319967+00:00.json
重现当时的情况:
这个重现并不会真的连接服务器执行任务,只是用日志看看运行的情况而已
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [student@workstation ~]$ ansible-navigator replay ping-artifact-2025-01-07T07\:42\:43.857915+00\:00.json -m stdout PLAY [演示register和debug] ***************************************************** TASK [检查主机可达性] ********************************************************** ok: [localhost] TASK [显示ping结果] ************************************************************ ok: [localhost] => { "ping_result" : { "changed" : false , "failed" : false , "ping" : "pong" } } PLAY RECAP ********************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
当然,artifact有这种重现的好处,也有弊端,你如果交互式输入任何的密码,都将记录下来,有泄密问题,不需要日志的时候,可以关闭artifact收集
1 [student@workstation ~]$ vim /home/student/.ansible-navigator.yml
1 2 3 4 5 6 7 8 --- ansible-navigator: execution-environment: image: utility.lab.example.com/ee-supported-rhel8:latest pull: policy: missing playbook-artifact: enable: false
除了artifact之外,Ansible也有一个log记录,Ansible 提供了⼀个内置⽇志基础架构,可通过 ansible.cfg 配置⽂件 default 部分中的log_path 参数进⾏配置即可
⾝份验证问题 这种问题一般是以下几种可能性
你用的什么用户连接的受管主机?密码对不对?是否做了免密? 这个在ansible.cfg中,可以配置:
1 2 3 4 5 6 7 8 9 [student@workstation ~] $ grep -v -e ^$ -e ^'[defaults] remote_user =studenthost_key_checking =False [privilege_escalation] become =True become_ask_pass =False become_method =sudobecome_user =root
你连接的用户在远程是否有配置sudo特权? 这个就需要在每个受管主机上,确认/etc/sudoers
文件配置,是否为student用户授权了无需密码的sudo权限
连不上主机是否因为清单中指定了错误的解析? 如果你的主机名杂乱无序,可以在清单中写成有序的名称,手工把名称指向IP即可
以下假设我的主机名太乱,我可以让ansible认为我的主机名是servera.lab.example.com
1 servera.lab.example.com ansible_host =172.25 .250.10
Check_mode 测试环境毕竟和生产环境还是有点区别,我们可以在playbook中启用检查模式,用生产环境去测试我们的代码是否能成功,但是又不真的修改生产环境,一举两得
这个有两个用法
ansible-navigator run xxx.yaml -m stdout -C/–check
写进playbook里
这个check_mode可以写在play这里,也可以写到模块上,和模块对齐
1 2 3 4 5 6 7 8 --- - name: lixiaohui checkmode test hosts: localhost check_mode: true tasks: - name: debug ansible.builtin.debug: msg: ok
测试模块 测试网站返回的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: 使用FQCN方法的uri模块示例 hosts: localhost tasks: - name: 使用uri模块发送GET请求 ansible.builtin.uri: url: https://api.example.com/data return_content: yes register: response - name: 显示响应内容 ansible.builtin.debug: var: response.content
运行脚本,并测试脚本运行返回值是否为0,不为0就失败
脚本必须存在于控制节点上,会传输到受管主机并在其上执⾏
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: 使用FQCN方法的script模块示例 hosts: all tasks: - name: 运行本地脚本 ansible.builtin.script: cmd: /path/to/local_script.sh register: script_output - name: 显示脚本输出 ansible.builtin.debug: var: script_output.stdout
ansible.builtin.stat
模块用于获取远程文件的状态信息,例如是否存在、文件类型、权限等。
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: 使用FQCN方法的stat模块示例 hosts: all tasks: - name: 获取文件状态 ansible.builtin.stat: path: /path/to/file register: file_status - name: 显示文件状态 ansible.builtin.debug: var: file_status
ansible.builtin.assert
模块用于在Playbook中执行判断,以验证特定条件是否成立。如果条件不成立,它将使任务失败,并输出一条错误消息。这对于确保配置和状态符合预期非常有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 --- - name: 使用FQCN方法的assert模块示例 hosts: localhost tasks: - name: 检查变量值是否为预期 ansible.builtin.assert: that: - my_variable == "expected_value" fail_msg: "变量my_variable的值不是预期的expected_value" success_msg: "变量my_variable的值是预期的expected_value" - name: 显示成功信息 ansible.builtin.debug: msg: "断言通过,任务继续执行" when: my_variable == "expected_value"
ansible.builtin.fail
模块用于在Playbook中显式地使任务失败。它通常用于在检测到错误条件或不期望的状态时终止Playbook的执行,并输出一条自定义错误消息。
1 2 3 4 5 6 7 8 --- - name: 使用FQCN方法的fail模块示例 hosts: localhost tasks: - name: 检查变量值并失败 ansible.builtin.fail: msg: "遇到非预期的变量值,任务失败" when: my_variable != "expected_value"
第九章 ⾃动执⾏ Linux 管理任务 软件安装 rhel7以及以前用yum,7之后用dnf,其他发行版还有用apt的,不过模块可以改为ansible.builtin.package
来实现自动检测和处理
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 --- - name: 使用dnf模块管理软件包和软件包组的示例 hosts: all become: yes tasks: - name: 安装多个软件包 ansible.builtin.dnf: name: - httpd - vim - git state: present - name: 安装开发工具包组 ansible.builtin.dnf: name: "@Development Tools" state: present - name: 更新所有软件包到最新版本 ansible.builtin.dnf: name: '*' state: latest - name: 删除未使用的软件包 ansible.builtin.dnf: name: some_unused_package state: absent
需要注意的是是,在支持列表的时候,就不要循环,在第一个模块上,我们安装了多个软件,就等于:
1 dnf install httpd vim git -y
如果是下面这种,效率就比较低了
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: 使用dnf模块管理软件包和软件包组的示例 hosts: all become: yes tasks: - name: 安装多个软件包 ansible.builtin.dnf: state: present loop: - httpd - vim - git
这个loop就相当于执行下面的命令
1 2 3 dnf install httpd -y dnf install vim -y dnf install git -y
收集已安装软件包信息 ansible.builtin.package_facts
模块用于收集关于已安装软件包的信息,并将这些信息作为事实返回。这个模块非常有用,可以帮助你在Playbook中根据系统上已安装的软件包进行条件判断。
1 2 3 4 5 6 7 8 9 10 11 --- - name: 使用package_facts模块收集软件包信息 hosts: localhost become: yes tasks: - name: 收集软件包信息 ansible.builtin.package_facts: - name: 显示已安装的包信息 ansible.builtin.debug: var: ansible_facts.packages
运行看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [student@workstation ~]$ ansible-navigator run -m stdout package.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [使用package_facts模块收集软件包信息] ***************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************* ok: [localhost] TASK [收集软件包信息] ************************************************************************************************************** ok: [localhost] TASK [显示已安装的包信息] ********************************************************************************************************** ok: [localhost] => { "ansible_facts.packages" : { "acl" : [ { "arch" : "x86_64" , "epoch" : null, "name" : "acl" , "release" : "1.el8" , "source" : "rpm" , "version" : "2.2.53" } ],
部署yum仓库到远程 在远程的系统上准备仓库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 --- - name: Configure the company Yum/DNF repositories hosts: servera.lab.example.com become: yes tasks: - name: Ensure Example Repo exists ansible.builtin.yum_repository: file: example name: example-internal description: Example Inc. Internal YUM/DNF repo baseurl: http://materials.example.com/yum/repository/ enabled: yes gpgcheck: yes gpgkey: http://materials.example.com/yum/repository/xxxx.gpgkey state: present
管理用户和组 创建用户和组
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 --- - name: 创建用户组和用户的示例 hosts: all become: yes tasks: - name: 创建用户组 ansible.builtin.group: name: mygroup state: present - name: 创建附加用户组 ansible.builtin.group: name: secondarygroup state: present - name: 创建用户并加入主组,同时生成SSH密钥 ansible.builtin.user: name: myuser group: mygroup generate_ssh_key: true ssh_key_bits: 2048 ssh_key_file: .ssh/id_rsa state: present - name: 添加用户到附加组 ansible.builtin.user: name: myuser groups: secondarygroup append: yes state: present - name: 为用户设置密码 ansible.builtin.user: name: myuser password: "{{ 'my_password' | password_hash('sha512') }} " state: present
配置sudo权限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- - name: 修改sudo配置以允许group01组无密码使用sudo hosts: all become: yes tasks: - name: 确保sudoers.d目录存在 ansible.builtin.file: path: /etc/sudoers.d state: directory mode: '0750' - name: 修改sudo配置以允许group01组无密码使用sudo ansible.builtin.lineinfile: path: /etc/sudoers.d/group01 state: present create: true mode: '0440' line: "%group01 ALL=(ALL) NOPASSWD: ALL" validate: /usr/sbin/visudo -cf %s
管理系统任务与进程调度 管理系统任务
at 命令可调度作业在特定时间运⾏⼀次。Cron ⼦系统将作业调度为按循环计划运⾏
以下示例中,touch命令将在10分钟后执行
1 2 3 4 5 6 7 8 9 10 --- - name: Schedule a touch command at a specific time hosts: all tasks: - name: Schedule touch command ansible.posix.at: command: "touch /path/to/your/file.txt" count: 10 units: minutes unique: yes
以下示例指定了任务在每天凌晨2点运行
1 2 3 4 5 6 7 8 9 10 11 --- - name: Schedule a cron job to create a file hosts: all tasks: - name: Create a cron job to touch a file daily at 2 AM ansible.builtin.cron: name: "create_file" minute: "0" hour: "2" job: "touch /path/to/your/file.txt" user: "lixiaohui"
管理服务
管理服务一般来说,有两个模块,分别是
ansible.builtin.service
ansible.builtin.systemd
ansible.builtin.systemd
通常只能管理systemd服务,且具有daemon-reload
功能
1 2 3 4 5 6 7 8 9 10 --- - name: Ensure apache is running and enabled hosts: all tasks: - name: Ensure apache is running and enabled ansible.builtin.systemd: name: httpd state: started enabled: yes daemon_reload: yes
ansible.builtin.service
这个可以和很多服务管理系统搭配,比如刚才说的systemd
、upstart
、sysVinit
等等,但是需要注意的是,不具有daemon-reload
功能
1 2 3 4 5 6 7 8 9 --- - name: Ensure apache is running and enabled hosts: all tasks: - name: Ensure apache is running and enabled ansible.builtin.service: name: httpd state: started enabled: yes
重启节点
有时候执行某些任务的时候,需要重启后继续执行,而不是中断,就可以用reboot模块
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: Update software and reboot the server hosts: all tasks: - name: Update all packages ansible.builtin.yum: name: "*" state: latest - name: Reboot the server ansible.builtin.reboot: reboot_timeout: 600 msg: "Reboot after updates"
使⽤存储系统⻆⾊配置存储
存储角色只能在未分区、整个磁盘或逻辑卷(LV)上创建文件系统。它不能在分区中创建文件系统。
存储角色配置只会影响在以下变量中列出的文件系统、卷和池:
storage_volumes
在所有要管理的未分区磁盘中的文件系统列表。
storage_volumes 也可以包含 raid 卷。
storage_pools
要管理的池列表。
目前唯一支持的池类型是 LVM。使用 LVM 时,池代表卷组(VG)。每个池中都有一个要由角色管理的卷列表。对于 LVM,每个卷对应一个带文件系统的逻辑卷(LV)。
1 2 3 4 5 6 7 8 9 10 11 12 --- - name: Example of a simple storage device hosts: all roles: - name: redhat.rhel_system_roles.storage storage_volumes: - name: extra type: disk disks: - /dev/vdg fs_type: xfs mount_point: /opt/extra
配置解释
name: Example of a simple storage device
是这次任务的描述。
hosts: all
指定任务将在所有主机上执行。
roles
部分指定了要使用的 Ansible 角色,这里是 redhat.rhel_system_roles.storage
。
storage_volumes
定义了一个存储卷的配置:
name: extra
是存储卷的名称。
type: disk
指定了存储卷的类型,这里是一个磁盘。
disks: /dev/vdg
列出要管理的物理磁盘,这里是 /dev/vdg
。
fs_type: xfs
指定了要创建的文件系统类型,这里是 xfs
。
mount_point: /opt/extra
指定了文件系统的挂载点。
这个配置将创建一个名为 extra
的存储卷,使用 /dev/vdg
磁盘,并将其格式化为 xfs
文件系统,并挂载到 /opt/extra
。
再来创建一个lvm的看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- - name: Configure storage on webservers hosts: webservers roles: - name: redhat.rhel_system_roles.storage storage_pools: - name: vg01 type: lvm disks: - /dev/vdb storage_volumes: - name: lvol01 size: 128m mount_point: /data fs_type: xfs state: present
配置解释
name: Configure storage on webservers
是该任务的描述。
hosts: webservers
指定任务将在 webservers
主机组上执行。
roles
部分指定了要使用的 Ansible 角色,这里是 redhat.rhel_system_roles.storage
。
storage_pools
定义了一个存储池的配置:
storage_volumes
定义了一个逻辑卷的配置:
name: lvol01
是逻辑卷的名称。
size: 128m
指定逻辑卷的大小,这里是 128 MB。
mount_point: /data
指定文件系统的挂载点,这里是 /data
。
fs_type: xfs
指定文件系统的类型,这里是 xfs
。
state: present
确保逻辑卷的存在。
这个配置将创建一个名为 vg01
的 LVM 存储池,使用 /dev/vdb
磁盘,并在该存储池中创建一个名为 lvol01
的逻辑卷,将其格式化为 xfs
文件系统,并挂载到 /data
。
不过以上操作可能比较复杂,也可以考虑用社区的parted模块
1 2 3 4 - name: Ensure that /dev/sda1 exists ansible.builtin.command: cmd: parted --script mklabel gpt mkpart primary 1MiB 100% creates: /dev/sda1
创建了分区后,可以用community.general.filesystem
模块格式化,然后ansible.builtin.mount
挂载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 --- - name: Ensure filesystem is created and mounted hosts: all tasks: - name: Create an ext4 filesystem on /dev/sda1 community.general.filesystem: fstype: ext4 dev: /dev/sda1 - name: Mount the filesystem ansible.builtin.mount: path: /mnt/data src: /dev/sda1 fstype: ext4 state: mounted
管理⽹络配置 静态IP配置方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 --- - name: Configure network on webservers hosts: webservers vars: network_connections: - name: eth0 type: ethernet ip: address: - 192.168 .1 .100 /24 gateway4: 192.168 .1 .1 dns: - 8.8 .8 .8 - 8.8 .4 .4 state: up roles: - role: redhat.rhel_system_roles.network
DHCP 配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 --- - name: Configure DHCP and Auto IPv6 on enp1s0 hosts: all vars: network_connections: - name: enp1s0 type: ethernet ip: dhcp4: true auto6: true state: up roles: - role: redhat.rhel_system_roles.network
完全删除 enp1s0 的配置示例如下:
1 2 3 4 5 6 7 8 9 10 11 --- - name: Configure network on webservers hosts: all vars: network_connections: - name: enp1s0 type: ethernet state: down persistent_state: absent roles: - role: redhat.rhel_system_roles.network
设置主机名
1 2 3 4 5 6 7 --- - name: Ensure the hostname is set hosts: servera tasks: - name: Set the hostname to webserver1 ansible.builtin.hostname: name: webserver1
设置防火墙
1 2 3 4 5 6 7 8 9 10 --- - name: Ensure port 80 is open hosts: all tasks: - name: Open port 80 /tcp ansible.posix.firewalld: port: 80 /tcp permanent: true state: enabled immediate: yes
1 2 3 4 5 6 7 8 9 10 --- - name: Ensure http service is enabled hosts: all tasks: - name: Enable http service ansible.posix.firewalld: service: http permanent: true state: enabled immediate: yes