软件配置

有多种选项可以配置堆栈中服务器上运行的软件。这些选项大致可以分为以下几类:

  • 自定义镜像构建

  • 用户数据启动脚本和 cloud-init

  • 软件部署资源

本节将描述这些选项中的每一个,并提供在堆栈中使用它们的示例。

镜像构建

影响服务器上配置软件的第一个机会是使用自定义构建的镜像启动它们。您可能需要这样做有几个原因,包括:

  • 启动速度 - 由于所需的软件已经存在于镜像中,因此无需在启动时下载和安装任何内容。

  • 启动可靠性 - 软件下载可能由于瞬态网络故障和不一致的软件仓库等原因而失败。

  • 测试验证 - 自定义构建的镜像可以在投入生产环境之前在测试环境中进行验证。

  • 配置依赖性 - 启动后配置可能依赖于已经安装并启用的代理。

有许多工具可用于构建自定义镜像,包括:

本指南中的示例需要自定义镜像,将使用 diskimage-builder

用户数据启动脚本和 cloud-init

启动服务器时,可以指定传递给该服务器的用户数据内容。此用户数据要么从配置的 config-drive 提供,要么从 Metadata 服务 提供

此用户数据如何被消耗取决于启动的镜像,但默认云镜像中最常用的工具是 cloud-init

无论镜像是否使用 cloud-init,都应该能够在 user_data 属性中指定一个 shell 脚本,并在服务器启动期间执行它

resources:

  the_server:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data: |
        #!/bin/bash
        echo "Running boot script"
        # ...

注意

调试这些脚本时,使用 nova console-log <server-id> 查看启动日志通常很有用,以查看启动脚本的执行进度。

通常需要根据堆栈中的参数或资源设置变量值。可以使用 str_replace intrinsic 函数来完成此操作

parameters:
  foo:
    default: bar

resources:

  the_server:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data:
        str_replace:
          template: |
            #!/bin/bash
            echo "Running boot script with $FOO"
            # ...
          params:
           $FOO: {get_param: foo}

警告

如果执行堆栈更新并且 user_data 的内容有任何更改,则服务器将被替换(删除并重新创建),以便可以在新服务器上运行修改后的启动配置。

当这些脚本增长时,它们可能难以在模板中维护,因此可以使用 get_file intrinsic 函数将脚本保存在单独的文件中

parameters:
  foo:
    default: bar

resources:

  the_server:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data:
        str_replace:
          template: {get_file: the_server_boot.sh}
          params:
            $FOO: {get_param: foo}

注意

str_replace 可以替换任何字符串,而不仅仅是以 $ 开头的字符串。但是,对于上面的示例,这样做很有用,因为可以通过传递环境变量来执行脚本文件进行测试。

选择 user_data_format

OS::Nova::Server user_data_format 属性确定 user_data 应该如何为服务器格式化。对于默认值 HEAT_CFNTOOLSuser_data 作为 heat-cfntools cloud-init 启动配置数据的一部分捆绑。虽然 HEAT_CFNTOOLSuser_data_format 的默认值,但它被认为是遗留的,RAWSOFTWARE_CONFIG 通常会更合适。

对于 RAW,user_data 将未经修改地传递给 Nova。对于启用了 cloud-init 的镜像,以下都是有效的 RAW user_data

resources:

  server_with_boot_script:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data_format: RAW
      user_data: |
        #!/bin/bash
        echo "Running boot script"
        # ...

  server_with_cloud_config:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data_format: RAW
      user_data: |
        #cloud-config
        final_message: "The system is finally up, after $UPTIME seconds"

对于 SOFTWARE_CONFIGuser_data 作为软件配置数据的一部分捆绑,并且元数据是从相关的 软件部署资源 派生的。

信号和等待条件

通常需要暂停堆栈资源的进一步创建,直到启动配置脚本通知它已达到某种状态。这通常是为了通知服务现在处于活动状态,或者传递生成的数据,这些数据是另一个资源所需要的。资源 OS::Heat::WaitConditionOS::Heat::SwiftSignal 都使用不同的技术和权衡来执行此功能。

