Helm包管理工具入门学习及使用

2022-09-29 19:32:33 浏览数 (1)

[TOC]

0x00 前言简述

1.背景介绍

描述: 随着业务容器化与向微服务的架构转变,通过分解巨大的单体应用为多个服务的方式降低了单体应用的复杂性,使得每个微服务都可以独立部署和扩展,可以更加有效的实现快速迭代与部署并且减少了应用程序开发到上线周期;

但是于此同时又有新的问题浮现出来,由于单体应用被拆分称为多个组件导致服务部署的数量增加,而直接采用K8s为每一个独立应用进行独立部署、扩容带来非常大的挑战;

例如采用Kubernetes做应用编排带来了以下挑战:

  • 1) 如何管理、配置、与更新大量的K8s配置文件
  • 2) 如何部署一个含有大量的配置文件的复杂的K8s应用
  • 3) 如何便于分享与复用K8s配置和应用
  • 4) 如何参数化配置模板并支持多个环境
  • 5) 如何管理应用发布、回滚以及查看历史发布信息
  • 6) 如何控制一个部署周期中的某一个环节
  • 7) 如何在应用发布后验证

而恰好这些挑战Helm可以有效的帮助我们解决上述问题;


2.基础介绍

Q: 什么是Helm?

答: Helm英 /helm/实际是一个 Kubernetes 包管理器是查找、分享和使用软件构建 Kubernetes 的最优方式, 即使是最复杂的 Kubernetes 应用程序,都可以帮助您定义,安装和升级。 可将它类似于CentOS下的Yum软件管理仓库(即K8s中的apt与yum)是部署环境的流程封装, 并且 Helm 应用中心公共库中拥有大量的图表 Chart (类似于Docker Hub仓库) 易于创建、发版、分享和发布,所以停止复制粘贴,开始使用 Helm 吧。 Helm 是 CNCF 的毕业项目,由 Helm 社区维护。

官网地址: https://helm.sh/ 项目地址: https://github.com/helm/helm 应用中心: https://hub.helm.sh/ 仓库中心: https://artifacthub.io/packages

Q: Helm 作用简述?

答: Helm 是 Kuernetes服务编排领域的一个子项目, 它通过软件打包的形式支持发布的版本管理和控制, 很大程度上简化了Kunernetes应用部署和管理的复杂性; Helm将K8s相关的控制器资源(例如Deployment、Services、Statufulset、ingress等)打包到一个图表chart中,而图表chart又可以保存在chart仓库之中, 便于存储与分享Chart; Helm简化了K8s部署应用的版本控制、打包、发布、删除、更新、回滚等操作; 简单的说: 简化了 Kubernetes 应用的部署和管理;

Q : Helm 特点有哪些?

  • 复杂性管理 : 即使是最复杂的应用,图表 Charts 依然可以描述, 提供使用单点授权的可重复安装应用程序。
  • 易于升级 :随时随地升级和自定义的钩子消除您升级的痛苦。
  • 分发简单 :图表 Charts 很容易在公共或私有化服务器上发版,分发和部署站点。
  • 回滚 : 使用 helm rollback 可以轻松回滚到之前的发布版本。

PS : Helm与kubernetes和Docker之间的关系?

答 : Helm 是对于kubernetes服务的编排,而Kubernetes是对于Docker容器的编排;

Q: 什么是Helm图表?

答: Helm 图表是又一个个yaml格式的文件构成,并且有一定项目结构要求编写成功后可以上传到Chart仓库中即 Helm 应用中心公共库;

三个重要概念

  • 1) Chart : 创建Kubernetes应用程序实例所需的一组信息, 包括各种Kubernetes对象的配置模板、参数定义、依赖关系、文档说明等, 类似于apt/yum中的软件安装包。
  • 2) Config : 包含可合并到打包图表中以创建可发布对象的配置信息。
  • 3) Release : 是图表Chart的运行实例与特定的配置组合在一起即chart被Helm运行后将会生成对应的一个release

PS : 当 chart 被安装到 Kubernetes集群中就生成一个 release, 当多次将chart安装到同一个集群时候每一个安装都是一个Release;


3.架构组件

版本说明 描述: Helm的版本用 x.y.z 描述,x是主版本,y是次版本,z是补丁版本,遵循语义化版本术语。

可支持的版本偏差:

  • 从 Helm 3 开始 Helm 编译时假定与针对 n-3 版本的Kubernetes兼容。
  • 由于 Helm 2 对 Kubernetes 次版本变更的支持稍微严格一点则假定与Kubernetes的 n-1 版本兼容。

WeiyiGeek.Helm版本架构

补充说明: 在2019年发布Helm V3.*版本和之前版本相比有以下变化

  • (1) v3.* 版本删除Tiller架构变化
  • (2) release 可以在不同命名空间重用
  • (3) 将 chart 推送到 docker 仓库中

版本差异说明: https://helm.sh/docs/topics/v2_v3_migration/

参考下表来确定哪个版本的Helm与您的集群兼容: 补充-2022年5月31日 15:08:10

代码语言:javascript复制
Helm 版本	支持的 Kubernetes 版本
3.8.x	1.23.x - 1.20.x
3.7.x	1.22.x - 1.19.x
3.6.x	1.21.x - 1.18.x
3.5.x	1.20.x - 1.17.x
3.4.x	1.19.x - 1.16.x
3.3.x	1.18.x - 1.15.x
3.2.x	1.18.x - 1.15.x
3.1.x	1.17.x - 1.14.x
3.0.x	1.16.x - 1.13.x
2.16.x	1.16.x - 1.15.x
2.15.x	1.15.x - 1.14.x

参考地址: https://helm.sh/zh/docs/topics/version_skew/

4.组件说明

描述: Helm 是一个可执行文件是用Go编程语言编写的, 它分为两个不同部分即Helm客户端和Helm库 该库使用Kubernetes客户端库与Kubernetes进行通信, 当前该库使用REST JSON接口方式,它在Kubernetes内部的Secrets中存储信息即它不需要自己的数据库

