在 Nomad 上以微服务模式部署 Loki:job.nomad.hcl 与 config.yml 全解析
2026/9/12 16:04:19 网站建设 项目流程

在 Nomad 上以微服务模式部署 Loki:job.nomad.hcl 与 config.yml 全解析

【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki

本文以 examples/nomad/loki-microservices/README.md 及其配套的 job.nomad.hcl 与 config.yml 为主体,系统讲解如何在 HashiCorp Nomad 集群中以微服务(Microservices)模式部署 Loki:包括作业定义结构、统一配置文件的每个关键参数、运行与扩缩容操作,以及面向生产的 HTTP 端点安全(Consul Connect + Traefik 认证)与 GRPC 通信加密(Vault PKI + mTLS)方案。读完本文,你将能够基于这套示例搭建一套可横向伸缩、具备健康检查与安全防护的分布式 Loki 集群。

微服务模式:为什么需要它

Loki 本质上是一个由众多微服务组成的分布式系统,但它采用了一种独特的构建模型:所有微服务都打包在同一个二进制中,通过-target命令行参数决定启动时运行哪些组件(参见 docs/sources/get-started/deployment-modes.md 中的说明)。微服务(分布式)部署模式正是把每个组件作为独立进程运行,每个进程通过-target指定自己的职责。

与单机模式(-target=all)和高可用单机模式相比,微服务模式的特点是:

维度单机模式HA 单机模式微服务模式
持久性
高可用
执行路径分离
运维复杂度
可扩展性

微服务模式以最高的运维复杂度换取最细粒度的扩展能力,官方建议用于规模很大的集群,或需要精确控制各组件伸缩与集群运维的场景。本文示例在 Nomad 中实际部署了 8 个组件(compactor、ruler、distributor、ingester、querier、query-scheduler、query-frontend、index-gateway),覆盖了读写链路的全部核心进程。

Loki 微服务模式架构图

前置条件与文件结构

根据 examples/nomad/README.md 的说明,运行这套示例需要满足以下条件:

硬性要求

  • 安装了较新版本的 Nomad,且 Docker driver 健康可用;
  • Nomad 启用了 Consul 集成(服务发现与健康检查依赖 Consul);
  • 可访问 S3 存储(示例默认使用 S3 兼容对象存储,如 MinIO)。

可选要求

  • Vault 集成:用于安全地提供 S3 凭据;
  • 配置了 Consul provider 的 Traefik:用于在多个 Loki 实例之间负载均衡。

示例目录包含三个文件:

文件作用
README.md使用说明与生产建议
job.nomad.hclNomad 作业定义:8 个组件组、任务、服务注册、健康检查
config.yml所有组件共用的 Loki 配置文件(通过 Consul Template 注入)

一个值得注意的细节:README 开篇提到使用 boltdb-shipper,但仓库内实际的 config.yml 已采用TSDB 索引 + S3 对象存储 + index-gateway的组合(store: tsdbschema: v13tsdb_shipper配置块),并以 job.nomad.hcl 中的index-gateway组与之配套。实际部署时应以配置文件为准:TSDB 是当前推荐的索引后端,index-gateway 负责为 querier 提供统一的索引读取入口。

作业定义全览:job.nomad.hcl 的 8 个组件组

job.nomad.hcl 定义了一个名为loki的作业,内含 8 个 group,每个 group 对应一个 Loki 组件,每个组内部都以-target=<组件名>启动同一个grafana/loki:${var.version}镜像:

默认副本数启动 target说明
compactor1compactor索引压缩、删除请求与保留策略执行
ruler1ruler告警与记录规则评估
distributor2distributor写入入口,校验并转发日志流
ingester2ingester内存写入、WAL 与 flush
querier2querier执行查询(经 scheduler 调度)
query-scheduler2query-scheduler查询队列与调度
query-frontend2query-frontend查询入口、缓存与拆分
index-gateway1index-gatewayTSDB 索引读取网关

作业文件顶部通过 HCL 变量控制版本:

variable "version" { type = string description = "Loki version" default = "3.7.2" }

所有任务统一使用 Docker driver、以nobody用户运行、kill_timeout = "90s"(给 ingester 等组件足够的优雅退出时间以 flush WAL 数据),并追加三个标准启动参数:

args = [ "-target=distributor", "-config.file=/local/config.yml", "-config.expand-env=true", ]

其中-config.expand-env=true允许配置文件中使用${ENV_VAR}语法展开环境变量(S3 凭据即通过此机制注入)。

有状态组件的关键配置

ingester(写路径核心)使用ephemeral_disk的 sticky 模式,让/alloc数据在作业更新时尽量保留(WAL 就存放在NOMAD_ALLOC_DIR/data/wal):

ephemeral_disk { size = 4000 sticky = true }

同时用 constraint 保证副本分布在不同故障域(必须与配置文件中的instance_availability_zone保持一致):

constraint { distinct_property = node.unique.name # distinct_property = node.datacenter # distinct_property = attr.platform.aws.placement.availability-zone }

compactorrulerindex-gateway同样配置了 sticky 的 ephemeral_disk,用于存放索引目录、规则文件与压缩工作目录。

服务注册与健康检查

每个组都注册了 Consul 服务,并通过meta携带两个标签——alloc_id(取 Nomad 环境变量NOMAD_ALLOC_ID)和component(组件名),这两者会被后文 Prometheus 的 relabel 规则消费:

service { name = "loki-distributor" port = "http" meta { alloc_id = NOMAD_ALLOC_ID component = "distributor" } ... }

健康检查统一指向 Loki 内置的/ready端点,例如:

check { name = "Loki distributor" port = "http" type = "http" path = "/ready" interval = "20s" timeout = "1s" }

query-schedulerindex-gateway还通过network块固定了 GRPC 端口映射(static = 9096/static = 9097),以便其他组件通过 Consul DNS 稳定寻址(如配置中的loki-query-scheduler.service.consul:9096)。

S3 凭据注入

需要访问对象存储的组(compactor、ruler、ingester、querier、index-gateway)通过 template 生成环境变量文件:

template { data = <<-EOH S3_ACCESS_KEY_ID=<access_key> S3_SECRET_ACCESS_KEY=<secret_access_key> EOH destination = "secrets/s3.env" env = true }

生产环境建议把<access_key>占位符替换为 Vault 模板(参见 examples/nomad/README.md 中基于secret/minio/loki或 AWS secret engine 的写法),避免明文凭据入库。

运行与扩缩容

loki-microservices目录内直接运行:

nomad run job.nomad.hcl

新版 Nomad 也支持显式的作业命令形式:

nomad job run job.nomad.hcl

如需部署指定版本,可覆盖variable.version默认值,或从命令行传入:

nomad job run -var="version=3.7.2" job.nomad.hcl

扩缩容两种方式

方式一:修改作业文件中的count注意本示例的作业由 8 个 group 组成(README 中group "loki"是泛指),需要按组分别调整各自group块内的count,然后重新运行nomad run job.nomad.hcl应用变更。

方式二:使用 Nomad CLI 动态扩缩容(推荐,无需改文件):

nomad job scale loki distributor <count>

其中loki是作业名,distributor是组名,<count>为目标副本数。得益于分布式部署模式,你可以独立、精确地伸缩每个组件——例如只增加 querier 以提升查询吞吐,或只增加 ingester 以承接写入洪峰,这正是微服务模式相比单机模式的核心价值。

统一配置解析:config.yml

所有组件共享同一份 config.yml,它大量使用 Nomad 环境变量模板占位符({{ env "..." }}),由 Consul Template 在渲染时替换为实际值。逐块解读如下。

server:监听端口与 TLS 预留

server: log_level: info http_listen_port: {{ env "NOMAD_PORT_http" }} grpc_listen_port: {{ env "NOMAD_PORT_grpc" }} # grpc_tls_config: # client_auth_type: "RequireAndVerifyClientCert" # client_ca_file: "/secrets/certs/CA.pem" # cert_file: "/secrets/certs/cert.pem" # key_file: "/secrets/certs/key.pem"

HTTP/GRPC 端口直接取自 Nomad 动态分配的端口环境变量。注释掉的grpc_tls_config是为后文 mTLS 方案预留的开关,启用后要求客户端证书必须通过 CA 校验(RequireAndVerifyClientCert)。

common:复制因子、ring 与故障域

common: replication_factor: 2 # Tell Loki which address to advertise instance_addr: {{ env "NOMAD_IP_grpc" }} # Failure domain # Must be the same as specified in job constraints instance_availability_zone: {{ env "node.unique.name" }} zone_awareness_enabled: true ring: # Tell Loki which address to advertise in ring instance_addr: {{ env "NOMAD_IP_grpc" }} kvstore: store: consul prefix: loki/ consul: host: {{ env "attr.unique.network.ip-address" }}:8500
  • replication_factor: 2:每个日志流被复制到 2 个 ingester;
  • instance_addrring.instance_addr:向 ring 宣告的地址取自NOMAD_IP_grpc,解决多网卡/动态 IP 场景下的地址误宣告问题(这也是 examples/nomad/README.md 特意强调不用 memberlist 而改用 Consul ring 的原因);
  • instance_availability_zone取自node.unique.name,配合zone_awareness_enabled: true启用感知故障域的复制,必须与作业文件中 ingester 组的 constraint 对应;
  • ring 状态存储使用Consulkvstore.store: consul,前缀loki/),由 distributor、ingester、compactor、ruler 等组件共享,这也是各组件 ring 端点(/distributor/ring/ingester/ring等)的数据来源。对应的 Ring 配置在源码中定义于 pkg/loki/common/common.go 的RingConfigInstanceAddr字段。