OS::Heat::WaitCondition 作为对 Orchestration API 资源信号的调用实现。使用针对仅限定为等待条件处理资源的用户帐户的凭据创建令牌。在创建句柄时创建该用户,并与属于堆栈的项目相关联,该项目位于专门用于编排服务的身份域中。

发送信号是一个简单的 HTTP 请求,例如使用 curl 的示例

curl -i -X POST -H 'X-Auth-Token: <token>' \
     -H 'Content-Type: application/json' -H 'Accept: application/json' \
     '<wait condition URL>' --data-binary '<json containing signal data>'

包含信号数据的 JSON 预计格式如下

{
  "status": "SUCCESS",
  "reason": "The reason which will appear in the 'heat event-list' output",
  "data": "Data to be used elsewhere in the template via get_attr",
  "id": "Optional unique ID of signal"
}

所有这些值都是可选的,如果未指定,则将设置为以下默认值

{
  "status": "SUCCESS",
  "reason": "Signal <id> received",
  "data": null,
  "id": "<sequential number starting from 1 for each signal received>"
}

如果 status 设置为 FAILURE,则资源(和堆栈)将进入 FAILED 状态,并将 reason 作为失败原因。

以下模板示例使用便利属性 curl_cli,该属性构建一个带有有效令牌的 curl 命令

resources:
  wait_condition:
    type: OS::Heat::WaitCondition
    properties:
      handle: {get_resource: wait_handle}
      # Note, count of 5 vs 6 is due to duplicate signal ID 5 sent below
      count: 5
      timeout: 300

  wait_handle:
    type: OS::Heat::WaitConditionHandle

  the_server:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data_format: RAW
      user_data:
        str_replace:
          template: |
            #!/bin/sh
            # Below are some examples of the various ways signals
            # can be sent to the Handle resource

            # Simple success signal
            wc_notify --data-binary '{"status": "SUCCESS"}'

            # Or you optionally can specify any of the additional fields
            wc_notify --data-binary '{"status": "SUCCESS", "reason": "signal2"}'
            wc_notify --data-binary '{"status": "SUCCESS", "reason": "signal3", "data": "data3"}'
            wc_notify --data-binary '{"status": "SUCCESS", "reason": "signal4", "id": "id4", "data": "data4"}'

            # If you require control of the ID, you can pass it.
            # The ID should be unique, unless you intend for duplicate
            # signals to overwrite each other.  The following two calls
            # do the exact same thing, and will be treated as one signal
            # (You can prove this by changing count above to 7)
            wc_notify --data-binary '{"status": "SUCCESS", "id": "id5"}'
            wc_notify --data-binary '{"status": "SUCCESS", "id": "id5"}'

            # Example of sending a failure signal, optionally
            # reason, id, and data can be specified as above
            # wc_notify --data-binary '{"status": "FAILURE"}'
          params:
            wc_notify: { get_attr: [wait_handle, curl_cli] }

outputs:
  wc_data:
    value: { get_attr: [wait_condition, data] }
    # this would return the following json
    # {"1": null, "2": null, "3": "data3", "id4": "data4", "id5": null}

  wc_data_4:
    value: { 'Fn::Select': ['id4', { get_attr: [wait_condition, data] }] }
    # this would return "data4"

OS::Heat::SwiftSignal 通过创建一个 Object Storage API 临时 URL 实现,该 URL 填充了带有 HTTP PUT 的信号数据。编排服务将轮询此对象,直到信号数据可用。使用对象版本控制来存储多个信号。

发送信号是一个简单的 HTTP 请求,例如使用 curl 的示例

curl -i -X PUT '<object URL>' --data-binary '<json containing signal data>'

上面的模板示例只需要将 type 更改为 swift 信号资源

resources:
  signal:
    type: OS::Heat::SwiftSignal
    properties:
      handle: {get_resource: wait_handle}
      timeout: 300

  signal_handle:
    type: OS::Heat::SwiftSignalHandle
  # ...

