加载中

构建集成的技巧

本节为开发者提供了一系列改进集成开发的技巧。它结合了提示、指南、建议和诀窍。本文档可能会根据整个平台(Elastic Package Registry、Elastic Agent 和 Kibana)的业务或技术需求在未来进行调整。

elastic-package 是一个用 Go 编写的命令行工具,用于开发 Elastic 集成包。它有助于对包进行 lint 检查、格式化、测试和构建。这是开发集成的官方构建工具。请参阅“入门”部分以快速上手并了解其功能。

要使用与 CI 所使用的版本(定义在 go.mod 中)一致的 elastic-package 版本,请使用以下命令(在 Integrations 仓库中)

$ go build github.com/elastic/elastic-package
$ ./elastic-package help
		
  1. 将初始版本设置为 0.1.0

    将集成标记为较低的版本(如 0.0.1)意味着它仍处于非常早期的阶段,且很可能根本无法工作。它可能只是部分开发完成。

  2. 为集成选择一到两个类别。

    可用类别列表位于 Package Registry 源码中:https://github.com/elastic/package-registry/blob/1dd3e7c4956f7e34809bb87acae50b2a63cd7ad0/packages/package.go#L29-L55

  3. 确保 Kibana 的版本条件设置为 +^7.10.0+ 而不是 >=7.10.0。否则,该包也会出现在 8.0.0 中,但无法确定它是否与 >= 8.0.0 兼容。

    conditions:
      kibana.version: '^7.10.0'
    		
  4. 设置正确的包所有者(Github 团队或个人帐户)

    团队的最佳候选者:elastic/integrationselastic/security-service-integrations

    相应地更新 .github/CODEOWNERS 文件。

最重要的建议是:先收集数据!从收集样本数据或从设备(虚拟或其他)生成数据开始。然后,将这些数据存储在文件中以供日后参考。这些数据可以加载到 Kibana 实例中进行检查并开始创建仪表板,也可以用于流水线测试。

修改仓库的流程是:fork 它,然后从 fork 的副本向仓库创建 PR。fork 后,将其克隆到开发环境中。

elastic-package create package 命令会将创建的内容放入当前目录,因此请在 packages/ 目录下运行它,或者事后移动新目录。

$ cd packages
$ elastic-package create package
Create a new package
? Package type: [Use arrows to move, type to filter]
  input
> integration
? Package name: (new_package)
? Version: (0.0.1)
? License: [Use arrows to move, type to filter]
> Elastic-2.0
  Apache-2.9
  None - I will add a license later.
? Package title: (New Package)
? Description: (This is a new package.)
? Categories: [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]
> [x] custom
? Kibana version constraint: (^8.11.4)
? Required Elastic subscription: [Use arrows to move, type to filter]
> basic
  gold
  platinum
  enterprise
? Github owner: (elastic/integrations)
? Owner type: [Use arrows to move, type to filter]
> elastic - Owned and supported by Elastic
  partner - Vendor-owned with support from Elastic
  community - Supported by the community

New package has been created: new_package
Done
		

这将创建真正可用的目录结构和默认文件。

$ cd new_package
$ find .
.
./manifest.yml
./docs
./docs/README.md
./img
./img/sample-screenshot.png
./img/sample-logo.svg
./LICENSE.txt
./changelog.yml
		

新数据流只能在包目录内创建,因此在创建数据流时,请确保位于新目录中。

$ cd new_package
$ elastic-package create data-stream
Create a new data stream
? Data stream name: (new_data_stream)
? Data stream title: (New Data Stream)
? Type: [Use arrows to move, type to filter]
> logs
  metrics
New data stream has been created: new_data_stream
Done
		

这将创建一个带有默认摄取流水线的新数据流目录结构。所有更新仅存在于新的 data_stream 目录中。

$ find data_stream
data_stream
data_stream/new_data_stream
data_stream/new_data_stream/elasticsearch
data_stream/new_data_stream/manifest.yml
data_stream/new_data_stream/agent
data_stream/new_data_stream/agent/stream
data_stream/new_data_stream/agent/stream/stream.yml.hbs
data_stream/new_data_stream/fields
data_stream/new_data_stream/fields/base-fields.yml
		

如果数据流正在处理日志,将其命名为 log 是个好主意,因为它简短且具有描述性。

有时日志的格式需要特殊解析,例如 key=value msg="something with spaces" 日志。可以使用 Painless 脚本来处理这种情况。这是一个处理器示例

