puppet 如何向操作系统发送命令? [英] How does puppet send commands to the OS?

查看:53
本文介绍了puppet 如何向操作系统发送命令?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 Puppet 的新手,但对这些概念非常了解.Puppet Manifest 调用 Puppet 模块,模块执行实际任务.

我想了解 Puppet 模块层发生了什么.命令实际上是如何执行的?以下面的例子为例,哪些命令实际上传递给了操作系统?另外,它是在哪里定义的?

<前>包{'ntp':确保 => 已安装,}

解决方案

总结: Puppet 根据系统的 facts 确定需要运行的命令以及 Puppet 本身的配置.

因此,当 Puppet 编译目录以在其系统上运行时,它看起来如下所示:

我需要安装一个Pacakge资源,叫ntp.我是CentOS系统,RedHat家族.默认在RedHat上,我使用yum命令.所以我需要运行yum install ntp"

更长的解释

Puppet 知道要运行的命令以及如何运行它们的方式称为资源抽象层.

归根结底,Puppet 并没有做任何神奇的事情:系统上运行的命令与人类操作员运行的命令相同.

也许 Puppet 已经找到了一个聪明的方法来做到这一点,并考虑到您所在平台的晦涩错误和陷阱,或者由于您尝试执行的操作包含拼写错误或类似错误而引发错误.

但最终,必须使用系统实际应用程序和工具实际执行操作.

这就是 RAL 的真正用武之地.它是 Puppet 中最大的抽象层:将与基础系统的所有交互转变为一致的接口.

在您给出的示例中,包相当简单.至少在过去的二十年里,安装软件包的概念(大部分)几乎与每个操作系统相同:

packagesystemtool 关键字forinstall packagename

通常,安装关键字是 install,但也有一些例外.例如,BSD 的 pkg 使用 pkg add.

但是:可以在该包中管理的实际属性可能会有很大差异:

  • 你能指定版本吗?
  • 你能降级那个版本吗?
  • 如果软件包已经安装,是否需要指定不同的命令来升级它?

大量其他可选参数,例如代理信息、错误日志级别.

RAL 允许用户以一致的方式定义资源的特征,而不管实现如何:

type { 'title':属性 =>'价值',}

每个资源都遵循相同的语法:

  • 资源类型(例如用户、包、服务、文件)
  • 用大括号定义资源块.
  • 一个标题,用冒号与资源正文分开 一个由属性和值对组成的正文

所以我们的包声明看起来像这样:

package {'tree':确保 =>'展示',}

RAL 可以在已定义的每个平台上处理该行为,并在可用的情况下支持不同的包功能,所有这些功能都以明确定义的方式默认对用户隐藏.

我听过的关于 RAL 最好的比喻是天鹅在湖上的湖面上滑翔:

<块引用>

当你在水面上看一只天鹅时,它看起来优雅而优雅优雅,滑翔.它看起来几乎没有工作.

看不见的是水面下的活动.那只天鹅正在踢它的蹼足,远不如它抬头那么优雅:偶人正在运行的实际命令是在水下踢腿.

好的,足够的背景,你可能会问......

它实际上是如何工作的?RAL 将系统上的所有资源拆分为两个元素:

  • 类型:资源有效属性的高级模型
  • 提供者:特定于平台的类型实现

这使您可以以适用于任何系统的方式描述资源.每一种资源,不管它是什么,都有一个或多个提供者.提供者是底层操作系统和资源类型之间的接口.

通常,一个类型会有一个默认的提供者,但如果需要,您可以指定一个特定的提供者.

对于包,默认提供者将是系统的默认包提供者:yum 用于 RHEL,apt 用于 Debian,pkg对于 BSD 等.这是由 a 决定的,它从系统中获取事实.

例如,yum 提供程序具有以下内容:

defaultfor :osfamily =>:红色的帽子

但是您可能想要安装一个 pip 包或 gem.为此,您将指定提供程序,以便使用不同的命令安装它:

package {'tree':确保 =>'展示',提供者 =>'点',}

这意味着我们要对 RAL 说:嘿,我知道 yum 是安装包的默认设置,但这是我需要的 python 包,所以我告诉你改用 pip"

>

属性类型中最重要的资源在不同操作系统中通常在概念上是相同的,无论实际实现有何不同.

就像我们说的,大多数软件包将使用 package installer install packagename

因此,资源的描述可以从其实现中抽象出来:

Puppet 使用 RAL 来读取和修改系统上的资源状态.由于它是一个声明式系统,Puppet 首先了解资源应该具有什么状态.

