Kubernetes 入门教程之十

大纲

Kubernetes 核心技术

部署集群性能监控平台

开源软件 cAdvisor(Container Advisor)可用于监控所在节点的容器运行状态,当前已经被默认集成到 Kubernetes 的 Kubelet 组件内,默认使用 TCP 4194 端口。在中小型规模容器集群中,通常使用 Prometheus + Grafana 来实现容器集群性能数据的采集、存储与展示。

集群可监控的指标

在 Kubernetes 集群中,可以监控的指标有以下这些:

  • 节点级(Node Metrics):监控每个工作节点(Worker Node)的系统资源使用情况

    • CPU 使用率 / 空闲率 / 负载(Load Average)
    • 内存使用量 / 可用内存
    • 磁盘使用量 / I/O 吞吐
    • 网络流量(带宽、收发包量、错误包)
    • 节点状态(Ready / NotReady)
    • 节点文件系统使用率(根分区、容器存储路径)
  • Pod / 容器级(Pod & Container Metrics):监控集群中每个 Pod 或容器的资源使用情况

    • Pod 的 CPU 使用率 / 限额 / 请求
    • Pod 的内存使用量 / 限额 / 请求
    • 容器重启次数
    • 容器启动时间、运行时间
    • 网络收发流量(in/out bytes)
    • 文件系统使用(容器内存储卷)
    • 容器状态(Running / Waiting / Terminated)
  • Kubernetes 组件指标(Control Plane Metrics):监控 K8s 控制面(Control Plane)自身的健康状态

    • API Server 请求速率 / 延迟 / 错误率
    • Scheduler 调度延迟 / 排队任务数
    • Controller Manager 队列长度 / 事件处理速率
    • Etcd 存储延迟 / Leader 选举状态 / 写入吞吐
  • 服务与网络(Service & Network Metrics):监控 Service、Ingress、DNS、网络插件等组件

    • Service 请求速率 / 成功率 / 延迟
    • Ingress 访问量 / 响应时间 / 4xx、5xx 错误率
    • DNS 查询速率 / 失败率(CoreDNS)
    • CNI 插件流量、丢包、延迟
  • 工作负载与资源对象状态(Workload Metrics):反映 K8s 资源对象(如 Deployment、DaemonSet、Job 等)的运行健康度

    • Deployment 可用副本数 / 期望副本数
    • ReplicaSet、StatefulSet 状态
    • Job 成功 / 失败次数
    • CronJob 最近执行时间
    • Namespace 级资源使用量
    • HPA(HorizontalPodAutoScaler)触发状态
  • 存储指标(Storage Metrics):监控 PV、PVC、StorageClass 的使用与性能

    • PV 容量使用率
    • PVC 绑定状态
    • I/O 延迟、读写吞吐
    • 挂载错误 / 超时
  • 应用与业务指标(Application Metrics): 通过 Prometheus Exporter 或 SDK 自定义的应用性能指标

    • 请求 QPS(请求数每秒)
    • 错误率(Error Rate)
    • 响应时间(Latency)
    • 业务统计(订单数、任务完成数等)
    • 自定义计数器 / 计时器 / 直方图(Histogram)
  • 告警与事件(Events & Alerts):用于监控异常行为与故障

    • Pod CrashLoopBackOff
    • 节点不可用
    • 资源超限(CPU / 内存)
    • Deployment 副本不足
    • PV 绑定失败 / 存储空间不足
    • API 请求超时 / 错误率过高

集群性能监控方案