Helm Client 是面向最终用户的命令行客户端负责以下工作:

  • 本地图表开发
  • 管理储存库
  • 管理发布
  • 与Helm库的接口
  • 发送要安装的图表
  • 请求升级或卸载现有版本

Helm Library 提供了用于执行所有Helm操作的逻辑。它与Kubernetes API服务器连接并提供以下功能:

  • 结合图表和配置以构建发布
  • 将图表安装到Kubernetes中并提供后续发行对象
  • 通过与Kubernetes交互来升级和卸载图表
  • 独立的Helm库封装了Helm逻辑,以便不同的客户端可以利用它。

Helm 包含两个组件:Helm 客户端 和 Tiller 服务器

  • Helm 客户端负责 chart 和 release 的创建和管理以及和 Tiller的交互。
  • Tiller 服务器运行在 Kubernetes 集群中,它会处理Helm客户端的请求,与 Kubernetes API Server 交互。

帮助文档 快速开始: https://helm.sh/zh/docs/intro/quickstart/

PS : 当前实践版本 V3.4.1 请读者注意当版本有所改变时某些特性及其配置可能会发生改变,请参考Helm官网为准;


0x01 基础安装

描述: 如有最新请参考安装官方安装文档: https://helm.sh/zh/docs/intro/install/

1.环境需求

  • 1) 安装有K8s集群
  • 2) 安装和配置Helm到Master节点上

PS : 对于Helm的最新版本我们建议使用Kubernetes的最新稳定版, 在大多数情况下它是倒数第二个 minor release可以参考上述的K8s与Helm版本的对应表;

2.部署说明

部署环境说明:

代码语言:javascript复制
~$ kubectl version
  # Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.3", GitCommit:"1e11e4a2108024935ecfcb2912226cedeafd99df", GitTreeState:"clean", BuildDate:"2020-10-14T12:50:19Z", GoVersion:"go1.15.2", Compiler:"gc", Platform:"linux/amd64"}

~$ kubeadm version
  # kubeadm version: &version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.3", GitCommit:"1e11e4a2108024935ecfcb2912226cedeafd99df", GitTreeState:"clean", BuildDate:"2020-10-14T12:47:53Z", GoVersion:"go1.15.2", Compiler:"gc", Platform:"linux/amd64"}

~$ lsb_release -a
  # Distributor ID: Ubuntu
  # Description:    Ubuntu 20.04.1 LTS
  # Release:        20.04
  # Codename:       focal

~$ docker info
  # Server Version: 19.03.13
Helm Client

安装方式

1) 包管理器安装(Ubuntu-snap)

代码语言:javascript复制
# 1.Apt (Debian/Ubuntu) Helm社区成员贡献了针对Apt的一个 Helm包包通常是最新的。
curl https://baltocdn.com/helm/signing.asc | sudo apt-key add -
sudo apt-get install apt-transport-https --yes
echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

# 2.Snap : Snapcrafters社区维护了 Helm 包的Snap版本:
sudo snap install helm --classic

2) 脚本安装(自动拉取最新的Helm版本并在本地安装)

代码语言:javascript复制
# 常规
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh

# 或者
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

3) 二进制安装(下载解压并添加到系统PATH之中)

代码语言:javascript复制
# 1.下载并解压
ARCH=$(dpkg --print-architecture)
VERSION="3.4.1"
wget https://get.helm.sh/helm-v${VERSION}-linux-${ARCH}.tar.gz -O /tmp/helm-v${VERSION}-linux-${ARCH}.tar.gz
tar -zxf /tmp/helm-v3.4.1-linux-amd64.tar.gz

# 2.复制到 /usr/local/bin/
sudo cp helm /usr/local/bin/

# 3.版本验证
$ helm version
version.BuildInfo{Version:"v3.4.1", GitCommit:"c4e74854886b2efe3321e185578e6db9be0a6e29", GitTreeState:"clean", GoVersion:"go1.14.11"}
Helm Libaray

3.基础配置

Helm基础使用

描述: 以下是Helm基础使用配置

代码语言:javascript复制
# (1) 设置Helm命令补全
echo "source <(helm completion bash)" >> ~/.bashrc
helm completion bash > /etc/bash_completion.d/helm

# (2) 第三方Chart仓库添加/查看/更新/删除
# 官网需要翻墙: helm repo add stable https://charts.helm.sh/stable
~$ helm repo add stable https://charts.helm.sh/stable
  # "stable" has been added to your repositories
~$ helm repo add azure http://mirror.azure.cn/kubernetes/charts
# ~$ helm repo add aliyun https://apphub.aliyuncs.com/
~$ helm repo add aliyun  https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
  # "aliyun" has been added to your repositories
~$ helm repo list
  # NAME    URL
  # stable  http://mirror.azure.cn/kubernetes/charts
  # aliyun  https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
~$ helm repo update
  # Hang tight while we grab the latest from your chart repositories...
  # ...Successfully got an update from the "aliyun" chart repository
  # ...Successfully got an update from the "stable" chart repository
  # Update Complete. ⎈Happy Helming!⎈
~$ helm repo remove aliyun
  # "aliyun" has been removed from your repositories

# (3) 应用搜索(搜索使用模糊字符串匹配算法)
~$ helm search hub redis   # Helm 官网Chart仓库中心
  # URL                                                     CHART VERSION   APP VERSION     DESCRIPTION
  # https://hub.helm.sh/charts/drycc-canary/redis           1.0.0                           A Redis database for use inside a Kubernetes cl...
  # https://hub.helm.sh/charts/groundhog2k/redis            0.1.2           6.0.9           A Helm chart for Redis on Kubernetes
  # https://hub.helm.sh/charts/wener/redis                  12.1.1          6.0.9           Open source, advanced key-value store. It is of...
  ....