选择使用 OS::Heat::WaitConditionOS::Heat::SwiftSignal 将取决于几个因素

  • OS::Heat::SwiftSignal 依赖于 Object Storage API 的可用性

  • OS::Heat::WaitCondition 依赖于编排服务是否配置了专用的堆栈域(这可能取决于 Identity V3 API 的可用性)。

  • 保护信号 URL 使用令牌身份验证或秘密 webhook URL 的偏好。

软件配置资源

启动配置脚本也可以作为自己的资源进行管理。这允许配置一次定义并在多个服务器资源上运行。这些软件配置资源通过对 Orchestration API 的专用调用存储和检索。无法修改现有软件配置资源的内容,因此更改任何现有软件配置资源的堆栈更新将导致 API 调用以创建新的配置并删除旧的配置。

资源 OS::Heat::SoftwareConfig 用于存储由文本脚本表示的配置,例如

resources:
  boot_script:
    type: OS::Heat::SoftwareConfig
    properties:
      group: ungrouped
      config: |
        #!/bin/bash
        echo "Running boot script"
        # ...

  server_with_boot_script:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data_format: SOFTWARE_CONFIG
      user_data: {get_resource: boot_script}

资源 OS::Heat::CloudConfig 允许将 cloud-init cloud-config 表示为模板 YAML,而不是块字符串。这允许在构建 cloud-config 时包含 intrinsic 函数。这还确保 cloud-config 是有效的 YAML,尽管不会对有效的 cloud-config 进行进一步检查。

parameters:
  file_content:
    type: string
    description: The contents of the file /tmp/file

resources:
  boot_config:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        write_files:
        - path: /tmp/file
          content: {get_param: file_content}

  server_with_cloud_config:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data_format: SOFTWARE_CONFIG
      user_data: {get_resource: boot_config}

资源 OS::Heat::MultipartMime 允许将多个 OS::Heat::SoftwareConfigOS::Heat::CloudConfig 资源组合成一个 cloud-init 多部分消息

parameters:
  file_content:
    type: string
    description: The contents of the file /tmp/file

  other_config:
    type: string
    description: The ID of a software-config resource created elsewhere

resources:
  boot_config:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        write_files:
        - path: /tmp/file
          content: {get_param: file_content}

  boot_script:
    type: OS::Heat::SoftwareConfig
    properties:
      group: ungrouped
      config: |
        #!/bin/bash
        echo "Running boot script"
        # ...

  server_init:
    type: OS::Heat::MultipartMime
    properties:
      parts:
      - config: {get_resource: boot_config}
      - config: {get_resource: boot_script}
      - config: {get_param: other_config}

  server:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data_format: SOFTWARE_CONFIG
      user_data: {get_resource: server_init}

软件部署资源

在许多情况下,不希望在配置发生更改时替换服务器。资源 OS::Heat::SoftwareDeployment 允许在服务器的整个生命周期中添加或删除任意数量的软件配置。

为软件部署构建自定义镜像

OS::Heat::SoftwareConfig 资源用于存储软件配置,OS::Heat::SoftwareDeployment 资源用于将配置资源与一个服务器关联。group 属性在 OS::Heat::SoftwareConfig 上指定哪个工具将消耗配置内容。

OS::Heat::SoftwareConfig 能够定义 inputs 的模式,以及配置脚本支持哪些模式。输入映射到配置工具分配变量/参数的任何概念。

同样,outputs 映射到工具导出结构化数据的能力。对于不支持此功能的工具,始终可以将输出写入已知的文件路径以供 hook 读取。

资源 OS::Heat::SoftwareDeployment 允许将值分配给配置输入,并且资源保持在 IN_PROGRESS 状态,直到服务器向 heat 发出信号,指示配置脚本生成了哪些输出值(如果有)。

自定义镜像脚本

以下每个示例都需要使用自定义镜像启动服务器。以下脚本使用 diskimage-builder 创建稍后示例中所需的镜像

# Clone the required repositories. Some of these are also available
# via pypi or as distro packages.
git clone https://opendev.org/openstack/tripleo-image-elements
git clone https://opendev.org/openstack/heat-agents

# Install diskimage-builder from source
sudo pip install git+https://opendev.org/openstack/diskimage-builder

