环境:Kubernetes v1.32.5、Rocky Linux 8.10、单节点 k8s、Zabbix 7.4、PostgreSQL 16 + TimescaleDB 2.17.2。
| 项目 | 配置 |
|---|---|
| Namespace | zabbix |
| 数据库 | TimescaleDB timescale/timescaledb:2.17.2-pg16 |
| Zabbix Server | zabbix/zabbix-server-pgsql:alpine-7.4-latest |
| Zabbix Web | zabbix/zabbix-web-nginx-pgsql:alpine-7.4-latest |
| Agent | zabbix/zabbix-agent2:alpine-7.4-latest |
| 数据根目录 | /data/zabbix |
| 域名 | zabbix.******.com |
| 外部地址 | https://zabbix.******.com:31443 |
| Ingress | nginx,NodePort HTTPS 31443 |
单节点不具备高可用能力。PV 使用 Local Volume,节点或磁盘故障会影响整个系统;生产环境应使用独立数据盘、定期备份并规划异地恢复。
当前域名应解析为节点 IP:
zabbix.******.com. A 10.12.9.6确认镜像已缓存或节点能从镜像仓库拉取:
docker images --format '{{.Repository}}:{{.Tag}}' | grep -E 'zabbix|timescale'创建 /root/zabbix/manifests/00-storage-pv-pvc.yaml 前,先准备目录:
sudo install -d -m 0777 /data/zabbix/postgresql /data/zabbix/server0777 仅适用于测试环境,生产环境应按容器运行 UID/GID 收紧权限。
创建 StorageClass:
应用:
Retain 防止删除 PVC 时自动删除宿主机数据;WaitForFirstConsumer 让 Local PV 与调度节点一致。
完整 /root/zabbix/manifests/00-storage-pv-pvc.yaml:
创建两个 PV:
| PV | 宿主机路径 | 容量 | PVC |
|---|---|---|---|
zabbix-postgresql-pv | /data/zabbix/postgresql | 30Gi | zabbix-postgresql-data |
zabbix-server-pv | |
每个 PV 都必须包含以下字段:
PVC 必须显式绑定对应 PV:
storageClassName: zabbix-local
volumeName: zabbix-postgresql-pv应用并确认:
创建数据库工作负载前,两个 PV/PVC 均应为 Bound。
创建密码 Secret,不要将密码写入清单或 Shell 历史:
完整 /root/zabbix/manifests/01-postgresql.yaml:
核心数据库环境变量:
数据库容器挂载:
应用并验证:
Zabbix Server 会创建多个数据库连接。测试环境建议至少 max_connections = 100。
若已经出现 FATAL: sorry, too many clients already,先缩容 Zabbix Server 释放连接,再修改:
完整 /root/zabbix/manifests/02-server.yaml:
Server 使用 Deployment、Service zabbix-server,对内暴露 TCP 10051。关键环境变量:
Server 数据卷挂载到 /var/lib/zabbix,并引用 PVC zabbix-server-data。单节点应设置:
部署和验证:
正常日志应包含:
server #0 started
HA manager started in active mode完整 /root/zabbix/manifests/03-web.yaml:
Web 镜像使用 zabbix/zabbix-web-nginx-pgsql:alpine-7.4-latest,Service 名称 zabbix-web,端口 8080。
关键环境变量:
部署后验证:
预期 HTTP 返回 200 OK。
使用真实证书创建 TLS Secret:
证书 SAN 必须包含 zabbix.******.com。
Ingress /root/zabbix/manifests/04-ingress.yaml:
kubectl apply -f /root/zabbix/manifests/04-ingress.yaml
curl -kI https://zabbix.******.com:31443预期返回 HTTP/2 200。入口 NGINX 的 NodePort 为 31443 时,访问地址必须带端口。
首次登录凭据:
用户名:Admin
密码:zabbix登录后立即修改默认管理员密码。
完整 /root/zabbix/manifests/05-agent2.yaml:
部署 Agent 2 DaemonSet,镜像:zabbix/zabbix-agent2:alpine-7.4-latest。关键配置:
容器使用宿主机网络,监听节点 TCP 10050;挂载 /proc 和 /sys 为只读,供主机指标采集。
在 Zabbix Web 新增主机:
| 字段 | 值 |
|---|---|
| Host name | k8s |
| Interface | 10.12.9.6:10050 |
| Template | Linux by Zabbix agent active |
不要把默认的 Zabbix server 主机改名为 k8s。默认主机用于 Server 自身;Agent 2 的 ZBX_HOSTNAME=k8s 必须匹配新建主机名。
使用主动模板时,不要附加 Linux by Zabbix agent 被动模板,否则 Server 会尝试主动连接 Agent,可能导致 ZBX 被动可用性标记为红色。
验收标准:
Bound。200,Web 可以登录。k8s 主机在“最新数据”中持续产生 CPU、内存和磁盘数据。备份配置和数据库:
停止服务但保留数据:
彻底删除前必须确认备份可用。之后按顺序删除 PVC、PV、TLS Secret、Namespace 和宿主机目录内容:
仅当没有任何其他 PVC/PV 使用 zabbix-local 时才删除 StorageClass:
kubectl get nodes -o wide
kubectl get ingressclass
kubectl get svc -n ingress-nginx -o wide
kubectl get storageclass
df -hT /data
getent hosts zabbix.******.comapiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: zabbix-local
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: false
---
apiVersion: v1
kind: Namespace
metadata:
name: zabbix
labels:
app.kubernetes.io/part-of: zabbix
istio-injection: disabledkubectl apply -f /root/zabbix/manifests/00-storageclass-namespace.yaml
kubectl create serviceaccount default -n zabbix --dry-run=client -o yaml | kubectl apply -f -apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: zabbix-local
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: false
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: zabbix-postgresql-pv
spec:
capacity:
storage: 30Gi
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: zabbix-local
local:
path: /data/zabbix/postgresql
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: zabbix-server-pv
spec:
capacity:
storage: 5Gi
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: zabbix-local
local:
path: /data/zabbix/server
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: zabbix-postgresql-data
namespace: zabbix
spec:
accessModes: [ReadWriteOnce]
storageClassName: zabbix-local
volumeName: zabbix-postgresql-pv
resources:
requests:
storage: 30Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: zabbix-server-data
namespace: zabbix
spec:
accessModes: [ReadWriteOnce]
storageClassName: zabbix-local
volumeName: zabbix-server-pv
resources:
requests:
storage: 5Gi| 5Gi |
zabbix-server-data |
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Retain
storageClassName: zabbix-local
local:
path: /data/zabbix/COMPONENT
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [k8s]kubectl apply --dry-run=server -f /root/zabbix/manifests/00-storage-pv-pvc.yaml
kubectl apply -f /root/zabbix/manifests/00-storage-pv-pvc.yaml
kubectl get pvc -n zabbix
kubectl get pv | grep '^zabbix-'read -rsp '请输入 Zabbix 数据库密码: ' ZABBIX_DB_PASSWORD
echo
kubectl create secret generic zabbix-db-secret -n zabbix \
--from-literal=POSTGRES_PASSWORD="$ZABBIX_DB_PASSWORD" \
--from-literal=ZABBIX_DB_PASSWORD="$ZABBIX_DB_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
unset ZABBIX_DB_PASSWORDapiVersion: v1
kind: Service
metadata:
name: zabbix-postgresql
namespace: zabbix
spec:
clusterIP: None
selector:
app.kubernetes.io/name: zabbix-postgresql
ports:
- name: postgres
port: 5432
targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zabbix-postgresql
namespace: zabbix
spec:
serviceName: zabbix-postgresql
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: zabbix-postgresql
template:
metadata:
labels:
app.kubernetes.io/name: zabbix-postgresql
spec:
nodeSelector:
kubernetes.io/hostname: k8s
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
securityContext:
fsGroup: 999
containers:
- name: postgresql
image: timescale/timescaledb:2.17.2-pg16
imagePullPolicy: IfNotPresent
ports:
- name: postgres
containerPort: 5432
env:
- name: POSTGRES_DB
value: zabbix
- name: POSTGRES_USER
value: zabbix
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: zabbix-db-secret
key: POSTGRES_PASSWORD
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgresql-data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: [sh, -c, pg_isready -U zabbix -d zabbix]
initialDelaySeconds: 15
periodSeconds: 10
livenessProbe:
exec:
command: [sh, -c, pg_isready -U zabbix -d zabbix]
initialDelaySeconds: 60
periodSeconds: 20
resources:
requests: {cpu: 250m, memory: 512Mi}
limits: {cpu: "1", memory: 1Gi}
volumes:
- name: postgresql-data
persistentVolumeClaim:
claimName: zabbix-postgresql-dataPOSTGRES_DB: zabbix
POSTGRES_USER: zabbix
POSTGRES_PASSWORD:
valueFrom:
secretKeyRef:
name: zabbix-db-secret
key: POSTGRES_PASSWORD
PGDATA: /var/lib/postgresql/data/pgdatavolumeMounts:
- name: postgresql-data
mountPath: /var/lib/postgresql/data
volumes:
- name: postgresql-data
persistentVolumeClaim:
claimName: zabbix-postgresql-datakubectl apply -f /root/zabbix/manifests/01-postgresql.yaml
kubectl rollout status statefulset/zabbix-postgresql -n zabbix --timeout=180s
kubectl exec -n zabbix statefulset/zabbix-postgresql -- pg_isready -U zabbix -d zabbix
kubectl exec -n zabbix statefulset/zabbix-postgresql -- \
psql -U zabbix -d zabbix -c "CREATE EXTENSION IF NOT EXISTS timescaledb;"kubectl exec -n zabbix statefulset/zabbix-postgresql -- \
sh -c 'psql -U zabbix -d zabbix -c "ALTER SYSTEM SET max_connections = 100;"'
kubectl rollout restart statefulset/zabbix-postgresql -n zabbix
kubectl rollout status statefulset/zabbix-postgresql -n zabbix --timeout=180s
kubectl exec -n zabbix statefulset/zabbix-postgresql -- \
psql -U zabbix -d zabbix -c "SHOW max_connections;"kubectl scale deployment/zabbix-server -n zabbix --replicas=0
kubectl wait --for=delete pod -l app.kubernetes.io/name=zabbix-server -n zabbix --timeout=120sapiVersion: v1
kind: Service
metadata:
name: zabbix-server
namespace: zabbix
spec:
selector:
app.kubernetes.io/name: zabbix-server
ports:
- name: zabbix-trapper
port: 10051
targetPort: 10051
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: zabbix-server
namespace: zabbix
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: zabbix-server
template:
metadata:
labels:
app.kubernetes.io/name: zabbix-server
spec:
nodeSelector:
kubernetes.io/hostname: k8s
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: zabbix-server
image: zabbix/zabbix-server-pgsql:alpine-7.4-latest
imagePullPolicy: IfNotPresent
ports:
- name: zabbix-trapper
containerPort: 10051
env:
- name: DB_SERVER_HOST
value: zabbix-postgresql
- name: POSTGRES_DB
value: zabbix
- name: POSTGRES_USER
value: zabbix
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: zabbix-db-secret
key: ZABBIX_DB_PASSWORD
- name: ZBX_LISTENPORT
value: "10051"
volumeMounts:
- name: server-data
mountPath: /var/lib/zabbix
readinessProbe:
tcpSocket: {port: 10051}
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
tcpSocket: {port: 10051}
initialDelaySeconds: 60
periodSeconds: 20
resources:
requests: {cpu: 250m, memory: 512Mi}
limits: {cpu: "1", memory: 1Gi}
volumes:
- name: server-data
persistentVolumeClaim:
claimName: zabbix-server-dataDB_SERVER_HOST: zabbix-postgresql
POSTGRES_DB: zabbix
POSTGRES_USER: zabbix
POSTGRES_PASSWORD:
valueFrom:
secretKeyRef:
name: zabbix-db-secret
key: ZABBIX_DB_PASSWORDnodeSelector:
kubernetes.io/hostname: k8s
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedulekubectl apply -f /root/zabbix/manifests/02-server.yaml
kubectl rollout status deployment/zabbix-server -n zabbix --timeout=300s
kubectl logs -n zabbix deploy/zabbix-server --tail=100apiVersion: v1
kind: Service
metadata:
name: zabbix-web
namespace: zabbix
spec:
selector:
app.kubernetes.io/name: zabbix-web
ports:
- name: http
port: 8080
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: zabbix-web
namespace: zabbix
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: zabbix-web
template:
metadata:
labels:
app.kubernetes.io/name: zabbix-web
spec:
nodeSelector:
kubernetes.io/hostname: k8s
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: zabbix-web
image: zabbix/zabbix-web-nginx-pgsql:alpine-7.4-latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
env:
- name: DB_SERVER_HOST
value: zabbix-postgresql
- name: POSTGRES_DB
value: zabbix
- name: POSTGRES_USER
value: zabbix
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: zabbix-db-secret
key: ZABBIX_DB_PASSWORD
- name: ZBX_SERVER_HOST
value: zabbix-server
- name: PHP_TZ
value: Asia/Shanghai
readinessProbe:
httpGet: {path: /, port: 8080}
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet: {path: /, port: 8080}
initialDelaySeconds: 60
periodSeconds: 20
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 500m, memory: 512Mi}DB_SERVER_HOST: zabbix-postgresql
POSTGRES_DB: zabbix
POSTGRES_USER: zabbix
POSTGRES_PASSWORD: <引用 zabbix-db-secret 的 ZABBIX_DB_PASSWORD>
ZBX_SERVER_HOST: zabbix-server
PHP_TZ: Asia/Shanghaikubectl apply -f /root/zabbix/manifests/03-web.yaml
kubectl rollout status deployment/zabbix-web -n zabbix --timeout=300s
kubectl run zabbix-web-check -n zabbix --rm -i --restart=Never \
--image=curlimages/curl:8.10.1 -- curl -fsSI http://zabbix-web:8080/apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: zabbix
namespace: zabbix
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "32m"
spec:
ingressClassName: nginx
tls:
- hosts: [zabbix.******.com]
secretName: zabbix-tls
rules:
- host: zabbix.******.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: zabbix-web
port:
number: 8080apiVersion: v1
kind: ServiceAccount
metadata:
name: zabbix-agent
namespace: zabbix
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: zabbix-agent2
namespace: zabbix
spec:
selector:
matchLabels:
app.kubernetes.io/name: zabbix-agent2
template:
metadata:
labels:
app.kubernetes.io/name: zabbix-agent2
spec:
serviceAccountName: zabbix-agent
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
tolerations:
- operator: Exists
containers:
- name: agent2
image: zabbix/zabbix-agent2:alpine-7.4-latest
imagePullPolicy: IfNotPresent
ports:
- name: agent
containerPort: 10050
hostPort: 10050
env:
- name: ZBX_SERVER_HOST
value: zabbix-server.zabbix.svc.cluster.local
- name: ZBX_SERVER_ACTIVE
value: zabbix-server.zabbix.svc.cluster.local
- name: ZBX_HOSTNAME
value: k8s
securityContext:
runAsUser: 1997
runAsGroup: 1997
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
- name: sys
mountPath: /host/sys
readOnly: true
readinessProbe:
tcpSocket: {port: 10050}
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
tcpSocket: {port: 10050}
initialDelaySeconds: 30
periodSeconds: 20
resources:
requests: {cpu: 50m, memory: 64Mi}
limits: {cpu: 250m, memory: 256Mi}
volumes:
- name: proc
hostPath: {path: /proc, type: Directory}
- name: sys
hostPath: {path: /sys, type: Directory}hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
env:
- name: ZBX_SERVER_HOST
value: zabbix-server.zabbix.svc.cluster.local
- name: ZBX_SERVER_ACTIVE
value: zabbix-server.zabbix.svc.cluster.local
- name: ZBX_HOSTNAME
value: k8skubectl apply -f /root/zabbix/manifests/05-agent2.yaml
kubectl rollout status daemonset/zabbix-agent2 -n zabbix --timeout=180s
kubectl get pods -n zabbix -l app.kubernetes.io/name=zabbix-agent2 -o widekubectl get pods -n zabbix -o wide
kubectl get pvc -n zabbix
kubectl get ingress -n zabbix
kubectl get events -n zabbix --sort-by='.lastTimestamp' | tail -40
curl -kI https://zabbix.******.com:31443BACKUP_DIR=/root/zabbix/backup/$(date +%F-%H%M%S)
mkdir -p "$BACKUP_DIR"
kubectl get pvc,pv -o yaml > "$BACKUP_DIR/storage.yaml"
kubectl get ingress,secret,configmap -n zabbix -o yaml > "$BACKUP_DIR/resources.yaml"
kubectl exec -n zabbix statefulset/zabbix-postgresql -- \
pg_dump -U zabbix -d zabbix > "$BACKUP_DIR/zabbix.sql"kubectl delete -f /root/zabbix/manifests/05-agent2.yaml
kubectl delete -f /root/zabbix/manifests/04-ingress.yaml
kubectl delete -f /root/zabbix/manifests/03-web.yaml
kubectl delete -f /root/zabbix/manifests/02-server.yaml
kubectl delete -f /root/zabbix/manifests/01-postgresql.yamlkubectl delete pvc zabbix-postgresql-data zabbix-server-data -n zabbix
kubectl delete pv zabbix-postgresql-pv zabbix-server-pv
kubectl delete secret zabbix-tls zabbix-db-secret -n zabbix --ignore-not-found
kubectl delete namespace zabbix
sudo find /data/zabbix/postgresql -mindepth 1 -delete
sudo find /data/zabbix/server -mindepth 1 -deletekubectl get pv -o custom-columns=NAME:.metadata.name,SC:.spec.storageClassName,CLAIM:.spec.claimRef.namespace/.spec.claimRef.name | grep zabbix-local
kubectl delete storageclass zabbix-local