【Python】不一样的Ansible(一)

news2024/11/25 20:55:03

不一样的Ansible——进阶学习

  • 前言
  • 正文
    • 概念
      • Ansible Core
      • Plugins和Modules
    • 插件
      • 插件类型
      • 编写自定义插件
        • 基本要求
        • 插件选项
        • 文档标准
        • 编写插件
      • 添加一个本地插件
        • 注册为内置插件
        • 指定插件目录
    • 其他一些技巧
      • 更改Strategy
  • 结语

前言

Ansible 是一个极其简单的 IT 自动化引擎,可自动执行云配置、配置管理、应用程序部署、服务内编排和许多其他 IT 需求;基本上每一个运维工程师都会听过或者使用过Ansible(这是必然的,只要你需要管理超过2位数的服务器,Ansible基本是一个必备的工具),如果你还不知道Ansible是怎么用的,一定要找一个环境试试,这是一个真正让运维工作自动化并且事半功倍的工具。

Ansible不使用任何代理程序,也就是不需要部署agent,也不使用额外的自定义安全基础设施,因此很容易部署 - 最重要的是,使用yaml配置就能编写整个自动化流程,不需要学习太多额外的编程知识。

这一部分的内容,不是对Ansible做基本介绍的,而是对其中一些进阶的或者平时不会知道的东西做一个学习和记录的。比如各种插件的特殊用法、如何在我们自己的Python程序中去使用Ansible、以及一些其他的技巧或者知识点。

本系列文章的所有内容基于Ansible 9编写,Ansible 9使用Ansible-core 2.16

正文

概念

Ansible Core

Ansible Coreansible-coreAnsible的主要构件和架构,包含了许多的核心逻辑,比如:

  • CLI 工具,如 ansible-playbook、ansible-doc 等,用于驱动自动化并与之交互。
  • Ansible 语言使用 YAML 创建一套用于开发 Ansible Playbook 的规则,包括conditionals, blocks, includes, loops以及其他的一些内容。
  • 允许通过Ansible collections进行扩展的架构框架

总结来说,Ansible Core提供你目前所熟知的所有Ansible的基础能力,并且允许开发者在现有基础上拓展Ansible的功能。

Plugins和Modules

Ansible中非常重要的两个概念,插件和模块,那么他们有什么区别,以及怎么在自己实现拓展功能的时候选择使用哪个特性:

  • Plugins:扩展了Ansible Core的功能。大多数插件都在/usr/bin/ansible进程的控制节点上执行。插件为Ansible的核心功能提供选项和扩展:转换数据、记录输出、连接Inventory
  • Modules:本质也是一种Plugins,但是其特点在于能够在远程服务器上执行自动化任务。模块以独立脚本的形式运行,由Ansible在控制节点之外的进程中执行。模块与Ansible的接口主要是 json,接受参数并在退出前通过向stdout打印json字符串返回信息。与其他插件(必须用 Python 编写)不同,模块可以用任何语言编写;不过Ansible只提供PythonPowershell版本的模块。

这其实很好理解,Plugins是用来添加Ansible的核心代码逻辑的,因此需要Ansible能够把它加载进来,Ansible既然是Python编写的,自然也要求Plugins是要用Python编写,而Modules是通过ssh分发到受管节点执行的,那么他可以是任意程序,只要远程服务器能够运行它就可以了,默认Ansible的那些模块其实在受管节点也是调用了Python解释器来运行的

插件

插件类型