- script:
    tag: script_kv_parse
    description: Parse key/value pairs from message.
    lang: painless
    source: >-
      ctx["stormshield"] = new HashMap();

      def kvStart = 0;
      def kvSplit = 0;
      def kvEnd = 0;
      def inQuote = false;

      for (int i = 0, n = ctx["message"].length(); i < n; ++i) {
        char c = ctx["message"].charAt(i);
        if (c == (char)'"') {
          inQuote = !inQuote;
        }
        if (inQuote) {
          continue;
        }

        if (c == (char)'=') {
          kvSplit = i;
        }
        if (c == (char)' ' || (i == n - 1)) {
          if (kvStart != kvSplit) {
            def key = ctx["message"].substring(kvStart, kvSplit);
            def value = ctx["message"].substring(kvSplit + 1, i).replace("\"", "");
            ctx["stormshield"][key] = value;
          }

          kvStart = i + 1;
          kvSplit = i + 1;
        }
      }
		

在使用 Painless 中的函数时,需要先定义函数。以下是使用 Painless 重命名字段而不是使用 rename 处理器的示例

- script:
    tag: expand_dynamic_fields
    description: Expands some dynamic fields.
    lang: painless
    source: >-
      void handleMove(Map context, String namespace) {
          if (context.containsKey("_temp_") && ! context.containsKey("integration")) {
              context["integration"] = new HashMap();
              context["integration"]["logtype"] = context["_temp_"]["logtype"];
              context["_temp_"].remove("logtype");
          }

          context["integration"][namespace] = context["_temp_"];
          context.remove("_temp_");
      }

      handleMove(ctx, ctx._temp_.logtype);
		

如果代码可以通过重构来使用 forEach 循环,则可能不需要函数。

包根目录和 data_stream 目录中的 _dev 目录包含用于控制包构建和测试某些方面的文件。

docs/README.md 文件通常由 _dev/build/docs/README.md 自动生成,该文件还会处理一些 Go 格式指令,用于将字段信息、示例事件和输入文档添加到文档中。

对于系统测试,_dev/deploy 目录控制如何运行服务部署。

示例 _dev/deploy/docker/docker-compose.yml

version: "2.3"
services:
  integration-udp:
    image: docker.elastic.co/observability/stream:v0.16.0
    volumes:
      - ./sample_logs:/sample_logs:ro
    command: log --start-signal=SIGHUP --delay=5s --addr elastic-agent:5144 -p=udp /sample_logs/integration.log
		

示例日志可以放在 _dev/deploy/docker/sample_logs/integration.log

<13>1 2024-03-08T10:14:08+00:00 integration-1 serverd - - - id=firewall time="2024-03-08 10:14:08" fw="integration-1" tz=+0000 startime="2024-03-08 10:14:08" error=0 user="admin" address=192.168.197.1 sessionid=1 msg="example syslog line" logtype="server"
<13>1 2024-03-08T10:14:08+00:00 integration-1 serverd - - - id=firewall time="2024-03-08 10:14:08" fw="integration-1" tz=+0000 startime="2024-03-08 10:14:08" error=0 user="admin" address=192.168.197.1 sessionid=1 msg="example syslog line 2" logtype="server"
		

这两个文件将共同启动一个服务,将示例日志写入端口 5144 上的 UDP 套接字。Elastic Agent 将在此时监听数据,并在系统测试中处理它。

要通过 Kibana/Agent/Fleet 策略配置集成,请使用输入信息更新 data_stream/log/manifest.yml 文件。以下是如何定义参数和接受变量的示例

title: "Integration logs"
type: logs
streams:
  - input: udp
    title: Integration UDP logs
    description: Collect UDP logs
    template_path: udp.yml.hbs
    vars:
      - name: tags
        type: text
        title: Tags
        multi: true
        required: true
        show_user: false
        default:
          - forwarded
      - name: udp_host
        type: text
        title: Listen Address
        description: The bind address to listen for UDP connections. Set to `0.0.0.0` to bind to all available interfaces.
        multi: false
        required: true
        show_user: true
        default: localhost
      - name: udp_port
        type: integer
        title: Listen Port
        description: The UDP port number to listen on.
        multi: false
        required: true
        show_user: true
        default: 514
      - name: preserve_original_event
        required: true
        show_user: true
        title: Preserve original event
        description: Preserves a raw copy of the original event, added to the field `event.original`.
        type: bool
        multi: false
        default: false
      - name: udp_options
        type: yaml
        title: Custom UDP Options
        multi: false
        required: false
        show_user: false
        default: |
          #read_buffer: 100MiB
          #max_message_size: 50KiB
          #timeout: 300s
        description: Specify custom configuration options for the UDP input.
      - name: processors
        type: yaml
        title: Processors
        multi: false
        required: false
        show_user: false
        description: >
          Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://esdocs.cn/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.

      - name: tz_offset
        type: text
        title: Timezone offset (Country/City or +HH:mm format)
        required: false
        show_user: false
		