为了同步资源,它使用 RAL 查询当前状态,将其与所需状态进行比较,然后再次使用 RAL 进行任何必要的更改.它使用工具获取系统的当前状态,然后找出需要做什么才能将该状态更改为资源定义的状态.

当 Puppet 应用包含资源的目录时,它会读取目标系统上资源的实际状态,将实际状态与所需状态进行比较,并在必要时更改系统以强制执行所需状态.

让我们看看 RAL 将如何管理:

  • 我们已将类型指定为包.
  • 包的标题/名称是ntp
  • 我在 RHEL7 系统上运行它,所以默认提供程序是 yum.
  • yum 是 rpm 的子"提供者:它使用 RPM 命令来检查系统上是否安装了该软件包.(这比运行yum info"快得多,因为它不拨打任何互联网电话,如果 yumrepo 失败也不会失败)
  • 然而,安装命令将是 yum install

所以之前我们讨论了 Puppet 如何使用 RAL 来读取和修改系统上资源的状态.

RAL 的getter"是提供者中的 self.instances 方法.

根据资源类型,这通常通过以下两种方式之一完成:

  • 读取磁盘上的文件,遍历文件中的行并将其转换为资源
  • 在终端上运行一个命令,将标准输出分成几行,将它们转换为成为资源的哈希值

rpm 实例步骤与后者一致.它运行带有一些给定标志的 rpm -qa 以检查系统上有哪些软件包:

def self.instances包 = []# 列出所有的包开始execpipe("#{command(:rpm)} -qa #{nosignature} #{nodigest} --qf '#{self::NEVRA_FORMAT}'") { |process|# 现在将每个返回的行转换为一个包对象process.each_line { |线|hash = nevra_to_hash(行)包<

所以它正在运行 /usr/bin/rpm -qa --nosignature --nodigest --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}|%{VERSION} %{RELEASE} %{ARCH}\n',然后从该命令中获取标准输出,循环遍历该命令的每一行输出,并使用 nevra_to_hash 方法来将 STDOUT 的行转换为哈希.

self::NEVRA_REGEX = %r{^(\S+) (\S+) (\S+) (\S+) (\S+)$}self::NEVRA_FIELDS = [:name, :epoch, :version, :release, :arch]私人的# @param line [String] 一行rpm包查询信息# @return [Hash] 从包信息中解析出的 NEVRA_FIELDS 字符串# 或者一个空哈希,如果我们解析失败# @api 私有def self.nevra_to_hash(line)line.strip!哈希 = {}if match = self::NEVRA_REGEX.match(line)self::NEVRA_FIELDS.zip(match.captures) { |f, v|哈希[f] = v }hash[:provider] = self.namehash[:ensure] = "#{hash[:version]}-#{hash[:release]}"hash[:ensure].prepend("#{hash[:epoch]}:") if hash[:epoch] != '0'别的Puppet.debug("无法匹配 rpm 行 #{line}")结尾返回哈希值结尾

所以基本上它是输出上的正则表达式,然后将这些位从正则表达式转换为给定的字段.

这些哈希值成为资源的当前状态.

我们可以运行 --debug 来查看它的实际效果:

Debug:预取包的 yum 资源调试:执行:'/usr/bin/rpm --version'调试:执行 '/usr/bin/rpm -qa --nosignature --nodigest --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}|%{VERSION} %{RELEASE} %{ARCH}\n''调试:执行:'/usr/bin/rpm -q ntp --nosignature --nodigest --qf %{NAME} %|EPOCH?{%{EPOCH}}:{0}|%{VERSION} %{RELEASE} %{ARCH}\n'调试:执行:'/usr/bin/rpm -q ntp --nosignature --nodigest --qf %{NAME} %|EPOCH?{%{EPOCH}}:{0}|%{VERSION} %{RELEASE} %{ARCH}\n --whatprovides'

