微服务版Hello World之使用Helm部署

180 阅读4分钟

这是《微服务版 Hello World》系列文章的第二篇,这篇文章讲解如何通过 Helm 部署例如 MySQL 这样的基础服务,并且演示了如何通过 Helm 将一个 Hello World 的 Rust 程序部署到 k8s 集群。

Helm 是 k8s 的包管理工具,就类似于 Ubuntu 中的 Apt 或者 CentOS 中的 Yum/Dnf。我们可以用它简化 在 k8s 中的配置,管理应用的生命周期。

image.png

安装和配置

具体的安装步骤可以参考官方文档,支持中英韩日等各国语言。在安装之前,需要先确保已经有一个拥有权限访问的 k8s 的集群,以及在本地已经安装好了 kubectl。

下面以 Ubuntu 为例,安装 Helm:

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

如果你是 macOS 可以通过 brew 来安装:

brew install helm

通过 helm version 来验证是否安装成功,类似如下输出:

$ helm version
version.BuildInfo{Version:"v3.18.1", GitCommit:"f6f8700a539c18101509434f3b59e6a21402a1b2", GitTreeState:"clean", GoVersion:"go1.24.3"}

在使用 helm 之前,需要先将 k8s 的 ~/.kube/config 拷贝到 helm 所在的机器的 ~/.kube/config 文件中。

核心概念

在使用 Helm 之前,需要理解 3 个核心的概念,有助于我们更好的使用它。

  • **Chart:**就类似于 Docker 中的镜像、Apt 中的 dpkg、Yum 中的 RPM 文件。包含了运行在 k8s 中运行所需要的所有的资源,应用、工具或者服务的配置文件。Chart 可以让你的配置文件复用、模版化,而不是手工的复制;
  • **Repository:**类似于绝大部分的包管理系统的架构,都会有一个 Repository,比如 Java 的Maven、Rust 的 Cargo、Docker 的 Docker Hub、Apt/Yum 的 Repository、Git 的 Repository。存储的是 Chart,可以通过客户端 Push 或者 Pull;
  • **Version:**如果 Docker Image 的 Tag,包管理工具很核心的一个作用,就是版本管理。基于版本管理,你才可以做版本的发布和回滚。

添加仓库

首先,我们需要先将添加一个 Repository,这样 helm 才知道去哪里找 Chart :

$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories

接着更新 Repository 的索引:

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

验证是否更添加成功:

$ helm repo list
NAME    URL                               
bitnami https://charts.bitnami.com/bitnami

搜索 mysql 的 chart:

$ helm search repo mysql
NAME                    CHART VERSION   APP VERSION     DESCRIPTION                                       
bitnami/mysql           13.0.0          9.3.0           MySQL is a fast, reliable, scalable, and easy t...
bitnami/phpmyadmin      18.1.8          5.2.2           phpMyAdmin is a free software tool written in P...
bitnami/mariadb         20.5.6          11.4.7          MariaDB is an open source, community-developed ...
bitnami/mariadb-galera  14.2.6          11.4.7          MariaDB Galera is a multi-primary database clus...

查看 MySQL Chart 的详细信息(省略输出):

$ helm show chart bitnami/mysql
$ helm show values bitnami/mysql

为 MySQL 创建一个命名空间:

$ kubectl create namespace mysql

创建 PV 和 PVC

PV(Persistent Volume)指的是持久卷,定义集群级别的存储资源,表示集群中的一块实际的存储空间。

image 1.png

PVC(Persistent Volume Claim)指的是持久卷声明,用来联通 Pod 和 PV,声明 Pod 如何使用 PV:

创建 StorageClass

首先创建 StorageClass,配置如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
  annotations:
    # 将此存储类设为集群的默认存储类
    storageclass.kubernetes.io/is-default-class: "true"

# 不使用动态存储提供者
# 不会自动创建 PersistentVolume(PV)
# 适用于本地存储、NFS 等静态存储
provisioner: kubernetes.io/no-provisioner

# 延迟绑定模式
# PVC 创建后不会立即绑定到 PV
# 等到有 Pod 使用这个 PVC 时才会进行绑定
# 确保 PV 在 Pod 调度的节点上可用
# 默认为 Immediate (立即绑定)模式
volumeBindingMode: WaitForFirstConsumer

# 允许卷自动扩展(但不允许缩容)
allowVolumeExpansion: true

