Alloy介绍以及使用

1. 简介

image
Alloy是一个指标收集器,用于收集、转换和发送指标数据。

2. 安装

官方文档链接

2.1 在linux上以二进制文件安装

  1. 下载
    访问 Alloy 的 GitHub Releases 页面:
    👉 https://github.com/grafana/alloy/releases
    在Assets中下载alloy-linux-amd64.zip文件
  2. 创建用户alloy
sudo useradd --shell /bin/false alloy
  1. 文件放入/home/alloy文件夹中,解压后,将二进制文件重命名为alloy
unzip <file>
  1. 添加执行权限
chmod +x alloy
  1. /etc/systemd/system下创建alloy.service文件
[Unit]
Description=Vendor-neutral programmable observability pipelines.
Documentation=https://grafana.com/docs/alloy/
Wants=network-online.target
After=network-online.target

[Service]
Restart=always
User=alloy
Environment=HOSTNAME=%H
WorkingDirectory=/home/alloy
ExecStart=/home/alloy/alloy run --storage.path=/home/alloy /home/alloy/config.alloy
ExecReload=/usr/bin/env kill -HUP $MAINPID
TimeoutStopSec=20s

[Install]
WantedBy=multi-user.target
  1. 管理命令
  • systemctl daemon-reload 重载配置文件
  • systemctl start alloy 启动
  • systemctl status alloy 查看状态
  • systemctl enable alloy.service 开机自启
  • systemctl restart alloy 重启
  • systemctl stop alloy 停止
  • journalctl -u alloy 查看日志

3. 配置文件

3.1 Alloy配置语法

Alloy 配置语法是声明式的,意味着你描述想要什么,而不是如何实现。解析器会自动评估元素之间的所有依赖关系,因此块和属性的顺序无关紧要。

注释

Alloy 配置文件支持单行 // 注释和块 /* */ 注释。

// 这是单行注释

/*
这是块注释
可以跨多行
*/

标识符

Alloy 语法中的标识符如果包含一个或多个 UTF-8 字母、数字或下划线,则视为有效。标识符不能以数字开头。

解析器使用标识符用于:

  1. 属性名称:键值对中的键。
  2. 组件名称:块名称的部分,如 prometheus.scrape 中的 prometheus
  3. 组件标签:用于区分组件实例的用户定义名称。
  4. 变量引用:在表达式和函数调用中使用的名称。

有效的标识符:

  • my_component
  • Component123
  • _private
  • métrica_café(支持 Unicode 字母)

无效的标识符:

  • 123component(以数字开头)
  • my-component(包含连字符)
  • my component(包含空格)
  • my.component(包含点号,有特殊含义)

属性和块

Alloy 配置使用两个主要语法元素构建:属性和块。属性设置单个值,而块对相关配置进行分组并创建组件实例。

属性

属性使用 ATTRIBUTE_NAME = ATTRIBUTE_VALUE 格式配置单个设置。解析器在组件配置期间评估属性以确定运行时行为。

你可以将属性放置为:

  1. 顶层设置:全局 Alloy 配置。
  2. 组件参数:组件块内的设置。
  3. 嵌套块设置:嵌套块内的配置。

不同属性上下文的示例:

// 顶层属性
log_level = "debug"

// 带有属性的组件块
prometheus.scrape "app" {
  targets         = [{ "__address__" = "localhost:9090" }]
  scrape_interval = "15s"

  // 带有属性的嵌套块
  basic_auth {
    username = "admin"
    password = sys.env("ADMIN_PASSWORD")
  }
}

ATTRIBUTE_NAME 必须是有效的 Alloy 标识符

ATTRIBUTE_VALUE 可以是:

  1. 常量值:字符串、数字、布尔值、数组或对象。
  2. 表达式:计算动态值的函数调用、组件引用或计算。

块使用花括号配置 Alloy 组件并组织相关设置。解析器处理块以创建和配置组件实例。

块结构:

  1. 块名称:标识组件类型(必需)。
  2. 块标签:用于区分多个实例的用户定义标识符(可选)。
  3. 块体:包含属性和嵌套块(必需)。