# Required by diskimage-builder to discover element collections
export ELEMENTS_PATH=tripleo-image-elements/elements:heat-agents/

# The base operating system element(s) provided by the diskimage-builder
# elements collection. Other values which may work include:
# centos7, debian, opensuse, rhel, rhel7, or ubuntu
export BASE_ELEMENTS="fedora selinux-permissive"
# Install and configure the os-collect-config agent to poll the metadata
# server (heat service or zaqar message queue and so on) for configuration
# changes to execute
export AGENT_ELEMENTS="os-collect-config os-refresh-config os-apply-config"


# heat-config installs an os-refresh-config script which will invoke the
# appropriate hook to perform configuration. The element heat-config-script
# installs a hook to perform configuration with shell scripts
export DEPLOYMENT_BASE_ELEMENTS="heat-config heat-config-script"

# Install a hook for any other chosen configuration tool(s).
# Elements which install hooks include:
# heat-config-cfn-init, heat-config-puppet, or heat-config-salt
export DEPLOYMENT_TOOL=""

# The name of the qcow2 image to create, and the name of the image
# uploaded to the OpenStack image registry.
export IMAGE_NAME=fedora-software-config

# Create the image
disk-image-create vm $BASE_ELEMENTS $AGENT_ELEMENTS \
     $DEPLOYMENT_BASE_ELEMENTS $DEPLOYMENT_TOOL -o $IMAGE_NAME.qcow2

# Upload the image, assuming valid credentials are already sourced
openstack image create --disk-format qcow2 --container-format bare \
    $IMAGE_NAME < $IMAGE_NAME.qcow2

注意

上面的脚本使用 diskimage-builder,请确保环境已经满足 diskimage-builder 的 requirements.txt 中的所有要求。

使用脚本进行配置

自定义镜像脚本 已经包含 heat-config-script 元素,因此构建的镜像已经具有使用 shell 脚本进行配置的能力。

配置输入映射到 shell 环境变量。脚本可以通过写入 $heat_outputs_path.output name 文件来与 heat 通信输出。请参阅以下示例,了解一个期望输入 foobar 并生成输出 result 的脚本。

resources:
  config:
    type: OS::Heat::SoftwareConfig
    properties:
      group: script
      inputs:
      - name: foo
      - name: bar
      outputs:
      - name: result
      config: |
        #!/bin/sh -x
        echo "Writing to /tmp/$bar"
        echo $foo > /tmp/$bar
        echo -n "The file /tmp/$bar contains `cat /tmp/$bar` for server $deploy_server_id during $deploy_action" > $heat_outputs_path.result
        echo "Written to /tmp/$bar"
        echo "Output to stderr" 1>&2

  deployment:
    type: OS::Heat::SoftwareDeployment
    properties:
      config:
        get_resource: config
      server:
        get_resource: server
      input_values:
        foo: fooooo
        bar: baaaaa

  server:
    type: OS::Nova::Server
    properties:
      # flavor, image etc
      user_data_format: SOFTWARE_CONFIG

outputs:
  result:
    value:
      get_attr: [deployment, result]
  stdout:
    value:
      get_attr: [deployment, deploy_stdout]
  stderr:
    value:
      get_attr: [deployment, deploy_stderr]
  status_code:
    value:
      get_attr: [deployment, deploy_status_code]

注意

一个配置资源可以与多个部署资源关联,并且每个部署可以指定 serverinput_values 属性的相同或不同的值。

如上述模板的 outputs 部分所示,result 配置输出值作为 deployment 资源上的一个属性可用。同样,捕获的 stdout、stderr 和 status_code 也作为属性可用。

使用 os-apply-config 配置

os-collect-configos-refresh-configos-apply-config 的 agent 工具链实际上可以单独使用,以将 heat 堆栈配置数据注入到运行自定义镜像的服务器中。

要使用这种方法,自定义镜像需要具备以下条件

  • 已安装所有软件依赖项

  • os-refresh-config 脚本在配置更改时执行

  • os-apply-config 模板将 heat 提供的配置数据转换为服务配置文件

