在上一篇文章中,介绍了 K8s 的基本使用。本文中会介绍 Helm 的作用和基本使用。

在使用 Kubernetes 时,通常需要通过 kubectl apply -f 命令逐个创建资源。如果切换到另一个 namespace,或者部署到另一套 Kubernetes 集群时,往往需要重复这些操作。

Helm 的出现正是为了解决这个问题。它帮助我们在 Kubernetes 中部署服务时,只需一个命令即可完成整个过程。Helm 是管理 Kubernetes 应用的最佳工具,能够查找、分享和使用软件构建 Kubernetes 应用。其主要特点包括:

  • 复杂性管理:即使是最复杂的应用,Helm Chart 也可以完整描述,并提供可重复安装的单点授权应用程序。
  • 易于升级:随时随地的升级和自定义钩子使得升级变得简单而高效。
  • 简单分发:Helm Chart 可以轻松发布在公共或私有服务器上,便于分发和部署。
  • 轻松回滚:使用 helm rollback 命令,可以轻松回滚到之前的发布版本。

有关安装方法,可以参考官网教程

创建 Helm Chart

本地创建 Chart

下面介绍如何创建自己的 Helm Chart,我们将通过创建一个 hello-helm Chart 来部署 Kubernetes 资源。

首先,建议使用 helm create 命令来生成一个初始的 Chart,这个命令会自动生成一些 Kubernetes 资源定义的初始文件,并按照官方推荐的目录结构组织文件,如下所示:

1
helm create hello-helm

生成的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
└── hello-helm
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml

我们将 templates 目录下默认生成的 yaml 文件删除,并替换为之前教程中创建的 yaml 文件。最终的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
├── hello-helm
│ ├── Chart.yaml
│ ├── charts
│ ├── templates
│ │ ├── hellok8s-configmaps.yaml
│ │ ├── hellok8s-deployment.yaml
│ │ ├── hellok8s-secret.yaml
│ │ ├── hellok8s-service.yaml
│ │ ├── ingress.yaml
│ │ ├── nginx-deployment.yaml
│ │ └── nginx-service.yaml
│ ├── values-dev.yaml
│ └── values.yaml
└── image
├── HelloKubernetes.java
└── dockerfile

