feat: add Grafana Alloy log collection manifests for K8s

- add alloy-daemonset to run Alloy on every node via DaemonSet
  - add alloy-configmap to scrape Pod logs through the K8s API and parse
    zap JSON fields (level, traceID, pod, namespace) into Loki labels
  - add alloy-rbac granting pods/log read access for log collection
  - forward parsed logs to loki-service for Grafana querying
This commit is contained in:
douxu 2026-06-22 16:06:09 +08:00
parent ca68cf6c18
commit 98a28b62eb
3 changed files with 159 additions and 0 deletions

View File

@ -0,0 +1,81 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: alloy-config
namespace: default
data:
config.alloy: |
// 发现集群内所有 Pod
discovery.kubernetes "pods" {
role = "pod"
}
// 重写元数据标签,并只保留带 app label 的 Pod
discovery.relabel "pods" {
targets = discovery.kubernetes.pods.targets
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
target_label = "app"
}
// 只采集有 app label 的 Pod
rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
action = "keep"
regex = ".+"
}
}
// 通过 Kubernetes API 抓取容器日志(无需挂载宿主机日志目录)
loki.source.kubernetes "pods" {
targets = discovery.relabel.pods.output
forward_to = [loki.process.parse.receiver]
}
// 解析 zap 输出的 JSON 日志,并将关键字段提升为 Loki Label
loki.process "parse" {
forward_to = [loki.write.default.receiver]
// 解析结构化字段
stage.json {
expressions = {
level = "level",
traceID = "traceID",
spanID = "spanID",
caller = "caller",
pod = "pod",
namespace = "namespace",
node = "node",
}
}
// 提升为 Label,支持在 Grafana 中按实例/Trace 过滤
stage.labels {
values = {
level = "",
traceID = "",
pod = "",
namespace = "",
node = "",
}
}
}
// 推送到 Loki
loki.write "default" {
endpoint {
url = "http://loki-service:3100/loki/api/v1/push"
}
}

View File

@ -0,0 +1,48 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: alloy
namespace: default
spec:
selector:
matchLabels:
app: alloy
template:
metadata:
labels:
app: alloy
spec:
serviceAccountName: alloy
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: alloy
image: grafana/alloy:v1.16.3
imagePullPolicy: IfNotPresent
args:
- run
- /etc/alloy/config.alloy
- --storage.path=/var/lib/alloy/data
- --server.http.listen-addr=0.0.0.0:12345
ports:
- containerPort: 12345
name: http
volumeMounts:
- name: config
mountPath: /etc/alloy
- name: data
mountPath: /var/lib/alloy/data
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
volumes:
- name: config
configMap:
name: alloy-config
- name: data
emptyDir: {}

View File

@ -0,0 +1,30 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: alloy
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: alloy
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/proxy", "services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: alloy
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: alloy
subjects:
- kind: ServiceAccount
name: alloy
namespace: default