~$ helm search repo dashboard | grep -v "DEPRECATED"   #  第三方 Chart 仓库
  # NAME                            CHART VERSION   APP VERSION     DESCRIPTION
  # aliyun/kubernetes-dashboard     0.6.0           1.8.3           General-purpose web UI for Kubernetes clusters
  # aliyun/jasperreports            0.2.5           6.4.2           The JasperReports server can be used as a stand...
  # aliyun/kube-ops-view            0.4.1                           Kubernetes Operational View - read-only system ...
  # aliyun/uchiwa                   0.2.3                           Dashboard for the Sensu monitoring framework
  # aliyun/weave-cloud              0.1.2                           Weave Cloud is a add-on to Kubernetes which pro...
  # aliyun/weave-scope              0.9.2           1.6.5           A Helm chart for the Weave Scope cluster visual...
Chart仓库镜像

仓库名称

charts仓库URL

命令

stable

https://charts.helm.sh/stable

helm repo add stable https://charts.helm.sh/stable

bitnami

https://charts.bitnami.com/bitnami

helm repo add bitnami https://charts.bitnami.com/bitnami

azure

http://mirror.azure.cn/kubernetes/charts

helm repo add azure http://mirror.azure.cn/kubernetes/charts

apphub

https://apphub.aliyuncs.com/

helm repo add apphub https://apphub.aliyuncs.com/

ali-stable

https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

helm repo add ali-stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

ali-incubator

https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts-incubator/

helm repo add ali-incubator https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts-incubator/

其他常用软件仓库:

代码语言:javascript复制
ingress https://kubernetes.github.io/ingress-nginx
grafana https://grafana.github.io/helm-charts

0x02 入门实践

使用简述

  • 1.创建并编写新的Chart
  • 2.测试创建的Chart
  • 3.将Chart应用进行打包成为tgz格式
  • 4.上传Chart到Chart仓库或者从远程仓库中下载Chart
  • 5.在K8s集群中运行Chart自动生成一个release
  • 6.利用Helm管理Chart的发布、结束生命周期

1.Hello World - 自定义模板

描述: 我们采用Helm执行我们自定义模板生成chart图表,并且运行图表实例生成release;

Helm 模板项目结构:

代码语言:javascript复制
~/K8s/Day10/demo1$ tree .
.
├── Chart.yaml   # 当前chart属性配置信息
├── templates    # 编写yaml文件放到这个目录中包括但不限于Service、Pod、DaemonSet、ReplicaSet、Deployment以及  ·StatefulSet
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml  # yaml文件可以的使用全局变量

1 directory, 4 files

操作流程:

Step 1.创建自描述文件 Chart.yaml 文件必须有 name 和 version 定义,注意apiVersion非必须但是建议填写否则验证配置时会有警告;

代码语言:javascript复制
# PS : Chart.yaml 是大写
cat <<'EOF' > ./Chart.yaml   
appVersion: 1.0 
apiVersion: v2
name: hello-world 
description: A Helm chart for Kubernetes
type: application
version: 1.0.0 
EOF

Step 2.创建项目配置文件 Values.yaml 便于后面的资源清单使用

代码语言:javascript复制
cat <<'EOF' > ./values.yaml 
replicas: 1
image:
  repository: harbor.weiyigeek.top/test/nginx
  tag: 'v1.0' 
EOF

Step 3.创建模板文件,用于生成 Kubernetes 资源清单 (manifests) 模板目录名称必须是templates

代码语言:javascript复制
mkdir ./templates

# Deployment
cat <<'EOF' > ./templates/deployment.yaml 
apiVersion: apps/v1
kind: Deployment 
metadata:
  name: hello-world-deployment
  labels:
    app: hello-world
spec:
  replicas: {{ .Values.replicas }}
  selector:
    matchLabels:  
      app: hello-world       # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: hello-world 
    spec:
      containers:
        - name: nginx-hello
          image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80 
              protocol: TCP 
EOF

# Service
cat <<'EOF' > ./templates/service.yaml 
apiVersion: v1 
kind: Service 
metadata:
  name: hello-world-svc 
spec:
  type: NodePort 
  ports:
  - port: 80 
    targetPort: 80 
    nodePort: 30080
    protocol: TCP
  selector:
    app: hello-world 
EOF

Step 4.使用模板动态生成K8s资源清单,非常需要验证模板配置是否正常或者能提前预览生成的结果在运行前进行Debug;

代码语言:javascript复制
# (1) 验证Chart模板配置是否正确
~/K8s/Day10/demo1$ helm lint .
  # ==> Linting .
  # [INFO] Chart.yaml: icon is recommended
  # 1 chart(s) linted, 0 chart(s) failed


# (2) 使用 --dry-run --debug 选项来打印出生成的清单文件内容而不执行部署
~/K8s/Day10/demo1$ helm --debug install . --dry-run  --generate-name
  # install.go:172: [debug] Original chart version: ""
  # install.go:189: [debug] CHART PATH: /home/weiyigeek/K8s/Day10/demo1

  # NAME: chart-1606294992
  # LAST DEPLOYED: Wed Nov 25 17:03:12 2020
  # NAMESPACE: default
  # STATUS: pending-install
  # REVISION: 1
  # TEST SUITE: None
  # USER-SUPPLIED VALUES:
  # {}

  # COMPUTED VALUES:
  # image:
  #   repository: harbor.weiyigeek.top/test/nginx
  #   tag: v1.0

  # HOOKS:
  # MANIFEST:
  # ---
  # # Source: hello-world/templates/service.yaml
  # apiVersion: v1
  # kind: Service
  # metadata:
  #   name: hello-world-svc
  # spec:
  #   type: NodePort
  #   ports:
  #   - port: 80
  #     targetPort: 80
  #     nodePort: 30080
  #     protocol: TCP
  #   selector:
  #     app: hello-world
  # ---
  # # Source: hello-world/templates/deployment.yaml
  # apiVersion: apps/v1
  # kind: Deployment
  # metadata:
  #   name: hello-world-deployment
  #   labels:
  #     app: hello-world
  # spec:
  #   replicas: 1
  #   selector:
  #     matchLabels:
  #       app: hello-world       # 匹配的Pod标签非常重要
  #   template:
  #     metadata:
  #       labels:
  #         app: hello-world
  #     spec:
  #       containers:
  #         - name: nginx-hello
  #           image: harbor.weiyigeek.top/test/nginx:v1.0
  #           imagePullPolicy: IfNotPresent
  #           ports:
  #             - containerPort: 80
  #               protocol: TCP

