微服务架构师封神之路06-一个简单例子,入门helm

  • 定义一个简单的需求
  • helm是干什么的?
  • helm chart 的文件目录结构
  • helloworld的chart实现
  • Chart.yaml
  • templates文件夹
  • deployment.yaml
  • service.yaml
  • values.yaml
  • 等会儿马上要用到的几个helm命令
  • helm lint
  • helm uninstall
  • helm package
  • helm install
  • 修改pom.xml
  • 如何通过helm命令修改chart的部署参数

定义一个简单的需求

  • 用spring-boot实现一个微服务,helloworld
  • 如果要了解项目的细节,可以参考我前面的两篇文章(不过要快速解决问题,直接阅读本文应该也没有问题):
  • 微服务架构师封神之路01-利用minikube部署一个最简单的应用。介绍了如何创建docker image,并直接使用kubernetes命令来部署用用和创建service。
  • 微服务架构师封神之路05-使用yaml文件装载kubernetes部署信息,maven实现自动部署。介绍了如何将应用的deployment和service的所有部署信息写入yaml文件,应用kubectl apply + yaml文件的方式来实现部署。
  • 微服务helloworld需要一个前端的负载均衡,kubernetes service
  • 用helm实现maven项目自动部署

helm是干什么的?

我们已经有了kubernetes,为什么还要费劲的使用helm?这个问题留在后面的例子里逐步来说明。
helm是建立在kubernetes层次之上,有了helm,你可以利用helm的命令来实现部署,删除等等的操作,并且可以方便的管理部署信息。
helm有三个重要的概念:

  • chart. chart是一个helm package,里面包括了应用在kubernetes上部署所需要的所有信息。
  • repository. 存放chart的地方,收集和共享chart信息,有点类似docker registry。
  • release. A Release is an instance of a chart running in a Kubernetes cluster.
    这三个概念的关系可以简单的描述为,

Helm installs charts into Kubernetes, creating a new release for each installation. And to find new charts, you can search Helm chart repositories.

更详细的信息可以参阅 helm doc

helm chart 的文件目录结构

ssm框架和微服务框架 微服务框架halo_kubernetes

这其中templates文件夹,Chart.yaml和values.yaml两个文件是必须的。

  • Chart.yaml,用来保存chart的信息
  • values.yaml,保存一些kubernetes部署所需信息的默认值,这些默认值用来替换templates目录下yaml文件中的变量。
  • templates, 关于kubernetes部署的yaml文件都保存在这个目录下面。在yaml文件中定义了一些变量,形如{{ .Chart.Name }}和{{ .Values.deployment.replicas }}。{{ .Chart.Name }}是Chart.yaml文件中的name变量;{{ .Values.deployment.replicas }}就是Values.yaml中的deployment.replicas变量。

helloworld的chart实现

我们要实现微服务及其service的部署,只要将包含这两个对象kubernetes部署信息的yaml文件放入templates目录下。

先看整体的看一下目录结构,

ssm框架和微服务框架 微服务框架halo_ssm框架和微服务框架_02

下面我们再一个一个文件的细看。

Chart.yaml

必需文件,用来保存chart信息。

apiVersion: v1
name: @project.artifactId@
version: @project.version@
appVersion: @project.version@
description: @project.description@
keywords:
- @project.artifactId@

以下3项是必须的

apiVersion: The chart API version (required)
 name: The name of the chart (required)
 version: A SemVer 2 version (required)

我们在文件中使用了一些变量替换符,待会pom会使用maven-resources-plugin来拷贝这些文件到target的指定位置。这些变量在maven build的过程中会被环境变量所替换。
另外需要注意的是,一般我们定义maven替换的变量,使用类似${project.artifactId}的变量名,但是在spring-boot中,必须要使用@project.artifactId@,否则无效。

templates文件夹