某些块在具有不同标签时可以在配置中多次出现:

// 同一组件类型的多个实例
prometheus.scrape "frontend" {
  targets = [{ "__address__" = "frontend:8080" }]
}

prometheus.scrape "backend" {
  targets = [{ "__address__" = "backend:9090" }]
}

嵌套块提供结构化配置:

prometheus.remote_write "production" {
  endpoint {
    url = "https://prometheus.example.com/api/v1/write"

    basic_auth {
      username = "metrics"
      password = local.file.credentials.content
    }
  }

  queue_config {
    capacity           = 10000
    max_samples_per_send = 2000
  }
}

示例

使用以下模式创建无标签块:

BLOCK_NAME {
  // 块体可以包含属性和嵌套的无标签块
  IDENTIFIER = EXPRESSION // 属性

  NESTED_BLOCK_NAME {
    // 嵌套块体
  }
}

使用以下模式创建带标签块:

// 创建带标签块的模式:
BLOCK_NAME "BLOCK_LABEL" {
  // 块体可以包含属性和嵌套的无标签块
  IDENTIFIER = EXPRESSION // 属性

  NESTED_BLOCK_NAME {
    // 嵌套块体
  }
}

块命名规则

解析器对块名称和标签执行特定规则:

块名称必须是以下之一:

  1. 有效的组件名称:点分隔的标识符,如 prometheus.scrapelocal.file
  2. 特殊配置块:用于全局设置的内置块,如 loggingtracing

块标签(需要时)必须:

  1. 有效的标识符:遵循与属性名称相同的规则。
  2. 双引号字符串:用双引号包裹,而不是单引号。
  3. 在作用域内唯一:相同类型的两个块不能有相同的标签。

展示正确块命名的示例:

// 组件名称: "local.file", 标签: "api_key"
local.file "api_key" {
  filename  = sys.env("API_KEY_PATH")
  is_secret = true
}

// 组件名称: "prometheus.scrape", 标签: "web_servers"
prometheus.scrape "web_servers" {
  targets      = discovery.kubernetes.services.targets
  metrics_path = "/metrics"
}

解析器验证以下内容:

  1. 组件名称存在:组件类型必须在 Alloy 中可用。
  2. 标签唯一:在同一组件类型内。
  3. 语法正确:遵循标识符和引号规则。

终止符

解析器需要终止符来分隔语句并确定表达式的结束位置。所有块和属性定义必须以换行符结束,Alloy 称之为终止符

换行符在以下情况下充当终止符:

  1. 完整表达式之后:任何值、计算或函数调用之后。
  2. 闭合分隔符之后])} 之后。
  3. 语句结尾之后:属性赋值或块定义之后。

解析器在其他上下文中忽略换行符,允许灵活的格式:

// 这种格式是有效的 - 额外的换行符会被忽略
local.file "example" {
  filename = "/path/to/file"


  is_secret = true


  // 注释也可以有额外的间距
}

// 表达式可以跨多行
targets = [
  { "__address__" = "server1:9090" },
  { "__address__" = "server2:9090" },
  { "__address__" = "server3:9090" }
]

格式化

Alloy 提供了内置的格式化工具以确保一致的代码风格。使用alloy fmt命令来格式化你的配置文件。

格式化工具会:

  • 标准化缩进
  • 删除不必要的空白
  • 确保一致的行结尾
  • 验证语法

3.2 组件

组件是 Alloy 的构建块。每个组件执行单一任务,例如检索密钥、收集指标或处理数据。

组件结构

每个组件都有两个主要部分:

  • 参数(Arguments): 配置组件行为的设置。这些是你在组件块内定义的属性和块。
  • 导出(Exports): 组件提供给其他组件使用的值。运行中的组件会生成这些导出,并且它们可能随时间变化。

当 Alloy 加载你的配置时:

  1. 它读取你在每个组件块中提供的参数。
  2. 它使用这些参数创建组件的运行实例。
  3. 组件开始工作,并可能在运行时更新其导出。
  4. 其他组件可以在它们的参数中引用这些导出。