这配置了 Kibana 添加集成表单的标签、输入框、描述以及 template_path 应该是什么。template_path 是一个 Handlebars 模板文件,用于配置 Agent 策略,该策略由 filebeat 摄取,并将打开配置的端口并通过 syslog 处理器传递信息。

使用上述配置的示例文件可以放在 data_stream/log/agent/stream/udp.yml.hbs

host: "{{udp_host}}:{{udp_port}}"
tags:
{{#if preserve_original_event}}
  - preserve_original_event
{{/if}}
{{#each tags as |tag i|}}
  - {{tag}}
{{/each}}
{{#contains "forwarded" tags}}
publisher_pipeline.disable_host: true
{{/contains}}
processors:
- add_locale: ~
{{#if preserve_original_event}}
- copy_fields:
     fields:
       - from: message
         to: event.original
{{/if}}
- syslog:
    field: message
    format: rfc5424
{{#if tz_offset}}
    timezone: "{{tz_offset}}"
{{/if}}
{{#if processors}}
{{processors}}
{{/if}}
		

流水线测试是让处理器工作并解决 Painless 错误的最佳方式。

在创建带有映射的字段以将数据发送到堆栈时,创建一个带有嵌套字段的 data_stream/log/fields/fields.yml 文件。此信息有时可以从文档网站上抓取。这是一个示例

- name: integration
  type: group
  fields:
    - name: logtype
      type: keyword
      description: The specific type of log this is from.
    - name: alarm
      type: group
      fields:
        - name: action
          type: keyword
          description: 'Behavior associated with the filter rule.  Value: pass or block'
        - name: alarmid
          type: keyword
          description: 'Alarm ID Decimal format. Example: "85"'
        - name: class
          type: keyword
          description: 'Information about the alarms category. String of characters in UTF-8 format. Example: protocol, system, filter'
		

对于第一个仪表板,请查看现有的仪表板以供参考。克隆现有的仪表板作为起点。已安装的仪表板是 Managed(托管的),不能直接修改,但可以克隆然后编辑。

要导出仪表板以包含在集成中,请使用 elastic-package export dashboards。使用一致的模式命名仪表板,如 [Integration Name] Overview。在名称中添加 -- export this one 可以使其在导出时更容易找到。

运行 elastic-package export dashboards 时,它会列出所有仪表板并允许过滤。使用箭头键导航,空格键选择,回车键确认。导出的仪表板将保存为类似 kibana/dashboards/integration-88888888-4444-4444-4444-cccccccccccc.json 的文件(带有实际的 UUID)。编辑该文件以从标题中删除任何 -- export this one

如果以后需要对仪表板进行编辑,可能需要用新的导出文件完全替换该文件,因为如果需要克隆,所有的 UUID 都会改变。

在仪表板视图中时,请创建一个数据过滤器,否则 elastic-package check 将无法通过仪表板检查。在 KQL 搜索框旁边有一个 + 按钮,用于此目的。一个好的初始过滤器是 data_stream.dataset : integration-name.log

可以使用 elastic-package edit dashboards 编辑现有的仪表板。默认情况下,仪表板是只读的。此子命令会删除此标志,以便可以对其进行编辑和导出。

流水线应在默认摄取流水线的顶级 on_failure 部分中包含这些处理器

on_failure:
  - set:
      field: event.kind
      value: pipeline_error
  - append:
      field: error.message
      value: 'Processor {{{_ingest.on_failure_processor_type}}} with tag {{{_ingest.on_failure_processor_tag}}} in pipeline {{{_ingest.pipeline}}} failed with message: {{{_ingest.on_failure_message}}}'
		

任何可能失败的处理器都必须包含一个 tag。如果没有标签,则无法在错误消息中识别失败的处理器。

# With a tag:

Processor conditional with tag grok_test in pipeline default-1711726648444819000 failed with message: cannot access method/field [foo] from a null def reference

# Without a tag:

Processor conditional with tag  in pipeline default-1711726648444819000 failed with message: cannot access method/field [foo] from a null def reference
		

当处理器失败时,将调用 on_failure 处理程序,并发生两件事

  1. 事件的 event.kind 字段将设置为 pipeline_error
  2. 描述性消息将附加到 error.message。此消息将包括处理器类型、处理器标签、发生错误的流水线以及失败消息。

错误消息示例

Processor grok with tag grok_test in pipeline default-1711726615736144000 failed with message: Provided Grok expressions do not match field value: [abc]
Processor conditional with tag grok_test in pipeline default-1711726648444819000 failed with message: cannot access method/field [foo] from a null def reference
		

虽然 on_failure 处理程序可以直接添加到处理器中,但不应将它们用于处理错误消息。这成为问题的一个例子是处理器的条件(if 语句)失败时。在这种情况下,该处理器的 on_failure 将永远不会运行,而是会落到顶级 on_failure 处理程序。相反,它们应该用于在处理器失败时进行任何清理,例如删除字段。

grok 处理器非常强大,但它的配置方式可能会导致性能不佳或难以理解。

对于简单的模式或标记由空格分隔的模式,请考虑使用 dissect 处理器。dissect 处理器通常比 grok 处理器快 2-4 倍,并且根据所用模式的复杂性,速度甚至可以更快。

考虑 grok 模式

^Connection allowed from %{IP:source.ip} to %{IP:destination.ip} at %{TIMESTAMP:event.start}$
		

等效的 dissect 模式将是

Connection allowed from %{source.ip} to %{destination.ip} at %{event.start}
		

在某些情况下无法应用 dissect,例如

  • 需要多个模式
  • 模式中的某些标记是可选的
  • 需要将标记拆分为字段

其他说明

  • 如果用 dissect 替换 grok,请小心将字段提取为特定类型的模式。Dissect 仅提取为字符串,因此需要 convert 处理器。

在 dissect 无法工作且仍需要 grok 的情况下,如果可能,请考虑使用更简单的模式。

考虑来自 Cisco ASA 流水线的这个示例。这些是原始模式

patterns:
  - "Group <%{NOTSPACE:source.user.group.name}> User <%{CISCO_USER:source.user.name}> IP <%{IP:source.address}>"
  - "Group %{NOTSPACE:source.user.group.name} User %{CISCO_USER:source.user.name} IP %{IP:source.address}"
pattern_definitions:
  HOSTNAME: "\\b(?:[0-9A-Za-z][0-9A-Za-z-_]{0,62})(?:\\.(?:[0-9A-Za-z][0-9A-Za-z-_]{0,62}))*(\\.?|\\b)"
  IPORHOST: "(?:%{IP}|%{HOSTNAME})"
  CISCO_USER: (?:\*\*\*\*\*|(?:(?:LOCAL\\)?(?:%{HOSTNAME}\\)?%{USERNAME}\$?(?:@%{HOSTNAME})?(?:, *%{NUMBER})?))
		

简化后的模式

patterns:
  - '^Group <%{NOTBRACKET:source.user.group.name}> User <%{NOTBRACKET:source.user.name}> IP <%{NOTBRACKET:source.address}>'
  - '^Group %{NOTSPACE:source.user.group.name} User %{NOTSPACE:source.user.name} IP %{NOTSPACE:source.address}'
pattern_definitions:
  NOTBRACKET: "[^<>]+"
		

第一个模式使用尖括号来包含值(可能包含空格),因此创建了一个包含除尖括号之外的所有字符的模式定义。第二个模式使用空格来分隔字段,因此使用了 NOTSPACE 模式来捕获字段值。这种简化的结果是,之前复杂的 HOSTNAMEIPORHOSTCISCO_USER 模式现在可以删除了。

大多数 grok 匹配整个字段。在这些情况下,应使用开始和结束锚点(^$)将模式锚定在整个字符串上。这对性能尤为重要,因为如果模式无法匹配字符串,它会尝试在字段的子字符串中寻找匹配项。

elastic-package build
	Builds the package. Also useful for re-rendering the README.

elastic-package check
	Runs the formatter and linter against the package. Also checks if the README has been updated.

Note: Chain build and check together and run them in that order. Check sometimes requires a package being built first.

elastic-package stack up -vd [--version VERSION]
	Bring the stack up. "-vd" is short for verbose output and detach from containers when done. Specify version if desired, such as '--version 8.12.1'.

elastic-package stack down
	Bring down the stack. Destroys containers.

elastic-package stack up -vd --services package-registry
	Recreates the package-registry container. Use after the build command to make the registry aware of your new package. Beware: If you install the package in Kibana, you can no longer update the package at that version. Increment the package version to make new packages show up. Remember to revert the version back to the original before submitting a PR.

elastic-package test [pipeline|static|asset|policy|system] -v
    Run package tests. Make sure you are in the package's directory. A stack needs to be running for this to work. It is not necessary to build the package for the tests. Pipeline tests are great for rapid iteration given how quickly they run and how comprehensive the validations are. System tests are great for end-to-end tests and validating any changes made to Filebeat (this includes the *.yml.hbs files in data_stream/NAME/agent/stream).

elastic-package test [pipeline|static|asset|policy|system] -v -g
    Regenerate the expected files (pipeline test) or sample_event.json (system system) after the tests run. Ensure that the output is expected before committing changes, as regressions could accidentally become the new expected behavior.
		
  1. 开发集成并将更改传播到包注册表时,首先重建包

    $ cd packages/apache
    $ elastic-package build
    		

    然后,重建并重新部署 Package Registry

    在 Integrations 仓库中执行以下命令很重要。

    $ elastic-package stack up -v -d --services package-registry
    		

    说明:重建并重启带有 Package Registry 的容器比使用挂载的卷要快得多。

  1. Ping “Team:Integrations”。

    使用团队标签通知相关团队成员有关传入的拉取请求。

  1. 配置选项的描述应尽可能简短。

    仅包含有关配置选项的有意义的信息。

    好的候选内容:对产品配置的引用、接受的字符串值、解释。

    不好的候选内容:从 A、B、C、D……X、Y、Z 数据集中收集指标。

  2. 描述应易于阅读。

    改写句子,如:Collect foo_Bar3 metrics 改为 Collect Foo Bar metrics

  3. 描述应易于理解。

    简化句子,如果不需要,不要提供有关输入的信息。

    不好的候选内容:收集应用程序日志(日志输入)

    好的候选内容:收集应用程序日志收集应用程序的标准日志

  4. 截图描述的大小写很重要。

    这些描述会在 Kibana UI 中可视化。保持它们整洁一致会创造更好的用户体验。

    不好的候选内容:filebeat running on ec2 machine

    好的候选内容:Filebeat running on AWS EC2 machine

  5. 如果一个包依赖于仅在特定堆栈或 beats 版本中可用的特性或字段,则应在包的 manifest.yml 中相应地调整 kibana.version 条件

    conditions:
       kibana.version: '^8.7.0'
    		
    注意

    具有上述条件的包版本将仅在 Kibana 版本 >=8.7.0 中可用

  6. 如果一个包依赖于仅在特定 Elastic Agent 版本中可用的特性,请使用 Agent 版本条件。根据您的需求有两种方法

    包级别 — 限制所有输入;需要 Kibana 9.4 或更高版本

    conditions:
      kibana
        version: '^9.4.0'
      agent:
        version: '^9.3.0'
    		

    输入模板级别 — 有条件地为满足版本约束的代理呈现配置块(在 .hbs 流模板中)

    {{#semverSatisfies _meta.agent.version "^9.3.0"}}
    program: |
      ...
    {{/semverSatisfies}}
    		

    当所有输入都需要更新的 Agent 时,请使用包级别方法。当只有部分配置使用更新的 Agent 功能,并且您希望包保持与旧版 Agent 可用性时,请使用模板级别方法。

    请参阅 Agent 版本条件 以获取完整详细信息。

    注意

    使用未发布的 Kibana 版本更改仪表板和可视化可能不安全,因为 Kibana 团队可能会更改 Kibana 代码,并可能更改数据模型。无法保证您的更改不会在新版 Kibana 发布时被破坏。

  1. 在本地运行 elastic-package checkelastic-package test

    要验证集成是否按预期工作,请执行与 CI 相同的步骤

    $ cd packages/apache
    $ elastic-package check -v
    $ elastic-package test -v
    		

    请记住,elastic-package test 命令需要运行一个实时集群。

  1. 删除空的字段文件。

    如果字段文件(例如 package-fields.yml)不包含任何字段定义或仅定义了根目录,则可以将其删除。

© . This website operates independently and is not affiliated with or endorsed by Elasticsearch B.V. All brand names, logos, and trademarks are the property of their respective owners.