ingester:WAL 与回放控制

ingester: wal: dir: {{ env "NOMAD_ALLOC_DIR" }}/data/wal flush_on_shutdown: true replay_memory_ceiling: "1G"

WAL 存放在 sticky 的/alloc/data/wal,保证重启后可回放未 flush 的数据;flush_on_shutdown: true让进程在退出时主动 flush;replay_memory_ceiling: "1G"限制 WAL 回放期间的内存占用上限。

frontend 与 frontend_worker:查询链路接入

frontend: scheduler_address: loki-query-scheduler.service.consul:9096 compress_responses: true log_queries_longer_than: 5s frontend_worker: scheduler_address: loki-query-scheduler.service.consul:9096

query-frontend 与 querier 的 worker 都通过 Consul DNS 连接到 query-scheduler 的固定端口 9096,实现查询请求的排队、调度与结果缓存;compress_responses: true压缩响应减少带宽,log_queries_longer_than: 5s记录慢查询。

schema_config 与 storage_config:TSDB + S3

schema_config: configs: - from: 2022-05-15 store: tsdb object_store: s3 schema: v13 index: prefix: index_ period: 24h storage_config: tsdb_shipper: active_index_directory: {{ env "NOMAD_ALLOC_DIR" }}/data/index cache_location: {{ env "NOMAD_ALLOC_DIR" }}/data/index-cache index_gateway_client: server_address: loki-index-gateway.service.consul:9097 aws: endpoint: https://minio.service.consul bucketnames: loki region: us-west-1 access_key_id: ${S3_ACCESS_KEY_ID} secret_access_key: ${S3_SECRET_ACCESS_KEY} s3forcepathstyle: true
  • 索引使用 TSDB 格式(schema v13),每日一个索引周期;tsdb_shipper把活跃索引放在/alloc/data/index(Nomad 更新作业时尽量保留),并通过index_gateway_client指向 index-gateway 的 9097 端口读取共享索引;
  • 对象存储指向 S3 兼容端点(示例为https://minio.service.consul),bucketnames: lokis3forcepathstyle: true(MinIO 等兼容存储必需),凭据来自-config.expand-env=true展开的${S3_ACCESS_KEY_ID}/${S3_SECRET_ACCESS_KEY}

compactor、ruler 与 limits_config

compactor: working_directory: {{ env "NOMAD_ALLOC_DIR" }}/compactor delete_request_store: s3 compaction_interval: 24h retention_enabled: true ruler: alertmanager_url: https://alertmanager.service.consul enable_alertmanager_v2: true enable_api: true external_url: https://loki-ruler.service.consul rule_path: {{ env "NOMAD_ALLOC_DIR" }}/tmp/rules storage: type: local local: directory: {{ env "NOMAD_TASK_DIR" }}/rules wal: dir: {{ env "NOMAD_ALLOC_DIR" }}/data/ruler limits_config: reject_old_samples: true reject_old_samples_max_age: 168h

compactor 每 24h 压缩一次索引并将删除请求存储到 S3,同时开启保留策略;ruler 使用local规则存储(规则文件通过 job.nomad.hcl 中 ruler 组的dynamic "template"rules/**目录注入容器,详见 examples/nomad/README.md);reject_old_samples拒绝超过 168h 的陈旧写入,防止乱序数据污染。

生产建议一:统一采集各组件指标

将 config.yml 中server.http_listen_port暴露的/metrics端点接入 Prometheus。由于所有组件都注册了 Consul 服务,可以直接用consul_sd_configs一次性发现全部 8 个组件:

- job_name: "loki" consul_sd_configs: - services: - "loki-compactor" - "loki-ruler" - "loki-distributor" - "loki-ingestor" - "loki-querier" - "loki-index-gateway" - "loki-query-frontend" - "loki-query-scheduler" relabel_configs: - source_labels: ["__meta_consul_service_metadata_alloc_id"] target_label: "instance" - source_labels: ["__meta_consul_service_metadata_component"] target_label: "component"

relabel 规则把 job 中service.meta里声明的alloc_idcomponent映射为 Prometheus 的instancecomponent标签,从而能在告警与面板中区分同一组件的不同副本及组件类型。

生产建议二:用 Consul Connect 保护 HTTP 端点

微服务模式下 HTTP 端点(push/query 接口、ring 状态页)不应直接暴露。README 给出了一套结合 Nomad bridge 网络、Consul Connect 与 Traefik 的加固方案。

第一步:网络设为bridge模式并显式声明三个端口——httpgrpc以及专供健康检查使用的health

network { mode = "bridge" port "http" {} port "health" {} port "grpc" {} }

第二步:任务中发布全部三个端口(不要漏掉health),并在 Loki 配置中将 HTTP 绑定到回环地址 127.0.0.1,使外部无法直接访问:

task "distributor" { driver = "docker" user = "nobody" kill_timeout = "90s" config { image = "grafana/loki:${var.versions.loki}" ports = [ "http", "health", # do not forget to publish health port "grpc", ]
server: http_listen_address: 127.0.0.1 http_listen_port: 80

第三步:注册服务时开启 Consul Connect sidecar proxy,把本地 80 端口包起来,仅通过expose开放/metrics/ready两个路径给健康检查使用;同时为 Traefik 打上路由与 basicauth 标签:

service { name = "loki-distributor" port = "http" meta { alloc_id = NOMAD_ALLOC_ID component = "distributor" } tags = [ "traefik.enable=true", "traefik.consulcatalog.connect=true", "traefik.http.routers.loki-distributor.entrypoints=https", "traefik.http.routers.loki-distributor.rule=Host(`loki-distributor.service.consul`)", "traefik.http.middlewares.loki-distributor.basicauth.users=promtail:$$apr1$$wnih40yf$$vcxJYiqcEQLknQAZcpy/I1", "traefik.http.routers.loki-distirbutor.middlewares=loki-distributor@consulcatalog", "traefik.http.routers.loki-distributor-ring.entrypoints=https", "traefik.http.routers.loki-distributor-ring.rule=Host(`loki-distributor.service.consul`) && Path(`/distributor/ring`)", "traefik.http.middlewares.loki-distributor-ring.basicauth.users=devops:$apr1$bNIZL02A$QrOgT3NAOx.koXWnqfXbo0", "traefik.http.routers.loki-distributor-ring.middlewares=loki-distributor-ring@consulcatalog", ] check { name = "Loki distributor" port = "health" type = "http" path = "/ready" interval = "20s" timeout = "1s" } connect { sidecar_service { proxy { local_service_port = 80 expose { path { path = "/metrics" protocol = "http" local_path_port = 80 listener_port = "http" } path { path = "/ready" protocol = "http" local_path_port = 80 listener_port = "health" } } } } } }

注意:Traefik 标签中的 basicauth 密码以$$转义$,防止 Nomad 模板引擎提前展开;/distributor/ring这类 ring 状态页单独路由并配置不同的凭据。其余组件(compactor、ingester、ruler、query-frontend 等)按相同模式套用即可,作业文件中已为各组件预置了对应的 Traefik 路由标签(见 job.nomad.hcl)。basicauth 凭据可用如下命令生成:

docker run --rm httpd:alpine htpasswd -nb promtail password123

生产建议三:用 Vault PKI 与 mTLS 保护 GRPC 通信

Consul Connect 无法用于组件间的 GRPC 通信——因为部分组件需要连接其他组件的所有实例(如 ingester 与 querier 之间),而 Connect 面向固定拓扑。README 给出的替代方案是用 Vault 的 PKI secret engine 为每个实例签发证书,实现 mTLS。

步骤 1:locals中定义证书文件与 Vault 返回字段的映射,让后续模板更简洁:

locals { certs = { "CA" = "issuing_ca", "cert" = "certificate", "key" = "private_key", } }

步骤 2:为每个任务添加dynamic "template",遍历local.certs生成三个文件(CA、证书、私钥),每份证书 TTL 10 天、包含服务通用名与实例 IP 的ip_sans

dynamic "template" { for_each = local.certs content { data = <<-EOH {{- with secret "pki/issue/internal" "ttl=10d" "common_name=loki-<component_name>.service.consul" (env "attr.unique.network.ip-address" | printf "ip_sans=%s") -}} {{ .Data.${template.value} }} {{- end -}} EOH destination = "secrets/certs/${template.key}.pem" change_mode = "restart" splay = "5m" } }

证书续期时(change_mode = "restart")任务会自动重启以加载新证书,splay = "5m"则避免所有实例同时重启。

步骤 3:启用配置文件中预留的 TLS 开关——服务端开启server.grpc_tls_config(要求校验客户端证书),各客户端启用grpc_client_config并指定 CA/证书/私钥路径与 snappy 压缩,例如:

ingester_client: grpc_client_config: grpc_compression: snappy tls_enabled: true tls_ca_path: "/secrets/certs/CA.pem" tls_cert_path: "/secrets/certs/cert.pem" tls_key_path: "/secrets/certs/key.pem"

config.yml 中query_schedulerfrontendfrontend_worker以及storage_config.tsdb_shipper.index_gateway_client均已预留同结构的注释块,按需逐一取消注释并填写证书路径即可。

从源码看配置背后的机制

这套配置的每一处都对应 Loki 源码中的真实实现:

  • Ring 与地址宣告common.ringinstance_addrinstance_availability_zone等字段定义于 pkg/loki/common/common.go 的CommonConfig/RingConfig结构(含InstanceAddrInstanceInterfaceNames等字段),ring 状态最终通过 Consul KV 共享,各组件 ring 端点(如/distributor/ring/ingester/ring/compactor/ring/ruler/ring)即由此驱动;
  • 健康检查:所有组的check都依赖 Loki 内置的/ready端点,它反映组件是否完成启动与 ring 注册,是 Consul 健康检查与 Consul Connectexpose的共同依据;
  • -config.expand-env=true:配合 template 生成的s3.env环境变量,实现 S3 凭据与配置的运行时注入,避免在配置文件中硬编码密钥。

小结

通过 job.nomad.hcl 与 config.yml 的组合,可以快速在 Nomad 上拉起一套由 8 个可独立伸缩组件构成的分布式 Loki:TSDB 索引 + S3 对象存储、Consul 支撑的 ring 与服务发现、基于/ready的全面健康检查,以及面向生产的 HTTP(Consul Connect + Traefik basicauth)与 GRPC(Vault PKI + mTLS)双重加固方案。

如果需要复杂度更低的起点,可以参考仓库内的另外两套 Nomad 示例:examples/nomad/loki-monolithic/README.md(单机模式)与 examples/nomad/loki-ha-monolithic/README.md(高可用单机模式);更多 Nomad 通用技巧(负载均衡、Vault 凭据、ruler 规则注入)可查阅 examples/nomad/README.md。根据集群规模与运维能力在三者之间选择,才是部署 Loki 的正确姿势。

【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询