动手练习 Helm

随笔4个月前发布 菲菲的菲
45 0 0

在上一篇文章中,介绍了 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 版本的代码,主要是从系统环境中获取 MESSAGENAMESPACEDB_URLDB_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.yamlhellok8s-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.yamlvalues-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.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 分支已存在,否则会报错)。

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

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 练习手册

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...