环境准备
yaml
## 操作系统:ubuntu 22.04
master:
192.168.10.128
worker:
192.168.10.129
nfs server/harbor:
192.168.10.136
创建nfs和部署nfs-subdir-external-provisioner
192.168.10.136节点部署nfs服务端
yaml
# 在 192.168.10.136 上执行
sudo apt update
sudo apt install -y nfs-kernel-server
sudo mkdir -p /data/nfs
sudo chmod 777 /data/nfs
sudo tee /etc/exports <<EOF
/data/nfs 192.168.10.128(rw,sync,no_subtree_check,no_root_squash)
/data/nfs 192.168.10.129(rw,sync,no_subtree_check,no_root_squash)
EOF
sudo exportfs -ra
sudo systemctl enable nfs-kernel-server
sudo systemctl restart nfs-kernel-server
echo "=== NFS 共享列表 ==="
sudo showmount -e localhost
k8s节点部署nfs客户端(master、worker)
yaml
sudo apt update
sudo apt install -y nfs-common
测试 NFS 连通性(任选一个节点):
yaml
showmount -e 192.168.10.136
# 测试挂载
sudo mkdir -p /mnt/test
sudo mount -t nfs 192.168.10.136:/data/nfs /mnt/test
df -h | grep nfs
sudo umount /mnt/test
在 K8s 中部署 NFS Provisioner
在 master 上执行
yaml
# 添加仓库
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update nfs-subdir-external-provisioner
# 安装(指定 192.168.10.136为 NFS 服务器)
helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--namespace kube-system \
--set nfs.server=192.168.10.136 \
--set nfs.path=/data/nfs \
--set storageClass.name=nfs-client \
--set storageClass.defaultClass=false
发现拉取镜像失败
yaml
kubectl -n kube-system get pod

更换nfs-subdir-external-provisioner镜像为国内镜像
yaml
kubectl -n kube-system edit deploy nfs-subdir-external-provisioner
## swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2

验证
yaml
kubectl -n kube-system get deploy nfs-subdir-external-provisioner

将 nfs-client 设为默认 StorageClass
shell
kubectl patch storageclass nfs-client -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

namespace
yaml
vim ns.yaml
yaml
apiVersion: v1
kind: Namespace
metadata:
name: observability
yaml
kubectl apply -f ns.yaml
minio
minio.yaml
yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: minio-pvc
namespace: observability
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: minio
name: minio
namespace: observability
spec:
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
containers:
- name: minio
image: minio/minio:RELEASE.2025-03-12T18-04-18Z
imagePullPolicy: IfNotPresent
command: ["/usr/bin/minio"]
args: ["server", "/data", "--console-address", ":9090"]
volumeMounts:
- mountPath: /data
name: data
ports:
- containerPort: 9090
name: console
- containerPort: 9000
name: api
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 1Gi
volumes:
- name: data
persistentVolumeClaim:
claimName: minio-pvc
---
apiVersion: v1
kind: Service
metadata:
name: minio-service
namespace: observability
spec:
type: NodePort
selector:
app: minio
ports:
- name: console
port: 9090
protocol: TCP
targetPort: 9090
nodePort: 30300
- name: api
port: 9000
protocol: TCP
targetPort: 9000
nodePort: 30200
部署
yaml
kubectl apply -f minio.yaml

yaml
kubectl -n observability get pod
kubectl -n observability get svc

验证
http://192.168.10.129:30300/login
访问k8s节点ip:30300,默认用户名密码都是minioadmin

minIO配置
创建loki和tempo的bucket

创建 ak、sk 然后赋予相关bucket的权限
AK:kjq5aB9MN5OVLVX1b1nw
SK:MPJfzGvQDNIOzhh5QL0v3WYY8d0EsTXyE3lZP45I

创建授权