image 文件夹中包含用于构建镜像的内容,其中 HelloKubernetes.java 定义了 hellok8s:v6 版本的代码,主要是从系统环境中获取 MESSAGENAMESPACEDB_URLDB_PASSWORD 这几个环境变量,并将它们拼接成字符串返回。这些环境变量的来源会通过 helm values 和 Kubernetes 配置来设置。

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
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class HelloKubernetes {

public static void main(String[] args) throws IOException {
final String[] hostHolder = new String[1];
try {
hostHolder[0] = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
e.printStackTrace();
hostHolder[0] = "unknown";
}

// 获取环境变量
String message = System.getenv("MESSAGE");
String namespace = System.getenv("NAMESPACE");
String dbURL = System.getenv("DB_URL");
String dbPassword = System.getenv("DB_PASSWORD");

// 创建一个 HttpServer 实例,监听端口 3000
HttpServer server = HttpServer.create(new InetSocketAddress(3000), 0);

server.createContext("/", new HttpHandler() {
@Override
public void handle(HttpExchange exchange) throws IOException {
String response = String.format(
"[v6] Hello, Helm! Message from helm values: %s, From namespace: %s, From host: %s, Get Database Connect URL: %s, Database Connect Password: %s",
message, namespace, hostHolder[0], dbURL, dbPassword
);
exchange.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
});

server.setExecutor(null); // 使用默认的执行器
server.start();
}
}

下面是 Dockerfile 的内容:

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
# 使用 OpenJDK 8 的 Alpine 作为构建阶段的基础镜像
FROM openjdk:8-jdk-alpine AS builder

# 创建工作目录 /src
WORKDIR /src

# 将当前目录下的所有文件复制到容器的 /src 目录中
COPY . .

# 编译 Java 源代码
RUN javac HelloKubernetes.java

# 使用 distroless 作为运行阶段的基础镜像,以提高安全性和减小镜像大小
FROM gcr.io/distroless/java-debian10

# 设置工作目录为根目录
WORKDIR /

# 从构建阶段复制所有编译好的字节码文件到运行阶段镜像中
COPY --from=builder /src/ /app/

# 设置类路径,并运行应用程序
ENTRYPOINT ["java", "-cp", "/app", "HelloKubernetes"]

# 暴露应用程序监听的端口
EXPOSE 3000

进入 image 文件夹,进行镜像的构建。注意,将 tangrl177 替换为你自己的 DockerHub 账号名。

1
2
docker build . -t tangrl177/hellok8s:v6
docker push tangrl177/hellok8s:v6

接下来,我们修改根目录下的 values.yaml 文件,用于定义自定义的配置信息。将 Kubernetes 资源文件中容易变化的参数提取出来,放在 values.yaml 文件中。注意,将 tangrl177 替换为你自己的 DockerHub 账号名。完整的 values.yaml 配置信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
application:
name: hellok8s
hellok8s:
image: tangrl177/hellok8s:v6
replicas: 3
message: "It works with Helm Values[v6]!"
database:
url: "http://DB_ADDRESS_DEFAULT"
password: "db_password"
nginx:
image: nginx
replicas: 2

在 Helm 中,默认使用 Go template 语法来引用这些值。例如,我们可以将 ConfigMap 资源定义成如下的 hellok8s-configmaps.yaml 文件,其中 metadata.name 的值使用 {{ .Values.application.name }}-config,会从 values.yaml 中获取 application.name 的值 hellok8s,并拼接 -config,生成的 ConfigMap 名称就是 hellok8s-config

1
2
3
4
5
6
7
# hellok8s-configmaps.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.application.name }}-config
data:
DB_URL: {{ .Values.application.hellok8s.database.url }}

同理,可以将 Secret 资源定义成如下的 hellok8s-secret.yaml 文件,并使用 b64enc 方法将值进行 Base64 编码:

1
2
3
4
5
6
7
# hellok8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.application.name }}-secret
data:
DB_PASSWORD: {{ .Values.application.hellok8s.database.password | b64enc }}

最后,修改 hellok8s-deployment.yaml 文件,将所有变量引用改为从 values.yaml 文件中获取,并添加代码中需要的 NAMESPACE 环境变量,使用 Helm 内置的 .Release.Namespace 对象获取当前命名空间:

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
# hellok8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.application.name }}-deployment
spec:
replicas: {{ .Values.application.hellok8s.replicas }}
selector:
matchLabels:
app: hellok8s
template:
metadata:
labels:
app: hellok8s
spec:
containers:
- image: {{ .Values.application.hellok8s.image }}
name: hellok8s-container
env:
- name: DB_URL
valueFrom:
configMapKeyRef:
name: {{ .Values.application.name }}-config
key: DB_URL
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.application.name }}-secret
key: DB_PASSWORD
- name: NAMESPACE
value: {{ .Release.Namespace }}
- name: MESSAGE
value: {{ .Values.application.hellok8s.message }}

类似地,ingress.yaml 文件的 metadata.name 也可以改为从 values.yaml 中获取:

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
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Values.application.name }}-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- http:
paths:
- path: /hello
pathType: Prefix
backend:
service:
name: service-hellok8s-clusterip
port:
number: 3000
- path: /
pathType: Prefix
backend:


service:
name: service-nginx-clusterip
port:
number: 4000

nginx-deployment.yaml 文件的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: {{ .Values.application.nginx.replicas }}
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: {{ .Values.application.nginx.image }}
name: nginx-container

nginx-service.yamlhellok8s-service.yaml 文件的内容没有变化。

1
2
3
4
5
6
7
8
9
10
11
12
# hellok8s-service.yaml
apiVersion: v1
kind: Service
metadata:
name: service-hellok8s-clusterip
spec:
type: ClusterIP
selector:
app: hellok8s
ports:
- port: 3000
targetPort: 3000
1
2
3
4
5
6
7
8
9
10
11
12
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: service-nginx-clusterip
spec:
type: ClusterIP
selector:
app: nginx
ports:
- port: 4000
targetPort: 80

hello-helm 目录下执行 helm upgrade 命令进行安装。安装成功后,执行 curl 命令可以直接查看结果,并通过 helm 管理所有资源:

1
2
3
4
5
# 如果使用 minikube,需要开启 ingress 功能
minikube addons enable ingress

# 在本地安装 Chart
helm upgrade --install hello-helm --values values.yaml .