# 回收策略为当 PVC 被删除时,自动删除对应的 PV
reclaimPolicy: Delete

然后应用配置:

$ kubectl apply -f local-storage.yaml 
storageclass.storage.k8s.io/local-storage created

创建 PV

首先创建配置文件 mysql-pv.yaml ,内容如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
  labels:
    type: local
spec:
  # 存储容量
  capacity:
    storage: 5Gi
  # 访问模式
  accessModes:
    - ReadWriteOnce  # 单节点读写
  # 回收策略
  persistentVolumeReclaimPolicy: Retain
  # 引用我们创建的 StorageClass
  storageClassName: local-storage
  # 本地存储配置
  hostPath:
    path: /data/mysql
    type: DirectoryOrCreate  # 如果目录不存在则创建

然后应用这个配置:

$ kubectl apply -f mysql-pv.yaml 
persistentvolume/mysql-pv created

创建 PVC

编辑配置文件 mysql-pvc.yaml ,编辑内容如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: specific-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  # 这里要配置我们之前创建 StorageClass 时候指定的名字 
  storageClassName: "local-storage"

应用配置:

$ kubectl apply -f ./mysql-pvc.yaml 
persistentvolumeclaim/specific-pvc created

如下:

$ helm install my-mysql bitnami/mysql
NAME: my-mysql
LAST DEPLOYED: Wed Jun  4 18:55:43 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: mysql
CHART VERSION: 13.0.0
APP VERSION: 9.3.0
...省略其他输出...

使用 Helm 部署 MySQL

完成了 StorageClas、PV、PVC 的创建之后,我们就可以部署 MySQL 了。

通过命令行部署

最简单的方式就是通过命令行:

$ helm install mysql bitnami/mysql \
  --set primary.service.type=NodePort \
  --set primary.service.nodePorts.mysql=30300 \
  --set auth.rootPassword=secretpassword \
  --set primary.persistence.enabled=true \
  --set primary.persistence.existingClaim=specific-pvc \
  --namespace default

NAME: mysql
LAST DEPLOYED: Thu Jun  5 10:44:01 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: mysql
CHART VERSION: 13.0.0
APP VERSION: 9.3.0
...省略后面的输出...

你可以通过 helm list 来查看已经部署的应用,验证 MySQL 的 states 是不是 deployed

$ helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
mysql   default         1               2025-06-05 10:44:01.366457644 +0800 CST deployed        mysql-13.0.0    9.3.0      

排查并修复错误

但是如果你通过 kubectl get pods 来查看的话,发现容器其实没有启动成功:

$ kubectl get pods
NAME      READY   STATUS             RESTARTS      AGE
mysql-0   0/1     CrashLoopBackOff   5 (93s ago)   4m53s

可以通过 kubectl logs mysql-0 --previous 来查看上次错误的原因, 重点看下面的输出:

mysql 02:50:16.64 INFO  ==> 
mysql 02:50:16.64 INFO  ==> ** Starting MySQL setup **
mysql 02:50:16.65 INFO  ==> Validating settings in MYSQL_*/MARIADB_* env vars
mysql 02:50:16.65 INFO  ==> Initializing mysql database
mkdir: cannot create directory '/bitnami/mysql/data': Permission denied"

原因很清楚了:Permission denied 。 查看 MySQL 部署的节点:

$ kubectl get pods -o wide
NAME      READY   STATUS    RESTARTS   AGE    IP           NODE           NOMINATED NODE   READINESS GATES
mysql-0   1/1     Running   0          6m5s   10.244.2.3   k8s-worker-2   <none>           <none>

然后在这个节点上手动创建目录:

sudo mkdir -p /data/mysql
# 因为 Bitnami MySQL 是通过 UID 1001 运行的,
# 所以将目录的所有者设置为 1001
sudo chown -R 1001:1001 /data/mysql
sudo chmod -R 755 /data/mysql

最后进入容器,登录 MySQL 验证:

image 2.png

暴露服务

虽然我们可以通过 kubectl exec 进入容器访问 MySQL,但这只是一种测试的手段。实际上,我们需要对外暴露服务,可以通过外部的客户端访问服务。先来看看 helm 默认创建的服务:

$ kubectl get svc
NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
kubernetes       ClusterIP   10.96.0.1     <none>        443/TCP          6d17h
mysql            NodePort    10.111.2.66   <none>        3306:30300/TCP   13s
mysql-headless   ClusterIP   None          <none>        3306/TCP         13s
  • mysql 服务的 typeNodePortCLUSTER-IP 集群内部可以通过CLUSTER-IP(10.103.162.252) 进行访问, 对外暴露了 30300 端口;
  • mysql-headless 一般用于集群内部通信、主从复制、直接 Pod 访问,它绕过了集群的负载均衡。

我通过安装 mysql-client 这个官方的命令行工具来测试一下是否可以访问:

image 3.png

Helm 部署 Hello World

接下来将会循序渐进实现一个微服务版本的 Hello World,并部署到 k8s 集群中。在开始前,你需要先完成 k8s 集群以及 Helm 的部署,可以参考如下两篇文章:

编写后端接口

首先,我们需要先使用 Rust 编写一个 Hello World 的接口。采用了 Rust + Axum 的技术栈,依赖如下:

[dependencies]
axum = "0.8.4"
tokio = { version = "1.45.1", features = ["full"] }

编写代码如下:

use axum::Router;
use axum::routing::get;

#[tokio::main]
async fn main() {
    let app = Router::new().route(
        "/",
        get(|| async { "Hello World" })
    );

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

通过 curl [http://localhost:8080](http://localhost:8080) 访问,可以看到返回 Hello World

打包应用

首先,我们需要将这个应用打包成为一个镜像,Dockerfile 的示例如下:

FROM rust:1.87 AS builder

WORKDIR /app

COPY Cargo.toml Cargo.lock* ./

RUN mkdir src && echo "fn main() {}" > src/main.rs

# 只是为了安装依赖
# 利用 Docker 的层缓存来加速构建过程
# 只有 Cargo.toml 或 Cargo.lock 发生变化才会重新构建这一层
RUN cargo build --release
RUN rm src/main.rs

COPY src ./src

# 再次构建实际的应用
RUN cargo build --release

# 第二阶段:运行时阶段
FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

RUN useradd -r -s /bin/false appuser

WORKDIR /app

COPY --from=builder /app/target/release/api /app/

RUN chown -R appuser:appuser /app

USER appuser

EXPOSE 80

CMD ["./api"]

然后使用 Docker 构建这个镜像:

sudo docker build -t hello-srv:1.0.0 .

提交到 Docker Hub

构建好本地的镜像之后,需要提交到远程 Repository 中去。这里以 Docker Hub 为例。在 Docker Hub 上创建 Repository:

image 4.png

创建 Personal Access Token:

image 5.png

然后使用 Personal Access Token 登录:

image 6.png

最后,我们将本地的镜像 Push 到远程 Repository 中去,先 tag 后 push:

$ sudo docker tag hello-srv:1.0.0 devtree2023/hello-micro-service:1.0.0
$ sudo docker push devtree2023/hello-micro-service:1.0.0
The push refers to repository [docker.io/devtree2023/hello-micro-service]
8ba85eb97c88: Pushed 
e065968942e1: Pushed 
52e9419a0357: Pushed 
c8ebf9116bdf: Pushed 
1336096ea94b: Pushed 
41d20f587704: Mounted from library/debian 
1.0.0: digest: sha256:443505262937306e8dc581c88a971481241c2154e1d90b4a8a949d2be2b13a6a size: 1574

⚠️ 注意 ⚠️ : sudo docker login 显示 Succeeded 和 docker push 却显示 “denied: requested access to the resource is denied“。这是因为 sudo 是用 root 的账号,token 是存储在 root 的目录下的,所以 docker push 是读取不到的。

使用 Helm 部署 Hello World

接着,我们就可以使用 Helm 来将这个简单的 Hello World 程序部署到 k8s 的集群中去了。

创建模板

使用 helm create 命令可以创建模板,创建后的目录结构如下图所示:

image 7.png

下面是对目录结构的详细说明:

文件/目录作用说明
charts/存放当前 chart 依赖的子 chart,可以包含其他 Helm charts 作为依赖项
Chart.yamlChart 的元数据文件,包含 chart 名称、版本、描述、维护者信息等核心配置
templates/存放 Kubernetes 资源模板文件,这些模板会与 values.yaml 中的值结合生成最终的 K8s 资源
templates/deployment.yamlKubernetes Deployment 资源模板,定义应用的部署配置(副本数、容器镜像、资源限制等)
templates/_helpers.tpl模板助手文件,定义可重用的模板函数和变量,避免代码重复
templates/hpa.yamlHorizontal Pod Autoscaler 模板,用于配置 Pod 水平自动扩缩容
templates/ingress.yamlIngress 资源模板,用于配置外部访问路由和负载均衡
templates/NOTES.txt部署后显示给用户的说明文档,通常包含访问应用的方法和重要信息
templates/serviceaccount.yamlServiceAccount 资源模板,用于配置 Pod 的身份认证和权限
templates/service.yamlKubernetes Service 资源模板,定义应用的网络服务配置
templates/tests/存放 Helm 测试模板,用于验证部署后的应用是否正常工作
templates/tests/test-connection.yaml连接测试模板,验证应用的网络连接是否正常
values.yaml默认配置值文件,包含所有可配置的参数及其默认值,用于渲染模板

编辑模板

接下来,我们需要编辑一些模板文件。首先要编辑的是 values.yaml ,一定要编辑的内容如下:

image:
	# 修改镜像的名称
  repository: devtree2023/hello-micro-service
  # 修改镜像的 tag
  tag: "1.0.0"
  
# 修改服务的端口
service:
    type: NodePort
    port: 30080

# 容器内监听的端口
containerPort: 80
   
ingress:
    # 启用外部访问
    enabled: true
    # 内部访问的域名
    host: hello-world-srv.local

然后还需要修改一下 templates/deployment.yaml 配置,修改 containerPort

containers:
	ports:
		- name: http
			containerPort: {{ .Values.containerPort | default 80}}

顺便 templates/service.yaml 也修改一下:

spec:
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.containerPort | default 80 }}

