Kubenav 使用手机管理你的 k8 s 集群

移动端 K8S 多集群管理利器:Kubenav

作者 guoxudong 发表于 2021年4月12日

背景

相信广大 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 内容如下:

---
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,基本上除写代码以外的大部分工作都可以在移动端完成,妈妈再也不用担心我蹲在日本街头拿手机远程指挥同事排查问题了!