json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::loki/*",
"arn:aws:s3:::tempo/*"
]
}
]
}
prometheus grafana
kube-prometheus-stack-values.yaml
json
vim kube-prometheus-stack-values.yaml
json
# ============================================
# kube-prometheus-stack values.yaml
# 适配 K8s 1.34 测试环境
# 节点:master(37) + worker(103, 98)
# NFS StorageClass: nfs-client
# ============================================
# --------------------------------------------
# 全局配置
# --------------------------------------------
global:
rbac:
create: true
# --------------------------------------------
# Prometheus Operator
# --------------------------------------------
prometheusOperator:
enabled: true
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
# --------------------------------------------
# Prometheus
# --------------------------------------------
prometheus:
enabled: true
prometheusSpec:
# 数据保留 7 天(测试环境)
retention: "7d"
# 持久化存储
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: nfs-client
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
# 资源限制
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
# 服务发现配置
serviceMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelector: {}
podMonitorSelectorNilUsesHelmValues: false
podMonitorSelector: {}
# 启用 exemplar 存储(Grafana Exemplars 功能的前提)
enableFeatures:
- exemplar-storage
# 启用 remote write receiver(OTel Collector 写入 metrics 所需)
enableRemoteWriteReceiver: true
service:
type: NodePort
nodePort: 30090
port: 9090
targetPort: 9090
# --------------------------------------------
# Alertmanager
# --------------------------------------------
alertmanager:
enabled: true
config:
global:
resolve_timeout: 5m
route:
group_by: ['alertname', 'job']
group_wait: 30s
group_interval: 5m
repeat_interval: 12h
receiver: 'null'
routes:
- match:
severity: critical
receiver: 'null'
receivers:
- name: 'null'
alertmanagerSpec:
# 持久化存储
storage:
volumeClaimTemplate:
spec:
storageClassName: nfs-client
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
# --------------------------------------------
# Grafana(核心配置)
# --------------------------------------------
grafana:
enabled: true
# ====== 关键:禁用 chart 自动创建的默认数据源,避免冲突 ======
defaultDatasourceEnabled: false
adminUser: admin
adminPassword: admin123
persistence:
enabled: true
type: pvc
storageClassName: nfs-client
accessModes: ["ReadWriteOnce"]
size: 5Gi
service:
type: NodePort
nodePort: 30030
port: 80
targetPort: 3000
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
env:
GF_PLUGINS_PREINSTALL_DISABLED: "true"
# ========== 显式声明 Prometheus 数据源,解锁 Exemplars 配置 ==========
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://kube-prometheus-stack-prometheus.observability.svc.cluster.local:9090
isDefault: true
editable: true
jsonData:
httpMethod: POST
# 关键:设置 Prometheus type,否则 Exemplars 区域不会显示 Add 按钮
prometheusType: Prometheus
cacheLevel: Low
manageAlerts: true
# exemplar 配置留空,由你在 UI 手动配置
exemplarTraceIdDestinations: []
# ========== 启用 Dashboard 自动导入 ==========
sidecar:
dashboards:
enabled: true
label: grafana_dashboard
labelValue: "1"
# 搜索所有命名空间中的 ConfigMap
searchNamespace: ALL
# 更新频率
reloadOnChange: true
datasources:
enabled: true
label: grafana_datasource
labelValue: "1"
searchNamespace: ALL
# 启用默认的 K8s 监控 Dashboard
defaultDashboardsEnabled: true
# 测试环境不需要高可用
replicas: 1
affinity: {}
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
# --------------------------------------------
# Node Exporter(节点监控)
# --------------------------------------------
nodeExporter:
enabled: true
# --------------------------------------------
# kube-state-metrics(K8s 资源监控)
# --------------------------------------------
kube-state-metrics:
enabled: true
image:
registry: swr.cn-north-4.myhuaweicloud.com/ddn-k8s
repository: registry.k8s.io/kube-state-metrics/kube-state-metrics
tag: v2.19.1
pullPolicy: IfNotPresent
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
# --------------------------------------------
# Prometheus Adapter(HPA 指标,可选)
# --------------------------------------------
prometheusAdapter:
enabled: false
# --------------------------------------------
# Thanos Sidecar(可选,测试环境关闭)
# --------------------------------------------
thanosSidecar:
enabled: false
部署命令
json
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts \
--timeout 300s
helm repo update prometheus-community --timeout 300s
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace observability \
-f kube-prometheus-stack-values.yaml \
--timeout 10m
## 如果上面因为网络报错,可使用下面的wget命令直接下载,或者通过链接离线下载并上传
wget https://github.com/prometheus-community/helm-charts/releases/download/kube-prometheus-stack-87.1.0/kube-prometheus-stack-87.1.0.tgz
helm install kube-prometheus-stack ./kube-prometheus-stack-87.1.0.tgz \
--namespace observability \
-f kube-prometheus-stack-values.yaml \
--timeout 10m

json
# 查看所有组件状态
kubectl -n observability get pod

验证
访问 Grafana
账号:admin / admin123

查看 Prometheus
json
kubectl -n observability port-forward svc/kube-prometheus-stack-prometheus 9090:9090
浏览器访问 http://任意节点IP:30090/

卸载
json
kubectl -n observability get all
helm uninstall kube-prometheus-stack -n observability
kubectl -n observability delete pvc kube-prometheus-stack-grafana
tempo
tempo-values.yaml
注意替换成自己的ak和sk
yaml
vim tempo-values.yaml
yaml
# ============================================
# Tempo Distributed - 测试环境配置
# 数据存储:MinIO
# ============================================
# 启用 HTTP 流式查询(Grafana 搜索trace需要)
streamOverHTTPEnabled: true
# --------------------------------------------
# 1. 存储配置:使用外部 MinIO (S3 兼容)
# --------------------------------------------
storage:
trace:
backend: s3
s3:
bucket: tempo
endpoint: minio-service.observability.svc.cluster.local:9000
region: cn-north-1
access_key: kjq5aB9MN5OVLVX1b1nw
secret_key: MPJfzGvQDNIOzhh5QL0v3WYY8d0EsTXyE3lZP45I
insecure: true
# --------------------------------------------
# 2. 禁用内置 MinIO
# --------------------------------------------
minio:
enabled: false
# --------------------------------------------
# 3. 接收器配置
# --------------------------------------------
traces:
otlp:
grpc:
enabled: true
port: 4317
http:
enabled: true
port: 4318
zipkin:
enabled: false
jaeger:
thriftCompact:
enabled: false
thriftHttp:
enabled: false
opencensus:
enabled: false
# --------------------------------------------
# 4. Ingester:3 副本,测试环境允许共节点
# --------------------------------------------
ingester:
replicas: 3
persistence:
enabled: false
# 关键修复:affinity 必须是完整对象,不能用数组语法覆盖
affinity: {}
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# --------------------------------------------
# 5. Distributor(合并后的唯一配置)
# --------------------------------------------
distributor:
replicas: 1
config:
log_received_spans:
enabled: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
# --------------------------------------------
# 6. 其他组件
# --------------------------------------------
querier:
replicas: 1
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
queryFrontend:
replicas: 1
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
compactor:
replicas: 1
resources:
requests:
cpu: 100m
memory: 512Mi # 从 128Mi 提升到 512Mi
limits:
cpu: 500m
memory: 1Gi # 从 256Mi 提升到 1Gi
# --------------------------------------------
# 7. Metrics Generator
# --------------------------------------------
metricsGenerator:
enabled: true
replicas: 1
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
# --------------------------------------------
# 8. Memcached
# --------------------------------------------
memcached:
enabled: true
replicas: 1
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 100m
memory: 256Mi
# --------------------------------------------
# 9. Gateway
# --------------------------------------------
gateway:
enabled: true
replicas: 1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
部署
bash
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update grafana
## 网络原因会需要尝试多次
helm install tempo grafana/tempo-distributed \
--namespace observability \
-f tempo-values.yaml \
--timeout 10m \
--wait
##如果部署失败,发现pod的日志提示amd不支持,则可以部署下面的
helm install tempo grafana/tempo-distributed \
-n observability \
--version 1.8.0 \
--set global.image.tag=2.3.1 \
-f tempo-values.yaml --timeout 10m
bash
kubectl -n observability get pod

验证
添加 Tempo 数据源
- URL :
[http://tempo-gateway.observability.svc.cluster.local](http://tempo-gateway.observability.svc.cluster.local) - 类型: Tempo


卸载
bash
helm uninstall tempo -n observability
loki
loki-values.yaml
注意替换成自己的ak和sk
yaml
vim loki-values.yaml
yaml
# ============================================
# Loki Helm Chart 自定义配置 (Simple Scalable Mode)
# 适配 K8s 1.34 测试环境
# 存储后端:外部 MinIO (S3 兼容)
# ============================================
# 部署模式:SimpleScalable(适合测试环境)
deploymentMode: SimpleScalable
# 禁用内置 MinIO(使用外部 MinIO)
minio:
enabled: false
# 关键:忽略 MinIO 弃用警告(chart v17+ 需要)
ignoreMinioDeprecation: true
# Loki 核心配置
loki:
# Schema 配置
schemaConfig:
configs:
- from: "2024-04-01"
store: tsdb
object_store: s3
schema: v13
index:
prefix: loki_index_
period: 24h
# 存储配置:使用外部 MinIO 的 S3 接口
storage:
type: s3
bucketNames:
## 使用minio的bucket
chunks: loki
ruler: loki
admin: loki
s3:
endpoint: minio-service.observability.svc.cluster.local:9000
region: cn-north-1
accessKeyId: "kjq5aB9MN5OVLVX1b1nw"
secretAccessKey: "MPJfzGvQDNIOzhh5QL0v3WYY8d0EsTXyE3lZP45I"
insecure: true
s3forcepathstyle: true
# 压缩配置
ingester:
lifecycler:
ring:
replication_factor: 1 # 测试环境改为 1
chunk_encoding: snappy
# 查询配置
querier:
max_concurrent: 4
# 模式分析器
pattern_ingester:
enabled: true
# 限制配置
limits_config:
allow_structured_metadata: true
volume_enabled: true
retention_period: 168h # 7天保留
# 通用配置
auth_enabled: false
# ============================================
# 组件配置
# ============================================
# backend 需要持久化(存储索引和规则)
backend:
replicas: 1
persistence:
enabled: true
storageClass: nfs-client
size: 5Gi
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
# read 是无状态组件,不需要持久化存储
read:
replicas: 1
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
# write 需要持久化(缓存日志块)
write:
replicas: 1
persistence:
enabled: true
storageClass: nfs-client
size: 10Gi
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
# Gateway 配置(NGINX 入口)
gateway:
enabled: true
service:
type: NodePort
nodePort: 30032
# 禁用 access-log-exporter(AMD64 v2 不兼容)
accessLogs:
enabled: false
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
# 缓存配置(测试环境可以关闭或简化)
chunksCache:
enabled: false
resultsCache:
enabled: false
# Loki Canary(日志监控探针)
lokiCanary:
enabled: true
# 容忍调度到 master
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
部署
shell
helm repo add grafana-community https://grafana-community.github.io/helm-charts
helm update grafana-community
helm install loki grafana-community/loki \
--namespace observability \
-f loki-values.yaml \
--timeout 10m \
--wait
##网络不行的话,就手动下载
wget https://github.com/grafana-community/helm-charts/releases/download/loki-18.1.1/loki-18.1.1.tgz
helm upgrade --install loki ./loki-18.1.1.tgz \
--namespace observability \
-f loki-values.yaml \
--timeout 10m \
--wait
## 如果exporter有问题则去掉exporter
kubectl patch deployment loki-gateway -n observability --type='json' -p='[
{
"op": "remove",
"path": "/spec/template/spec/containers/1"
}
]'
json
# 查看所有组件状态
kubectl -n observability get pod

验证
添加数据源
shell
http://loki-gateway.observability.svc.cluster.local:80

shell
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}')
# 推送测试日志
curl -X POST "http://${NODE_IP}:30032/loki/api/v1/push" \
-H "Content-Type: application/json" \
-d '{
"streams": [{
"stream": {"job":"test","level":"info","app":"myapp"},
"values": [
["'$(date +%s%N)'", "测试日志 1: Hello Loki"],
["'$(($(date +%s%N)+1000000000))'", "测试日志 2: 这是一条测试"],
["'$(($(date +%s%N)+2000000000))'", "测试日志 3: Grafana 可以查询了"]
]
}]
}'
echo "推送完成,等待 2 秒..."
sleep 2
# 使用 range query 查询(最近 5 分钟)
START=$(date -d '5 minutes ago' +%s)000000000
END=$(date +%s)000000000
# 验证查询
curl -s "http://${NODE_IP}:30032/loki/api/v1/query_range?query=%7Bjob%3D%22test%22%7D&start=${START}&end=${END}&limit=100" | python3 -m json.tool 2>/dev/null || cat
推送后,在 Grafana Explore 中输入:
shell
{job="test"}
点击 Run query,应该能看到 3 条日志。

卸载
bash
kubectl delete pvc -n observability -l app.kubernetes.io/name=loki
helm uninstall loki -n observability
promtail
promtail-values.yaml
bash
config:
clients:
- url: http://loki-gateway.observability.svc.cluster.local/loki/api/v1/push
snippets:
extraScrapeConfigs: |
- job_name: kubernetes-pods-custom
kubernetes_sd_configs:
- role: pod
pipeline_stages:
- cri: {}
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_pod_container_name]
target_label: container
- action: drop
source_labels: [__meta_kubernetes_pod_phase]
regex: Succeeded|Failed|Completed
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
部署
bash
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update grafana
helm upgrade --install promtail grafana/promtail \
-n observability \
-f promtail-values.yaml \
--timeout 10m \
--wait
json
# 查看所有组件状态
kubectl -n observability get pod

卸载
bash
helm uninstall promtail -n observability
在loki的数据源设置租户id X-Scope-OrgID =1

部署 OpenTelemetry Collector
otel-collector.yaml
bash
vim otel-collector.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector
namespace: observability
spec:
replicas: 1
selector:
matchLabels:
app: otel-collector
template:
metadata:
labels:
app: otel-collector
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.104.0
args:
- --config=/etc/otel-collector-config/otel-collector-config.yaml
ports:
- containerPort: 4317
name: otlp-grpc
- containerPort: 4318
name: otlp-http
- containerPort: 13133
name: health
volumeMounts:
- name: config
mountPath: /etc/otel-collector-config
volumes:
- name: config
configMap:
name: otel-collector-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector-config
namespace: observability
data:
otel-collector-config.yaml: |
extensions:
health_check:
endpoint: 0.0.0.0:13133
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
resource:
attributes:
- key: service.namespace
value: "default"
action: upsert
exporters:
otlp/tempo:
endpoint: tempo-distributor.observability.svc.cluster.local:4317
tls:
insecure: true
# loki exporter 0.104.0 最简配置
loki:
endpoint: http://loki-gateway.observability.svc.cluster.local:80/loki/api/v1/push
tls:
insecure: true
prometheusremotewrite:
endpoint: http://kube-prometheus-stack-prometheus.observability.svc.cluster.local:9090/api/v1/write
tls:
insecure: true
external_labels:
collector: otel-collector
service:
extensions: [health_check]
telemetry:
metrics:
address: 0.0.0.0:8889
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/tempo]
logs:
receivers: [otlp]
processors: [batch, resource]
exporters: [loki]
metrics:
receivers: [otlp]
processors: [batch, resource]
exporters: [prometheusremotewrite]
---
apiVersion: v1
kind: Service
metadata:
name: otel-collector
namespace: observability
spec:
selector:
app: otel-collector
ports:
- name: otlp-grpc
port: 4317
targetPort: 4317
- name: otlp-http
port: 4318
targetPort: 4318
部署
yaml
kubectl apply -f ./otel-collector.yaml
json
# 查看所有组件状态
kubectl -n observability get pod

卸载
bash
kubectl delete -f ./otel-collector.yaml
OpenTelemetry实践系列-1 部署服务篇环境准备 - 掘金