所以它使用 RAL 来获取当前状态.Puppet 正在执行以下操作:

  • 嗯,这是 RHEL 系统上名为ntp"的包资源,所以我应该使用 RPM
  • 让我们获取安装的 RPM 包的当前状态(例如实例方法
  • ntp 不在这里...
  • 所以我们需要安装ntp
  • Yum 提供程序然后指定安装所需的命令.

这里有很多逻辑:

def 安装想要 = @resource[:name]error_level = self.class.error_levelupdate_command = self.class.update_command# 如果不允许使用虚拟包,则进行查询以确保存在真实的包除非@resource.allow_virtual?执行([命令(:cmd), '-d', '0', '-e', error_level, '-y', install_options, :list, Wanted].compact)结尾应该 = @resource.should(:ensure)self.debug "确保 => #{should}"操作=:安装案例应该什么时候:最新current_package = self.query如果 current_package &&!current_package[:ensure].to_s.empty?操作 = update_commandself.debug "确保最新,所以使用 #{operation}"别的self.debug "确保最新,但包不存在,所以使用 #{:install}"操作=:安装结尾应该=零当真,假,符号# 经过应该=零别的# 添加包版本想要 += "-#{应该}"如果需要.scan(ARCH_REGEX)self.debug 在包中检测到 Arch 参数!-将 arch 移动到版本字符串的末尾"想要.gsub!(/(.+)(#{ARCH_REGEX})(.+)/,'\1\3\2')结尾current_package = self.query如果 current_package如果 rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(current_package[:ensure])) <0self.debug "将包 #{@resource[:name]} 从版本 #{current_package[:ensure]} 降级到 #{should}"操作 = :降级elsif rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(current_package[:ensure])) >0self.debug "将包 #{@resource[:name]} 从版本 #{current_package[:ensure]} 升级到 #{should}"操作 = update_command结尾结尾结尾# el-4 和 el-5 上的 Yum 在尝试安装无法识别的软件包时返回退出状态 0;# 确保我们捕获输出以检查错误.no_debug = if Facter.value(:operatingsystemmajrelease).to_i >5 then ["-d", "0"] else [] endcommand = [command(:cmd)] + no_debug + ["-e", error_level, "-y", install_options, operation, Want].compact输出 = 执行(命令)if output =~/^No package #{wanted} available\.$/引发 Puppet::Error,找不到包 #{wanted}"结尾# 如果指定了版本,则再次查询是否是匹配的版本如果应该is = self.query引发 Puppet::Error,找不到包 #{self.name}",除非是# FIXME: 即使应该 == :latest 我们是否应该引发异常# yum 将我们更新为@param_hash[:ensure] 以外的版本?vercmp_result = rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(is[:ensure]))引发 Puppet::Error,无法更新到版本 #{should},而是使用版本 #{is[:ensure]}"如果 vercmp_result != 0结尾结尾

这是一些严重的天鹅腿踢.这里有很多逻辑,对于一个包在 Yum 上的更复杂的用例,但要确保它适用于各种可用的 Yum 版本,包括 RHEL 4 和 5.

逻辑是这样分解的:我们没有指定版本,所以我们不需要检查要安装的版本.只需使用指定的默认选项运行 yum install tr​​ee

Debug: Package[tree](provider=yum): Ensuring =>展示调试:执行:'/usr/bin/yum -d 0 -e 0 -y install tr​​ee'注意:/Stage[main]/Main/Package[tree]/ensure: created

Ta-dah,已安装.

I am new to Puppet, but understand the concepts quite well. Puppet Manifests call Puppet Modules and the Modules perform the actual task.

I am trying to understand what happens at the Puppet Module layer. How does the command actually execute? Taking the example of the following, what commands are actually passed on to the operating system? Also, where is that defined?

package { 'ntp':
  ensure => installed,
}

解决方案

Summary: Puppet determines the commands that need to be run, based on the facts of the system and the configuration within Puppet itself.

So, when Puppet compiles the catalog to run on it's system it looks like the following:

"I need to install a Pacakge resource, called ntp. I am CentOS system, of the RedHat family. By default on RedHat, I use the yum command. So I need to run yum install ntp"

Longer Explanation

The way that Puppet knows the commands to run and how to run them is known as the Resource Abstraction Layer.

When it's all boiled down, Puppet is not doing anything magical: the commands that are being run on the system are the same commands that would be run by a human operator.

Maybe Puppet has figured out a clever way to do it, and takes into account obscure bugs and gotchas for the platform you're on, or is raising an error because what you're trying to do contains a spelling mistake or similar.

But eventually, the action has to actually be performed using the systems actual applications and tooling.

That's where the RAL actually comes in. It's the biggest layer of abstraction in Puppet: turning all interactions with the base system into a consistent interface.

In the example you give, packages are fairly simple. The concept of installing a package is (mostly) the same to pretty much every operating system in at least the last two decades:

packagesystemtool keywordforinstall packagename

Generally, the install keyword is install, but there are a few exceptions. BSD's pkg which uses pkg add for example.

However: the actual attributes that can be managed in that package can vary a lot:

  • Can you specify the version?
  • Can you downgrade that version?
  • If the package is already installed, do you need to specify a different command to upgrade it?

A huge swath of other optional parameters such as proxy information, error logging level.

The RAL allows the user to define the characteristics of a resource regardless of the implementation in a consistent way:

type { 'title':  
  attribute => 'value',
}

Every resource follows the same syntax:

  • A resource type (eg. user, package, service, file)
  • Curly braces to define the resource block.
  • A title, separated from the body of the resource with a colon A body consisting of attributes and value pairs

So our package declaration looks like this:

package {'tree':  
  ensure => 'present',
}

The RAL can handle that behavior on every platform that has been defined, and support different package features where available, all in a well-defined way, hidden from the user by default.

The best metaphor I've heard for the RAL is it is the Swan gliding along on the lake on the Lake:

When you look at a swan on a body of water, it looks elegant and graceful, gliding along. It barely looks like it's working at all.

What's hidden from the eye is the activity going on beneath the water’s surface. That swan is kicking it's webbed feet, way less gracefully that it looks up top: The actual command Puppet is running is the kicking legs under the water.

Ok, enough background, you're probably asking...

How does it actually work? The RAL splits all resources on the system into two elements:

  • Types: High-level Models of the valid attributes for a resource
  • Providers: Platform-specific implementation of a type

This lets you describe resources in a way that can apply to any system. Each resource, regardless of what it is, has one or more providers. Providers are the interface between the underlying OS and the resource types.

Generally, there will be a default provider for a type, but you can specify a specific provider if required.

For a package, the default provider will be the default package provider for a system: yum for RHEL, apt for Debian, pkg for BSD etc. This is determined by a, which takes the facts from the system.

For example, the yum provider has the following:

defaultfor :osfamily => :redhat

But you might want to install a pip package, or a gem. For this you would specify the provider, so it would install it with a different command:

package {'tree':  
  ensure   => 'present',
  provider => 'pip', 
}

This would mean we're saying to the RAL: "Hey, I know yum is the default to install a package, but this is a python package I need, so I'm telling you to use pip instead"

The most important resources of an attribute type are usually conceptually the same across operating systems, regardless of how the actual implementations differ.

Like we said, most packages will be installed with package installer install packagename

So, the description of a resource can be abstracted away from its implementation:

Puppet uses the RAL to both read and modify the state of resources on a system. Since it's a declarative system, Puppet starts with an understanding of what state a resource should have.

To sync the resource, it uses the RAL to query the current state, compare that against the desired state, and then use the RAL again to make any necessary changes. It uses the tooling to get the current state of the system and then figures out what it needs to do to change that state to the state defined by the resource.

When Puppet applies the catalog containing the resource, it will read the actual state of the resource on the target system, compare the actual state to the desired state, and, if necessary, change the system to enforce the desired state.

Let's look at how the RAL will manage this:

  • We've given the type as package.
  • The title/name of the package is ntp
  • I'm running this on a RHEL7 system, so the default provider is yum.
  • yum is a "child" provider of rpm: it uses the RPM command to check if the package is installed on the system. (This is a lot faster than running "yum info", as it doesn't make any internet calls, and won't fail if a yumrepo is failing)
  • The install command however, will be yum install

So previously we talked about how Puppet uses the RAL to both read and modify the state of resources on a system.

The "getter" of the RAL is the self.instances method in the provider.

Depending on the resource type, this is generally done in one of two ways:

  • Read a file on disk, iterate through the lines in a file and turn those into resources
  • Run a command on the terminal, break the stdout into lines, turn those into hashes which become resources

The rpm instances step goes with the latter. It runs rpm -qa with some given flags to check what packages are on the system:

def self.instances  
    packages = []

    # list out all of the packages
    begin
      execpipe("#{command(:rpm)} -qa #{nosignature} #{nodigest} --qf '#{self::NEVRA_FORMAT}'") { |process|
        # now turn each returned line into a package object
        process.each_line { |line|
          hash = nevra_to_hash(line)
          packages << new(hash) unless hash.empty?
        }
      }
    rescue Puppet::ExecutionFailure
      raise Puppet::Error, "Failed to list packages", $!.backtrace
    end

    packages
  end

So it's running /usr/bin/rpm -qa --nosignature --nodigest --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n', then taking the stdout from that command, looping through each line of output from that, and using the nevra_to_hash method to turn the lines of STDOUT it into a hash.

self::NEVRA_REGEX  = %r{^(\S+) (\S+) (\S+) (\S+) (\S+)$}  
self::NEVRA_FIELDS = [:name, :epoch, :version, :release, :arch]

private  
  # @param line [String] one line of rpm package query information
  # @return [Hash] of NEVRA_FIELDS strings parsed from package info
  # or an empty hash if we failed to parse
  # @api private
  def self.nevra_to_hash(line)
    line.strip!
    hash = {}

    if match = self::NEVRA_REGEX.match(line)
      self::NEVRA_FIELDS.zip(match.captures) { |f, v| hash[f] = v }
      hash[:provider] = self.name
      hash[:ensure] = "#{hash[:version]}-#{hash[:release]}"
      hash[:ensure].prepend("#{hash[:epoch]}:") if hash[:epoch] != '0'
    else
      Puppet.debug("Failed to match rpm line #{line}")
    end

    return hash
  end

So basically it's a regex on the output, then turns those bits from the regex into the given fields.

These hashes become the current state of the resource.

We can run --debug to see this in action:

Debug: Prefetching yum resources for package  
Debug: Executing: '/usr/bin/rpm --version'  
Debug: Executing '/usr/bin/rpm -qa --nosignature --nodigest --qf '%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n''  
Debug: Executing: '/usr/bin/rpm -q ntp --nosignature --nodigest --qf %{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n'  
Debug: Executing: '/usr/bin/rpm -q ntp --nosignature --nodigest --qf %{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\n --whatprovides'  

So it uses the RAL to fetch the current state. Puppet is doing the following:

  • Hmm, this is a package resource titled 'ntp' on a RHEL system, so I should use RPM
  • Let's get the current state of the RPM packages installed (eg. the instances method
  • ntp isn't here...
  • So we need ntp to be installed
  • the Yum provider then specifies the command required to install.

There's a lot of logic here:

def install  
    wanted = @resource[:name]
    error_level = self.class.error_level
    update_command = self.class.update_command
    # If not allowing virtual packages, do a query to ensure a real package exists
    unless @resource.allow_virtual?
      execute([command(:cmd), '-d', '0', '-e', error_level, '-y', install_options, :list, wanted].compact)
    end

    should = @resource.should(:ensure)
    self.debug "Ensuring => #{should}"
    operation = :install

    case should
    when :latest
      current_package = self.query
      if current_package && !current_package[:ensure].to_s.empty?
        operation = update_command
        self.debug "Ensuring latest, so using #{operation}"
      else
        self.debug "Ensuring latest, but package is absent, so using #{:install}"
        operation = :install
      end
      should = nil
    when true, false, Symbol
      # pass
      should = nil
    else
      # Add the package version
      wanted += "-#{should}"
      if wanted.scan(ARCH_REGEX)
        self.debug "Detected Arch argument in package! - Moving arch to end of version string"
        wanted.gsub!(/(.+)(#{ARCH_REGEX})(.+)/,'\1\3\2')
      end

      current_package = self.query
      if current_package
        if rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(current_package[:ensure])) < 0
          self.debug "Downgrading package #{@resource[:name]} from version #{current_package[:ensure]} to #{should}"
          operation = :downgrade
        elsif rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(current_package[:ensure])) > 0
          self.debug "Upgrading package #{@resource[:name]} from version #{current_package[:ensure]} to #{should}"
          operation = update_command
        end
      end
    end

    # Yum on el-4 and el-5 returns exit status 0 when trying to install a package it doesn't recognize;
    # ensure we capture output to check for errors.
    no_debug = if Facter.value(:operatingsystemmajrelease).to_i > 5 then ["-d", "0"] else [] end
    command = [command(:cmd)] + no_debug + ["-e", error_level, "-y", install_options, operation, wanted].compact
    output = execute(command)

    if output =~ /^No package #{wanted} available\.$/
      raise Puppet::Error, "Could not find package #{wanted}"
    end

    # If a version was specified, query again to see if it is a matching version
    if should
      is = self.query
      raise Puppet::Error, "Could not find package #{self.name}" unless is

      # FIXME: Should we raise an exception even if should == :latest
      # and yum updated us to a version other than @param_hash[:ensure] ?
      vercmp_result = rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(is[:ensure]))
      raise Puppet::Error, "Failed to update to version #{should}, got version #{is[:ensure]} instead" if vercmp_result != 0
    end
  end

This is some serious Swan leg kicking. There's a lot of logic here, for the more complex use case of a package on Yum, but making sure it works on the various versions of Yum avaliable, including RHEL 4 and 5.

The logic is broken down thusly: We haven't specified a version, so we don't need to check what version to install. Simply run yum install tree with the default options specified

Debug: Package[tree](provider=yum): Ensuring => present  
Debug: Executing: '/usr/bin/yum -d 0 -e 0 -y install tree'  
Notice: /Stage[main]/Main/Package[tree]/ensure: created  

Ta-dah, installed.

这篇关于puppet 如何向操作系统发送命令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