在上一篇文章中,介绍了 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 资源定义的初始文件,并按照官方推荐的目录结构组织文件,如下所示:
helm create hello-helm
- 1
生成的目录结构如下:
.
└── 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
我们将 templates
目录下默认生成的 yaml
文件删除,并替换为之前教程中创建的 yaml
文件。最终的结构如下:
.
├── 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
image
文件夹中包含用于构建镜像的内容,其中 HelloKubernetes.java
定义了 hellok8s:v6
版本的代码,主要是从系统环境中获取 MESSAGE
、NAMESPACE
、DB_URL
、DB_PASSWORD
这几个环境变量,并将它们拼接成字符串返回。这些环境变量的来源会通过 helm values
和 Kubernetes 配置来设置。
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();
}
}
- 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
下面是 Dockerfile 的内容:
# 使用 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
- 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
进入 image
文件夹,进行镜像的构建。注意,将 tangrl177
替换为你自己的 DockerHub 账号名。
docker build . -t tangrl177/hellok8s:v6
docker push tangrl177/hellok8s:v6
- 1
- 2
接下来,我们修改根目录下的 values.yaml
文件,用于定义自定义的配置信息。将 Kubernetes 资源文件中容易变化的参数提取出来,放在 values.yaml
文件中。**注意,将 tangrl177
替换为你自己的 DockerHub 账号名。**完整的 values.yaml
配置信息如下:
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在 Helm 中,默认使用 Go template 语法来引用这些值。例如,我们可以将 ConfigMap 资源定义成如下的 hellok8s-configmaps.yaml
文件,其中 metadata.name
的值使用 {{ .Values.application.name }}-config
,会从 values.yaml
中获取 application.name
的值 hellok8s
,并拼接 -config
,生成的 ConfigMap 名称就是 hellok8s-config
。
# hellok8s-configmaps.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.application.name }}-config
data:
DB_URL: {{ .Values.application.hellok8s.database.url }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
同理,可以将 Secret 资源定义成如下的 hellok8s-secret.yaml
文件,并使用 b64enc
方法将值进行 Base64 编码:
# hellok8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.application.name }}-secret
data:
DB_PASSWORD: {{ .Values.application.hellok8s.database.password | b64enc }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
最后,修改 hellok8s-deployment.yaml
文件,将所有变量引用改为从 values.yaml
文件中获取,并添加代码中需要的 NAMESPACE
环境变量,使用 Helm 内置的 .Release.Namespace
对象获取当前命名空间:
# 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 }}
- 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
类似地,ingress.yaml
文件的 metadata.name
也可以改为从 values.yaml
中获取:
# 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
- 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
nginx-deployment.yaml
文件的内容如下:
# 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
nginx-service.yaml
和 hellok8s-service.yaml
文件的内容没有变化。
# 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在 hello-helm
目录下执行 helm upgrade
命令进行安装。安装成功后,执行 curl
命令可以直接查看结果,并通过 helm
管理所有资源:
# 如果使用 minikube,需要开启 ingress 功能
minikube addons enable ingress
# 在本地安装 Chart
helm upgrade --install hello-helm --values values.yaml .
- 1
- 2
- 3
- 4
- 5
如果使用 minikube 或 Docker Desktop,则可能需要公开服务。可以使用 minikube
提供的命令来查看服务列表,并通过 curl
进行测试:
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/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这样,你就可以通过 Helm 一键管理和部署所有 Kubernetes 资源了!
Rollback
Helm 提供了强大的 Rollback 功能,允许你快速回滚到之前的版本。如果一次更新出现了问题,你可以轻松地恢复到之前的状态。
首先,我们先通过修改 values.yaml
文件,将 message
字段更新为 "It works with Helm Values[v2]!"
,以表示这是一个新的版本。
# 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
然后,使用 helm upgrade
命令更新 Kubernetes 资源。你可以通过 curl
命令来验证资源是否已更新成功。
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/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
如果这次更新出现问题,你可以使用 helm rollback
命令快速回滚到之前的版本。需要注意的是,与 Kubernetes 中的 Deployment 回滚类似,Helm 回滚后的 REVISION 版本号会增加。例如,如果你回滚到 REVISION 1,回滚后的 REVISION 版本号将是 3,而不是直接回到 1。回滚后,使用 curl
命令时会看到返回的字符串恢复为 v1 版本。
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/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
通过上述步骤,你可以看到 Helm 的 Rollback 功能如何在版本更新出现问题时帮助你迅速恢复到之前的稳定版本。这使得管理 Kubernetes 资源更加灵活和安全。
多环境配置
多环境配置
使用 Helm 进行多环境部署非常方便。首先,你可以为不同的环境创建不同的 values
文件。例如,创建一个 values-dev.yaml
文件,用于自定义 dev
环境的配置信息。
# values-dev.yaml
application:
hellok8s:
message: "It works with Helm Values values-dev.yaml!"
database:
url: "http://DB_ADDRESS_DEV"
password: "db_password_dev"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在部署时,你可以使用多次指定 --values
或 -f
参数的方式来加载不同的 values
文件。最后指定的文件(即最右边的文件)具有最高的优先级,因此 values-dev.yaml
文件中的值会覆盖 values.yaml
中相同的值。同时,使用 -n dev
可以在名为 dev
的 namespace 中创建 Kubernetes 资源。执行命令后,通过 curl
命令可以看到返回的字符串中读取的是 values-dev.yaml
文件中的配置,并且显示 From namespace = dev
。
helm upgrade --install hello-helm -f values.yaml -f values-dev.yaml -n dev .
kubectl get pods -n dev
- 1
- 2
- 3
除了使用 values
文件外,你还可以通过 --set
直接在命令行中设置独立的值。例如,使用以下命令可以覆盖 values.yaml
和 values-dev.yaml
文件中的 message
字段:
helm upgrade --install hello-helm -f values.yaml -f values-dev.yaml --set application.hellok8s.message="It works with set helm values" -n dev .
- 1
这种方法在 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
命令来安装该指定包:
helm package hello-helm
helm repo index .
helm upgrade --install hello-helm hello-helm-0.1.0.tgz
- 1
- 2
- 3
- 4
- 5
通过上述步骤,我们可以看到,所谓的 Helm 打包与发布过程,其实就是生成并上传 hello-helm-0.1.0.tgz
和 index.yaml
文件。而 Helm 的下载与安装过程则是将 .tgz
和 index.yaml
文件下载并使用 helm upgrade --install
命令进行安装。
自动发布到 GitHub Pages
为了将生成的 hellok8s
Helm Chart 自动发布,我们可以先删除手动生成的 hello-helm-0.1.0.tgz
和 index.yaml
文件,然后使用 GitHub Actions 自动生成和发布这些文件。
以下是 GitHub Action 的配置示例,来自 官方文档 或本教程的源码仓库 .github/workflows/release.yml
文件。该配置会在每次代码推送到远程仓库时,自动将 helm-charts
目录下的所有 Charts 打包并发布到 gh-pages
分支(确保 gh-pages
分支已存在,否则会报错)。
可以通过下面的命令创建新分支:
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
- 1
- 2
- 3
- 4
- 5
- 6
下面是 .github/workflows/release.yml
文件:
# .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 }}"
- 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 仓库的 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 集群当中。
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
发布到社区
最后,你可以将自己的 Helm Charts 发布到社区中,例如 ArtifactHub,这样更多人可以使用你的 Chart。像本教程中的 hellok8s
Helm Chart 就已经发布在 ArtifactHub 上。通过这些步骤,你就可以将自己的 Helm Chart 打包、发布并共享给全球的开发者使用了。
代码仓库
https://github.com/rongliangtang/helm-demo
参考
Helm 官方文档
Kubernetes 练习手册