常见的 Kubernetes 集群性能监控方案

  • Heapster + InfluxDB + Grafana

    • 架构:Heapster + InfluxDB + Grafana
    • 功能:Heapster 汇聚各 Node 上 cAdvisor 的监控数据,存入 InfluxDB 后通过 Grafana 展示。
    • 状态:已被 Kubernetes 官方弃用,自 Kubernetes v1.13 起不再维护。
    • 适用场景:早期集群监控,仅作学习了解。
  • Metrics Server

    • 角色:Heapster 的官方替代品。
    • 功能:提供实时的资源用量指标(CPU、内存)给 kubectl top、Horizontal Pod Autoscaler(HPA)等使用。
    • 限制:不存储历史数据,不提供可视化界面或持久化存储。
    • 状态:Kubernetes 官方的核心组件。
    • 适用场景:HPA 自动扩缩容、轻量实时监控。
  • Prometheus + Grafana

    • 架构:Prometheus + Grafana
    • 功能:
      • Prometheus = 数据收集 + 存储 + 告警。
      • Grafana = 数据展示 + 可视化分析界面。
    • 优点:
      • Prometheus 会周期性自动拉取 kubelet、cAdvisor、kube-state-metrics、node-exporter 等监控指标
      • 监控维度丰富(节点、容器、Pod、Service、集群状态)
      • 数据查询灵活(PromQL)
      • 支持历史数据存储
    • 状态:当前主流的开源监控方案。
    • 适用场景:中小型或自建环境中的主力方案。
  • Prometheus Operator + kube-prometheus-stack

    • 架构组件:
      • Prometheus(采集与存储指标)
      • Alertmanager(告警)
      • Grafana(可视化)
      • kube-state-metrics(集群资源状态)
      • node-exporter(节点系统指标)
    • 特点:
      • Operator 自动化管理 Prometheus、Alertmanager、Grafana 等部署与配置。
      • 社区维护的 “kube-prometheus-stack” Helm Chart 是生产级推荐方案。
    • 状态:功能最全、生态最活跃。
    • 适用场景:生产级集群的监控、告警、可视化一体化方案。
  • Weave Scope / Weave Cloud

    • 功能:
      • 直观展示 Pod、容器、Service 之间的拓扑关系与状态。
      • 支持查看部分性能指标(CPU、内存、网络流量)。
    • 定位:
      • 更偏向于可视化与运维调试工具。
      • 不属于严格意义上的 “性能监控方案”。
    • 适用场景:
      • 集群拓扑观测、实时诊断、开发或测试环境。
      • 可作为 Prometheus 或 Metrics Server 的补充。

不同 Kubernetes 集群性能监控方案的对比

性能监控方案类型是否官方推荐是否可替代 Heapster 可视化展示是否持久化存储
Heapster + InfluxDB + Grafana 旧版指标监控❌ 已弃用
Metrics Server 资源指标采集✔ 官方推荐
Prometheus + Grafana 指标监控✔ 官方推荐
Prometheus Operator Stack 生产级集群监控告警平台✔ 官方推荐
Weave Scope / Weave Cloud 拓扑与可视化⚙️ 可选部分

性能监控平台部署

本节将基于 Prometheus + Grafana 搭建 Kubernetes 集群的性能监控平台。

版本说明
组件版本
Kubernetesv1.19.10
Prometheusv2.0.0
Grafanav4.4.3
NodeExporterv1.10.2
准备工作
拉取镜像

在 Kubernetes 部署服务时,为了避免部署过程中出现镜像拉取超时(Image Pull Timeout)的问题,建议:

  • 提前将相关镜像预拉取到所有节点里面,确保部署时无需从远程仓库重新下载镜像;
  • 或者搭建本地镜像仓库(比如 Harbor),提高镜像拉取的速度与可靠性。

在 Kubernetes 集群的所有节点上(包括 Master 和 Worker),分别执行以下命令,提前将镜像拉取到本地

1
2
3
4
5
6
7
8
# 拉取 Node Exporter 镜像
docker pull prom/node-exporter:v1.10.2

# 拉取 Prometheus 镜像
docker pull prom/prometheus:v2.0.0

# 拉取 Grafana 镜像
docker pull grafana/grafana:4.2.0
安装 CoreDNS
安装 Ingress

特别注意

建议先安装 CoreDNS,然后再安装 Ingress,因为 Ingress 在启动时需要解析域名,有时候会依赖集群 DNS 组件(比如 CoreDNS)。

部署步骤
Prometheus 部署
  • 创建 YAML 配置文件 prometheus-config.yml,用于部署 Prometheus 的 ConfigMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: kube-system
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:

- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https

- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics

- job_name: 'kubernetes-cadvisor'
kubernetes_sd_configs:
- role: node
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

- job_name: 'kubernetes-service-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name

- job_name: 'kubernetes-services'
kubernetes_sd_configs:
- role: service
metrics_path: /probe
params:
module: [http_2xx]
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
action: keep
regex: true
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: blackbox-exporter.example.com:9115
- source_labels: [__param_target]
target_label: instance
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
target_label: kubernetes_name

- job_name: 'kubernetes-ingresses'
kubernetes_sd_configs:
- role: ingress
relabel_configs:
- source_labels: [__meta_kubernetes_ingress_annotation_prometheus_io_probe]
action: keep
regex: true
- source_labels: [__meta_kubernetes_ingress_scheme,__address__,__meta_kubernetes_ingress_path]
regex: (.+);(.+);(.+)
replacement: ${1}://${2}${3}
target_label: __param_target
- target_label: __address__
replacement: blackbox-exporter.example.com:9115
- source_labels: [__param_target]
target_label: instance
- action: labelmap
regex: __meta_kubernetes_ingress_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_ingress_name]
target_label: kubernetes_name

- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
  • 创建 YAML 配置文件 prometheus-deploy.yml,用于部署 Prometheus 的 Deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
name: prometheus-deployment
name: prometheus
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- image: prom/prometheus:v2.0.0
name: prometheus
command:
- "/bin/prometheus"
args:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention=24h"
ports:
- containerPort: 9090
protocol: TCP
volumeMounts:
- mountPath: "/prometheus"
name: data
- mountPath: "/etc/prometheus"
name: config-volume
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 500m
memory: 2500Mi
serviceAccountName: prometheus
volumes:
- name: data
emptyDir: {}
- name: config-volume
configMap:
name: prometheus-config
  • 创建 YAML 配置文件 prometheus-svc.yml,用于部署 Prometheus 的 Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
labels:
app: prometheus
name: prometheus
namespace: kube-system
spec:
type: NodePort
ports:
- port: 9090
targetPort: 9090
nodePort: 30003
selector:
app: prometheus
  • 创建 YAML 配置文件 prometheus-rbac.yml,用于对 Prometheus 进行 RBAC 授权
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/proxy
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups:
- extensions
resources:
- ingresses
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: kube-system
  • 通过上述的 YAML 配置文件,快速部署 Prometheus(注意 K8s 资源对象的部署顺序)
1
2
3
4
5
6
7
8
9
10
11
# 部署 ConfigMap
kubectl apply -f prometheus-config.yml

# 部署 Deployment
kubectl apply -f prometheus-deploy.yml

# 部署 Service
kubectl apply -f prometheus-svc.yml

# RBAC 授权
kubectl apply -f prometheus-rbac.yml
  • 查看相关的 Pod
1
kubectl get pods -n kube-system -l app=prometheus
1
2
NAME                         READY   STATUS    RESTARTS   AGE
prometheus-68546b8d9-bg69k 1/1 Running 0 96s
  • 查看相关的 Service
1
kubectl get svc -n kube-system -l app=prometheus
1
2
NAME         TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
prometheus NodePort 10.0.0.21 <none> 9090:30003/TCP 2m12s
Grafana 部署
  • 创建 YAML 配置文件 grafana-deploy.yml,用于部署 Grafana 的 Deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana-core
namespace: kube-system
labels:
app: grafana
component: core
spec:
replicas: 1
selector:
matchLabels:
app: grafana
component: core
template:
metadata:
labels:
app: grafana
component: core
spec:
containers:
- image: grafana/grafana:4.2.0
name: grafana-core
imagePullPolicy: IfNotPresent
# env:
resources:
# keep request = limit to keep this container in guaranteed class
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi
env:
# The following env variables set up basic auth twith the default admin user and admin password.
- name: GF_AUTH_BASIC_ENABLED
value: "true"
- name: GF_AUTH_ANONYMOUS_ENABLED
value: "false"
# - name: GF_AUTH_ANONYMOUS_ORG_ROLE
# value: Admin
# does not really work, because of template variables in exported dashboards:
# - name: GF_DASHBOARDS_JSON_ENABLED
# value: "true"
readinessProbe:
httpGet:
path: /login
port: 3000
# initialDelaySeconds: 30
# timeoutSeconds: 1
volumeMounts:
- name: grafana-persistent-storage
mountPath: /var
volumes:
- name: grafana-persistent-storage
emptyDir: {}
  • 创建 YAML 配置文件 grafana-svc.yml,用于部署 Grafana 的 Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: kube-system
labels:
app: grafana
component: core
spec:
type: NodePort
ports:
- port: 3000
selector:
app: grafana
component: core
  • 创建 YAML 配置文件 grafana-ingress.yml,用于部署 Ingress 的路由规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana
namespace: kube-system
spec:
rules:
- host: k8s.grafana.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: grafana
port:
number: 3000
  • 通过上述的 YAML 配置文件,快速部署 Grafana(注意 K8s 资源对象的部署顺序)
1
2
3
4
5
6
7
8
# 部署 Deployment
kubectl apply -f grafana-deploy.yml

# 部署 Service
kubectl apply -f grafana-svc.yml

# 部署 Ingress 的路由规则
kubectl apply -f grafana-ingress.yml
  • 查看相关的 Pod
1
kubectl get pods -n kube-system -l app=grafana -o wide
1
2
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
grafana-core-6d6fb7566-5tv6t 1/1 Running 0 13m 10.244.0.16 k8s-node1 <none> <none>
  • 查看相关的 Service