组件语法

组件使用你前面学过的块语法。一般模式如下:

COMPONENT_NAME "LABEL" {
  // 参数(属性和嵌套块)
  attribute_name = "value"

  nested_block {
    setting = "value"
  }
}

COMPONENT_NAME 告诉 Alloy 要创建哪种类型的组件。"LABEL" 是你选择的唯一标识符,用于区分同一组件类型的多个实例。

组件名称

每个组件都有一个描述其用途的名称。例如:

  • local.file 从磁盘检索文件内容。
  • prometheus.scrape 收集 Prometheus 指标。
  • loki.write 将日志数据发送到 Loki。

你通过指定组件名称和用户定义的标签来定义组件:

local.file "my_config" {
  filename = "/etc/app/config.yaml"
}

prometheus.scrape "api_metrics" {
  targets  = [{"__address__" = "localhost:8080"}]
  forward_to = [prometheus.remote_write.default.receiver]
}

组件引用

你通过组合组件名称和标签来引用组件。例如,你可以将标签为 my_configlocal.file 组件引用为 local.file.my_config

组件名称和标签的组合在配置中必须是唯一的。这允许你定义同一组件类型的多个实例:

prometheus.scrape "api" {
  targets    = [{"__address__" = "api.example.com:8080"}]
  forward_to = [prometheus.remote_write.production.receiver]
}

prometheus.scrape "database" {
  targets    = [{"__address__" = "db.example.com:9090"}]
  forward_to = [prometheus.remote_write.production.receiver]
}

组件导出

组件可以通过导出共享数据。当一个组件引用另一个组件的导出时,它们之间就建立了连接。你将在表达式中了解更多关于这些组件引用的内容。

local.file "api_key" {
  filename = "/etc/secrets/api.key"
}

prometheus.remote_write "production" {
  endpoint {
    url = "https://prometheus.example.com/api/v1/write"

    basic_auth {
      username = "metrics"
      password = local.file.api_key.content  // 引用文件内容
    }
  }
}

在这个示例中:

  1. local.file 组件读取文件并导出其内容。
  2. prometheus.remote_write 组件将该内容用作密码。
  3. 当文件发生变化时,Alloy 会自动更新 local.file 组件的导出。
  4. 这会导致 Alloy 使用新密码重新评估 prometheus.remote_write 组件。

后续步骤

你可以在这里找到所有的组件

3.3 类型和值

你在前一节中了解了表达式的主要类型:字面量、组件引用、函数和算术运算。现在你将学习这些表达式所使用的类型和值,以及 Alloy 如何使用它们来确保你的配置正确运行。

理解类型有助于你编写可靠的表达式,并帮助你理解为什么某些组件和值的组合是兼容的,而另一些则不是。

值类型

Alloy 语法支持以下值类型:

  • number:任何数值,如 33.14
  • string:表示文本的 Unicode 字符序列,如 "Hello, world!"
  • bool:布尔值,要么 true,要么 false
  • array:值的序列,如 [1, 2, 3]。使用从零开始的整数索引数组元素。
  • object:由命名标签标识的一组值,如 { name = "John" }
  • function:表示一个例程的值,该例程通过参数计算另一个值,如 sys.env("HOME")。函数接受零个或多个参数作为输入,并始终返回单个值作为输出。
  • null:表示无值的类型。

名称和命名约定

除了上述类型之外,组件参考文档使用以下约定来引用类型:

  • any:任何类型的值。

  • map(T):所有值均为 T 类型的 object。例如,map(string) 是所有值均为字符串的对象。对象的键类型始终是字符串或转换为字符串的标识符。

  • list(T):所有值均为 T 类型的 array。例如,list(string) 是所有值均为字符串的数组。

  • duration:表示时间段的 string,如 "100ms""1h30m""10s"。有效单位包括:

    • h 表示小时。
    • m 表示分钟。
    • s 表示秒。
    • ms 表示毫秒。
    • ns 表示纳秒。

    你可以将递减单位的值组合在一起以相加。例如,"1h30m" 等同于 "90m"

