Alloy介绍以及使用
1. 简介

Alloy是一个指标收集器,用于收集、转换和发送指标数据。
2. 安装
2.1 在linux上以二进制文件安装
- 下载
访问 Alloy 的 GitHub Releases 页面:
👉 https://github.com/grafana/alloy/releases
在Assets中下载alloy-linux-amd64.zip文件 - 创建用户
alloy
sudo useradd --shell /bin/false alloy
- 文件放入
/home/alloy文件夹中,解压后,将二进制文件重命名为alloy
unzip <file>
- 添加执行权限
chmod +x alloy
- 在
/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
- 管理命令
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 字母、数字或下划线,则视为有效。标识符不能以数字开头。
解析器使用标识符用于:
- 属性名称:键值对中的键。
- 组件名称:块名称的部分,如
prometheus.scrape中的prometheus。 - 组件标签:用于区分组件实例的用户定义名称。
- 变量引用:在表达式和函数调用中使用的名称。
有效的标识符:
my_componentComponent123_privatemétrica_café(支持 Unicode 字母)
无效的标识符:
123component(以数字开头)my-component(包含连字符)my component(包含空格)my.component(包含点号,有特殊含义)
属性和块
Alloy 配置使用两个主要语法元素构建:属性和块。属性设置单个值,而块对相关配置进行分组并创建组件实例。
属性
属性使用 ATTRIBUTE_NAME = ATTRIBUTE_VALUE 格式配置单个设置。解析器在组件配置期间评估属性以确定运行时行为。
你可以将属性放置为:
- 顶层设置:全局 Alloy 配置。
- 组件参数:组件块内的设置。
- 嵌套块设置:嵌套块内的配置。
不同属性上下文的示例:
// 顶层属性
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 可以是:
- 常量值:字符串、数字、布尔值、数组或对象。
- 表达式:计算动态值的函数调用、组件引用或计算。
块
块使用花括号配置 Alloy 组件并组织相关设置。解析器处理块以创建和配置组件实例。
块结构:
- 块名称:标识组件类型(必需)。
- 块标签:用于区分多个实例的用户定义标识符(可选)。
- 块体:包含属性和嵌套块(必需)。
某些块在具有不同标签时可以在配置中多次出现:
// 同一组件类型的多个实例
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 {
// 嵌套块体
}
}
块命名规则
解析器对块名称和标签执行特定规则:
块名称必须是以下之一:
- 有效的组件名称:点分隔的标识符,如
prometheus.scrape或local.file。 - 特殊配置块:用于全局设置的内置块,如
logging或tracing。
块标签(需要时)必须:
- 有效的标识符:遵循与属性名称相同的规则。
- 双引号字符串:用双引号包裹,而不是单引号。
- 在作用域内唯一:相同类型的两个块不能有相同的标签。
展示正确块命名的示例:
// 组件名称: "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"
}
解析器验证以下内容:
- 组件名称存在:组件类型必须在 Alloy 中可用。
- 标签唯一:在同一组件类型内。
- 语法正确:遵循标识符和引号规则。
终止符
解析器需要终止符来分隔语句并确定表达式的结束位置。所有块和属性定义必须以换行符结束,Alloy 称之为终止符。
换行符在以下情况下充当终止符:
- 完整表达式之后:任何值、计算或函数调用之后。
- 闭合分隔符之后:
]、)或}之后。 - 语句结尾之后:属性赋值或块定义之后。
解析器在其他上下文中忽略换行符,允许灵活的格式:
// 这种格式是有效的 - 额外的换行符会被忽略
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 加载你的配置时:
- 它读取你在每个组件块中提供的参数。
- 它使用这些参数创建组件的运行实例。
- 组件开始工作,并可能在运行时更新其导出。
- 其他组件可以在它们的参数中引用这些导出。
组件语法
组件使用你前面学过的块语法。一般模式如下:
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_config 的 local.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 // 引用文件内容
}
}
}
在这个示例中:
local.file组件读取文件并导出其内容。prometheus.remote_write组件将该内容用作密码。- 当文件发生变化时,Alloy 会自动更新
local.file组件的导出。 - 这会导致 Alloy 使用新密码重新评估
prometheus.remote_write组件。
后续步骤
你可以在这里找到所有的组件
3.3 类型和值
你在前一节中了解了表达式的主要类型:字面量、组件引用、函数和算术运算。现在你将学习这些表达式所使用的类型和值,以及 Alloy 如何使用它们来确保你的配置正确运行。
理解类型有助于你编写可靠的表达式,并帮助你理解为什么某些组件和值的组合是兼容的,而另一些则不是。
值类型
Alloy 语法支持以下值类型:
number:任何数值,如3或3.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"!
布尔值
符号 true 和 false 表示布尔值。
数组
使用方括号 [] 括起来的逗号分隔值序列来构造数组。
[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.scrape 的 forward_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
}
}
}
这个流水线有两个组件:
local.file读取文件并导出其内容。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
}
这个流水线演示了几个关键概念:
- 服务发现:
discovery.kubernetes查找要监控的目标。 - 数据收集:
prometheus.scrape从这些目标收集指标。 - 数据转发:
forward_to属性通过在一组件和另一组件之间发送数据来建立连接。 - 身份验证:远程写入组件使用来自文件的凭据。
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
}
这个流水线展示了数据如何在多个处理阶段中流动:
- 发现:查找要监控的日志文件。
- 收集:从文件中读取日志条目。
- 转换:解析日志消息并提取元数据。
- 丰富:向日志条目添加结构化标签。
- 输出:将处理后的日志发送到远程存储。
流水线模式
使用这些常见模式来构建有效的数据处理工作流。
扇出模式(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"
}
}
这种模式展示了渐进式数据精炼:
- 解析:从原始日志中提取结构化数据。
- 过滤:仅保留相关的日志条目(错误级别)。
- 输出:将过滤后的日志发送到告警系统。
最佳实践
遵循这些准则来构建可维护且高效的流水线。
保持流水线专注
将复杂的流水线分解为逻辑阶段。每个组件都应该有明确的单一职责。
使用描述性标签
选择描述其用途的组件标签:
// 好:描述性标签
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 中
}
增量测试
逐步构建流水线。从基本的数据收集开始,然后添加处理和转发组件。
调试流水线
当流水线不能正常工作时:
- 检查组件健康状况:在 Alloy UI 中查看。不健康的组件会显示为红色。
- 验证组件导出:确保导出包含预期的数据。使用 UI 检查导出值。
- 检查组件依赖关系:确保数据流正确。检查
forward_to引用是否与接收器导出匹配。 - 检查引用循环:组件不能直接或间接地引用自身。
- 验证配置语法:确保正确拼写组件和导出名称。
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"}
}

浙公网安备 33010602011771号