1
kubectl get svc -n kube-system -l app=grafana
1
2
NAME      TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
grafana NodePort 10.0.0.242 <none> 3000:31671/TCP 3m41s
  • 查看相关的 Ingress 路由规则
1
kubectl get ingress -n kube-system
1
2
NAME      CLASS    HOSTS             ADDRESS   PORTS   AGE
grafana <none> k8s.grafana.com 80 86m
对外暴露节点
  • 创建 YAML 配置文件 node-exporter.yml,用于对外暴露节点(Node)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: kube-system
labels:
k8s-app: node-exporter
spec:
selector:
matchLabels:
k8s-app: node-exporter
template:
metadata:
labels:
k8s-app: node-exporter
spec:
containers:
- image: prom/node-exporter:v1.10.2
name: node-exporter
ports:
- containerPort: 9100
protocol: TCP
name: http

---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: node-exporter
name: node-exporter
namespace: kube-system
spec:
ports:
- name: http
port: 9100
nodePort: 31672
protocol: TCP
type: NodePort
selector:
k8s-app: node-exporter
  • 通过上述的 YAML 配置文件,对外暴露节点(Node)
1
2
# 部署 DaemonSet 和 Service
kubectl apply -f node-exporter.yml
  • 查看相关的 Pod
1
kubectl get pods -n kube-system -l k8s-app=node-exporter
1
2
3
4
5
NAME                  READY   STATUS    RESTARTS   AGE
node-exporter-2xgh6 1/1 Running 0 37s
node-exporter-6r2jz 1/1 Running 0 37s
node-exporter-l5hjf 1/1 Running 0 37s
node-exporter-q5zc2 1/1 Running 0 37s
  • 查看相关的 Service
1
kubectl get svc -n kube-system -l k8s-app=node-exporter
1
2
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
node-exporter NodePort 10.0.0.118 <none> 9100:31672/TCP 5m16s
  • 查看相关的 Ingress 规则
1
kubectl get ingress -n kube-system
1
2
NAME      CLASS    HOSTS             ADDRESS   PORTS   AGE
grafana <none> k8s.grafana.com 80 86m
验证步骤
通过 Service 访问 Grafana
  • 首先,查看 Grafana 相关的 Service