上面已经介绍了插件是什么,在Ansible中有多种插件类型,下面将官网的:

  • Action pluginsActionmodule一起执行playbook任务所需的动作,通常在后台自动运行,在模块实际运行前执行一些前期工作;
  • Become plugins:用于执行命令时进行提权或者用户切换,我们最常用的就是借助become完成sudo操作;
  • Cache pluginsCache插件允许Ansible存储收集到的受管节点信息或者inventory信息,避免因为从源数据检索而影响性能,这个最直观的体现就是我们执行playbook时如果没有关闭gather_fact,可能会等待很长时间,借助cache可以有效减少重复在受管节点执行操作时的这个步骤耗时;
  • Callback plugins:回调插件可以在Ansible响应事件时添加新的行为。默认情况下,回调插件可控制运行命令行程序时看到的大部分输出,但也可用于添加额外输出、与其他工具集成以及将事件汇聚到存储后端;对于想要对接Ansible做自动化管理的同学来说,这个插件很重要;
  • Cliconf pluginsCliconf插件是网络设备CLI接口的抽象。它们为Ansible在这些网络设备上执行任务提供了标准接口,主机维护不常用,我也没用过;
  • Connection pluginsConnection插件允许Ansible使用既定方式连接到目标主机,以便在上面执行任务,Ansible自身附带了许多连接插件,比如opensshparamiko,但每台主机一次只能使用一个;
  • Docs fragmentsDocs fragments可让使用者在一个地方记录多个插件或模块的通用参数;
  • Filter pluginsFilter插件也是一个功能常用的插件,他允许对数据进行特定的序列化或者其他自定义的处理,比如对字符串进行拆分和连接,默认Ansible使用的是jinja2提供的标准Filter功能,同时增加了一些特性;
  • Httpapi pluginsHttpapi 插件告诉Ansible如何与远程设备基于HTTPAPI交互,并在设备上执行任务;
  • Inventory pluginsInventory插件是一个允许用户指向仓库数据源的插件,也就是我们执行ad-hoc命令式的-i参数执行的逻辑;
  • Lookup pluginsLookup插件是Jinja2模板语言的Ansible特定扩展。用户可以使用该插件从外部来源(文件、数据库、key/value存储、API 和其他服务)访问playbook中的数据。与所有模板一样,LookupAnsible 控制节点上运行。Ansible使用标准模板系统提供查找插件返回的数据。你可以使用查找插件从外部资源加载变量或模板信息。
  • Modules:上文说过,Modules也是一种插件,所以这里也有它;
  • Module utilities:包含多个插件使用的共享代码,最好把它和Modules一起理解;
  • Netconf plugins:网络设备Netconf接口抽象,用于在网络设备上执行ansible任务;
  • Shell pluginsShell插件的作用是确保Ansible运行的基本命令格式正确,能够在目标计算机上运行,并允许用户配置与Ansible执行任务方式相关的某些行为,具体的情况可以跳转到
  • Strategy pluginsStrategy通过handles和受管节点的调度来控制playbook的执行流程,具体的策略可以查看:更改Strategy
  • Terminal pluginsTerminal插件包含有关如何正确初始化特定网络设备的SSH shell以配合Ansible使用的信息。这通常包括禁用自动分页、检测输出中的错误,以及在设备支持和需要的情况下启用特权模式。
  • Test pluginsTest插件可评估模板表达式并返回TrueFalse。有了测试插件,用户就可以创建条件(比如when关键字)来实现tasks、blocks、play、playbooks和roles的逻辑。Ansible使用作为Jinja一部分提供的标准测试,并添加了一些专门的测试插件
  • Vars pluginsVars插件拓展了一些变量的使用方式

上面所说的插件,其中一部分(ActionCacheCallbackConnectionFilterInventoryLookupTestVars)支持开发者进行自定义的编写,实现新的功能。

编写自定义插件

基本要求

让我们先来编写一个自定义的插件来为Ansible提供额外的核心能力,编写之前我们要知道一个Ansible插件必须要满足的条件:

  • 使用Python编写,这个在上一节已经说明原因
  • 对异常进行合适的处理,使用raise抛出异常
  • 返回值要是unicode编码的字符串
  • 符合Ansible的配置和文档标准

接下来针对上面的要求做一下分别的解释

使用Python编写

使用Python编写插件,这样它才能被PluginLoader加载,并作为任何模块都能使用的Python对象返回。由于插件将在控制节点上执行,因此必须使用兼容的 Python 版本编写插件

使用raise处理异常

在插件执行过程中遇到错误时,应通过引发AnsibleError()或类似类来返回错误信息。在将其他异常包装成错误信息时,应始终使用to_nativeAnsible函数,以确保不同Python版本的字符串兼容,下面是一个简单的样例:

from ansible.module_utils.common.text.converters import to_native

try:
    cause_an_exception()