Step 5.使用命令helm install RELATIVE_PATH_TO_CHART 创建一次Release然后查看相关信息

代码语言:javascript复制
# . 表示从当前目录安装 
# -g, --generate-name : 生成名称(并省略NAME参数)
helm install . -g
  # NAME: chart-1606295498
  # LAST DEPLOYED: Wed Nov 25 17:11:38 2020
  # NAMESPACE: default
  # STATUS: deployed
  # REVISION: 1
  # TEST SUITE: None

# (2) 查询一个特定的Release的状态 
~/K8s/Day10/demo1$ helm status chart-1606295498
  # NAME: chart-1606295498
  # ...
  # 输出结果同上

# (3) 列出已经部署的 Release 
~/K8s/Day10/demo1$ helm ls
# NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
# chart-1606295498        default         1               2020-11-25 17:11:38.35894709  0800 CST  deployed        hello-world-1.0.0       1.0

Step 6.验证与查看部署的Release效果

代码语言:javascript复制
# SVC
~/K8s/Day10/demo1$ kubectl get svc
  # NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
  # hello-world-svc   NodePort    10.103.145.171   <none>        80:30080/TCP   3m37s

# Deployment
~/K8s/Day10/demo1$ kubectl get deploy -o wide --show-labels
  # NAME                     READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS    IMAGES                                 SELECTOR          LABELS
  # hello-world-deployment   1/1     1            1           5m3s   nginx-hello   harbor.weiyigeek.top/test/nginx:v1.0   app=hello-world   app.kubernetes.io/managed-by=Helm,app=hello-world

# Pod
~/K8s/Day10/demo1$ kubectl get pod -o wide --show-labels
  # NAME                                      READY   STATUS      RESTARTS   AGE     IP            NODE          LABELS
  # hello-world-deployment-588f974b64-d8456   1/1     Running     0          3m55s   10.244.2.34   k8s-node-5    app=hello-world,pod-template-hash=588f974b64

# 访问效果:
~/K8s/Day10/demo1$ curl http://10.10.107.202:30080/host.html
  # Hostname: hello-world-deployment-588f974b64-d8456  <br>
  # Image Version: <u> 1.0 </u>
  # Nginx Version: 1.19.4
~/K8s/Day10/demo1$ curl http://10.244.2.34/host.html
  # Hostname: hello-world-deployment-588f974b64-d8456  <br>
  # Image Version: <u> 1.0 </u>
  # Nginx Version: 1.19.4

WeiyiGeek.NodePort访问

Step 7.版本迭代与收缩

代码语言:javascript复制
# (1) 在 values.yaml 中的值可以被部署 release 时用到
# 可采用指定参数 --values YAML_FILE_PATH 或 --set key1=value1, key2=value2 覆盖掉 
helm upgrade chart-1606308995 . --set image.tag="v2.0",version="1.0.1" --dry-run # 尝试执行
  # NAME: chart-1606308995
  # LAST DEPLOYED: Wed Nov 25 20:39:54 2020
  # NAMESPACE: default
  # STATUS: pending-upgrade
  # REVISION: 2
  # TEST SUITE: None
  # HOOKS:
  # MANIFEST:
  # ---
  # ...
  #   containers:
  #         - name: nginx-hello
  #           image: harbor.weiyigeek.top/test/nginx:v2.0  # 此时你会发现tags版本有所改变
  #           imagePullPolicy: IfNotPresent


# (2) 执行后程序release将会更新
~/K8s/Day10/demo1$ helm upgrade chart-1606308995 . --set image.tag="v2.0",version="1.0.1"
  # Release "chart-1606308995" has been upgraded. Happy Helming!
  # NAME: chart-1606308995
  # LAST DEPLOYED: Wed Nov 25 20:42:14 2020
  # NAMESPACE: default
  # STATUS: deployed
  # REVISION: 2
  # TEST SUITE: None

# (3) 查看效果
$ kubectl get pod -o wide
  # hello-world-deployment-cb494b88f-7dxk8   1/1     Running     0    35s   10.244.2.38   k8s-node-5 
~/K8s/Day10/demo1$ curl http://10.244.2.38/host.html
~/K8s/Day10/demo1$ curl http://10.10.107.202:30080/host.html  # NodePort
# Hostname: hello-world-deployment-cb494b88f-7dxk8  <br>
# Image Version: <u> 2.0 </u>
# Nginx Version: 1.19.4


# (4) 利用Helm进行扩容与收缩(由于我们将replicas写入到values.yaml并且在deployment.yaml文件中进行了引用)
$ helm upgrade chart-1606308995 . --set replicas="3",image.tag="v3.0",version="1.0.1" --dry-run 
$ helm upgrade chart-1606308995 . --set replicas="3",image.tag="v3.0",version="1.0.1"
  # Release "chart-1606308995" has been upgraded. Happy Helming!
  # NAME: chart-1606308995
  # LAST DEPLOYED: Wed Nov 25 21:01:02 2020
  # NAMESPACE: default
  # STATUS: deployed
  # REVISION: 3
  # TEST SUITE: None

~/K8s/Day10/demo1$ kubectl get pod -o wide  # 可以看到已经扩容成功
  # NAME                                      READY   STATUS      RESTARTS   AGE     IP            NODE      
  # hello-world-deployment-648bb6c9f8-595sr   1/1     Running     0          3m7s    10.244.2.45   k8s-node-5 
  # hello-world-deployment-648bb6c9f8-8xrm2   1/1     Running     0          3m9s    10.244.2.44   k8s-node-5  
  # hello-world-deployment-648bb6c9f8-rjsr8   1/1     Running     0          3m11s   10.244.2.43   k8s-node-5

# 访问验证
~/K8s/Day10/demo1$ curl http://10.10.107.202:30080/host.html  
  # Hostname: hello-world-deployment-648bb6c9f8-595sr ,Image Version: 3.0, Nginx Version: 1.19.4