1
kubectl get svc -n kube-system
1
2
3
4
5
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
grafana NodePort 10.0.0.242 <none> 3000:31671/TCP 20m
kube-dns ClusterIP 10.0.0.2 <none> 53/UDP,53/TCP,9153/TCP 8d
node-exporter NodePort 10.0.0.153 <none> 9100:31672/TCP 38m
prometheus NodePort 10.0.0.21 <none> 9090:30003/TCP 31m
  • 通过任意一个集群节点的 IP 与 Grafana 的 Service 对外暴露的端口(比如 http://192.168.2.191:31671),就可以在 Kubernetes 集群外部通过浏览器访问 Grafana 的控制台页面(如下图所示)

特别注意

在 Kubernetes 集群外部,浏览器通过任意集群节点的 IP + Service 对外暴露的端口(NodePort)来访问 Grafana 的控制台页面(比如 http://192.168.2.191:31671),这并没有使用到 Ingress。

通过 Ingress 访问 Grafana
  • 查看 Grafana 相关的 Ingress 路由规则
1
kubectl get ingress -n kube-system
1
2
NAME      CLASS    HOSTS             ADDRESS   PORTS   AGE
grafana <none> k8s.grafana.com 80 86m
  • 查看 Nginx Ingress Controller 所在的节点(Node)
1
kubectl get pods -n ingress-nginx -o wide
1
2
NAME                                       READY   STATUS    RESTARTS   AGE     IP              NODE        NOMINATED NODE   READINESS GATES
nginx-ingress-controller-5dc64b58f-s82x6 1/1 Running 0 6m51s 192.168.2.131 k8s-node2 <none> <none>
  • 在 K8s 集群外部的操作系统中,编辑系统配置文件 /etc/hosts,添加域名映射记录,其中 192.168.2.131 是 Nginx Ingress Controller 所在节点的 IP 地址(请自行更改 IP 地址
1
2
# 编辑系统配置文件,添加以下内容
vim /etc/hosts
1
192.168.2.131      k8s.grafana.com 
  • 在 K8s 集群外部的操作系统中,浏览器通过域名 k8s.grafana.com 访问 Ingress。如果可以成功访问 Grafana 的控制台页面(如下图所示),则说明 Ingress + Service + Grafana 可以正常运行

Grafana 展示监控数据图表
  • 首先,查看所有 Service,记住 Prometheus 的 ClusterIP(比如 10.0.0.21
1
kubectl get svc -n kube-system
1
2
3
4
5
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
grafana NodePort 10.0.0.242 <none> 3000:31671/TCP 20m
kube-dns ClusterIP 10.0.0.2 <none> 53/UDP,53/TCP,9153/TCP 8d
node-exporter NodePort 10.0.0.153 <none> 9100:31672/TCP 38m
prometheus NodePort 10.0.0.21 <none> 9090:30003/TCP 31m
  • 登录 Grafana 的控制台页面,默认的登录用户名和密码分别是 admin / admin,成功登录后的控制台页面如下:

  • 在 Grafana 的控制台页面中,添加相应的数据源,这里数据源地址里的 IP 是 Prometheus 的 Pod 在 K8s 集群内部的 IP 地址(ClusterIP),比如 10.0.0.21,端口号是 9090

  • 在 Grafana 的控制台页面中,导入 DashBoard 模板

  • 在模板输入框中填写数字 315(Grafana 模板的编号),然后让模板输入框失去焦点(或者点击 Load 按钮),等待一会模板信息就会加载出来

  • 模板信息加载出来后,手动选择之前添加的数据源,然后点击 Import 按钮就可以导入模板

  • 模板导入成功后,Grafana 会自动跳转到 DashBoard 页面

  • 在 DashBoard 页面中,如果可以看到 K8s 集群的监控图表,则说明基于 Prometheus + Grafana 的 K8s 集群性能监控平台搭建成功

实战部署 Java 应用程序

本节将介绍如何在 Kubernetes 环境中实现 Java 项目的 CI/CD(持续集成与持续交付),其中 Docker 镜像仓库采用阿里云容器镜像服务(ACR)。

容器交付的完整流程

Java 项目开发 / 部署的流程

Kubernetes 部署项目的流程

Kubernetes 实现 CI / CD 的流程

创建 Java 应用的镜像

制作 Java 应用的镜像

运行环境说明

制作镜像的所有操作,都可以在任意一台安装了 JDK + Maven + Docker 的机器上执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
demojenkins
├── demojenkins.iml
├── Dockerfile
├── pom.xml
└── src
├── main
│   ├── java
│   │   └── com
│   │   └── clay
│   │   └── demojenkins
│   │   ├── controller
│   │   │   └── UserController.java
│   │   └── DemoJenkinsApplication.java
│   └── resources
│   └── application.yml
└── test
└── java
└── com
└── clay
└── demojenkins
└── DemoJenkinsApplicationTests.java
  • (2) 使用 Maven 命令将 Java 项目打包成可执行的 Jar 包或者 War 包(运行依赖 Tomcat 等外部 Web 容器),比如 demojenkins.jar
1
2
3
4
5
# 进入项目根目录
# cd demojenkins

# 编译打包项目
# mvn clean package
  • (3) 使用 Docker 命令构建镜像
1
2
3
4
5
# 进入项目根目录(该目录下有 Dockerfile 文件)
# cd demojenkins

# 构建镜像(注意,命令的末尾有一个点号)
# docker build -t demojenkins:0.0.1 .
  • (4) 查看构建生成的 Docker 镜像
1
2
# 查看镜像列表
# docker images
1
2
3
4
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
demojenkins 0.0.1 51ecb07ed574 48 seconds ago 122MB
openjdk 8-jdk-alpine a3562aa0b991 6 years ago 105MB
...
  • (5) 测试 Docker 镜像是否可用
1
2
# 启动容器(后台启动)
# docker run -d --name demojenkins -p 8111:8111 demojenkins:0.0.1
1
2
# 查看容器的运行状态,输出示例如下所示
# docker ps -a
1
2
3
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS                     PORTS                    NAMES
85d0ce0e5edd demojenkins:0.0.1 "java -jar /demojenk…" 7 seconds ago Up 6 seconds 0.0.0.0:8111->8111/tcp demojenkins
...
1
2
# 通过 Curl 工具测试 Java 项目的接口,输出示例如下所示
# curl http://127.0.0.1:8111/user/findAll
1
hello 1763004675787
  • (6) 最后删除前面启动的 Docker 容器
1
# docker rm -f demojenkins
推送 Java 应用的镜像

运行环境说明

推送镜像的所有操作,都可以在任意一台安装了 JDK + Maven + Docker 的机器上执行。

  • (2) 创建命名空间(比如 java-dev

  • (3) 创建镜像仓库(比如 demojenkins),需要选择上面创建的命名空间,且代码源必须选择本地仓库

  • (4) 在终端中使用命令登录阿里云镜像仓库(用于登录的用户名为阿里云账号全名,密码为开通容器镜像服务时设置的密码,镜像仓库域名可以从阿里云特定镜像仓库的详情页面获取
1
2
3
4
5
# 登录阿里云镜像仓库
# docker login --username=xxxxx [镜像仓库域名]

# 比如
# docker login --username=xxxxx registry.cn-beijing.aliyuncs.com
  • (5) 推送镜像到阿里云镜像仓库
1
2
3
4
5
# 镜像打上标签
# docker tag [镜像ID] [镜像仓库域名]/[命名空间]/[镜像名称]:[镜像版本号]

# 比如
# docker tag 51ecb07ed574 registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.1
1
2
3
4
5
# 推送镜像
# docker push [镜像仓库域名]/[命名空间]/[镜像名称]:[镜像版本号]

# 比如
# docker push registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.1
部署 Java 应用的镜像

运行环境说明

部署镜像所有操作,必须在 Kubernetes 集群的任意一个 Master 节点上执行。

  • (1) 在 Kubernetes 集群中,创建镜像拉取凭据 Secret(用于登录的用户名为阿里云账号全名,密码为开通容器镜像服务时设置的密码,邮箱地址可以自定义,镜像仓库域名可以从阿里云特定镜像仓库的详情页面获取
1
2
3
4
5
6
# 创建Secret
# kubectl create secret docker-registry aliyun-regcred \
--docker-server=<镜像仓库域名> \
--docker-username=<登录用户名> \
--docker-password=<登录密码> \
--docker-email=<邮箱地址>
1
2
# 查看创建的Secret,输出示例如下
# kubectl get secret aliyun-regcred
1
2
NAME             TYPE                             DATA   AGE
aliyun-regcred kubernetes.io/dockerconfigjson 1 14s
  • (2) 创建一个 YAML 配置文件(比如 demojenkins-deploy.yaml),用于部署 Deployment 和 Service,使用了阿里云镜像仓库中的 Docker 镜像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: v1
kind: Service
metadata:
name: demojenkins # Service 的名称
spec:
type: NodePort # ervice 类型为 NodePort,可通过节点 IP 访问
selector:
app: demojenkins # 选择标签为 app=demojenkins 的 Pod 作为后端
ports:
- port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 8111 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demojenkins
spec:
replicas: 3 # Pod 的副本数量
selector:
matchLabels:
app: demojenkins
template:
metadata:
labels:
app: demojenkins
spec:
containers:
- name: demojenkins
image: <镜像仓库域名>/<命名空间>/<镜像名称>:<镜像版本号> # 阿里云镜像地址,比如:registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.1
ports:
- containerPort: 8111 # 容器内应用(比如 Tomcat)监听的端口
imagePullSecrets: # 引用镜像拉取凭据 Secret
- name: aliyun-regcred
  • (3) 应用 YAML 配置文件,创建 Deployment 和 Service 资源对象
1
2
# 应用YAML配置文件
# kubectl apply -f demojenkins-deploy.yaml
  • (4) 查看 Service 的列表
1
2
# 查看Service列表,输出示例如下
# kubectl get svc
1
2
3
NAME          TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
demojenkins NodePort 10.0.0.112 <none> 80:30577/TCP 2m25s
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 30h
  • (5) 查看 Deployment 的列表
1
2
# 查看Deployment列表,输出示例如下
# kubectl get deployments
1
2
NAME          READY   UP-TO-DATE   AVAILABLE   AGE
demojenkins 3/3 3 3 171m
  • (6) 查看所有 Pod 的运行状态
1
2
# 查看所有Pod的运行状态,输出示例如下
# kubectl get pods -o wide
1
2
3
4
NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES
demojenkins-5486657b45-9ck68 1/1 Running 0 169m 10.244.4.10 k8s-node1 <none> <none>
demojenkins-5486657b45-n6t88 1/1 Running 0 169m 10.244.2.6 k8s-node3 <none> <none>
demojenkins-5486657b45-v6zsl 1/1 Running 0 169m 10.244.3.8 k8s-node2 <none> <none>
1
2
3
4
5
# 如果Pod启动失败,可以查看Pod的详细运行情况来定位问题
# kubectl describe pod <pod-name>

# 删除启动失败的Pod,触发Deployment对Pod的重建
# kubectl delete pod <pod-name>
  • (7) 在 Kubernetes 集群外部,通过 curl 网络工具访问容器内的 Java 应用(IP 可以是 Kubernetes 集群任意一个节点的 IP 地址,端口号可以通过 kubectl get svc 命令获取得到
1
2
# 访问Java应用的接口,输出示例如下
# curl http://<node-ip>:30577/user/findAll
1
hello 1763023637288
  • (8) 若希望删除上面创建的 Secret、Deployment、Service、Pod 所有资源对象,可以执行以下命令
1
2
3
4
5
# 删除Deployment、Service、Pod
# kubectl delete -f demojenkins-deploy.yaml

# 删除Secret
# kubectl delete secret aliyun-regcred

扩容 / 缩容 Java 应用程序

运行环境说明

扩容和缩容操作,必须在 Kubernetes 集群的任意一个 Master 节点上执行。

扩容 Java 应用程序
  • (1) 当完成上述步骤并将 Java 应用部署到 Kubernetes 集群后,可以通过以下命令调整该应用对应 Pod 的副本数量,实现扩容操作
1
2
3
4
5
# 对Pod的副本进行扩容(比如,扩容至 5 个副本)
# kubectl scale deployment <Deployment名称> --replicas=5

# 比如
# kubectl scale deployment demojenkins --replicas=5
  • (2) 查看所有 Pod 的运行状态
1
2
# 查看所有Pod的运行状态,输出示例如下
# kubectl get pods -o wide
1
2
3
4
5
6
NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES
demojenkins-5486657b45-9ck68 1/1 Running 0 177m 10.244.4.10 k8s-node2 <none> <none>
demojenkins-5486657b45-bkc6s 1/1 Running 0 52s 10.244.1.8 k8s-node1 <none> <none>
demojenkins-5486657b45-n6t88 1/1 Running 0 177m 10.244.2.6 k8s-node3 <none> <none>
demojenkins-5486657b45-v6zsl 1/1 Running 0 177m 10.244.3.8 k8s-node2 <none> <none>
demojenkins-5486657b45-vjpxq 1/1 Running 0 58s 10.244.0.8 k8s-node1 <none> <none>
缩容 Java 应用程序
  • (1) 当完成上述步骤并将 Java 应用部署到 Kubernetes 集群后,可以通过以下命令调整该应用对应 Pod 的副本数量,实现缩容操作
1
2
3
4
5
# 对Pod的副本进行缩容(比如,缩容至 2 个副本)
# kubectl scale deployment <Deployment名称> --replicas=2

# 比如
# kubectl scale deployment demojenkins --replicas=2
  • (2) 查看所有 Pod 的运行状态
1
2
# 查看所有Pod的运行状态,输出示例如下
# kubectl get pods -o wide
1
2
3
NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES
demojenkins-5486657b45-9ck68 1/1 Running 0 3h4m 10.244.4.10 k8s-node2 <none> <none>
demojenkins-5486657b45-n6t88 1/1 Running 0 3h4m 10.244.2.6 k8s-node3 <none> <none>

升级 / 回滚 Java 应用程序

运行环境说明

升级和回滚操作,必须在 Kubernetes 集群的任意一个 Master 节点上执行。值得注意的是,这里的升级和回滚操作针对的是 Pod 所使用的 Docker 镜像版本,也就意味着在实际执行时,相当于对 Java 应用的版本进行升级或回退。

升级 Java 应用程序
  • (1) 项目代码迭代更新后,重新 制作 Java 应用的镜像,且镜像使用新的版本号(比如 0.0.2),并推送新版本镜像到阿里云仓库

  • (2) 为了更好观察 Java 应用的升级过程,先查看所有 Pod 的运行状态

1
2
# 查看所有Pod的运行状态,输出示例如下
# kubectl get pods -o wide
1
2
3
NAME                           READY   STATUS    RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES
demojenkins-5486657b45-9ck68 1/1 Running 0 3h4m 10.244.4.10 k8s-node2 <none> <none>
demojenkins-5486657b45-n6t88 1/1 Running 0 3h4m 10.244.2.6 k8s-node3 <none> <none>
  • (3) 当新版本镜像推送到阿里云仓库后,可以通过以下命令来升级 Java 应用对应 Pod 的版本(比如,升级到 0.0.2 版本)
1
2
3
4
5
# 升级Pod的版本
# kubectl set image deployment <Deployment名称> <容器名称>=<镜像仓库域名>/<命名空间>/<镜像名>:<版本号>

# 比如
# kubectl set image deployment demojenkins demojenkins=registry.cn-beijing.aliyuncs.com/java-dev/demojenkins:0.0.2
  • (4) 查看 Java 应用升级版本的状态(过程)
1
2
3
4
5
# 查看升级版本的状态
# kubectl rollout status deployment <Deployment名称>

# 比如,输出示例如下
# kubectl rollout status deployment demojenkins
1
2
3
4
5
6
Waiting for deployment "demojenkins" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "demojenkins" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "demojenkins" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "demojenkins" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "demojenkins" rollout to finish: 1 old replicas are pending termination...
deployment "demojenkins" successfully rolled out
  • (5) 再次查看所有 Pod 的运行状态,发现 Pod 名称已经发生变化
1
2
# 查看所有Pod的运行状态,输出示例如下
# kubectl get pods -o wide
1
2
3
NAME                          READY   STATUS    RESTARTS   AGE     IP           NODE        NOMINATED NODE   READINESS GATES
demojenkins-6c58b8c7b-mt8hp 1/1 Running 0 6m50s 10.244.1.9 k8s-node1 <none> <none>
demojenkins-6c58b8c7b-tng8r 1/1 Running 0 6m20s 10.244.3.9 k8s-node2 <none> <none>
回滚 Java 应用程序
  • (1) 为了更好观察 Java 应用的回滚过程,先查看所有 Pod 的运行状态
1
2
# 查看所有Pod的运行状态,输出示例如下
# kubectl get pods -o wide
1
2
3
NAME                          READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
demojenkins-6c58b8c7b-mt8hp 1/1 Running 0 10m 10.244.1.9 k8s-node1 <none> <none>
demojenkins-6c58b8c7b-tng8r 1/1 Running 0 10m 10.244.3.9 k8s-node2 <none> <none>
  • (2) 查看指定 Deployment 的所有历史版本
1
2
3
4
5
# 查看指定Deployment的所有历史版本
# kubectl rollout history deployment <Deployment名称>

# 比如,输出示例如下
# kubectl rollout history deployment demojenkins
1
2
3
4
deployment.apps/demojenkins 
REVISION CHANGE-CAUSE
1 <none>
2 <none>
  • (3) 当执行完上述步骤将 Java 应用 的版本从 0.0.1 升级到 0.0.2 后,若希望回滚到旧的版本(即用旧版本的 Pod 替换掉所有新版本的 Pod),可以执行以下命令
1
2
3
4
5
# 回滚指定Deployment到上一个版本
# kubectl rollout undo deployment <Deployment名称>

# 比如
# kubectl rollout undo deployment demojenkins
1
2
3
4
5
# 或者,回滚指定Deployment到指定的版本
# kubectl rollout undo deployment <Deployment名称> --to-revision=<版本>

# 比如
# kubectl rollout undo deployment demojenkins --to-revision=1
  • (4) 再次查看所有 Pod 的运行状态,发现 Pod 名称已经发生变化
1
2
# 查看所有Pod的运行状态,输出示例如下
# kubectl get pods -o wide
1
2
3
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES
demojenkins-5486657b45-knbnp 1/1 Running 0 66s 10.244.4.11 k8s-node2 <none> <none>
demojenkins-5486657b45-md6lh 1/1 Running 0 64s 10.244.2.7 k8s-node3 <none> <none>

平滑重启 Java 应用程序

运行环境说明

平滑重启操作,必须在 Kubernetes 集群的任意一个 Master 节点上执行。

什么是平滑重启
  • 在 Kubernetes 中实现 Java 应用程序的平滑重启,本质上是通过 Deployment 滚动更新(Rolling Update)的方式实现,也就是逐个重建 Pod,而不是将全部 Pod 停掉再重建,这样可以保证服务在重启过程中尽量不丢失请求。
  • 所谓 Java 应用平滑重启(Graceful Restart)有两个关键点:
    • (1) 不中断现有请求:Java 应用在收到 SIGTERM 信号时,应该先停止接收新请求,但允许正在处理的请求完成,再退出。
    • (2) 保证服务可用性:Kubernetes 通过 Deployment 的滚动更新或 Pod 生命周期钩子来保证至少有一部分实例在运行,避免服务全部不可用。

扩展阅读

更多关于 Kubernetes 如何实现 Java 应用程序平滑重启的详细介绍,可以看 这里

平滑重启的实现
  • (1) 手动触发 Deployment 滚动更新(Rolling Update),也就是说,Kubernetes 会逐个重建 Pod,而不是将全部 Pod 停掉再重建
1
2
3
4
5
# 触发Deployment滚动更新
# kubectl rollout restart deployment <Deployment名称>

# 比如
# kubectl rollout restart deployment demojenkins
  • (2) 特别注意,光是依靠 kubectl rollout restart deployment <Deployment名称> 命令,不一定就可以真正实现 Java 应用的平滑重启,它是有几个前提条件的,详细说明请看 这里

参考资料