Kubenav: 使用手机管理你的 K8S 集群

4 分钟阅读

背景

相信广大 SRE/运维工程师都和笔者一样,以防出现紧急情况,无论是双休日还是过年过节,笔记本电脑常伴左右。笔者也有过在休假时,业务系统出现大规模故障,蹲在日本街头拿手机远程指挥同事排查问题的经历。那段经历除了给茶余饭后增加一些谈资以外,更多的是在手边没有可以快速接入排查问题的设备时无尽的焦虑。而 Kubenav 的出现很大程度可以缓解这个焦虑。

Kubenav

Kubenav 号称是装在口袋里的 Kubernetes 集群导航,其提供了移动、桌面和 web 端 APP 用来查看和管理 Kubernetes 集群。

该应用使用 Ionic FrameworkCapacitor 开发。应用的前端部分使用 TypeScript 和 React 组件实现功能。后端部分使用 Go mobile 与 Kubernetes API Server 和 Cloud Providers 进行通信,实现了 kubenav 移动端和桌面端实现近 100% 的代码共享。

主要功能

Kubenav 基本上就是一个 Kubernetes Dashboard 的增强版,在其上可以非常方便的查看 Kubernetes 的各种资源,不止移动端,在桌面端和 web 端也十分好用。

  • 支持移动、桌面和 web 端:移动端支持安卓和 IOS,桌面端支持 Windows、Linux 和 Mac,由于几乎 100% 的代码共享,各端具有相同的使用体验。
  • 管理 Kubernetes 资源:可以管理所有的 Kubernetes 资源,包括 CRD 资源。
  • 多集群管理:支持多集群管理,除了通过 kubeconfig 导入集群之外,还提供了 Google GCP、AWS EKS、Azure AKS、DigitalOcean 等集群导入方式。
  • 搜索与筛选:可以全局搜索和筛选 Kubernetes 资源,无需选择固定 Namespace。
  • Logs 日志查看Exec 终端操作Port-Forwarding 等操作点击即可使用。

增强功能

除了 Kubernetes 资源的基本查看和操作外,Kubenav 还提供了插件功能,目前有 Helm、Prometheus、Elasticsearch、Jaeger 四种插件,其中笔者主要体验了 Helm 和 Prometheus 插件的使用。

Helm

Helm 插件

Helm 插件 是Kubenav 默认开启的一个插件,通过插件页面可以很轻松的查看到 Helm Chart 的全部信息,包括配置状态历史信息Value 值。其中最实用的就是 Value 的展示,可以直观的看到该 Chart 在部署时 --set 的 Value 值,免去了调试时 helm get values 的麻烦。

Prometheus

Prometheus 插件

Prometheus 插件则是需要事先在集群中安装 Prometheus,并在 Kubenav 的 Setting -> General 中手动开启 Prometheus 插件,之后就可以在集群中通过 ConfigMap 来在 Kubenav 中展示各种 Dashboard 了,可以理解为一个简化版的 Grafana,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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
---
apiVersion: v1
kind: ConfigMap
metadata:
  # Name of the ConfigMap. The name is used as reference in the "kubenav.io/prometheus-dashboards" annotation.
  name: nginx-ingress-dashboard
  # Dashboards namespace, which is configured in the settings via "Dashboards Namespace" or via the "--plugin.prometheus.dashboards-namespace" command-line flag.
  namespace: kubenav
  labels:
    # Required label, so that kubenav can found the dashboard.
    kubenav.io/prometheus-dashboard: "true"