~/K8s/Day10/demo1$ curl http://10.10.107.202:30080/host.html
  # Hostname: hello-world-deployment-648bb6c9f8-8xrm2 ,Image Version: 3.0, Nginx Version: 1.19.4
~/K8s/Day10/demo1$ curl http://10.10.107.202:30080/host.html
  # Hostname: hello-world-deployment-648bb6c9f8-rjsr8 ,Image Version: 3.0, Nginx Version: 1.19.4


# (5) 查看Chart相关信息以及历史
~/K8s/Day10/demo1$ helm get values chart-1606308995
# USER-SUPPLIED VALUES:    
  # image:
  #   tag: v3.0
  # replicas: 3  # 用户提供的变量,为例便于利用values进行扩容需要使用到values.yaml中定义的变量与值;
  # version: 1.0.1

~/K8s/Day10/demo1$ helm history chart-1606308995
  # REVISION(修正版本) UPDATED                         STATUS          CHART                   DESCRIPTION
  # 1               Wed Nov 25 20:56:35 2020        superseded      hello-world-1.0.0       Install complete
  # 2               Wed Nov 25 20:58:00 2020        superseded      hello-world-1.0.0       Upgrade complete
  # 3               Wed Nov 25 21:01:02 2020        deployed        hello-world-1.0.0       Upgrade complete

Step 7.Pod回滚操作

代码语言:javascript复制
# (1) 例如我们回滚到 REVISION = 1 的时候
# 语法:helm rollback RELEASE_NAME REVISION NUMBER 
~/K8s/Day10/demo1$ helm rollback chart-1606308995 1
  # Rollback was a success! Happy Helming!

# (2) 验证回滚
~/K8s/Day10/demo1$ curl http://10.10.107.202:30080/host.html # 可以看见已经回滚到初始版本
  # Hostname: hello-world-deployment-588f974b64-zrkgf  <br>
  # Image Version: <u> 1.0 </u>
  # Nginx Version: 1.19.4

Step 8.RELEASES删除操作

代码语言:javascript复制
# (1) 删除指定的Release即移除所有与这个 Release 相关的 Kubernetes 资源 
~/K8s/Day10/demo1$ helm uninstall chart-1606295498
  # release "chart-1606295498" uninstalled
~/K8s/Day10/demo1$ helm ls
  # NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION


# (2) 使用移除所有与指定 Release 相关的Kubernetes 资源但是包所有这个 Release 的记录 
$  helm uninstall chart-1606308995 --keep-history
  # release "chart-1606308995" uninstalled
~/K8s/Day10/demo1$ helm ls  # 输出为空
~/K8s/Day10/demo1$ helm ls --uninstalled   # 删除的相关记录
  # NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
  # chart-1606308995        default         4               2020-11-25 21:10:24.483012734  0800 CST uninstalled     hello-world-1.0.0


# (3) 再次执行将彻底删除图表chart-1606308995相关的容器
~/K8s/Day10/demo1$ helm uninstall chart-1606308995
  # release "chart-1606308995" uninstalled

Step 9.当测试无误时候我们可以将其打包上传到本地或者远程的应用仓库中(如何搭建本地Chart仓库在后面进行讲解)

代码语言:javascript复制
~/K8s/Day10/demo1$ helm package .
  # Successfully packaged chart and saved it to: /home/weiyigeek/K8s/Day10/demo1/hello-world-1.0.0.tgz
~/K8s/Day10/demo1$ ls -alh /home/weiyigeek/K8s/Day10/demo1/hello-world-1.0.0.tgz
  # -rw-r--r-- 1 weiyigeek weiyigeek 726 Nov 26 11:13 /home/weiyigeek/K8s/Day10/demo1/hello-world-1.0.0.tgz

2.Redis - Chart 模板

描述: 下面我们尝试使用Helm来从第三方Chart仓库拉取Redis的图表进行部署,可以让读者了解到使用Helm奥秘;

操作流程与步骤

Step 1.在第三方Chart仓库中搜索Redis相关应用

代码语言:javascript复制
~/K8s/Day10/demo1$ helm search repo redis
  # NAME                                    CHART VERSION   APP VERSION     DESCRIPTION
  # aliyun/redis                            1.1.15          4.0.8           Open source, advanced key-value store. It is of...
  # aliyun/redis-ha                         2.0.1                           Highly available Redis cluster with multiple se...

Step 2.拉取应用修改和安装

代码语言:javascript复制
# 1.拉取应用
~/K8s/Day10/demo2$ helm pull aliyun/redis
~/K8s/Day10/demo2$ ls
# redis-1.1.15.tgz
~/K8s/Day10/demo2$ tar -zxvf redis-1.1.15.tgz  # 解压可查看
  # redis/Chart.yaml
  # redis/values.yaml
  # redis/templates/NOTES.txt
  # redis/templates/_helpers.tpl
  # redis/templates/deployment.yaml
  # redis/templates/networkpolicy.yaml
  # redis/templates/pvc.yaml
  # redis/templates/secrets.yaml
  # redis/templates/svc.yaml
  # redis/.helmignore
  # redis/README.md

# 2.修改redis/values.yaml变量 (我主要列举出修改的对象)
serviceType: NodePort
redisPassword: weiyigeek
  path: /bitnami # Pod 中的挂载目录 坑
  storageClass: "nfs"

# 3.修改deployment的api-versions
apiVersion: apps/v1  # 注意点
kind: Deployment
spec:
  selector:
    matchLabels:
      app: {{ template "redis.fullname" . }} # 注意点必须有匹配的Pod标签

