K8s 离线部署 PLG(Prometheus + Loki + Grafana)
简介
Rancher 内置组件映射关系:
| 能力 | Rancher 封装 Chart | 社区等价 / 底层 |
|---|---|---|
| 指标监控 | rancher-monitoring | kube-prometheus-stack(含 Prometheus Operator、Prometheus Server、Alertmanager、Grafana、node-exporter、kube-state-metrics) |
| 日志采集 | rancher-logging | 扩展 Loki 作存储后端时,配合社区 loki-stack 接入 |
离线落地方案:kube-prometheus-stack 提供 P+G(Prometheus+Grafana),loki-stack 提供 L+P(Loki+Promtail),Grafana 二者共用、由 kube-prometheus-stack 提供。
- 镜像仓库:
harbor.tsysmart.com/plg-stack/ - 命名空间:
monitoring - 最终访问入口:
https://rancher.optsimu.tsysmart.cn/grafana、https://rancher.optsimu.tsysmart.cn/prometheus
概念速览(helm / chart / release / operator)
包管理三层关系——用本次部署的实物对号入座:
| 概念 | 类比 | 本环境中的实体 |
|---|---|---|
| chart | 部署"规格"包(注意:不是完整安装包,见下注) | kube-prometheus-stack-61.3.1.tgz,内含 templates/(YAML 模板)、values.yaml(默认参数)、子 chart、hook |
| helm | 包管理器(apt) | 渲染 chart+values → 提交集群 → 记账 |
| values | 安装时填的配置 | 命令行 --set、values-ingress.yaml |
| release | 已安装实例 + 账本 | prometheus、loki 两个;helm history / get values / rollback 都作用于账本 |
chart 并不等同 deb:deb 里装着程序本体(二进制),装上即能跑;chart 里只有"部署规格"(YAML 模板 + 参数),程序实体在容器镜像里,helm install 只是让集群按规格去镜像仓库拉取。更贴切的类比:chart ≈ deb 的 control 描述文件,镜像 ≈ payload。这正是离线部署必须多做"第一节:镜像搬运进 harbor"的原因——光把 .tgz 拷进集群是装不起来的。helm upgrade 的两种含义:
- 更新配置(chart 版本不变,只改 values)——本文第三节加 ingress 配置就属于这种,helm 重新渲染后把差量 apply,revision +1。日常绝大多数 upgrade 是这种,叫"更新"更贴切
- 升级版本(换新版 .tgz)——模板、values 键名、默认镜像 tag 都可能变;此时需重做第一节(解析/补录/推送新镜像 tag)
operator 层(和 helm 无关,是另一种维度的东西):
- CRD:向 K8s 注册的新资源类型(
Prometheus/Alertmanager/ServiceMonitor/PrometheusRule…),由 chart 里的 crds 子 chart 安装 - CR:用这些类型写的"需求单"(如一个
Prometheus资源 = "我要一个监控实例") - operator:常驻控制器(
prometheus-kube-prometheus-operator这个 pod),不停观察 CR 的期望状态并创建、维护实际工作负载。prometheus-0、alertmanager-0这两个 StatefulSet 就是 operator 生成的,不是 chart 直接给的 YAML
选型说明
为什么用 kube-prometheus-stack(operator 模式)而不是原生 PLG 独立部署
原生部署(裸 Prometheus + 手写 prometheus.yml)完全可行,差别在于:
| 原生独立部署 | kube-prometheus-stack | |
|---|---|---|
| 加监控目标 | 手改 prometheus.yml + reload | 业务方贴一个 ServiceMonitor CR 即可,operator 自动生成抓取配置并热加载(config-reloader 就是干这个的 sidecar) |
| 生命周期 | 升级/配置/证书全手动 | CR 声明期望状态,operator 持续调谐、自愈 |
| 配套 | 自己攒 | 十几张 K8s 核心 dashboard、全套告警规则、node-exporter/kube-state-metrics 接线开箱即用 |
| 成本 | 组件少、心智负担小 | 多一个 operator + 一堆 CRD(几十 MB 内存) |
"脱离 operator 控制"= 放弃 CR 声明式管理,退回手改静态配置的模式;集群规模一大,ServiceMonitor 自动发现的价值远大于 operator 本身的开销。
注:本方案的 L(loki-stack)恰恰是"原生"StatefulSet 部署、无 operator——因为 Loki 在此规模下运维足够简单,不需要再养一个控制器。Grafana 同理。
为什么用 monitoring 命名空间
没有技术强制,是约定俗成:
- 与 Rancher 监控应用的默认落位一致(将来切回 Rancher app 安装时布局不变)
- RBAC / NetworkPolicy 的权限边界圈在一个 ns 内
- Rancher/Grafana 界面里监控组件集中成一个
monitoring,不与业务 ns 混淆
为什么对标 rancher-monitoring
rancher-monitoring 的底层就是社区 chart kube-prometheus-stack(Rancher 只是加了自己的 values 封装和少量 dashboard 补丁)。直接用社区原版的原因:
- 功能等价,而 rancher 版 chart 依赖 Rancher 应用目录和 rancher-features,离线环境反而多一层
- 版本自主可控,直接跟进社区 release
- Rancher app 本质也是 helm 装同一套东西,本文档的操作手册两边通用
将来想切回 Rancher 托管:helm uninstall本部署 → Rancher 集群 → Apps/Charts 启用 monitoring 即可。
官方文档
- kube-prometheus-stack chart:https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack
- kube-prometheus 项目:https://github.com/prometheus-operator/kube-prometheus
- prometheus-operator:https://prometheus-operator.dev/
- Prometheus:https://prometheus.io/docs/introduction/overview/
- Grafana helm charts(含 loki-stack):https://github.com/grafana/helm-charts
- Loki:https://grafana.com/docs/loki/latest/
- LogQL 查询语法:https://grafana.com/docs/loki/latest/query/
- Rancher 监控/告警/日志:https://rancher.com/docs/rancher/v2.13/en/monitoring-alerting-logging/ (Rancher 版本对应:v2.13.3,查看方法
kubectl -n cattle-system get deploy rancher -o jsonpath='{.spec.template.spec.containers[0].image}') - ArtifactHub(chart 版本检索):https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack
前置条件
- 有网机器:docker(可登录 harbor)、helm v3
- 离线集群机器(test-130):kubectl、helm v3、两个 chart 的 .tgz
- 集群 Ingress 控制器:traefik(RKE2 默认),Rancher 与本集群共用域名
rancher.optsimu.tsysmart.cn
部署与运维
整体流程一览(一在有网机器上做,二~四都在离线集群 test-130 上做):
[有网机器] [test-130 离线集群]
下载 chart .tgz
├─ 解析镜像清单(+补录2个隐身镜像)
└─ push.sh 推送到 harbor ────────→ ① rewrite-images.sh + dry-run 验证
② helm install kube-prometheus-stack
③ helm install loki-stack
④ 验证 pods / 镜像 / Loki 日志链路
⑤ helm upgrade 配 Ingress 子路径
→ https://rancher.../grafana 上线
一、有网机器:制作镜像清单并推送 harbor
1. 下载 chart 包
# -k 忽略过期证书校验;按需选择可用加速代理
curl -k -LO https://ghproxy.net/https://github.com/grafana/helm-charts/releases/download/loki-stack-2.9.11/loki-stack-2.9.11.tgz
curl -k -LO https://ghfast.top/https://github.com/prometheus-community/helm-charts/releases/download/kube-prometheus-stack-61.3.1/kube-prometheus-stack-61.3.1.tgz
2. 解析镜像列表
tar -zxf kube-prometheus-stack-61.3.1.tgz
tar -zxf loki-stack-2.9.11.tgz
cd kube-prometheus-stack
helm template . | grep "image: " | awk '{print $2}' | tr -d '"' | sort -u > ../images-kube-stack.txt
cd ../loki-stack
helm template . | grep "image: " | awk '{print $2}' | tr -d '"' | sort -u > ../images-loki-stack.txt
helm template本身就是纯渲染(client-side),不需要--dry-run参数。
3. 手动补录两个镜像
helm template 的渲染产物覆盖不到这两个镜像,需手动加入清单:
# ① prometheus-config-reloader:不出现在 image: 字段,
# 而是 operator 的启动参数 --prometheus-config-reloader=<image>
echo "quay.io/prometheus-operator/prometheus-config-reloader:v0.75.1" >> images-kube-stack.txt
# ② kube-webhook-certgen:位于 admission-webhook 的 pre-install hook Job,
# 而 helm template 不渲染 hook
echo "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20221220-controller-v1.5.1-58-g787ea74b6" >> images-kube-stack.txt
换 chart 版本时,这两个镜像的新 tag 到 values.yaml 里查:prometheusOperator.prometheusConfigReloader.image / prometheusOperator.admissionWebhooks.patch.image。
4. push.sh 推送脚本
#!/bin/bash
# 定义你的 Harbor 仓库地址和项目名称
HARBOR_REGISTRY="harbor.tsysmart.com"
PROJECT_NAME="plg-stack"
FAIL_LOG=".push-failures.log"
: > "$FAIL_LOG"
# 合并两个镜像列表并去重
cat images-kube-stack.txt images-loki-stack.txt | sort -u | while read img; do
if [ -z "$img" ]; then continue; fi
# 去掉可能存在的双引号
img=$(echo "$img" | tr -d '"')
echo "=========================================="
echo "Processing: $img"
echo "=========================================="
# 1. 拉取原镜像(失败记录并跳过)
if ! docker pull "$img"; then
echo "!!! PULL FAILED: $img"
echo "pull $img" >> "$FAIL_LOG"
continue
fi
# 2. 去掉 registry 域名段(第一段含点/冒号视为 registry)
if [[ "$img" =~ ^[^/]+\.[^/]+/.+ ]]; then
image_with_ns_and_tag=$(echo "$img" | cut -d'/' -f2-)
else
image_with_ns_and_tag="$img"
fi
# 3. 目标地址:harbor.tsysmart.com/plg-stack/<原命名空间>/<镜像名>:<tag>
target_img="$HARBOR_REGISTRY/$PROJECT_NAME/$image_with_ns_and_tag"
# 4. 打 Tag 并推送(失败同样记录)
docker tag "$img" "$target_img"
if ! docker push "$target_img"; then
echo "!!! PUSH FAILED: $target_img"
echo "push $target_img" >> "$FAIL_LOG"
continue
fi
echo "Successfully pushed to: $target_img"
done
echo "=========================================="
if [ -s "$FAIL_LOG" ]; then
echo "存在失败条目,处理后重跑:"
cat "$FAIL_LOG"
exit 1
else
echo "All images pushed OK."
fi
5. 推送后核对
跑完必须看到 All images pushed OK.;再到 Harbor UI 的 plg-stack 项目里核对:
prometheus/prometheus
prometheus/alertmanager
prometheus/node-exporter
prometheus-operator/prometheus-operator
prometheus-operator/prometheus-config-reloader
ingress-nginx/kube-webhook-certgen
kube-state-metrics/kube-state-metrics
grafana/grafana grafana/loki grafana/promtail kiwigrid/k8s-sidecar
bats/bats(loki-stack 的 helm test 用)
(个别仓库路径可能带 docker.m.daocloud.io/ 中段,取决于 chart 默认值里的源 registry;以下文的 dry-run 验证命令为准。)
二、离线集群(test-130):helm 安装
1. 镜像改写脚本 rewrite-images.sh(post-renderer)
helm 渲染后自动流经此脚本,把上游 registry 统一替换为 harbor。规则与 push.sh 的目标路径规则一一对应。
cat > rewrite-images.sh <<'EOF'
#!/usr/bin/env bash
sed -E -e 's#(image:[[:space:]]*"?)(quay\.io/)#\1harbor.tsysmart.com/plg-stack/#g' -e 's#(image:[[:space:]]*"?)(docker\.io/)#\1harbor.tsysmart.com/plg-stack/#g' -e 's#(image:[[:space:]]*"?)(docker\.m\.daocloud\.io/)#\1harbor.tsysmart.com/plg-stack/docker.m.daocloud.io/#g' -e 's#(image:[[:space:]]*"?)(ghcr\.io/)#\1harbor.tsysmart.com/plg-stack/#g' -e 's#(image:[[:space:]]*"?)(registry\.k8s\.io/)#\1harbor.tsysmart.com/plg-stack/#g' -e 's#(image:[[:space:]]*"?)(k8s\.gcr\.io/)#\1harbor.tsysmart.com/plg-stack/#g' -e 's#(image:[[:space:]]*"?)(gcr\.io/)#\1harbor.tsysmart.com/plg-stack/#g' -e 's#(image:[[:space:]]*"?)([a-z0-9_-]+/[a-z0-9_./-]+:[a-z0-9._-]+)#\1harbor.tsysmart.com/plg-stack/\2#g'
EOF
chmod +x rewrite-images.sh
# 自测(脚本吃 stdin 吐 stdout,不能单独执行):
echo 'image: quay.io/prometheus/alertmanager:v0.27.0' | ./rewrite-images.sh
# 期望:image: harbor.tsysmart.com/plg-stack/prometheus/alertmanager:v0.27.0
脚本必须在 Linux 端生成(如上 heredoc)。若从 Windows 复制过去,注意去掉 CRLF:sed -i 's/\r$//' rewrite-images.sh。安装前先 dry-run 全量验证改写覆盖度:
helm template prometheus ./kube-prometheus-stack-61.3.1.tgz -n monitoring | ./rewrite-images.sh \
| grep -E "^\s*image:" | grep -v harbor.tsysmart.com # 应为空
helm template loki ./loki-stack-2.9.11.tgz -n monitoring \
--set prometheus.enabled=false --set grafana.enabled=false \
| ./rewrite-images.sh | grep -E "^\s*image:" | grep -v harbor.tsysmart.com # 应为空
2. 安装 kube-prometheus-stack
kubectl create namespace monitoring
helm install prometheus ./kube-prometheus-stack-61.3.1.tgz \
-n monitoring \
--set prometheusOperator.admissionWebhooks.patch.image.registry=harbor.tsysmart.com/plg-stack \
--set prometheusOperator.prometheusConfigReloader.image.registry=harbor.tsysmart.com/plg-stack \
--post-renderer ./rewrite-images.sh
两条 --set 的作用:
admissionWebhooks.patch.image:pre-install 证书 Job 的镜像。它不经过image:字段渲染,post-renderer 的 sed 改不到,必须显式指向 harbor。prometheusConfigReloader.image:operator 启动参数里的 sidecar 镜像,同理必须显式指向 harbor。
3. 安装 loki-stack
helm install loki ./loki-stack-2.9.11.tgz \
-n monitoring \
--set prometheus.enabled=false \
--set grafana.enabled=false \
--set grafana.sidecar.datasources.enabled=false \
--set loki.isDefault=false \
--post-renderer ./rewrite-images.sh
参数说明:
prometheus.enabled=false、grafana.enabled=false:只取它的 loki + promtail,Grafana/Prometheus 由 kube-prometheus-stack 统一提供,避免重复部署。grafana.sidecar.datasources.enabled=false:loki-stack 的 datasource ConfigMap 由这个开关控制(与grafana.enabled无关)。必须关闭——它会带isDefault: true标签,被 kube-prometheus-stack 的 Grafana sidecar 收集后与内置 Prometheus 数据源(同为 default)冲突,Grafana 启动会报Only one datasource per organization can be marked as default并 CrashLoop。loki.isDefault=false是第二道保险。
4. 安装后验证
kubectl get jobs -n monitoring # admission-create / admission-patch → Completed
kubectl get pods -n monitoring # 16 个 pod 全部 Running:
# alertmanager-0(2/2)、prometheus-0(2/2)、grafana(3/3)、operator、kube-state-metrics、
# loki-0、5×promtail、5×node-exporter
# 全量确认没有非 harbor 镜像(应为空):
kubectl get pods -n monitoring -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{range .spec.initContainers[*]}{.image}{"\n"}{end}{end}' \
| sort -u | grep -v '^harbor.tsysmart.com'
# 连启动参数里的镜像一起检查(应为空):
helm template prometheus ./kube-prometheus-stack-61.3.1.tgz -n monitoring \
--reuse-values --post-renderer ./rewrite-images.sh \
| grep -E "(quay\.io|docker\.io|ghcr\.io|registry\.k8s\.io|k8s\.gcr\.io)" | grep -v harbor
快速登录验证:
kubectl -n monitoring get secret prometheus-grafana -o jsonpath='{.data.admin-password}' | base64 -d; echo
kubectl port-forward --address 0.0.0.0 -n monitoring svc/prometheus-grafana 3000:80
# 浏览器:http://10.1.9.130:3000/
# 注意:grafana 容器是纯 http;port-forward 不加 --address 0.0.0.0 只绑 127.0.0.1,外部访问不到
5. 验证 Loki 日志链路
日志链路是 promtail(采集/推送)→ loki(存储)→ 查询端,验证按层进行,前两层完全不依赖 Grafana:
# ① loki 服务就绪
kubectl -n monitoring exec loki-0 -- wget -qO- http://localhost:3100/ready
# 期望输出:ready
# ② 数据真的写进来了:直接打 Loki 查询 API(LogQL)
kubectl -n monitoring port-forward svc/loki 3100:3100 &
curl -sG "http://localhost:3100/loki/api/v1/query" \
--data-urlencode 'query=count_over_time({namespace="monitoring"}[5m])'
# 返回 JSON 中 data.result 非空(能看到 vector 条目和计数)= promtail→loki 通畅
# 顺手看全量标签:
curl -s http://localhost:3100/loki/api/v1/labels
kill %1
# ③ 端到端(UI 层):Grafana → Explore → 数据源选 Loki(没有就手动加:
# Connections → Data sources → Loki,URL http://loki:3100)
# 输入查询 {namespace="monitoring"} ,有实时日志滚动即整链打通
判断口诀:①不通 = loki 本体问题(看 loki-0 日志);①通空 = 采集侧问题(看 promtail pod 日志,常见为推送地址或 RBAC);②有③无 = 只是 Grafana 数据源没配好。
三、配置域名快捷访问(Ingress 子路径)
目标:https://rancher.optsimu.tsysmart.cn/grafana 和 https://rancher.optsimu.tsysmart.cn/prometheus。
原理:traefik 路由按规则长度定优先级,Host(...) && PathPrefix(/grafana) 优先于 Rancher IngressRoute 的 Host(...) 兜底规则(同域名下 longhorn-ingress 共存即是先例)。应用子路径必须两端配套:入口侧 ingress 按前缀转发,应用侧告知自己活在子路径下(Grafana 的 root_url+serve_from_sub_path、Prometheus 的 externalUrl+routePrefix)。
1. 环境确认(一次性)
kubectl get ingressclass # → traefik
kubectl get ingress -A # 查看现有路由
2. values-ingress.yaml
grafana:
ingress:
enabled: true
ingressClassName: traefik
path: /grafana
pathType: Prefix
hosts:
- rancher.optsimu.tsysmart.cn
# 子路径模式下健康检查路径同样带前缀
livenessProbe:
httpGet:
path: /grafana/api/health
readinessProbe:
httpGet:
path: /grafana/api/health
# 注意两处键名细节:
# 1) ini 的键是字面量 "grafana.ini"(chart 以 index .Values "grafana.ini" 读取,
# 嵌套写法 grafana: { ini: {...} } 不生效)
# 2) 子路径开关的正确拼写是 serve_from_sub_path
"grafana.ini":
server:
root_url: "https://rancher.optsimu.tsysmart.cn/grafana/"
serve_from_sub_path: true
prometheus:
ingress:
enabled: true
ingressClassName: traefik
hosts:
- rancher.optsimu.tsysmart.cn
paths:
- /prometheus
pathType: Prefix
# operator 据此为 prometheus 容器加 --web.route-prefix=/prometheus,无需代理重写
prometheusSpec:
externalUrl: "https://rancher.optsimu.tsysmart.cn/prometheus/"
routePrefix: /prometheus
3. 应用配置
helm upgrade prometheus ./kube-prometheus-stack-61.3.1.tgz -n monitoring \
--reuse-values \
--post-renderer ./rewrite-images.sh \
-f values-ingress.yaml \
--set-string 'grafana.env.GF_SERVER_SERVE_FROM_SUB_PATH=true'
4. 验证
kubectl -n monitoring rollout status deploy/prometheus-grafana --timeout=240s
kubectl get pods -n monitoring | grep grafana # 只剩一个 3/3 Running
curl -sk -o /dev/null -w "%{http_code}\n" \
-H "Host: rancher.optsimu.tsysmart.cn" https://10.1.9.130:30662/grafana/api/health # 200
curl -sk -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
-H "Host: rancher.optsimu.tsysmart.cn" https://10.1.9.130:30662/grafana/ # 302 单跳到 /grafana/login
浏览器(建议无痕窗口,避免旧缓存的重定向):
- Grafana:
https://rancher.optsimu.tsysmart.cn/grafana- 用户名:
admin
- 用户名:
- Prometheus:
https://rancher.optsimu.tsysmart.cn/prometheus→ 打开/targets页应全 UP
密码(在 test-130 上执行获取):
kubectl -n monitoring get secret prometheus-grafana -o jsonpath='{.data.admin-password}' | base64 -d; echo
因已关闭 loki-stack 的 datasource CM,Grafana 内只有 Prometheus 一个自动数据源。需要 Loki 查日志时手动添加一次:Connections → Data sources → Loki,URL 填 http://loki:3100。四、升级 / 运维备忘
- helm 不持久化
--post-renderer和命令行--set:每次helm upgrade必须重带该 release 的全部--set+--post-renderer ./rewrite-images.sh(--reuse-values只能带回落盘的 values)。 - 升级 chart 版本时:重做第一节(解析镜像 + 补录两个镜像 + 推送 + 核对),再换 .tgz 执行本脚本。
常用排障命令:
helm get values prometheus -n monitoring # 查看生效的 user values
kubectl -n monitoring describe pod <pod> # 事件(ImagePullBackOff / 探针失败)
kubectl -n monitoring logs deploy/prometheus-grafana -c grafana --tail=50
推荐固化为脚本放在 ~/k8s/,install/upgrade 通用:
# install-kps.sh
helm upgrade --install prometheus ./kube-prometheus-stack-61.3.1.tgz -n monitoring \
--reuse-values \
--post-renderer ./rewrite-images.sh \
-f values-ingress.yaml \
--set prometheusOperator.admissionWebhooks.patch.image.registry=harbor.tsysmart.com/plg-stack \
--set prometheusOperator.prometheusConfigReloader.image.registry=harbor.tsysmart.com/plg-stack