data:
  # Title of the dashboard.
  title: "NGINX Ingress Controller"
  # Description of the dashboard.
  description: "Dashboard for NGINX Ingress Controller Metrics"
  # Array of variables.
  variables: |
    [
      {
        "name": "Namespace",
        "label": "controller_namespace",
        "query": "nginx_ingress_controller_config_hash",
        "allowAll": true
      },
      {
        "name": "ControllerClass",
        "label": "controller_class",
        "query": "nginx_ingress_controller_config_hash{namespace=~\"{{ .Namespace }}\"}",
        "allowAll": true
      },
      {
        "name": "Controller",
        "label": "controller_pod",
        "query": "nginx_ingress_controller_config_hash{namespace=~\"{{ .Namespace }}\",controller_class=~\"{{ .ControllerClass }}\"}",
        "allowAll": true
      },
      {
        "name": "Ingress",
        "label": "ingress",
        "query": "nginx_ingress_controller_requests{namespace=~\"{{ .Namespace }}\",controller_class=~\"{{ .ControllerClass }}\", controller_pod=~\"{{ .Controller }}\"}",
        "allowAll": true
      }
    ]    
  # Array of charts.
  charts: |
    [
      {
        "title": "Controller Request Volume",
        "unit": "ops",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "4",
          "lg": "4",
          "xl": "4"
        },
        "type": "singlestat",
        "queries": [
          {
            "label": "Request Volume",
            "query": "round(sum(irate(nginx_ingress_controller_requests{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\"}[2m])), 0.001)"
          }
        ]
      },
      {
        "title": "Controller Connections",
        "unit": "",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "4",
          "lg": "4",
          "xl": "4"
        },
        "type": "singlestat",
        "queries": [
          {
            "label": "Controller Connections",
            "query": "sum(avg_over_time(nginx_ingress_controller_nginx_process_connections{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\"}[2m]))"
          }
        ]
      },
      {
        "title": "Controller Success Rate",
        "unit": "%",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "4",
          "lg": "4",
          "xl": "4"
        },
        "type": "singlestat",
        "queries": [
          {
            "label": "Controller Success Rate",
            "query": "sum(rate(nginx_ingress_controller_requests{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\",status!~\"[4-5].*\"}[2m])) / sum(rate(nginx_ingress_controller_requests{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\"}[2m])) * 100"
          }
        ]
      },
      {
        "title": "Ingress Request Volume",
        "unit": "reqps",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "12",
          "lg": "6",
          "xl": "6"
        },
        "type": "area",
        "queries": [
          {
            "label": "{{ .ingress }}",
            "query": "round(sum(irate(nginx_ingress_controller_requests{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\",ingress=~\"{{ .Ingress }}\"}[2m])) by (ingress), 0.001)"
          }
        ]
      },
      {
        "title": "Ingress Success Rate",
        "unit": "%",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "12",
          "lg": "6",
          "xl": "6"
        },
        "type": "area",
        "queries": [
          {
            "label": "{{ .ingress }}",
            "query": "sum(rate(nginx_ingress_controller_requests{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\",ingress=~\"{{ .Ingress }}\",status!~\"[4-5].*\"}[2m])) by (ingress) / sum(rate(nginx_ingress_controller_requests{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\",ingress=~\"{{ .Ingress }}\"}[2m])) by (ingress) * 100"
          }
        ]
      },
      {
        "title": "Network I/O Pressure",
        "unit": "MB/s",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "12",
          "lg": "4",
          "xl": "4"
        },
        "type": "area",
        "queries": [
          {
            "label": "Received",
            "query": "sum (irate (nginx_ingress_controller_request_size_sum{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\"}[2m])) / 1024 / 1024"
          },
          {
            "label": "Sent",
            "query": "- sum (irate (nginx_ingress_controller_response_size_sum{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\"}[2m])) / 1024 / 1024"
          }
        ]
      },
      {
        "title": "Average Memory Usage",
        "unit": "MiB",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "12",
          "lg": "4",
          "xl": "4"
        },
        "type": "area",
        "queries": [
          {
            "label": "NGINX",
            "query": "avg(nginx_ingress_controller_nginx_process_resident_memory_bytes{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\"}) / 1024 / 1024"
          }
        ]
      },
      {
        "title": "Average CPU Usage",
        "unit": "Cores",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "12",
          "lg": "4",
          "xl": "4"
        },
        "type": "area",
        "queries": [
          {
            "label": "NGINX",
            "query": "sum (rate (nginx_ingress_controller_nginx_process_cpu_seconds_total{controller_pod=~\"{{ .Controller }}\",controller_class=~\"{{ .ControllerClass }}\",namespace=~\"{{ .Namespace }}\"}[2m]))"
          }
        ]
      },
      {
        "title": "Ingress Certificate Expiry",
        "unit": "Days",
        "size": {
          "xs": "12",
          "sm": "12",
          "md": "12",
          "lg": "12",
          "xl": "12"
        },
        "type": "area",
        "queries": [
          {
            "label": "{{ .host }}",
            "query": "(avg(nginx_ingress_controller_ssl_expire_time_seconds{namespace=~\"{{ .Namespace }}\"}) by (host) - time()) / 60 / 60 / 24"
          }
        ]
      }
    ]    

将该 ConfigMap 部署到 kubenav Namespace 中,就可以在 Kubenav 中看到 NGINX Ingress Controller 的 Dashboard 了,官方也提供了一些示例 Dashboard,这些 Dashboard 可以在 kubenav/deploy 中找到。

总结

iPad 端

除了手机端,Kubenav 在 iPad 端有着更好的表现力,十分适合像笔者这样的 iPad 重度使用者。无论是问题排查还是使用的灵活度都有了大大的提升,结合 AWS Console 和阿里云 APP,基本上除写代码以外的大部分工作都可以在移动端完成,妈妈再也不用担心我蹲在日本街头拿手机远程指挥同事排查问题了!