# 4.支持多种安装方式:(helm默认读取~/.kube/config信息连接k8s集群)
# $ helm install redis stable/redis	
# $ helm install redis redis-4.4.0.tgz
# $ helm install redis redis/
# $ helm install redis https://example.com/charts/redis-4.4.0.tgz
~/K8s/Day10/demo2$ helm install redis redis/
  # NAME: redis
  # LAST DEPLOYED: Wed Nov 25 21:55:53 2020
  # NAMESPACE: default
  # STATUS: deployed
  # REVISION: 1
  # TEST SUITE: None
  # NOTES:
  # Redis can be accessed via port 6379 on the following DNS name from within your cluster:
  # redis-redis.default.svc.cluster.local
  # To get your password run:
  #     REDIS_PASSWORD=$(kubectl get secret --namespace default redis-redis -o jsonpath="{.data.redis-password}" | base64 --decode) # 实际就是上面设置redis密码weiyigeek将它存入了secret中

  # To connect to your Redis server:
  # 1. Run a Redis pod that you can use as a client:
  #    kubectl run --namespace default redis-redis-client --rm --tty -i 
  #     --env REDIS_PASSWORD=$REDIS_PASSWORD 
  #    --image bitnami/redis:4.0.8-r2 -- bash

  # 2. Connect using the Redis CLI:
  #   redis-cli -h redis-redis -a $REDIS_PASSWORD

Step 3.查看创建PV卷利用nfs(如何使用看我前面所写的K8S存储文章以及参考K8S官网) ref: http://kubernetes.io/docs/user-guide/persistent-volumes/

代码语言:javascript复制
# (1) 创建 PV
cat >  pv.yaml <<'EOF'
# 1.访问模式为 ReadWriteOnce - pv-test-2
apiVersion: v1 
kind: PersistentVolume 
metadata:
  name: pv-test-2
spec:
  capacity:            #容量
    storage: 1Gi 
  accessModes:
    - ReadWriteOnce 
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs  # 非常重要 (后面PVC将会根据访问模式以及存储类名称进行绑定PV)
  nfs:
   path: /nfs/data2
   server: 10.10.107.202
EOF
~/K8s/Day10/demo2$ kubectl create -f pv.yaml

# (2) 以下已经是Chart应用绑定PV后的结果
~/K8s/Day10/demo2/redis$ kubectl get pv -o wide
  # NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                 STORAGECLASS   REASON   AGE     VOLUMEMODE
  # pv-test-1   1Gi        RWO            Retain           Available                         nfs                     6d20h   Filesystem
  # pv-test-2   1Gi        RWO            Retain           Bound       default/redis-redis   nfs                     6d20h   Filesystem

~/K8s/Day10/demo2/redis$ kubectl describe pv pv-test-2
  # Name:            pv-test-2
  # Labels:          <none>
  # Annotations:     pv.kubernetes.io/bound-by-controller: yes
  # Finalizers:      [kubernetes.io/pv-protection]
  # StorageClass:    nfs
  # Status:          Bound
  # Claim:           default/redis-redis
  # Reclaim Policy:  Retain
  # Access Modes:    RWO
  # VolumeMode:      Filesystem
  # Capacity:        1Gi
  # Node Affinity:   <none>
  # Message:
  # Source:
  #     Type:      NFS (an NFS mount that lasts the lifetime of a pod)
  #     Server:    10.10.107.202
  #     Path:      /nfs/data2
  #     ReadOnly:  false
  # Events:        <none>

# (3) NFS目录中的持久化目录
~/K8s/Day10/demo2/redis$ tree /nfs/data2/redis/
  # /nfs/data2/redis/
  # ├── conf
  # │   └── redis.conf
  # └── data
  #     └── appendonly.aof
  # 2 directories, 2 files

Step 4.连接并测试redis

代码语言:javascript复制
# (1) Pod 查看
~/K8s/Day10/demo2/redis$ kubectl get pod -o wide
  # NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
  # redis-redis-84db677d99-mb2m4   1/1     Running   0          11m   10.244.2.49   k8s-node-5   <none>           <none>

# (2) 进入Pod的Shell之中
$ kubectl exec -it redis-redis-84db677d99-mb2m4 -- bash
I have no name!@redis-redis-84db677d99-mb2m4:/$ cat /bitnami/redis/conf/redis.conf | grep "weiyi"
  # requirepass weiyigeek
I have no name!@redis-redis-84db677d99-mb2m4:/$ ls
  # app-entrypoint.sh  bin  bitnami  boot  dev  entrypoint.sh  etc  home  init.sh  lib  lib64  media  mnt  opt  proc  redis-inputs.json  root  run  run.sh  sbin  srv  sys  tmp  usr  var
I have no name!@redis-redis-84db677d99-mb2m4:/$ ls /bitnami/
  # redis

# (3) 连接测试Redis
REDIS_PASSWORD=$(kubectl get secret --namespace default redis-redis -o jsonpath="{.data.redis-password}" | base64 --decode)
~/K8s/Day10/demo2$ redis-cli -h 10.244.2.49  -a $REDIS_PASSWORD
10.244.2.47:6379> ping
PONG
10.244.2.47:6379> info
# Server
redis_version:4.0.8
......
10.244.2.47:6379> set name weiyigeek
OK
10.244.2.47:6379> KEYS *
1) "name"
10.244.2.47:6379> GET name
"weiyigeek"
10.244.2.47:6379> save   # 将 数据 存储进Redis 文件 dump.rdb 以便后续重启redis后数据依然存在;
OK

# (4) 查看存放的dump.rdb文件
~/K8s/Day10/demo2/redis$ ls /nfs/data2/redis/data/
  # appendonly.aof  dump.rdb

Step 5.验证持久化操作设置

代码语言:javascript复制
~/K8s/Day10/demo2/redis/templates$ kubectl delete pod redis-redis-84db677d99-mb2m4
  # pod "redis-redis-84db677d99-mb2m4" deleted

~/K8s/Day10/demo2/redis/templates$ kubectl get pod -o wide
  # redis-redis-84db677d99-5cjnk   1/1     Running   0          26s   10.244.2.50   k8s-node-5

~/K8s/Day10/demo2/redis/templates$ redis-cli -h 10.244.2.50 -a $REDIS_PASSWORD
  # 10.244.2.50:6379> get name
  # "weiyigeek"

3.Library - Chart 仓库(Local&Remote)

描述: 我们在为自建的Chart图表打包分享时候除了可以直接pakeage打包分享外,更多的是采用私有的Harbor或者Helm公共的Chart应用中心进行分享和下载;