我们需要定义kubernetes deployment和service,可以选择把他们定义在一个yaml文件中或分开定义两个文件。我把它们分开定义成两个文件,deployment是deployment,service是service,清晰方便一点。

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
spec:
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  replicas: {{ .Values.deployment.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: {{ .Values.docker.registry }}/@project.artifactId@:@project.version@
          ports:
            - containerPort: {{ .Values.deployment.targetPort }}
          env:
            - name: JAVA_OPTS
              value: -server -Xms32m -Xmx32m

可以看到里面定义了两类变量,

  • {{ .Chart.xxx }} 和 {{ .Values.yyy }},前面已经说了,.Chart的值要去Chart.yaml中拿;.Values的值要去values.yaml中拿。这里要着重强调一下,为什么要定义values.yaml哪?这也是为什么需要helm的一个原因。values.yaml中定义的是它们的默认值,如果我们在使用helm部署chart的时候为这些变量指定其它的值,那么部署过程就会使用指定的这些值,而不是使用默认值。
  • @xxx.yyy@,这些变量替换符号是为maven-resources-plugin准备的,它们会在maven build的过程中被替换掉,替换后的文件都放在target里的指定目录下。

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
spec:
  selector:
    app: {{ .Chart.Name }}
  type: NodePort
  ports:
    - protocol: TCP
      port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      nodePort: {{ .Values.service.nodePort }}

同样也有定义变量,它们的值存放在Chart.yaml或values.yaml中。

values.yaml

现在我们再来看看values.yaml。刚刚在deployment.yaml和service.yaml中定义的那些变量的值到底是多少?你自己对照这两个文件找一找。

docker:
    registry: b5wang

deployment:
    replicas: 1
    targetPort: 9090

service:
    port: 9090
    targetPort: 9090
    nodePort: 30090

为什么我们会把这些值定义成变量?

因为我想在不修改任何文件的条件下,通过改变helm部署命令的参数,来动态的修改部署信息。

等会儿马上要用到的几个helm命令

helm lint

用来测试chart的文件结构,格式等。

helm lint <PATH> [flags]

flags是形如–xxx的一些参数,可以用helm lint --help来获取详细信息。
在maven build中的test phase我们可以使用这个命令来对chart文件进行检查,及时报错终止build。

helm uninstall

如果helm已经部署过同名的chart要先删除,否着会报错。

helm uninstall <RELEASE_NAME> --namespace <K8S_NAMESPACE>

helm package

打包chart

helm package <PATH> -d <TARGET_DIR>

把指定的chart目录打包成versioned chart archive file,并把结果放到指定的目录。

helm install

将chart部署到kubernetes。

helm install <NAME> <CHART_PATH> [flags]

修改pom.xml

为了实现从打包chart到部署的完整流程,我们需要在pom中定义以下的操作,

  1. (maven-resources-plugin)拷贝制作docker image必要的文件到${project.build.directory}/docker-build
  2. (maven-resources-plugin)拷贝打包chart archive必要的文件到${project.build.directory}/helm-chart/${project.artifactId}
  3. (exec-maven-plugin)测试chart文件,如果有错误及时终止maven build - helm lint
  4. (exec-maven-plugin)生成部署所需的docker image - docker build
  5. (exec-maven-plugin)如果存在的话,删除已经部署的同名chart - helm uninstall
  6. (exec-maven-plugin)打包chart - helm package
  7. (exec-maven-plugin)部署chart - helm install
    下面是完整的pom.xml,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
    </parent>

    <groupId>com.b5wang.cloudlab</groupId>
    <artifactId>helloworld</artifactId>
    <version>1.2-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>helloworld</name>
    <description>A sample project for micro-services with spring-boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <docker.registry>b5wang</docker.registry>
        <!-- dependencies' version -->
        <spring-boot-dependencies.version>2.3.0.RELEASE</spring-boot-dependencies.version>
        <log4j-1.2-api.version>2.13.3</log4j-1.2-api.version>
    </properties>

    <build>
        <finalName>${project.artifactId}</finalName>

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- =========================================================================
              == Copy resources into target folder                                      ==
              ========================================================================= -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <!-- resources to build docker image -->
                    <execution>
                        <id>generate-docker-build</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/docker-build</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${project.basedir}/src/docker</directory>
                                    <filtering>true</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                    <!-- resources to build helm chart -->
                    <execution>
                        <id>generate-exploded-chart</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/helm-chart/${project.artifactId}</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${project.basedir}/src/helm/chart</directory>
                                    <filtering>true</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- =========================================================================
              == Execute commands                                                       ==
              ========================================================================= -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <!-- test helm chart files format -->
                    <execution>
                        <id>exec-helm-lint</id>
                        <phase>test</phase>
                        <configuration>
                            <executable>helm</executable>
                            <commandlineArgs>lint ${project.build.directory}/helm-chart/${project.artifactId}</commandlineArgs>
                        </configuration>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                    <!-- Build docker image -->
                    <execution>
                        <id>exec-docker-build</id>
                        <phase>package</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <configuration>
                            <executable>docker</executable>
                            <commandlineArgs>build --no-cache --tag ${docker.registry}/${project.artifactId}:${project.version} -f ${project.build.directory}/docker-build/Dockerfile ${project.build.directory}</commandlineArgs>
                        </configuration>
                    </execution>
                    <!-- delete released helm chart, ignore if no release -->
                    <execution>
                        <id>exec-helm-delete</id>
                        <phase>package</phase>
                        <configuration>
                            <executable>helm</executable>
                            <commandlineArgs>uninstall ${project.artifactId} --namespace default</commandlineArgs>
                            <successCodes>
                                <successCode>0</successCode>
                                <successCode>1</successCode>
                            </successCodes>
                        </configuration>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                    <!-- package helm chart -->
                    <execution>
                        <id>exec-helm-package</id>
                        <phase>package</phase>
                        <configuration>
                            <executable>helm</executable>
                            <commandlineArgs>package ${project.build.directory}/helm-chart/${project.artifactId} -d ${project.build.directory}</commandlineArgs>
                        </configuration>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                    <!-- release helm chart -->
                    <execution>
                        <id>exec-helm-install</id>
                        <phase>pre-integration-test</phase>
                        <configuration>
                            <executable>helm</executable>
                            <commandlineArgs>install ${project.artifactId} ${project.build.directory}/${project.artifactId}-${project.version}.tgz --wait --debug</commandlineArgs>
                        </configuration>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- fix error java.lang.ClassNotFoundException: org.apache.maven.doxia.siterenderer.DocumentContent -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.8.2</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-project-info-reports-plugin</artifactId>
                <version>3.0.0</version>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot-dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-1.2-api</artifactId>
            <version>${log4j-1.2-api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>
</project>

现在你可以运行mvn clean install来测试一下。

如何通过helm命令修改chart的部署参数

我们在helloworld的部署配置文件(templates文件夹下的deployment.yaml和service.yaml文件)中定义了一些变量,它们的默认值是放在values.yaml中的。
比如,{{ .Values.deployment.replicas }}和{{ .Values.service.nodePort }}这两个变量,在values.yaml中它们的默认值是

deployment:
    replicas: 1

service:
    nodePort: 30090

我想在运行helm部署的时候修改它们的值。在target目录下找到已经打好的chart包,helloworld-1.2-SNAPSHOT.tgz。
运行如下命令,

helm install target\helloworld-1.1-SNAPSHOT.tgz --name helloworld --wait --debug --set deployment.replicas=2 --set service.nodePort=30099

这次再去minikube-dashboard看看deployment的详细信息,replicas和nodePort是不是已经变了?
这就是helm的方便之处。chart中定义了kubernetes部署应用所需要的所有信息,但它的配置不是死的,可以在应用部署的时候再决定采用那些具体的参数值。并不需要修改任何部署文件。如果你的应用很复杂,这种部署方式就显得很灵活了。

今天我们通过一个很简单的例子让大家了解了helm的基本功能。但还没有涉及到helm repository,就是指定helm chart的名字,helm在部署的时候如果本地没有找到chart会自动的去repository上面寻找所需要的chart。以后我通过自己的学习再慢慢为大家介绍。谢谢~

参考