这是《微服务版 Hello World》系列文章的第二篇,这篇文章讲解如何通过 Helm 部署例如 MySQL 这样的基础服务,并且演示了如何通过 Helm 将一个 Hello World 的 Rust 程序部署到 k8s 集群。
Helm 是 k8s 的包管理工具,就类似于 Ubuntu 中的 Apt 或者 CentOS 中的 Yum/Dnf。我们可以用它简化 在 k8s 中的配置,管理应用的生命周期。
安装和配置
具体的安装步骤可以参考官方文档,支持中英韩日等各国语言。在安装之前,需要先确保已经有一个拥有权限访问的 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)指的是持久卷,定义集群级别的存储资源,表示集群中的一块实际的存储空间。
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 验证:
暴露服务
虽然我们可以通过 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服务的type是NodePort,CLUSTER-IP集群内部可以通过CLUSTER-IP(10.103.162.252)进行访问, 对外暴露了 30300 端口;mysql-headless一般用于集群内部通信、主从复制、直接 Pod 访问,它绕过了集群的负载均衡。
我通过安装 mysql-client 这个官方的命令行工具来测试一下是否可以访问:
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:
创建 Personal Access Token:
然后使用 Personal Access Token 登录:
最后,我们将本地的镜像 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 命令可以创建模板,创建后的目录结构如下图所示:
下面是对目录结构的详细说明:
| 文件/目录 | 作用说明 |
|---|---|
charts/ | 存放当前 chart 依赖的子 chart,可以包含其他 Helm charts 作为依赖项 |
Chart.yaml | Chart 的元数据文件,包含 chart 名称、版本、描述、维护者信息等核心配置 |
templates/ | 存放 Kubernetes 资源模板文件,这些模板会与 values.yaml 中的值结合生成最终的 K8s 资源 |
templates/deployment.yaml | Kubernetes Deployment 资源模板,定义应用的部署配置(副本数、容器镜像、资源限制等) |
templates/_helpers.tpl | 模板助手文件,定义可重用的模板函数和变量,避免代码重复 |
templates/hpa.yaml | Horizontal Pod Autoscaler 模板,用于配置 Pod 水平自动扩缩容 |
templates/ingress.yaml | Ingress 资源模板,用于配置外部访问路由和负载均衡 |
templates/NOTES.txt | 部署后显示给用户的说明文档,通常包含访问应用的方法和重要信息 |
templates/serviceaccount.yaml | ServiceAccount 资源模板,用于配置 Pod 的身份认证和权限 |
templates/service.yaml | Kubernetes 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 集群。