Q: 什么是 Chart 仓库?

答: Chart 仓库是一个可用来存储 index.yml 与 打包的 chart tgz文件的 HTTP Server, 当需要分享Chart时候需要上传Chart相关文件到仓库之中提供下载;

  • PS : 其实任何一个能够提供YAML与tar文件的 HTTP Server 都可以当作Chat仓库;
  • PS : 在 ~/.helm中的index.yaml记录了chart仓库中全部的chart的索引例如名称、URL、版本等一些元数据信息;

下面我们讲解 Chart 仓库搭建的两种方式及其使用;

  • 1)Harbor (PS需要安装chartmuseum)
  • 2)Helm 官方: https://artifacthub.io/ https://github.com/artifacthub/
  • 3)Helm 本地仓库
  • 4)Github Page
  • 5)Google Cloud Storafe(GCS) bucket

(1) Harbor 搭建Chart 仓库 操作流程:

Step 1.在现有的Harbor服务器上进行安装chartmuseum支持,在启用后默认创建的项目就带有helm charts功能了。

代码语言:javascript复制
~/harbor$ ls
  # common     docker-compose.yml  harbor.v2.1.1.tar.gz  harbor.yml.tmpl  LICENSE  server.crt  server.key
  # common.sh  harbor_install.log  harbor.yml            install.sh       prepare  server.csr  server.key.org
~/harbor$ sudo docker-compose ps
~/harbor$ sudo ./install.sh --with-chartmuseum
  # ✔ ----Harbor has been installed and started successfully.----  # 表示安装成功

Step 2.在Helm安装节点上安装push插件(如果安装时候已经带有push插件便可以不用安装了)

代码语言:javascript复制
# (1) 在线安装
~$ helm plugin install https://github.com/chartmuseum/helm-push
Downloading and installing helm-push v0.9.0 ...
https://github.com/chartmuseum/helm-push/releases/download/v0.9.0/helm-push_0.9.0_linux_amd64.tar.gz


# (2) 离线安装
~/software$ tar -zxvf helm-push_0.9.0_linux_amd64.tar.gz -C ./helm-push
# LICENSE
# plugin.yaml
# bin/helmpush
~/software$ ls ./helm-push
# bin  LICENSE  plugin.yaml
~/software/helm-push$ cp bin/helmpush /home/weiyigeek/.local/share/helm/plugins/helm-push/bin/helmpush
# ~/software$ helm plugin install ./helm-push
# Error: plugin already exists

Step 3.私有的Harbor的Chart仓库添加及其上传Chart图表到库中

代码语言:javascript复制
# (1) 添加harbor的chartrepo URL到Helm本地仓库中
~/K8s/Day10/demo1$ helm repo add local https://harbor.weiyigeek.top/chartrepo/test  --insecure-skip-tls-verify
"local" has been added to your repositories

# (2) index 生成以及将push 图表 到库中
~/K8s/Day10/demo1$ helm repo index . --url https://harbor.weiyigeek.top/chartrepo/test
~/K8s/Day10/demo1$ helm push --insecure -u weiyigeek -p Harbor12345 hello-world-1.0.0.tgz local
# Pushing hello-world-1.0.0.tgz to local...
# Done.

WeiyiGeek.Chart上传Harbor

温馨提示: 要想Harbor支持Chart仓库服务, 我们需要在安装时执行如下命令./install.sh --with-chartmuseum.

代码语言:javascript复制
# add 企业内部私有 harbor repo
helm repo add --username admin --password 123456 harbor https://harbor.weiyigeek.top/chartrepo/library

# 安装 helm-push 插件
helm plugin install https://github.com/chartmuseum/helm-push

# 推送 chart 到仓库
helm cm-push mysql harbor

Step 4.下载和搜索私有的Chart应用

代码语言:javascript复制
# (1) 仓库更新
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "local" chart repository
...Successfully got an update from the "aliyun" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈

# (2) 搜索我们上传的Chart应用
~/K8s/Day10$ helm search repo hello-world
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
local/hello-world       1.0.0           latest          A Helm chart for Kubernetes Hello Wrold

# (3) 按照Chart应用 
~/K8s/Day10$ helm install hello-world local/hello-world --insecure-skip-tls-verify
  # NAME: hello-world
  # LAST DEPLOYED: Thu Nov 26 13:48:13 2020
  # NAMESPACE: default
  # STATUS: deployed
  # REVISION: 1

Step 5.验证安装的Chart应用

代码语言:javascript复制
# (1) 查看release
~/K8s/Day10/demo3$ helm ls
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
hello-world     default         1               2020-11-26 13:48:13.358015764  0800 CST deployed        hello-world-1.0.0       latest

# (2) 查询 Pod 
~/K8s/Day10/demo3$ kubectl get pod -o wide
hello-world-deployment-588f974b64-8wfdj   1/1     Running   0          87s     10.244.2.51   k8s-node-5

# (3) 访问Pod提供的服务页面 
~/K8s/Day10/demo3$ curl http://10.244.2.51/host.html
  # Hostname: hello-world-deployment-588f974b64-8wfdj  <br>
  # Image Version: <u> 1.0 </u>
  # Nginx Version: 1.19.4
~/K8s/Day10/demo3$ curl http://10.10.107.202:30080/host.html
  # Hostname: hello-world-deployment-588f974b64-8wfdj  <br>
  # Image Version: <u> 1.0 </u>
  # Nginx Version: 1.19.4

(2) 本地Chart仓库

PS : 3.X的版本已不再支持helm server -address 0.0.0.0:8879 -repo-path ./charts建立本地Chart仓库;

代码语言:javascript复制
cd ~/.helm/


# PS : 建议使用域名来绑定本地仓库所在的机器 IP 例如helm.weiyigeek.top


# 将本地Chart仓库URL信息等Metadata信息 记录在index.yaml文件中
helm package mychart/

mv mychart-1.0.tgz test-mychart/


# 通过helm repo add 添加仓库
helm repo add localcharts http://helm.weiyigeek.top:8879/charts

# 查找上传的chart
helm search repo mychart

0x03 Helm 命令