使用 Helm 就是那么简单,配置这些就足够了。

创建私有 Docker Repository 的凭证

有没有想过,我们的 Repository 是私有的。如果只是配置 image 的 name 和 tag,是无法成功拉取的。我们肯定还要配置访问 Private Repository 的凭证的。

创建 Docker 认证的 Secret:

$ kubectl create secret docker-registry docker-secret \
  --docker-username=<username> \
  --docker-password=<your-dockerhub-password>'

secret/docker-secret created

可以通过 kubectl get secrets 命令来查看已经创建成功的 secrets

$ kubectl get secrets

NAME                          TYPE                             DATA   AGE
docker-secret                 kubernetes.io/dockerconfigjson   1      39s
mysql                         Opaque                           2      7h14m
sh.helm.release.v1.mysql.v1   helm.sh/release.v1               1      7h14m

最后需要修改 values.yaml ,配置凭证:

imagePullSecrets: 
  - name: docker-secret

部署,发布!

最后一步,发布第一个版本:

$ helm install hello-srv-api ./hello-srv-api
NAME: hello-srv-api
LAST DEPLOYED: Thu Jun  5 19:13:41 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  http://hello-world-srv.local/

如果修改了 values.yaml 之后(例如修改 image.tag),可以通过 helm upgrade 命令来升级版本:

helm upgrade hello-srv-api -f values.yaml .

然后你可以通过查看服务详情,查看对外暴露的端口,然后通过 curl 来访问:

$ kubectl describe svc hello-srv-api | grep NodePort

Type:                     NodePort
NodePort:                 http  30920/TCP

$ kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE    IP            NODE           NOMINATED NODE   READINESS GATES
hello-srv-api-d46bb957d-tj5h8   1/1     Running   0          9m5s   10.244.1.23   k8s-worker-1   <none>           <none>
mysql-0                         1/1     Running   0          8h     10.244.2.6    k8s-worker-2   <none>           <none>

$ kubectl get nodes -o wide
NAME           STATUS   ROLES           AGE    VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
k8s-master     Ready    control-plane   7d2h   v1.32.5   10.211.55.10   <none>        Ubuntu 24.04.2 LTS   6.8.0-60-generic   containerd://1.7.24
k8s-worker-1   Ready    <none>          7d1h   v1.32.5   10.211.55.11   <none>        Ubuntu 24.04.2 LTS   6.8.0-60-generic   containerd://1.7.24
k8s-worker-2   Ready    <none>          7d1h   v1.32.5   10.211.55.12   <none>        Ubuntu 24.04.2 LTS   6.8.0-60-generic   containerd://1.7.24

$ curl 10.211.55.11:30920
Hello World

总结

这篇文章介绍了 Helm 的核心概念、如何安装,以及通过部署 MySQL 来演示了 Helm 的基本用法。最后部署一个 Hello World 的接口服务到 k8s 集群。