数字

Alloy 语法将整数、无符号整数和浮点值视为单一的 number 类型。这简化了 Alloy 配置文件的编写和阅读。

3    == 3.00     // true
5.0  == (10 / 2) // true
1e+2 == 100      // true
2e-3 == 0.002    // true

字符串

字符串是用双引号 "" 括起来的 Unicode 字符序列。

"Hello, world!"

字符串中的 \ 会启动一个转义序列来表示特殊字符。下表列出了支持的转义序列。

序列 替换为
\\ \ 字符 U+005C
\a 警报或响铃字符 U+0007
\b 退格字符 U+0008
\f 换页字符 U+000C
\n 换行字符 U+000A
\r 回车字符 U+000D
\t 水平制表字符 U+0009
\v 垂直制表字符 U+000B
\' ' 字符 U+0027
\" " 字符 U+0022,防止终止字符串
\NNN 字面字节(NNN 是三位八进制数字)
\xNN 字面字节(NN 是两位十六进制数字)
\uNNNN 基本多文种平面的 Unicode 字符(NNNN 是四位十六进制数字)
\UNNNNNNNN 辅助平面的 Unicode 字符(NNNNNNNN 是八位十六进制数字)

原始字符串

原始字符串是用反引号 `` 括起来的 Unicode 字符序列。原始字符串不支持转义序列。

`Hello, "world"!`

在反引号内,除了反引号之外的任何字符都可以出现。要包含反引号,可以使用 + 连接一个包含反引号的双引号字符串。

Alloy 按原样解释多行原始字符串。

`Hello,
"world"!`

Alloy 将前面的多行原始字符串解释为以下值的字符串。

Hello,
"world"!

布尔值

符号 truefalse 表示布尔值。

数组

使用方括号 [] 括起来的逗号分隔值序列来构造数组。

[0, 1, 2, 3]

为了可读性,你可以将值放在单独的行上。如果闭合方括号 ] 在不同行上,则在最后一个值后包含逗号。

[
  0,
  1,
  2,
]

对象

使用花括号 {} 括起来的逗号分隔键值对序列来构造对象。

{
  first_name = "John",
  last_name  = "Doe",
}

如果闭合花括号 } 在不同行上,则在最后一个键值对后包含逗号。

{ name = "John" }

如果键不是有效标识符,则用双引号括起来。

{
  "app.kubernetes.io/name"     = "mysql",
  "app.kubernetes.io/instance" = "mysql-abcxyz",
  namespace                    = "default",
}

注意:不要将对象与块混淆。

  • 对象是赋给属性的值。在单独的行上的键值对之间使用逗号。
  • 是由多个属性组成的命名结构元素。属性之间不使用逗号。

函数

你不能构造函数值。你可以从标准库调用函数或从组件导出函数。

Null

符号 null 表示空值。

特殊类型

密钥(Secrets)

secret 是一种特殊类型的字符串,永远不会向用户显示。你可以将 string 值赋给期望 secret 的属性,但反过来不行。你可以使用 convert.nonsensitive 将 secret 转换为字符串。你不能将 secret 赋给期望字符串的属性。

胶囊(Capsules)

capsule 是一种特殊类型,表示 Alloy 使用的内部类型类别。每种胶囊类型都有一个唯一的名称,显示为 capsule("<SOME_INTERNAL_NAME>")。你不能构造胶囊值。可以像其他类型一样在表达式中使用胶囊。胶囊之间不兼容。期望胶囊的属性只能接受相同内部类型的胶囊。如果属性期望 capsule("prometheus.Receiver"),你只能分配 capsule("prometheus.Receiver") 类型。使用或导出胶囊的组件会记录它们期望的具体胶囊类型。

在以下示例中,prometheus.remote_write 组件导出一个 receiver,它是 capsule("prometheus.Receiver") 类型。你可以在 prometheus.scrapeforward_to 属性中使用这个胶囊,它期望一个 capsule("prometheus.Receiver") 数组。

prometheus.remote_write "default" {
  endpoint {
    url = "http://localhost:9090/api/v1/write"
  }
}