如果使用 minikube 或 Docker Desktop,则可能需要公开服务。可以使用 minikube 提供的命令来查看服务列表,并通过 curl 进行测试:

1
2
3
4
5
6
7
minikube service list
minikube service ingress-nginx-controller -n ingress-nginx --url
# 输出示例:
# http://127.0.0.1:55201 http
# http://127.0.0.1:55202 https
curl http://127.0.0.1:55201/hello
curl http://127.0.0.1:55201/

这样,你就可以通过 Helm 一键管理和部署所有 Kubernetes 资源了!

Rollback

Helm 提供了强大的 Rollback 功能,允许你快速回滚到之前的版本。如果一次更新出现了问题,你可以轻松地恢复到之前的状态。

首先,我们先通过修改 values.yaml 文件,将 message 字段更新为 "It works with Helm Values[v2]!",以表示这是一个新的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
# values.yaml
application:
name: hellok8s
hellok8s:
image: tangrl177/hellok8s:v6
replicas: 3
message: "It works with Helm Values[v2]!"
database:
url: "http://DB_ADDRESS_DEFAULT"
password: "db_password"
nginx:
image: nginx
replicas: 2

然后,使用 helm upgrade 命令更新 Kubernetes 资源。你可以通过 curl 命令来验证资源是否已更新成功。

1
2
3
4
5
6
7
8
helm upgrade --install hello-helm --values values.yaml .
minikube service list
minikube service ingress-nginx-controller -n ingress-nginx --url
# 输出如下
# http://127.0.0.1:55201 http
# http://127.0.0.1:55202 https
curl http://127.0.0.1:55201/hello
curl http://127.0.0.1:55201/

如果这次更新出现问题,你可以使用 helm rollback 命令快速回滚到之前的版本。需要注意的是,与 Kubernetes 中的 Deployment 回滚类似,Helm 回滚后的 REVISION 版本号会增加。例如,如果你回滚到 REVISION 1,回滚后的 REVISION 版本号将是 3,而不是直接回到 1。回滚后,使用 curl 命令时会看到返回的字符串恢复为 v1 版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
helm ls
# NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
# hello-helm default 2 2024-08-26 17:08:22.20669 +0800 CST deployed hello-helm-0.1.0 1.16.0

helm rollback hello-helm 1
# Rollback was a success! Happy Helming!

helm ls
# NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
# hello-helm default 3 2024-08-26 17:11:46.380842 +0800 CST deployed hello-helm-0.1.0 1.16.0

minikube service list
minikube service ingress-nginx-controller -n ingress-nginx --url
# 输出如下
# http://127.0.0.1:55201 http
# http://127.0.0.1:55202 https
curl http://127.0.0.1:55201/hello
curl http://127.0.0.1:55201/

通过上述步骤,你可以看到 Helm 的 Rollback 功能如何在版本更新出现问题时帮助你迅速恢复到之前的稳定版本。这使得管理 Kubernetes 资源更加灵活和安全。

多环境配置

多环境配置

使用 Helm 进行多环境部署非常方便。首先,你可以为不同的环境创建不同的 values 文件。例如,创建一个 values-dev.yaml 文件,用于自定义 dev 环境的配置信息。

1
2
3
4
5
6
7
# values-dev.yaml
application:
hellok8s:
message: "It works with Helm Values values-dev.yaml!"
database:
url: "http://DB_ADDRESS_DEV"
password: "db_password_dev"

在部署时,你可以使用多次指定 --values-f 参数的方式来加载不同的 values 文件。最后指定的文件(即最右边的文件)具有最高的优先级,因此 values-dev.yaml 文件中的值会覆盖 values.yaml 中相同的值。同时,使用 -n dev 可以在名为 dev 的 namespace 中创建 Kubernetes 资源。执行命令后,通过 curl 命令可以看到返回的字符串中读取的是 values-dev.yaml 文件中的配置,并且显示 From namespace = dev

1
2
3
helm upgrade --install hello-helm -f values.yaml -f values-dev.yaml -n dev .

kubectl get pods -n dev

除了使用 values 文件外,你还可以通过 --set 直接在命令行中设置独立的值。例如,使用以下命令可以覆盖 values.yamlvalues-dev.yaml 文件中的 message 字段:

1
helm upgrade --install hello-helm -f values.yaml -f values-dev.yaml --set application.hellok8s.message="It works with set helm values" -n dev .

这种方法在 CI/CD 中非常常见,因为它允许你在部署过程中灵活地修改配置,而无需更改 values 文件的内容。

打包和发布 Helm Chart

在前面的例子中,我们展示了如何用一行命令在一个新的环境中安装所有需要的 Kubernetes 资源!接下来,我们将讨论如何将 Helm Chart 打包、分发并发布,以便其他人可以下载和使用它。

官方提供了两种教程来实现这一目标:一种是使用 GCS(Google Cloud Storage) 存储,另一种是使用 GitHub Pages 存储 Chart。本教程将采用第二种方法,并使用 chart-releaser-action 进行自动发布。

这个 GitHub Action 会自动将 Helm Chart 发布到 gh-pages 分支上。例如,本教程中的 hellok8s Helm Chart 就发布在 gh-pages 分支的 index.yaml 文件中。

手动打包与发布

在使用 GitHub Action 自动生成和发布 Chart 之前,我们先了解一下如何手动完成这些操作。

hello-helm 目录下,使用 helm package 命令将 Chart 目录打包成一个 .tgz 文件。接着,使用 helm repo index 命令基于包含已打包 Chart 的目录生成仓库的索引文件 index.yaml

最后,你可以使用 helm upgrade --install 命令来安装该指定包:

1
2
3
4
5
helm package hello-helm

helm repo index .

helm upgrade --install hello-helm hello-helm-0.1.0.tgz

通过上述步骤,我们可以看到,所谓的 Helm 打包与发布过程,其实就是生成并上传 hello-helm-0.1.0.tgzindex.yaml 文件。而 Helm 的下载与安装过程则是将 .tgzindex.yaml 文件下载并使用 helm upgrade --install 命令进行安装。

自动发布到 GitHub Pages

为了将生成的 hellok8s Helm Chart 自动发布,我们可以先删除手动生成的 hello-helm-0.1.0.tgzindex.yaml 文件,然后使用 GitHub Actions 自动生成和发布这些文件。

以下是 GitHub Action 的配置示例,来自 官方文档 或本教程的源码仓库 .github/workflows/release.yml 文件。该配置会在每次代码推送到远程仓库时,自动将 helm-charts 目录下的所有 Charts 打包并发布到 gh-pages 分支(确保 gh-pages 分支已存在,否则会报错)。

可以通过下面的命令创建新分支:

1
2
3
4
5
6
git checkout --orphan gh-pages
git rm -rf .
echo '# helm chart repo' >> README.md
git add README.md
git commit -m 'new branch'
git push origin gh-pages

下面是 .github/workflows/release.yml 文件:

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
# .github/workflows/release.yml
name: Release Charts

on:
push:
branches:
- main

jobs:
release:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"

- name: Install Helm
uses: azure/setup-helm@v1
with:
version: v3.8.1

- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.4.0
with:
charts_dir: helm-charts
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

接着,前往 GitHub 仓库的 Settings -> Pages -> Build and deployment -> Branch,选择 gh-pages 分支,GitHub 会自动在 https://username.github.io/project 上发布 Helm Chart。

使用发布后的 Chart

将 Chart 发布到 Github Pages 后,可以通过下面的命令进行快速安装。其中 helm repo add 表示将我创建好的 hellok8s chart 添加到自己本地的仓库当中,helm install 表示从仓库中安装 hellok8s/hello-helm 到 k8s 集群当中。

1
2
3
4
5
6
7
8
helm repo add hellok8s https://rongliangtang.github.io/helm-demo/
# "hellok8s" has been added to your repositories

helm install my-hello-helm hellok8s/hello-helm --version 0.1.0
# NAME: my-hello-helm
# NAMESPACE: default
# STATUS: deployed
# REVISION: 1

发布到社区

最后,你可以将自己的 Helm Charts 发布到社区中,例如 ArtifactHub,这样更多人可以使用你的 Chart。像本教程中的 hellok8s Helm Chart 就已经发布在 ArtifactHub 上。通过这些步骤,你就可以将自己的 Helm Chart 打包、发布并共享给全球的开发者使用了。

代码仓库

https://github.com/rongliangtang/helm-demo

参考

Helm 官方文档

Kubernetes 练习手册