项目 tripleo-image-elementstripleo-heat-templates 演示了这种方法。

使用 cfn-init 配置

使用 cfn-init hook 的唯一原因可能是迁移包含 AWS::CloudFormation::Init 元数据的模板,而无需完全重写配置元数据。此处包含它是因为它引入了许多新概念。

要使用 cfn-init 工具,需要将 heat-config-cfn-init 元素放在构建的镜像上,因此 自定义镜像脚本 需要进行以下修改

export DEPLOYMENT_TOOL="heat-config-cfn-init"

以前包含在资源元数据的 AWS::CloudFormation::Init 部分的配置数据,现在移动到配置资源(如以下示例所示)的 config 属性中

resources:

  config:
    type: OS::Heat::StructuredConfig
    properties:
      group: cfn-init
      inputs:
      - name: bar
      config:
        config:
          files:
            /tmp/foo:
              content:
                get_input: bar
              mode: '000644'

  deployment:
    type: OS::Heat::StructuredDeployment
    properties:
      name: 10_deployment
      signal_transport: NO_SIGNAL
      config:
        get_resource: config
      server:
        get_resource: server
      input_values:
        bar: baaaaa

  other_deployment:
    type: OS::Heat::StructuredDeployment
    properties:
      name: 20_other_deployment
      signal_transport: NO_SIGNAL
      config:
        get_resource: config
      server:
        get_resource: server
      input_values:
        bar: barmy

  server:
    type: OS::Nova::Server
    properties:
      image: {get_param: image}
      flavor: {get_param: flavor}
      key_name: {get_param: key_name}
      user_data_format: SOFTWARE_CONFIG

关于此模板示例,有几点需要注意

  • OS::Heat::StructuredConfig 类似于 OS::Heat::SoftwareConfig,不同之处在于 config 属性包含结构化的 YAML 而不是文本脚本。这对于许多其他配置工具(包括 ansible、salt 和 os-apply-config)非常有用。

  • cfn-init 没有输入的概念,因此 {get_input: bar} 充当一个占位符,该占位符将在创建部署资源时被 OS::Heat::StructuredDeploymentinput_values 值替换。

  • cfn-init 没有输出的概念,因此指定 signal_transport: NO_SIGNAL 将意味着部署资源将立即进入 CREATED 状态,而不是等待来自服务器的完成信号。

  • 该模板具有 2 个部署资源,它们使用不同的 input_values 部署相同的配置。这些资源在服务器上的部署顺序由每个资源 name 属性的值排序(10_deployment、20_other_deployment)决定。

使用 puppet 配置

puppet hook 使编写配置为 puppet 清单成为可能,这些清单将在无主环境中使用。

要将配置指定为 puppet 清单,需要将 heat-config-puppet 元素放在构建的镜像上,因此 自定义镜像脚本 需要进行以下修改

export DEPLOYMENT_TOOL="heat-config-puppet"
resources:

  config:
    type: OS::Heat::SoftwareConfig
    properties:
      group: puppet
      inputs:
      - name: foo
      - name: bar
      outputs:
      - name: result
      config:
        get_file: example-puppet-manifest.pp

  deployment:
    type: OS::Heat::SoftwareDeployment
    properties:
      config:
        get_resource: config
      server:
        get_resource: server
      input_values:
        foo: fooooo
        bar: baaaaa

  server:
    type: OS::Nova::Server
    properties:
      image: {get_param: image}
      flavor: {get_param: flavor}
      key_name: {get_param: key_name}
      user_data_format: SOFTWARE_CONFIG

outputs:
  result:
    value:
      get_attr: [deployment, result]
  stdout:
    value:
get_attr: [deployment, deploy_stdout]

这演示了 get_file 函数的使用,该函数将附加文件 example-puppet-manifest.pp 的内容,其中包含

file { 'barfile':
    ensure  => file,
    mode    => '0644',
    path    => '/tmp/$::bar',
    content => '$::foo',
}

file { 'output_result':
    ensure  => file,
    path    => '$::heat_outputs_path.result',
    mode    => '0644',
    content => 'The file /tmp/$::bar contains $::foo',
}