prometheus.scrape "default" {
  targets    = [/* ... */]
  forward_to = [prometheus.remote_write.default.receiver]
}

3.4 流水线

当组件相互引用彼此的导出时,就形成了流水线。你在前一节中了解了组件导出。这些是运行中的组件提供给其他组件使用的值。

// 简单的常量值
log_level = "debug"

// 引用组件导出的表达式
api_key = local.file.secret.content

当你在组件的参数中使用像 local.file.secret.content 这样的表达式时,你就创建了一个依赖关系。每当被引用的组件更新其导出时,Alloy 会自动重新评估依赖该组件的组件。

你的第一个流水线

这个流水线从文件中读取密码并使用它向远程系统进行身份验证:

local.file "api_key" {
    filename = "/etc/secrets/api.key"
}

prometheus.remote_write "production" {
    endpoint {
        url = "http://localhost:9090/api/v1/write"

        basic_auth {
            username = "admin"
            password = local.file.api_key.content
        }
    }
}

这个流水线有两个组件:

  1. local.file 读取文件并导出其内容。
  2. prometheus.remote_write 将该内容用作密码。

关键配置元素包括:

  • 组件导出local.file.api_key.content 导出文件的内容。
  • 组件引用password 属性引用了另一个组件的导出。
  • 自动更新:当文件发生变化时,Alloy 会自动更新远程写入组件使用的密码。

多阶段流水线

你可以将多个组件串联在一起,创建更复杂的流水线。这个示例展示了一个完整的指标收集流水线:

// 发现要抓取的 Kubernetes Pod
discovery.kubernetes "pods" {
  role = "pod"
}

// 从发现的 Pod 中抓取指标
prometheus.scrape "app_metrics" {
  targets    = discovery.kubernetes.pods.targets
  forward_to = [prometheus.remote_write.production.receiver]
}

// 将指标发送到远程存储
prometheus.remote_write "production" {
  endpoint {
    url = "https://prometheus.example.com/api/v1/write"

    basic_auth {
      username = "metrics"
      password = local.file.api_key.content
    }
  }
}

// 从文件读取 API 密钥
local.file "api_key" {
  filename  = "/etc/secrets/api-key"
  is_secret = true
}

这个流水线演示了几个关键概念:

  1. 服务发现discovery.kubernetes 查找要监控的目标。
  2. 数据收集prometheus.scrape 从这些目标收集指标。
  3. 数据转发forward_to 属性通过在一组件和另一组件之间发送数据来建立连接。
  4. 身份验证:远程写入组件使用来自文件的凭据。

forward_to 属性是一个特殊的配置元素,它在组件之间创建数据流连接。它接受一组组件接收器来处理数据。

日志处理流水线

这是一个更复杂的示例,通过多个转换阶段处理日志数据:

// 使用 glob 模式读取日志文件
loki.source.file "local_files" {
    targets    = [{__path__ = "/var/log/app/*.log"}]
    forward_to = [loki.process.add_labels.receiver]

    file_match {
        enabled     = true
        sync_period = "10s"
    }
}

// 从日志消息中提取数据并添加标签
loki.process "add_labels" {
    stage.logfmt {
        mapping = {
            "extracted_level"   = "level",
            "extracted_service" = "service",
        }
    }

    stage.labels {
        values = {
            "level"   = "extracted_level",
            "service" = "extracted_service",
        }
    }

    forward_to = [loki.write.grafana_cloud.receiver]
}

// 将处理后的日志发送到 Loki
loki.write "grafana_cloud" {
    endpoint {
        url = "https://logs-prod.grafana.net/loki/api/v1/push"

        basic_auth {
            username = "12345"
            password = local.file.api_key.content
        }
    }
}

// 读取 API 凭据
local.file "api_key" {
    filename  = "/etc/secrets/loki-key"
    is_secret = true
}

这个流水线展示了数据如何在多个处理阶段中流动:

  1. 发现:查找要监控的日志文件。
  2. 收集:从文件中读取日志条目。
  3. 转换:解析日志消息并提取元数据。
  4. 丰富:向日志条目添加结构化标签。
  5. 输出:将处理后的日志发送到远程存储。

