Kubernetes之Helm自定制chart包

1.Helm的概念

Helm这个东西一直有所耳闻,但一直没用于生产环境,本次借助公司的一套预生产环境实践了一把,总结一下,记录于此。

网上随处可见,此处粗略记录如下

Helm 有三个重要概念:

  • chart:包含了创建Kubernetes的一个应用实例的必要信息
  • config:包含了应用发布配置信息
  • release:是一个 chart 及其配置的一个运行实例

1.1.Helm的组件

Helm有以下两个组成部分:

Helm Structrue

Helm Client 是用户命令行工具,其主要负责如下:

  • 本地 chart 开发
  • 仓库管理
  • 与 Tiller sever 交互
  • 发送预安装的 chart
  • 查询 release 信息
  • 要求升级或卸载已存在的 release

Tiller Server是一个部署在Kubernetes集群内部的 server,其与 Helm client、Kubernetes API server 进行交互。Tiller server 主要负责如下:

  • 监听来自 Helm client 的请求
  • 通过 chart 及其配置构建一次发布
  • 安装 chart 到Kubernetes集群,并跟踪随后的发布
  • 通过与Kubernetes交互升级或卸载 chart
  • 简单的说,client 管理 charts,而 server 管理发布 release

2.Helm安装

注意点安装Helm之前需要有一套完整的Kubenertes集群,此处不做说明,可见我其它博文

我们可以在Helm Realese页面下载二进制文件,这里下载的v2.14.0版本,解压后将可执行文件helm拷贝到/bin/目录下即可,这样Helm客户端就在这台机器上安装完成了。

[root@cnvs-kubm-101-103 ~]# helm version  
Client: &version.Version{SemVer:"v2.14.3", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}
Error: could not find tiller

接着执行初始化操作,默认会去Google来取镜像,所以需要能科学上网,如果不能科学上网的可执行如下命令

$ helm init --upgrade --tiller-image cnych/tiller:v2.14.0

如果一直卡住或者报 google api 之类的错误,可以使用下面的命令进行初始化:

$ helm init --upgrade --tiller-image cnych/tiller:v2.14.0 --stable-repo-url https://cnych.github.io/kube-charts-mirror/

Helm 服务端正常安装完成后,Tiller默认被部署在kubernetes集群的kube-system命名空间下:

[root@cnvs-kubm-101-103 ~]# kubectl get pods -n kube-system -l name=tiller
NAME                             READY   STATUS    RESTARTS    AGE
tiller-deploy-7695cdcfb8-tzxcd   1/1     Running   1           5m

此时查看Helm版本就正常了