代码语言:javascript复制
$ helm --help
The Kubernetes package manager

Common actions for Helm:

- helm search:    search for charts
- helm pull:      download a chart to your local directory to view
- helm install:   upload the chart to Kubernetes
- helm list:      list releases of charts

Environment variables:

| Name                               | Description                                                                       |
|------------------------------------|-----------------------------------------------------------------------------------|
| $HELM_CACHE_HOME                   | set an alternative location for storing cached files.                             |
| $HELM_CONFIG_HOME                  | set an alternative location for storing Helm configuration.                       |
| $HELM_DATA_HOME                    | set an alternative location for storing Helm data.                                |
| $HELM_DEBUG                        | indicate whether or not Helm is running in Debug mode                             |
| $HELM_DRIVER                       | set the backend storage driver. Values are: configmap, secret, memory, postgres   |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use.                      |
| $HELM_MAX_HISTORY                  | set the maximum number of helm release history.                                   |
| $HELM_NAMESPACE                    | set the namespace used for the helm operations.                                   |
| $HELM_NO_PLUGINS                   | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.                        |
| $HELM_PLUGINS                      | set the path to the plugins directory                                             |
| $HELM_REGISTRY_CONFIG              | set the path to the registry config file.                                         |
| $HELM_REPOSITORY_CACHE             | set the path to the repository cache directory                                    |
| $HELM_REPOSITORY_CONFIG            | set the path to the repositories file.                                            |
| $KUBECONFIG                        | set an alternative Kubernetes configuration file (default "~/.kube/config")       |
| $HELM_KUBEAPISERVER                | set the Kubernetes API Server Endpoint for authentication                         |
| $HELM_KUBEASGROUPS                 | set the Groups to use for impersonation using a comma-separated list.             |
| $HELM_KUBEASUSER                   | set the Username to impersonate for the operation.                                |
| $HELM_KUBECONTEXT                  | set the name of the kubeconfig context.                                           |
| $HELM_KUBETOKEN                    | set the Bearer KubeToken used for authentication.                                 |

Helm stores cache, configuration, and data based on the following configuration order:

- If a HELM_*_HOME environment variable is set, it will be used
- Otherwise, on systems supporting the XDG base directory specification, the XDG variables will be used
- When no other location is set a default location will be used based on the operating system

By default, the default directories depend on the Operating System. The defaults are listed below:

| Operating System | Cache Path                | Configuration Path             | Data Path               |
|------------------|---------------------------|--------------------------------|-------------------------|
| Linux            | $HOME/.cache/helm         | $HOME/.config/helm             | $HOME/.local/share/helm |
| macOS            | $HOME/Library/Caches/helm | $HOME/Library/Preferences/helm | $HOME/Library/helm      |
| Windows          | %TEMP%helm               | %APPDATA%helm                 | %APPDATA%helm          |

Usage:
  helm [command]

Available Commands:
  completion  generate autocompletions script for the specified shell
  create      create a new chart with the given name
  dependency  manage a chart's dependencies
  env         helm client environment information
  get         download extended information of a named release
  help        Help about any command
  history     fetch release history
  install     install a chart
  lint        examine a chart for possible issues
  list        list releases
  package     package a chart directory into a chart archive
  plugin      install, list, or uninstall Helm plugins
  pull        download a chart from a repository and (optionally) unpack it in local directory
  repo        add, list, remove, update, and index chart repositories
  rollback    roll back a release to a previous revision
  search      search for a keyword in charts
  show        show information of a chart
  status      display the status of the named release
  template    locally render templates
  test        run tests for a release
  uninstall   uninstall a release
  upgrade     upgrade a release
  verify      verify that a chart at the given path has been signed and is valid
  version     print the client version information

Flags:
      --debug                       enable verbose output
  -h, --help                        help for helm
      --kube-apiserver string       the address and the port for the Kubernetes API server
      --kube-as-group stringArray   Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
      --kube-as-user string         Username to impersonate for the operation
      --kube-context string         name of the kubeconfig context to use
      --kube-token string           bearer token used for authentication
      --kubeconfig string           path to the kubeconfig file
  -n, --namespace string            namespace scope for this request
      --registry-config string      path to the registry config file (default "/home/weiyigeek/.config/helm/registry.json")
      --repository-cache string     path to the file containing cached repository indexes (default "/home/weiyigeek/.cache/helm/repository")
      --repository-config string    path to the file containing repository names and URLs (default "/home/weiyigeek/.config/helm/repositories.yaml")

Use "helm [command] --help" for more information about a command.

0x04 模板编写

代码语言:javascript复制
~/K8s/Day10/demo2$ cat redis/Chart.yaml
appVersion: 4.0.8
description: Open source, advanced key-value store. It is often referred to as a data
  structure server since keys can contain strings, hashes, lists, sets and sorted
  sets.
engine: gotpl
home: http://redis.io/
icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png
keywords:
- redis
- keyvalue
- database
maintainers:
- email: containers@bitnami.com
  name: bitnami-bot
name: redis
sources:
- https://github.com/bitnami/bitnami-docker-redis
version: 1.1.15

http://www.coderdocument.com/docs/helm/v2/charts/chart_lifecycle_hooks.html

Helm 使用流程简述:

对历史版本进行DIFF对比的三种方式:

1.使用 sdiff

代码语言:javascript复制
# 1.全量使用 -s and -w flags 仅显示变化的部分
sdiff <(helm get $release --revision $revision_1) <(helm get $release --revision $revision_1)

2.使用 vimdiff

代码语言:javascript复制
vimdiff <(helm get $release --revision $revision_1) <(helm get $release --revision $revision_1)

3.使用 helm diff

代码语言:javascript复制
helm diff revision [flags] [release] REVISION1 REVISION2

helm search hub distributed-jmeter -o yaml

  • app_version: “3.3” description: A Distributed JMeter Helm chart repository: name: cloudnativeapp url: https://cloudnativeapp.github.io/charts/curated/ url: https://artifacthub.io/packages/helm/cloudnativeapp/distributed-jmeter version: 1.0.1

0 人点赞