流水线模式

使用这些常见模式来构建有效的数据处理工作流。

扇出模式(Fan-out)

将一个组件的数据发送到多个目标。这使用带有多个接收器的 forward_to 属性:

prometheus.scrape "app_metrics" {
  targets = [{"__address__" = "app:8080"}]
  forward_to = [
    prometheus.remote_write.production.receiver,
    prometheus.remote_write.staging.receiver,
  ]
}

prometheus.remote_write "production" {
  endpoint {
    url = "https://prod-prometheus.example.com/api/v1/write"
  }
}

prometheus.remote_write "staging" {
  endpoint {
    url = "https://staging-prometheus.example.com/api/v1/write"
  }
}

这种模式适用于以下场景:

  • 在生产之前在预发布环境中测试更改。
  • 将不同的数据集发送到不同的系统。
  • 创建冗余数据存储以提高可靠性。

链式处理模式

通过多个阶段转换数据:

loki.source.file "raw_logs" {
  targets    = [{"__path__" = "/var/log/app.log"}]
  forward_to = [loki.process.parse.receiver]
}

loki.process "parse" {
  stage.json {
    expressions = {
      level   = "level",
      message = "msg",
    }
  }
  forward_to = [loki.process.filter.receiver]
}

loki.process "filter" {
  stage.match {
    selector = "{level=\"error\"}"
    action   = "keep"
  }
  forward_to = [loki.write.alerts.receiver]
}

loki.write "alerts" {
  endpoint {
    url = "https://loki.example.com/loki/api/v1/push"
  }
}

这种模式展示了渐进式数据精炼:

  1. 解析:从原始日志中提取结构化数据。
  2. 过滤:仅保留相关的日志条目(错误级别)。
  3. 输出:将过滤后的日志发送到告警系统。

最佳实践

遵循这些准则来构建可维护且高效的流水线。

保持流水线专注

将复杂的流水线分解为逻辑阶段。每个组件都应该有明确的单一职责。

使用描述性标签

选择描述其用途的组件标签:

// 好:描述性标签
prometheus.scrape "api_metrics"      { }
prometheus.scrape "database_metrics" { }

// 避免:通用标签
prometheus.scrape "scraper1" { }
prometheus.scrape "scraper2" { }

安全地处理密钥

适当标记敏感组件:

local.file "database_password" {
  filename  = "/etc/secrets/db-password"
  is_secret = true  // 防止值出现在 UI 中
}

增量测试

逐步构建流水线。从基本的数据收集开始,然后添加处理和转发组件。

调试流水线

当流水线不能正常工作时:

  1. 检查组件健康状况:在 Alloy UI 中查看。不健康的组件会显示为红色。
  2. 验证组件导出:确保导出包含预期的数据。使用 UI 检查导出值。
  3. 检查组件依赖关系:确保数据流正确。检查 forward_to 引用是否与接收器导出匹配。
  4. 检查引用循环:组件不能直接或间接地引用自身。
  5. 验证配置语法:确保正确拼写组件和导出名称。

Alloy UI 提供了有关组件状态、导出和健康状况的详细信息,以帮助排查流水线问题。

使用实例

alloy采集本地日志文件,并发送到远端loki


local.file_match "files" {
  path_targets = [{"__path__" = "/opt/runjar/xx/logs/*.log", "service" = "guard_api"}]
}

loki.source.file "tmpfiles" {
  targets    = local.file_match.files.targets
  forward_to = [loki.write.loki_wirte.receiver]
}

loki.write "loki_wirte" {
    endpoint {
    url = "https://xxxx/loki/api/v1/push"
    http_headers = {
    "X-Scope-OrgID" = ["yuanqu"],
    }
    basic_auth {
        username = "admin"
        password = "xxx"
    }
    batch_size = "200KB"
    }

    external_labels = {cluster="yuanqu-zs"}
}
posted @ 2026-04-23 15:58  Hekk丶  阅读(97)  评论(0)    收藏  举报