[root@cnvs-kubm-101-103 ~]# helm version
Client: &version.Version{SemVer:"v2.14.3", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.14.3", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}

另外一个值得注意的问题是RBAC, kubernetes 集群是1.10.0版本的,默认开启了RBAC访问控制,所以我们需要为Tiller创建一个ServiceAccount,让他拥有执行的权限,详细内容可以查看 Helm 文档中的Role-based Access Control。 创建rbac.yaml文件:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system

然后使用kubectl创建:

$ kubectl create -f rbac-config.yaml

创建了tiller的 ServceAccount 后还没完,因为我们的 Tiller 之前已经就部署成功了,而且是没有指定 ServiceAccount 的,所以我们需要给 Tiller 打上一个 ServiceAccount 的补丁:

$ kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

上面这一步非常重要,不然后面在使用 Helm 的过程中可能出现Error: no available release name found的错误信息。

##补充删除tiller
kubectl get -n kube-system secrets,sa,clusterrolebinding -o name|grep tiller|xargs kubectl -n kube-system delete
kubectl get all -n kube-system -l app=helm -o name|xargs kubectl delete -n kube-system

3.Helm的基本操作使用

本次以我公司的一个项目作为讲解过程

#创建一个lingxi-api的Chart包
[root@cnvskubm-101-157 xiejc]# helm create lingxi-api
Creating lingxi-api

###进入lingxi-api目录并删除子charts刚开始不用搞的那么复杂
[root@cnvskubm-101-157 xiejc]# cd lingxi-api/
[root@cnvskubm-101-157 lingxi-api]# ls
charts Chart.yaml templates values.yaml
[root@cnvskubm-101-157 lingxi-api]# rm -fr charts/

3.1.Helm整体结构目录说明

[root@cnvskubm-101-157 lingxi-api]# tree 
.
├── Chart.yaml                    #打包及安装更新的时候依赖的信息文件
├── templates                     #Chart模版的主目录
│   ├── deployment.yaml           #kubernetes的deployment的yaml文件
|   ├── horizontal-pod-autoscaler.yaml #pod自动收缩文件
│   ├── _helpers.tpl              #自我理解,一些全局的命名模版的信息文件,可用于子chart
│   ├── ingress.yaml              #kubernetes的ingress文件,可理解为一种对外开放访问的一种形式              
│   ├── NOTES.txt                 #安装时候的帮助信息
│   └── service.yaml              #kubernetes的service的yaml文件
└── values.yaml                   #.Values渲染到模版文件里的参数信息

1 directory, 7 files

3.2.贴出各个修改后的配置文件信息内容

###Deployment配置文件的信息内容
apiVersion: apps/v1       #kubernetes的api版本
kind: Deployment          #Deployment资源对象
metadata:                 #源数据
  name: {{ include "mybase.fullname" . }}  #指定name,次name是从_helpers.tpl文件中define申请的名称,.是指定当前的作用域
  labels:                 #指定标签
{{ include "mybase.labels" . | indent 4 }} #引用_helpers.tpl里面define申请的mybase.labels名称下的资源,然后空4个空格渲染
spec:                     #定义规格
  replicas: {{ .Values.replicaCount }} #指定运行的副本,从values.yaml文件里面获取
  selector:               #标签选择器
    matchLabels:          #组标签的定义
      app.kubernetes.io/name: {{ include "mybase.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
  template:               #管理上述申请的labels
    metadata:
      labels:
        app.kubernetes.io/name: {{ include "mybase.name" . }}
        app.kubernetes.io/instance: {{ .Release.Name }}   #.Release.Name实例的名称,go语言内置的函数
    spec:
    {{- with .Values.imagePullSecrets }} #指明作用域的范围为values.yaml文件里面的imagePullSecrets的资源
      imagePullSecrets: {{- toYaml . | nindent 8 }}  #拉取镜像的认证策略 -去除空格及换行符,|管道,nindent换行空8个空格
    {{- end }}            #作用域范围结束
      containers:         #指定容器信息
        - name: {{ .Chart.Name }}   #容器名称,go语言的内置函数
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" #镜像地址,修改的地方为values文件中
          env:            #pod的环境变量
            - name: TYPEORM_USERNAME
              value: "ptlingxidev"
            - name: TYPEORM_PORT
              value: "23306"
            - name: TYPEORM_PASSWORD
              value: "bvOJetgKf2"
            - name: TYPEORM_HOST
              value: "172.20.3.66"
            - name: TYPEORM_DATABASE
              value: "lingxidev"
            - name: THUMBNAIL_BUCKET
              value: "pt-cn-lingxi-files"
            - name: PIXABAY_API_KEY
              value: "14174599-356c4cd5ac1badc1efa62429e"
            - name: PAGE_FOLDER
              value: "pages"
            - name: OSS_REGION
              value: "oss-cn-beijing"
            - name: OSS_BUCKET
              value: "pt-cn-lingxi-files"
            - name: OSS_ACCESSKEYSECRET
              value: "L9OLLMTFbvnq7jMJnc7H93u6QexiiB"
            - name: OSS_ACCESSKEYID
              value: "LTAI4FhEE6vSrvNowkwAJ1Nx"
            - name: LX_DOMAIN
              value: "https://lingxi.ptone.cn"
            - name: IMAGE_FOLDER
              value: "images"
            - name: HTML_BUCKET
              value: "pt-cn-lingxi-files"
          imagePullPolicy: {{ .Values.image.pullPolicy }} #镜像拉取策略
          ports:         #容器端口定义
            - name: http
              containerPort: 3000  ##开放3000端口
          livenessProbe: #健康状态检查,如果检查失败,将杀死容器,根据pod的重启策略来操作
            httpGet:
              path: /health
              port: 3000
          readinessProbe: #健康状态检查,如果检查失败,会把pod从service endpoint中剔除
            httpGet:
            path: /health
            port: 3000
          resources:      #资源信息
            {{- toYaml .Values.resources | nindent 12 }}
        {{- with .Values.nodeSelector }}
        nodeSelector:
          {{- toYaml . | nindent 8 }}
        {{- end }}
      {{- with .Values.affinity }}
        affinity:
          {{- toYaml . | nindent 8 }}
        {{- end }}
        {{- with .Values.tolerations }}
          tolerations:  #污点信息
            {{- toYaml . | nindent 8 }}
        {{- end }}
##_helpers.tpl文件信息,太多了抽空慢慢注释说明
{{/* vim: set filetype=mustache: */}} ##{{/**/}}注释信息
{{/*
Expand the name of the chart.
*/}}
{{- define "mybase.name" -}}  ##-左边的去除左边的空格及换行,define申请一个mybase.name的资源
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}                   ##trimSuffix修剪字符串中的后缀"-"

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mybase.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mybase.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Common labels
*/}}
{{- define "mybase.labels" -}}
app.kubernetes.io/name: {{ include "mybase.name" . }}
helm.sh/chart: {{ include "mybase.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

说明:if/else何时生效,为真的时候生效

  • 一个布尔类型的
  • 一个数字
  • 一个的字符串
  • 一个nil(空或null
  • 一个空的集合(mapslicetupledictarray

除了上面的这些情况外,其他所有条件都为

##ingress文件信息
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "mybase.fullname" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
{{ include "mybase.labels" . | indent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
{{- if .Values.ingress.tls }}
  tls:
  {{- range .Values.ingress.tls }}
    - hosts:
      {{- range .hosts }}
        - {{ . | quote }}
      {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
{{- end }}
  rules:
  {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          - path: /
            backend:
            serviceName: {{ $fullName }}
            servicePort: http
  {{- end }}
{{- end }}
##registry-pull-secret.yaml拉取镜像认证内容
生成命令:
kubectl create secret docker-registry registry-pull-secret  --docker-username=user --docker-password=password --docker-email=email --docker-server=string --dry-run -o -yaml
##service文件内容信息
apiVersion: v1
kind: Service
metadata:
  name: {{ include "mybase.fullname" . }}
  labels:
{{ include "mybase.labels" . | indent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      protocol: TCP
      targetPort: 3000
  selector:
    app.kubernetes.io/name: {{ include "mybase.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}

3.2.1.Pod自动伸缩策略Horizontal Pod Autoscaler(hpa)

注意:首先必须确保集群中已经安装metrics-server的组件,否则无法获取集群内资源数据,无法进行以下操作。

Horizontal Pod Autoscaling并不适用于不能缩放的对象,例如“DaemonSet”。

原理:

通过集群内的资源监控系统(metrics-server),来获取集群中资源的使用状态。

根据CPU、内存、以及用户自定义的资源指标数据的使用量或连接数为参考依据,来制定一个临界点,一旦超出这个点,HPA就会自动创建出pod副本,若资源剩余过多就会减少相应的pod。

版本:

通过kubectl api-versions可以看到,目前有3个版本:

autoscaling/v1            #只支持通过cpu为参考依据,来改变pod副本数,默认版本
autoscaling/v2beta1       #支持通过cpu、内存、连接数以及用户自定义的资源指标数据为参考依据。
autoscaling/v2beta2       #同上,小的变动

查询:

##可获取资源信息
kubectl explain hpa   ##默认查询到的是autoscaling/v1版本
kubectl explain hpa --api-version=autoscaling/v2beta1   ##如果使用其他版本,可以使用--api-version指明版本
如pod容器的字段拼写忘记了
kubectl explain pods.spec.containers

此配置文件单独抽出作为说明,因为此策略在线上环境中是占很高地位的

##horizontal-pod-autoscaler文件内容信息
可使用命令进行生成配置,之后在进行修改(kubectl对Horizontal Pod Autoscaler的支持)
kubectl autoscale deployment lingxi-api -n lingxi-dev --cpu-percent=50 --min=1 --max=10 --dry-run -o yaml
注意:
  默认的版本是autoscaling/v1它只支持基于CPU自动缩放,不支持内容所以需要改
##############################################################################
apiVersion: autoscaling/v2beta1  ##api版本
kind: HorizontalPodAutoscaler    ##资源对象hpa自动收缩
metadata:                        ##源数据
  creationTimestamp: null        ##时间戳
  name: {{ include "mybase.fullname" . }}  ##自动伸缩的pod名称
spec:                            ##定义规格
  minReplicas: 1                 ##最小的伸缩个数
  maxReplicas: 8                 ##最大的伸缩个数
  scaleTargetRef:                ##定义伸缩目标的pod           
    apiVersion: apps/v1          ##api版本
    kind: Deployment             ##资源对象为Deployment
    name: {{ include "mybase.fullname" . }}  ##控制伸缩的pod名称
  metrics:                       ##获取数据
  - type: Resource               ##数据类型
    resource:                    ##数据类型为cpu
      name: cpu
      targetAverageUtilization: {{ .Values.cpu.targetAverageUtilization }}##注意此时是根据使用率,也可以根据使用量:targetAverageValue
  - type: Resource
    resource:                    ##数据类型为memory
      name: memory
      targetAverageUtilization: {{ .Values.memory.targetAverageUtilization }}
      ##注意此时是根据使用率,也可以根据使用量:targetAverageValue

同样需要变动信息只需要在lingxi-api的目录下进行值的修改就可以

3.3.安装Harbor作为chart的仓库

注意:如果已经有安装好的harbor服务,则无需以下操作。只需要在原服务上开启支持chart。(./install.sh --with-chartmuseum)

添加repo仓库

helm repo add goharbor https://helm.goharbor.io

在安装之前我们需要配置一下 kube config context

查看当前的context

kubectl config current-context

设置 context 指定对应的 namespace ,不指定使用的是 default

kubectl config set-context <current-context> --namespace test

这里是因为,helm 3 开始helm 3 的执行权限和kubectl config 的权限是一致的,通过kubectl config的方式来控制helm 3 的执行权限。

按时安装harbor ,这里为了简化测试操作,我关闭了数据卷的挂载并使用的是 NodePort 方式进行访问。

helm -n test install harbor goharbor/harbor --set persistence.enabled=false --set expose.type=nodePort --set expose.tls.enabled=false --set externalURL=http://172.20.101.157:30002

参数说明:

persistence.enabled=false 关闭存储,为了方便操作,真实使用时需要挂在存储
expose.type=nodePort 使用 NodePort 访问
expose.tls.enabled=false 关闭tls
externalURL=http://172.20.101.157:30002 设置登录 harbor 的外部链接

harbor 装好之后,我们访问 http://172.20.101.157:30002 进行登录 harbor, harbor 的默认账号密码是 admin/Harbor12345

创建一个lingx的项目,这里就不再演示了,创建好之后可进行打包chart及上传

添加 repo 到 helm 中

helm repo add reg http://172.20.101.175:30002/chartrepo/lingxi ##reg为repo名称,自定义

安装使用 helm-push 插件

helm plugin install https://github.com/chartmuseum/helm-push

安装好插件之后就可以进行打包上传

##打包chart
helm package ./lingxi-api  ##./lingxi-api为chart包的路径
##上传chart
helm push lingxi-api-0.1.0.tgz reg ##reg为helm repo的Harbor仓库的名称

4.后期维护人员变动操作

4.1注意啦,注意啦,注意啦,重要信息说三遍

后期维护人员进行升级如需要变更镜像地址,容器副本数量,镜像版本,镜像拉取策略,service类型等操作,只需操作lingxi-api/values.yaml文件

###文件内容如下,注释说明
replicaCount: 4          ##容器副本数量,后期可自行调整数量
image:                   ##镜像定义
  repository: reg.ptone.jp/lingxi/develop/lingxi-api  ##镜像地址,可变更
  tag: latest            ##镜像tag,可变更
  pullPolicy: Always     ##镜像拉取策略,可变更
imagePullSecrets:        ##镜像拉取认证token信息
  - name: registry-pull-secret ##secret名称
nameOverride: ""        ##字符串信息,可自行更改
fullnameOverride: ""
service:                ##service信息
  type: ClusterIP       ##service类型(NodePort,loadBalance)
  port: 3000            ##宿主机服务监听在3000端口
ingress:                ##ingress信息
  enabled: true         ##是否开启,不开启为false
  annotations: {}
  hosts:
    - host: api.lingxi365.cn  ##host名称
      paths: []
  tls: []              ##https类型
resources: {}          ##资源类型
nodeSelector: {}       ##宿主机调度
tolerations: []        ##污点标记 
affinity: {}           ##污点容忍

5.Helm常用操作命令小结

helm repo add --username=xx --password=xx repoName https://reg.ptone.jp/chartrepo/lingxi ##添加repo仓库
helm update                        ##更新repo仓库,添加repo仓库之后需要更新生效
helm repo list                     ##查看repo仓库列表
helm repo del repoName             ##删除一个repo仓库
helm search                        ##搜索仓库中的chart模版
helm search chartName              ##过滤搜索指定的chart模版
helm install ./lingxi-api --name lingxi-api ##./lingxi-api为目录路径,安装名为lingxi-api的helm
helm install --dry-run --debug ./lingxi-api ##调试模式,渲染模版信息
helm ls                            ##查看helm的名称
helm upgrade helmName ./lingxi-api ##更新已存在的helm,名称后面也可以跟上repo的仓库地址如(reg/lingxi-api)
helm status helmName               ##查看以安装的helm的状态信息
helm package ./lingxi-api          ##打包lingxi-api这个chart
helm push [chartName-version.tgz] [repoName] ##上传chart包
helm get manifest ReleaseName      ##查看渲染出来的模版信息
helm inspect values [repoName]     ##查看repoName里面的参数信息,自定义chart的时候可用到
helm history helmName              ##查看历史信息
helm rollback versionId            ##回滚版本
helm install --name lingxi-api --namespace lingxi-dev  reg/lingxi-api #指定命名空间
helm upgrade lingxi-api --namespace lingxi-dev lingxi/lingxi-api --install --set image.tag="$TAG" 
0
如无特殊说明,文章均为本站原创,转载请注明出处

该文章由 发布

这货来去如风,什么鬼都没留下!!!
发表我的评论

Hi,请填写昵称和邮箱!

取消评论
代码 贴图 加粗 链接 删除线 签到