except Exception as e:
    raise AnsibleError('Something happened, this was original exception: %s' % to_native(e))

由于Ansible仅在需要时才会对变量进行解析,因此filter插件和test插件应使用jinja2.exceptions.UndefinedErrorAnsibleUndefinedVariable异常,以确保未定义变量仅在必要时才引起程序的Fatal

返回值符合unicode编码

这个要求是为了保证过程中的字符串str能够在Jinja2中正常运行和解析,Ansible提供了响应的方法进行转换:

from ansible.module_utils.common.text.converters import to_text
result_string = to_text(result_string)
插件选项

为了给我们自己的插件定义可配置选项,我们需要在Python文件的DOCUMENTATION部分进行描述和声明,这种规范可以确保我们插件响应选项的文档时钟保持正确和最新,以下是格式定义:

options:
  option_name:
    description: describe this config option
    default: default value for this config option
    env:
      - name: NAME_OF_ENV_VAR
    ini:
      - section: section_of_ansible.cfg_where_this_config_option_is_defined
        key: key_used_in_ansible.cfg
    vars:
      - name: name_of_ansible_var
      - name: name_of_second_var
        version_added: X.x
    required: True/False
    type: boolean/float/integer/list/none/path/pathlist/pathspec/string/tmppath
    version_added: X.x

我们来看一个样例,这里以自带的Callback插件中的json.py演示:

# (c) 2016, Matt Martz <matt@sivel.net>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = '''
    callback: json
    short_description: Ansible screen output as JSON
    version_added: "2.2"
    description:
        - This callback converts all events into JSON output to stdout
    type: stdout
    requirements:
      - Set as stdout in config
    options:
      show_custom_stats:
        version_added: "2.6"
        name: Show custom stats
        description: 'This adds the custom stats set via the set_stats plugin to the play recap'
        default: False
        env:
          - name: ANSIBLE_SHOW_CUSTOM_STATS
        ini:
          - key: show_custom_stats
            section: defaults
        type: bool
    notes:
      - When using a strategy such as free, host_pinned, or a custom strategy, host results will
        be added to new task results in ``.plays[].tasks[]``. As such, there will exist duplicate
        task objects indicated by duplicate task IDs at ``.plays[].tasks[].task.id``, each with an
        individual host result for the task.
'''

ok,现在假设我们设定好了自己的配置项,如果要在插件内部使用对应的配置项,一般使用self.get_option(<option_name>)进行获取,不过有些插件的处理方式会有些不同;

配置部分遵循了Ansible中值的优先原则,当在同一个类别下定义多个值时,最后一个值优先,比如上述样例的这部分:

    vars:
      - name: name_of_ansible_var
      - name: name_of_second_var

此时option_name选项的值使用name_of_second_var,如果要对option值进行设置,则要使用self.set_option()方法;

文档标准

支持嵌入式文档(参见 ansible-doc 列表)的插件应包含格式良好的文档字符串,如果我们继承一个插件,则必须在文档中记录被继承插件的选项;

编写插件

Action plugin为例,编写一个插件,首先,编写的插件应该继承自对应的Base类,比如:

from ansible.plugins.action import ActionBase

class ActionModule(ActionBase):
    pass

允许使用_execute_module方法来调用内置的Module并获取返回结果:

module_return = self._execute_module(module_name='<NAME_OF_MODULE>',
                                     module_args=module_args,
                                     task_vars=task_vars, tmp=tmp)

我们以官方文档中的示例为准,进行讲解,这里添加了一个通过setup模块获取的内容进行服务器时间差异的比对动作的action

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from datetime import datetime
from ansible.plugins.action import ActionBase


class ActionModule(ActionBase):
    """Action"""
    def run(self, tmp=None, task_vars=None):
        super().run(tmp, task_vars)
        module_args = self._task.args.copy()
        module_return = self._execute_module(module_name='setup',
                                             module_args=module_args,
                                             task_vars=task_vars, tmp=tmp)
        ret = {}
        remote_date = None
        if not module_return.get('failed'):
            for key, value in module_return['ansible_facts'].items():
                if key == 'ansible_date_time':
                    remote_date = value['iso8601']

        if remote_date:
            remote_date_obj = datetime.strptime(remote_date, '%Y-%m-%dT%H:%M:%SZ')
            time_delta = datetime.utcnow() - remote_date_obj
            ret['delta_seconds'] = time_delta.seconds
            ret['delta_days'] = time_delta.days
            ret['delta_microseconds'] = time_delta.microseconds
        return {"ansible_facts": dict(ret)}
    

在上述代码中,调用了内部的setup模块,拉取受管节点的信息,在其返回值中,有这样的数据结构:

        "ansible_date_time": {
            "date": "2024-01-08",
            "day": "08",
            "epoch": "1704704528",
            "epoch_int": "1704704528",
            "hour": "17",
            "iso8601": "2024-01-08T09:02:08Z",
            "iso8601_basic": "20240108T170208306063",
            "iso8601_basic_short": "20240108T170208",
            "iso8601_micro": "2024-01-08T09:02:08.306063Z",
            "minute": "02",
            "month": "01",
            "second": "08",
            "time": "17:02:08",
            "tz": "CST",
            "tz_dst": "CST",
            "tz_offset": "+0800",
            "weekday": "Monday",
            "weekday_number": "1",
            "weeknumber": "02",
            "year": "2024"
        },

随后在自定义插件逻辑中我们比对了iso8061的值,计算出时间的差异值,然后返回,这个插件的返回值类似以下的格式:

172.18.0.25 | SUCCESS => {
    "ansible_facts": {
        "delta_days": 0,
        "delta_microseconds": 152565,
        "delta_seconds": 0
    },
    "changed": false
}

如果想要进一步修改插件,满足自己的需求,只需要在刚才编写的ActionModule中进行变更即可。

添加一个本地插件

上面我们已经编写了一个自定义的插件,但是只是把代码写好是没法按照预期的方式通过ad-hoc的方式使用的,大概率会出现以下的报错:

172.18.0.25 | FAILED! => {
    "msg": "The module myaction was not found in configured module paths"
}

而想要直接便利的使用自定义的插件,有以下几种方式进行配置:

注册为内置插件

这种方法不建议,只做介绍,直接将编写好的python文件拷贝到ansible的安装目录下,比如我们使用虚拟环境,通过pip安装的ansible,那么我们的ansible安装目录ANSIBLE_INSTALL_DIR大概是这样的:$ENV_HOME/lib/python3.9/site-packages/ansible,那么只需要把我们的插件放在$ANSIBLE_INSTALL_DIR/plugins/目录下对应的插件类型下:

image-20240108173808183

不建议使用这种方法的原因主要是因为我们个人编写的代码在规范性上和官方的多少有些差异,最好还是做一些区分,这样在出问题的时候排查也会变得容易一些

指定插件目录

这个方式是比较规范的,当我们编写的插件是一个提供给全局使用的插件时,我们就这样做就可以了,编辑ansible的配置文件/etc/ansible/ansible.cfg,添加action_plugins配置项:

[defaults]
# ...
# 可以自定义为自己需要的目录
action_plugins     =  /usr/share/ansible/plugins/action 

然后把我们的插件放在上面配置的目录下,就能正常使用了

这种配置便于我们对插件进行管理,且配置一次后,所有人都能使用

除此之外,插件也可以放在Collection中,这个在讲到Collection时再单独写

其他一些技巧

更改Strategy

默认情况下,Ansible使用linear strategy进行任务的调度,除此之外,Ansible还提供了以下集中Strategy plugins

  • debug strategy – 在debug模式下运行Task.
  • free strategy – 不等待其他受管节点完成当前状态,直接运行Task
  • host_pinned strategy – 尽可能快的在每个受管节点执行play(按照serial定义的批次执行,默认为全部),除非一个play可以在不被其他主机Task中断的情况下完成,否则Ansible不会给一个新的主机启动play,即:拥有执行中的play的受管节点数量不超过forks规定的数量
  • linear strategy – 默认的策略,线性执行,这个线性是指Task,也是我们熟知的,一个 Task会在所有服务器执行完后再执行下一个Task

假设我们希望每个节点都能速度拉满,我们可以把策略改成free,通过在playbook中指定或者更改配置文件都可以实现,先看更改playbook:

- hosts: all
  strategy: free
  tasks:
  # ...

或者更改ansible.cfg

[defaults]
strategy = free

结语

本篇文章主要抛砖引玉,说明了Ansible中模块和插件的区别,以及如何自己编写一个简单的Action Ansible插件,接下来会再多讲解一些其他类型的插件编写方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1366373.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MISRA C 解读

说明&#xff1a;本文由vector官方视频整理而来&#xff0c;原视频链接解读MISRA C_哔哩哔哩_bilibili 1、MISRA 简介 1.1 发起 MISRA (The Motor Industry Software Reliability Association ) 汽车工业软件可靠性联会&#xff0c;起先作为研究车载嵌入式软件制备准则的开发…

FFmpeg读取Assets资源文件

在Android开发中我们经常把原生资源文件放在assets目录下以供需要时读取&#xff0c;通过API提供的resources.assets.open(filename)/openFd(filenam)方法可以非常方便获得InputStream或FileDescriptor&#xff08;文件标识符&#xff09;&#xff0c;但是在使用FFmpeg读取Asse…

CTF-PWN-沙箱逃脱-【seccomp和prtcl-1】

文章目录 啥是seccomp#ifndef #define #endif使用使用格式 seccomp无参数条件禁用系统调用有参数条件禁用系统调用 prctl实例 seccomp_export_bpf 啥是seccomp 就是可以禁用掉某些系统调用&#xff0c;然后只能允许某些系统调用 #ifndef #define #endif使用 #ifndef #defin…

Neo4j恢复

主要记录从备份文件中恢复Neo4j 误删数据 为了模拟误删除场景&#xff0c;我们查询Person&#xff0c;并模拟误操作将其进行删除&#xff1b; match(p:Person) return p Step1&#xff1a; 关闭服务 Step2&#xff1a; 恢复数据 找到Neo4j的数据文件夹&#xff0c;我的安…

Linux第18步_安装“Ubuntu系统下的C语言编GCC译器”

Ubuntu系统没有提供C/C的编译环境&#xff0c;因此还需要手动安装build-essential软件包&#xff0c;它包含了 GNU 编辑器&#xff0c;GNU 调试器&#xff0c;和其他编译软件所必需的开发库和工具。本节用于重点介绍安装“Ubuntu系统下的C语言编译器GCC”和使用。 1、在安装前…

矢量,矢量化的梯度下降以及多元线性回归

一、矢量 定义&#xff1a;按照特定顺序排列的元素集合。可以被视为一维数组。 在机器学习中的作用&#xff1a; 特征表示&#xff1a;在机器学习任务中&#xff0c;输入数据通常以矢量的形式表示。例如&#xff0c;图像可以表示为像素值的矢量&#xff0c;文本可以表示为词…

CodeGPT,你的智能编码助手—CSDN出品

CodeGPT是由CSDN打造的一款生成式AI产品&#xff0c;专为开发者量身定制。 无论是在学习新技术还是在实际工作中遇到的各类计算机和开发难题&#xff0c;CodeGPT都能提供强大的支持。其涵盖的功能包括代码优化、续写、解释、提问等&#xff0c;还能生成精准的注释和创作相关内…

springCould中的gateway-从小白开始【9】

目录 1.&#x1f35f;网关是什么 2.&#x1f37f;gateway是什么 3.&#x1f95a;gateway能什么 4.&#x1f32d;核心概念 5.&#x1f9c2;工作流程 6.&#x1f9c8;实例 7.&#x1f953;gateway网关配置的方式 8.&#x1f373;配置动态路由 9.&#x1f9c7;pred…

软件开发和网络安全哪个更好找工作?

为什么今年应届毕业生找工作这么难&#xff1f; 有时间去看看张雪峰今年为什么这么火就明白了。 这么多年人才供给和需求错配的问题&#xff0c;在经济下行的今年&#xff0c;集中爆发。 供给端&#xff0c;大学生越来越多。需求端&#xff0c;低端工作大家不愿去&#xff0c;高…

springboot集成cas客户端

Background 单点登录SSO(Single Sign ON)&#xff0c;指在多个应用系统中&#xff0c;只需登录一次&#xff0c;即可在多个应用系统之间共享登录。统一身份认证CAS&#xff08;Central Authentication Service&#xff09;是SSO的开源实现&#xff0c;利用CAS实现SSO可以很大程…

年销5万的岚图没有爆款

作者 | 辰纹 来源 | 洞见新研社 3款车一年卖了5万台&#xff0c;这个销量不算多&#xff0c;可对于岚图来说&#xff0c;却很不容易&#xff0c;CEO卢放称这是“一场翻身仗”&#xff0c;在写给全体员工的“家信”中表达谢意&#xff0c;称是“大家的团结奋斗&#xff0c;驱动…

代码随想录刷题题Day28

刷题的第二十八天&#xff0c;希望自己能够不断坚持下去&#xff0c;迎来蜕变。&#x1f600;&#x1f600;&#x1f600; 刷题语言&#xff1a;C Day28 任务 ● 343. 整数拆分 ● 96.不同的二叉搜索树 1 整数拆分 343. 整数拆分 思路&#xff1a; 动态规划 &#xff08;1&a…

jenkins通过流水线自动部署项目(k8s部署)

参考&#xff1a;https://www.cnblogs.com/rb2010/p/16195443.html docker 拉取镜像到本地&#xff1a; docker pull docker.io/jenkins/jenkins:2.164配置卷挂载&#xff1a;使用nfs 参考&#xff1a;https://www.kuboard.cn/learning/k8s-intermediate/persistent/nfs.htm…

软考通过率真的低吗?

软考通过lv确实相对较低。软考通过lv低的原因主要有以下几点&#x1f447; ✅软考本身是计算机类别的考试&#xff0c;考试难度本身不小。 ✅报考没有条件限制&#xff0c;报考的人水平参差。弃考和零基础考生较多。 ✅不同的科目通过lv有所差异&#xff0c;初级的大致在30%&am…

如何将 element-ui 中的 el-select 默认展开

<el-form-item label"藕粉桂花糖糕" prop"state" required><el-selectref"mySelect"v-model"form.state"style"width: 280px"placeholder"请选择"><el-option label"藕粉" :value"…

二分查找(二)

点名 点名 某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records。假定仅有一位同学缺席&#xff0c;请返回他的学号。 二分法思路&#xff1a;判断数组的值和对应的下标是否相等&#xff0c;将数组分为两个区间&#xff0c;不相等区间的最左端&#xff0c;就是…

java⽇志体系

⽇志体系 1.体系概述2.日志的使用1.上古时代的sout2.开创先驱的log4j3.搞事情的JUL4.应运⽽⽣的JCL5.再起波澜的logback6.再度⻘春的log4j2 本篇在jdk21下测试通过 1.体系概述 1.日志接口 JCL&#xff1a;Apache基⾦会所属的项⽬&#xff0c;是⼀套Java⽇志接⼝&#xff0c;之…

视频做成二维码查看?多格式视频二维码生成器的使用方法

现在音视频是工作和生活中经常需要使用的一种内容表现形式&#xff0c;很多人都通过这种方式来查看视频内容&#xff0c;比如产品介绍、使用说明、安装教程等。通过一个二维码就可以来承载视频内容&#xff0c;与传统的方式相比拥有更快的内容传播速度&#xff0c;简化用户获取…

[蓝桥杯学习] 线段树

学习blibli 定义 线段树是一种特殊的平衡二叉查找树&#xff0c;使用线段树&#xff0c;可以实现数据的添加、查找和删除。 树的根结点表示了一个完整的单元区间&#xff0c;左右孩子的区间是将父结点的区间进行二分&#xff0c;左右孩子的区间之和&#xff0c;就是他们的根…

三菱plc学习入门(二,三菱plc指令,触点比较,计数器,交替,四则运算,转换数据类型)

今天&#xff0c;进行总结对plc的学习&#xff0c;下面是对plc基础的学习&#xff0c;希望对读者有帮助&#xff0c;欢迎点赞&#xff0c;评论&#xff0c;收藏&#xff01;&#xff01;&#xff01; 目录 触点比较 当数据太大了的时候&#xff08;LDD32位&#xff09; CMP比…