Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

基于Helm的Kubernetes资源管理

4
Jan
2019

基于Helm的Kubernetes资源管理

By Alex
/ in PaaS
/ tags K8S
0 Comments
Helm

Helm是Kubernetes的包管理器,由客户端组件helm和服务端组件Tiller组成。Helm能够将一组K8S资源打包统一管理。

入门

到官网下载Helm后解压,可以得到helm客户端二进制文件。然后执行下面的命令,在集群中安装Tiller:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 自动使用kubectl config current-context这个上下文对应的K8S集群
# 安装或者重新安装Tiller
helm init
 
# 国内访问,指定仓库镜像
helm init --stable-repo-url  http://mirror.azure.cn/kubernetes/charts
# 微软也提供了孵化器仓库的镜像 https://mirror.azure.cn/kubernetes/charts-incubator
 
# 如果向安装到其它K8S集群
helm init --kube-context ctx-name
 
# 如果想升级Tiller
helm init --upgrade
helm init --node-selectors "beta.kubernetes.io/os"="linux"   # 指定在何种Node上运行Tiller
          --service-account tiller                           # 指定使用的SA,需要cluster-admin权限
# 为Helm使用的SA添加权限
kubectl create clusterrolebinding kube-system-tiller-crb --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

Helm会在K8S集群中创建一个Deployment,其Pod使用镜像gcr.io/kubernetes-helm/tiller:v2.8.2

自动完成
.bashrc
Shell
1
2
# 添加下面这行,启用helm的命令行自动完成功能
source <(helm completion bash)
命令示例
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装
helm install stable/mysql --namespace dang
 
# 列出部署的Release
helm ls
# NAME            REVISION        UPDATED                         STATUS          CHART           NAMESPACE
# hazy-eagle      1               Fri Apr 20 00:14:33 2018        DEPLOYED        mysql-0.3.7     jx      
# womping-lamb    1               Fri Apr 20 00:16:33 2018        DEPLOYED        mysql-0.3.7     dang
 
# 删除已经部署的Release
helm delete hazy-eagle
 
# 即使删除后,历史轨迹还是保留的
helm status hazy-eagle
Chart

Helm的打包格式叫做Chart,所谓Chart就是一系列文件,它描述了一组相关的K8S集群资源。Chart中的文件安装特定的目录结构组织。

Chart的压缩包命名为chartname-version,例如nginx-1.2.3.tgz。

Chart.yaml

包含Chart的基本信息:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
name: "Chart的名称,必须"
version: "Chart的版本,必须"
kubeVersion: "兼容的K8S版本范围"
description: "简短描述"
keywords:
  - "此Chart的搜索关键字"
home: "项目的主页"
sources:
  - "项目源代码URL"
# 模板引擎
engine: gotpl
icon: "一个SVG或者PNG的URL"
appVersion: "应用程序版本"
deprecated: "是否被废弃,取值false/true"
tillerVersion: "此Chart需求的Tiller版本"
values.yaml

此文件中包含了必要的值定义(默认值):

YAML
1
2
3
4
imageRegistry: "quay.io/deis"
dockerTag: "latest"
pullPolicy: "Always"
storage: "s3"

如果需要删除某个默认值,你可以为其提供 null值

requirements.yaml

描述Chart的依赖关系:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dependencies:
  - name: apache
    version: 1.2.3
    repository: http://example.com/charts
  - name: mysql
    version: 3.2.1
    repository: http://another.example.com/charts
    # 依赖的别名
    alias: another-name-of-chart
    # 条件性依赖,指定YAML路径,如果这些路径在顶级父Chart上可以解析为布尔值,则
    # 此依赖被启用/禁用。如果指定多个路径,仅仅第一个可以解析的路径生效,并决定此Chart是否被依赖
    condition: subchart1.enabled, global.subchart1.enabled
    # 条件性依赖,根据顶级父Chart的Value来决定,例如tags.front-end=true则此Chart被依赖
    tags:
      - front-end
    # 从子代Chart导入值,子代values.yaml必须包含exports: data: ...
    import-values:
      - data
charts

此目录存放子Chart(Subchart)的定义,每个目录对应一个Subchart。

Subchart也就是当前Chart依赖的Chart,在requirements.yaml中声明。

templates

此目录包括一系列的模板:

  1. _helpers.tpl(或者任何 _开头的文件),通常在此文件中定义一些可重用的模板片断,此文件中的定义在任何资源定义模板中可用
  2. NOTES.txt 说明文档,供人类阅读
  3. 其它文件为K8S资源定义模板,通常不同的K8S资源放在不同文件中,并以资源类型为文件名

Tiller在部署Chart时,会使用你提供的Value来渲染templates目录中的所有第3类文件,并把结果YAML交付K8S,创建各种资源。

资源标签

由Helm管理的资源,应该添加以下标签:

标签 说明
heritage 必须添加此标签,并且值必须为 {{ .Release.Service }},用于确定一个资源是否由Tiller来管理
release 应当设置为 {{ .Release.Name }}
chart 应当设置为 {{ .Chart.Name }}-{{ .Chart.Version}}
模板开发

Helm目标遵从Go Template标准,其特点是双花括号形式的占位符,例如 {{.Values.imageRegistry}}。Go Template相关的知识可以参考我的文章:Go语言中的模板引擎

在目标中你可以使用 .Values前缀来访问值,--set选项或values.yaml中包含的值包含在此对象中。

预定义对象
值 说明
Release.Name 当前Release的名称
Release.Time 当前Release最后被更新的时间
Release.Namespace Chart被release到的名字空间
Release.Service 管理此Release的服务,通常是Tiller
Release.IsUpgrade 当前操作是升级还是回滚
Release.IsInstall 当前操作是否为安装
Release.Revision 当前Release的修订版,从1开始,每次helm upgrade后增加1
Chart 对应Chart.yaml中的数据
Values 经过合并处理后的指定义
Files

用于读取文件的内容,嵌入到资源定义中,示例:

Go
1
2
3
4
// 读取config.ini的内容
.Files.Get config.ini
// 读取为字节数组,而非默认的字符串
Files.GetBytes
Template.Name 当前模板的名称
Template.BasePath 模板所在目录
Capabilities

用于访问K8S集群所支持的特性:

Go
1
2
3
4
5
6
// 判断K8S是否支持指定的API版本,例如batch/v1
Capabilities.APIVersions.Has $version
// K8S主版本
Capabilities.KubeVersion.Major
// K8S次版本
Capabilities.KubeVersion.Minor
模板指令示例

Go
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// 从顶级命名空间(前导的点号)中寻找Release对象的Name属性
// 签到的点号表示“当前上下文对象”
{{ .Release.Name }}
 
 
/** 定义变量 **/
// 注意变量的前导$,赋值操作符:=
{{- $files := .Files }}
 
/** 调用函数 **/
// 语法 func arg1 arg2 ...
{{ quote .Values.favorite.drink }}  // 加引号
{{indent 2 "mug:true"}}             // 缩进
{{ default "tea" .Values.drink}}    // 默认值
{{ upper .Values.drink }}           // 转为大写
 
 
/** 使用管道 **/
// 前面的内容作为后面函数的入参
{{ .Values.favorite.drink | quote }}
// 如果值为空,则默认为tea,最后给值添加引号
{{ .Values.favorite.drink | default "tea" | quote }}
 
 
/** 空白字符处理 **/
{{- /* 删除左侧空白符       删除右侧空白符*/ -}}
// 注意留白
{{- 3 }} // 删除左侧空白,打印3
{{-3}}   // 打印-3
 
 
/** 条件分支 **/
// false、0、''、nil、空集合的值为假
{{ if PIPELINE }}
  // Do something
{{ else if OTHER PIPELINE }}
  // Do something else
{{ else }}
  // Default case
{{ end }}
 
 
/** with 指定作用域对象 **/
{{- with .Values.favorite }}
  // .drink实际上访问.Values.favorite.drink
  drink: {{ .drink | default "tea" | quote }}
{{- end }}
 
 
/** range,执行for-each循环 **/
//        此对象必须是数组
{{- range .Values.pizzaToppings }}
//     这里的点号代表数组元素
  - {{ . | title | quote }}
{{- end }}
// 指定需要遍历的元组,tuple函数用于创建列表
{{- range tuple "A" "B" "C" }}
  {{ . }}
{{- end }}
 
 
/** 模板相关 **/
 
// 定义一个模板,注意模板名称是全局性的,如果出现冲突,最后加载的那个被使用
// 通常模板名以当前chart的名字为前缀
{{- define "mychart.labels" }}
  // 模板体
{{- end }}
 
// 渲染模板
// 将当前对象传递给模板,作为其上下文
{{- template "mychart.labels" . }}
// template是一个Action而非Function,不能后接管道。因此要实现缩进效果,可以用include
// 通常都是使用include,而非template
{{ include "mychart.app" . | indent 4 }}
 
 
/** 读取文件内容 **/
// 访问指定的文件
{{- $files := .Files }}  // 赋值为变量
{{- range tuple "config1.toml" "config2.toml" "config3.toml" }}
{{ . }}: |-
  {{ $files.Get . }}  // 使用变量来访问.Files,因为当前上下文对象是"config*.toml"
{{- end }}
// 访问匹配通配符的文件
{{ range $path := .Files.Glob "**.yaml" }} {{ end }}
// 逐行处理
{{ range .Files.Lines "foo/bar.txt" }}
{{ . }}{{ end }}
// 要处理文件路径,可以使用path包的Base Dir Ext IsAbs Clean函数,但是首字母要改为小写
 
 
/** ConfigMap处理 **/
kind: ConfigMap
data:
{{ (.Files.Glob "foo/*").AsConfig | indent 2 }}
 
 
/** Secret处理 **/
kind: Secret
type: Opaque
data:
{{ (.Files.Glob "bar/*").AsSecrets | indent 2 }}  // 转换为Secret
data:
  token: |-
    {{ .Files.Get "config1.toml" | b64enc }}          // 手工进行Base64编码
Subchart模板

从父Chart的values.yaml中,可以定义传递给Subchart的值:

YAML
1
2
3
# 必须以Subchart名(对应charts子目录名)为根
subchartname:
  key: value

如果希望在任何级别的Subchart中,使用和父Chart一致的值键,可以使用全局值(Global Values):

YAML
1
2
3
# 在此定义全局值
global:
  key: name

在任何一个子Chart中,都可以通过 {{ .Values.global.key }}引用全局值。

父子Chart可以共享模板定义, 任何一个Chart中定义的 {{- define }}都可以被其它Chart使用。

Hooks

Helm提供了一种钩子机制,允许Chart开发者干预Release声明周期的某些点,使用Hooks你可以:

  1. 在任何其它Charts加载之前,加载一个ConfigMap或Secret
  2. 在安装新Chart之前,执行一个Job来备份数据库;在安装Chart之后,执行另一个Job来恢复数据库
  3. 在删除Release之前,运行Job进行优雅清理

支持的钩子包括:

钩子 说明
pre-install 模板渲染后,创建任何K8S资源之前
post-install 所有资源加载到K8S之后
crd-install

在执行任何检查之前,添加CRD资源,只能用于CRD的YAML,示例:

Shell
1
2
3
metadata:
  annotations:
    "helm.sh/hook": crd-install

 如果不加此钩子,则安装自定义资源时可能遭遇  no matches for kind "xxx" in version "xx.gmem.cc/v1"]错误

pre-delete 执行Release删除请求时,从K8S中删除任何资源之前
post-delete 执行Release删除请求时,从K8S中删除任何资源之后
pre-upgrade 升级Release前后,类似于install
post-upgrade
pre-rollback 回滚Release前后,类似于install
post-rollback
开发钩子

钩子可以开发为具有特定注解的Job:

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
28
29
30
31
32
33
apiVersion: batch/v1
kind: Job
metadata:
  name: "{{.Release.Name}}"
  labels:
    app.kubernetes.io/managed-by: {{.Release.Service | quote}}
    app.kubernetes.io/instance: {{.Release.Name | quote}}
    helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}"
  annotations:
    # 下面的注解声明此Job是一个钩子,而非正在安装的Release的一部分
    # 一个Job可以运行在多个阶段:"helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook": post-install
    # 权重决定钩子的执行顺序
    "helm.sh/hook-weight": "-5"
    # Hook资源(此Job)的删除策略
    #   hook-succeeded 执行成功后删除
    #   hook-failed  如果钩子执行失败,则删除
    #   before-hook-creation  提示Tiller在创建新的钩子之前,删除之前的版本
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    metadata:
      name: "{{.Release.Name}}"
      labels:
      app.kubernetes.io/managed-by: {{.Release.Service | quote}}
      app.kubernetes.io/instance: {{.Release.Name | quote}}
      helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}"
    spec:
      restartPolicy: Never
      containers:
      - name: post-install-job
        image: "alpine:3.3"
        command: ["/bin/sleep","{{default"10".Values.sleepyTime}}"]
Helm2 CLI
子命令 说明
create

创建指定名称的新Chart,会创建一个新的Chart目录,包含通用的文件和子目录:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
helm create app
.
└── app
    ├── charts                # 此Chart依赖的其它charts  
    ├── Chart.yaml            # Chart的信息
    ├── templates             # 模板文件
    │   ├── deployment.yaml
    │   ├── _helpers.tpl
    │   ├── ingress.yaml
    │   ├── NOTES.txt
    │   └── service.yaml
    └── values.yaml           # 模板默认值文件
delete

删除指定的Release

格式: helm delete [flags] RELEASE_NAME [...]

选项:

--dry-run 模拟运行
--purge 从Store中移除Release并让其名称可被使用

dependency 管理Chart的依赖
dependency build 构建出charts目录
dependency list 列出依赖
dependency update 根据requirements.yaml来更新charts
fetch

从仓库下载Chart,可选的,解压到本地目录

格式: helm fetch [flags] [chart URL | repo/chartname] [...]

选项:

--username string 仓库用户名
--password string 仓库密码
--repo string 定位Chart的仓库URL
--untar 执行解压
--untardir string 解压目标目录
--version string 指定Chart的版本

get

下载指定的Release

格式:helm get [flags] RELEASE_NAME

get hooks 下载指定Release的Hooks,Hooks为YAML格式,使用 --- 分隔
get manifest 下载指定Release的Manifest,也就是Helm提交给K8S的资源定义
get values 下载指定Release的Value文件
history 显示指定Release的Revisions列表
inspect 查看Chart的信息
inspect chart 查看Chart的Charts.yaml信息
inspect values 查看Chart的values.yaml信息
install

安装Chart归档,可以从Chart引用、打包Chart路径、解压Chart目录安装

Chart引用(Reference)是引用仓库中Chart的便捷方式。例如stable/mariadb表示stable仓库中的mariadb这个Chart

选项:

--name string 安装的Release的名字,如果不指定则自动生成
--namespace 安装到的名字空间
--name-template string 自动命名的模板文件
--no-hooks 安装期间阻止Hooks
--username string 仓库用户名
--password string 仓库密码
--replace 重用名称,即使已经被使用,生产环境下不安全
--set stringArray 设置变量值,格式示例:

Shell
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
--set name=alex,age=31
# 等价于:
name: alex
age: 31
 
--set user.name=alex,user.age=31
# 等价于:
user:
  name: alex
  age: 31
 
--set users={alex,meng}
# 等价于:
users:
  - alex
  - meng
 
# 从Helm 2.5开始,支持访问数组的任何索引
--set users[1].name=meng
 
# 包含空格的值,引号包围即可
-set standalone.javaMainArgs="queryHotRecommendBarJob 1"
# 特殊字符(, .)使用反斜杠转义,防止二义性
--set users=alex\,meng
# 等价于
users: alex,meng
 
# 不进行转义貌似也没事
--set runtime.javaMainClass=com.dangdang.digital.job.importtask.MediaInfoToRedisJob

--timeout int 单个K8S操作的超时时间,默认300s
-f  --values 变量文件,多个文件逗号分隔不得有空格,后面覆盖前面的
--version string 安装指定版本的Chart,不指定安装最新版本
--wait 等待直到所有Pod、PVC、Service、Deployment的最小数量Pod进入Ready状态,然后才标记Release为Successful。最长等待--timeout秒

示例:

Shell
1
2
3
4
5
6
7
8
9
# 安装redis,使用myvalues.yaml覆盖默认值
helm install -f myvalues.yaml ./redis
 
# 安装redis,使用set选项指定变量值
helm install --set name=prod ./redis
helm install --set-string long_int=1234567890 ./redis
 
# 从本地仓库安装,本地HTTP服务必须启动
helm install local/gitbucket --name gitbucket
list

列出所有Release

选项:

-a, --all 列出所有Release,不仅仅是DEPLOYED
-d 安装日期排序

package

将Chart目录打包为归档文件

格式: helm package [flags] [CHART_PATH] [...]

示例:

Shell
1
2
3
# CHART_PATH必须指向Chart.yaml所在目录
# --save 表示保存到本地仓库(~/.helm/repository/local)
helm package --save gitbucket-docker/charts/gitbucket/
repo

列出、添加、移除、升级、索引指定的Chart仓库

示例:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 删除名为local的远程仓库
helm repo remove local
 
# 列出当前使用的索引远程仓库
helm repo list
 
# 添加一个远程仓库
helm repo add gmem https://chartmuseum.gmem.cc
 
# 对本地仓库进行索引
cd ~/.helm/repository/local
helm repo index .
 
# 更新某个远程索引
helm repo update gmem
reset 从集群中卸载Tiller
rollback

将Release回滚到先前的修订版

格式: helm rollback [flags] [RELEASE] [REVISION]

选项:

--force 如果需要,通过delete/recreate来强制更新资源。默认情况下helm使用patch方式来修改资源,但是某些资源的字段是不可变的,只能删除并重新创建整个资源
--recreate-pods 为资源重新创建对应的Pod,也就是删除Pod致使其重新创建

search

从索引中搜索,注意,远程仓库的索引需要预先更新

格式: helm search [keyword] [flags]

选项:

-l, --versions 搜索所有版本
-v, --version string 搜索指定版本
-r, --regexp 使用正则式搜索

示例:

Shell
1
2
3
4
# 在仓库中搜索gitbucket
helm search gitbucket
# 列出仓库gmem的所有内容
helm search gmem/
serve 启动本地HTTP服务器作为Chart仓库,默认端口127.0.0.1:8879
status

显示指定Release的状态

格式: helm status [flags] RELEASE_NAME

template

在本地填充模板,不支持远程Chart仓库

选项:

-x 仅仅对指定的模板进行填充
-f 从文件中读取值,多个文件逗号分隔不得有空格,后面覆盖前面的
--set 从命令行读取值
--namespace 部署到的名字空间
--name Release名称
--output-dir 将填充后的模板输出到文件,而非stdout

示例:

Shell
1
2
helm template mychart -x templates/deployment.yaml
helm template media-job --name=media-job --set runtime.javaMainClass=cc\.gmem\.ClsName
upgrade

升级一个Release到新版本Chart。你需要指定RELEASE和CHART参数。CHART参数可以是引用(reponame/chartname形式)或者是指向Chart所在位置的路径/URL

关于滚动更新:

--recreate-pods,则无视deployment的更新策略设置,立即删除所有Pod,会导致downtime

仅仅当Deployment的Pod Spec发生变化,才可能触发滚动更新

格式: helm upgrade [RELEASE] [CHART] [flags]

选项:

--dry-run 模拟运行
--force 如果需要,通过delete再create来强制K8S资源的更新
-i, --install 如果RELEASE不存在,则创建之
--namespace 安装到的命名空间
--no-hooks 禁用pre/post upgrade钩子
--recreate-pods 如果可以,为K8S资源重新创建对应的Pod
--repo string 用于定位Chart的仓库URL
--reset-values 升级时,重置Helm Value为构建Chart时values.yaml中的默认值
--reuse-values 升级时,将上一个修订版的value作为默认值
--set 指定Helm Value
--timeout 等待单个K8S操作完成的时间,默认300s
-f, --values 指定Helm Value文件,多个文件逗号分隔不得有空格,后面覆盖前面的
--version 指定升级到的版本,如果不指定,默认使用最新的版本
--wait 等待K8S中所有资源就绪
--debug 输出更多的诊断信息
--home 指定Helm Home目录,默认~/.helm
--host 指定Tiller的地址,覆盖环境变量$HELM_HOST
--kube-context 指定使用的Kubeconfig上下文

示例:

Shell
1
2
3
# 安装或者升级media-info-to-redis-job到1.0.0版本
helm upgrade media-info-to-redis-job  gmem/media-job --namespace=dev  --version=1.0.0 \
    --force --install --reuse-values --set runtime.javaMainClass=MediaInfoToRedisJob
verify

校验指定的Chart已经签名且有效

格式: helm verify [flags] PATH

Helm3
Helm变更

Helm 3和Helm 2的主要区别:

  1. 移除了服务器端组件Tiller:由于RBAC从K8S 1.6开始默认启用,Tiller的功能已经没有必要。Helm 2中Release信息从Tiller读取,现在则直接从K8S API Server读取。Chart的渲染功能下沉到客户端进行
  2. 三方合并:在Helm 2中,升级、回滚时仅仅会对比新的资源清单、旧的资源清单,根据差异来决定需要增删改哪些资源。在Helm3中,资源的当前状态也被考虑。举例来说:
    1. 用户通过 kubectl scale -replicas=0 将副本置零,资源当前状态变化了。但是helm rollback可能没有任何效果(如果没有upgrade历史)
    2. 你安装了应用,其状态如下:
      YAML
      1
      2
      3
      containers:
      - name: server
        image: my_app:2.0.0

      然后,你安装了Istio,它会修改资源,添加Sidecar:

      YAML
      1
      2
      3
      4
      5
      containers:
      - name: server
        image: my_app:2.0.0
      - name: istio-sidecar
        image: istio-sidecar-proxy:1.0.0

      这时你通过Helm升级应用到2.1.0,资源变为:

      YAML
      1
      2
      3
      containers:
      - name: server
        image: my_app:2.1.0

      Istio的Sidecar消失了……在Helm3中不会出现这种问题,因为它会对旧资源清单、新资源清单、当前状态进行三方合并

  3. 将Secret作为默认存储驱动(Storage Driver):Helm 2 以ConfigMap作为默认存储驱动,Helm 3则以helm.sh/release类型的Secret作为存储驱动
  4. 默认情况下,必须提供Release名称,除非指定--generate-name 
  5. helm serve功能被移除,它能提供一个本地运行的Chart仓库
  6. 不再自动创建namespace
  7. helm upgrade 可以指定--history-max,默认10,历史记录最大数量
  8. helm init、helm home、helm reset、helm serve废除
  9. helm delete 重命名为 helm uninstall;helm fetch 重命名为  helm pull;helm inspect 重命名为 helm show
  10. helm template <chart> -x <path_to_template> 改为 helm template <release_name> <chart> -s <path_to_template>
  11. XDG风格的客户端数据布局。Helm2将数据存储在$HELM_HOME(默认$HOME/.helm)。Helm3保持了对Helm2的兼容性,此外还支持使用如下布局:
    1. $HELM_PATH_CACHE,缓存位置,例如 ${HOME}/.cache/helm/
    2. $HELM_PATH_CONFIG,配置位置,例如 ${HOME}/.config/helm/。仓库定义存放在/home/alex/.config/helm/repositories.yaml
    3. $HELM_PATH_DATA,数据位置,例如 ${HOME}/.local/share/helm
  12. 对CRD的支持更好,Helm 2的crd-install钩子工作的不好,在Helm 3被移除。在Helm 3中CRD应当放在Chart的crds目录下,在安装Release时该目录下的CRD会自动安装,升级、删除的时候则不做操作
三方合并

升级一个Release时,尚不存在的资源会创建,旧配置存在而新配置删除的资源则删除。逻辑复杂的时被修改的资源,Helm需要基于一定的策略,生成一个Patch。

Helm 2的策略比较简单,它只会比较新、旧配置(填充后的模板),其差异就是Patch。

Helm 3的策略较为复杂,Patch通过三方合并获得。三方合并逻辑由k8s.io/apimachinary提供,定义在/pkg/util/strategicpatch/patch.go中:

Go
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
49
50
51
52
53
54
55
56
57
58
59
// 三方合并会调和(Reconcile)新配置、旧配置,同时保留当前对旧配置的变更、删除
// 也就是说,三方合并保证K8S或人工修改的配置信息不会丢失
//                            旧        新        当前(Etcd中的实时状态)
func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
    // 将旧配置、新配置、当前(可能被手工修改过的)配置转换为映射
    originalMap := map[string]interface{}{}
    if len(original) > 0 {
        if err := json.Unmarshal(original, &originalMap); err != nil {
            return nil, mergepatch.ErrBadJSONDoc
        }
    }
 
    modifiedMap := map[string]interface{}{}
    if len(modified) > 0 {
        if err := json.Unmarshal(modified, &modifiedMap); err != nil {
            return nil, mergepatch.ErrBadJSONDoc
        }
    }
 
    currentMap := map[string]interface{}{}
    if len(current) > 0 {
        if err := json.Unmarshal(current, &currentMap); err != nil {
            return nil, mergepatch.ErrBadJSONDoc
        }
    }
 
    // 得到新配置新增、修改字段,相当于当前配置引发的增量,行为:
    // 1. 新配置有的,当前配置没有的字段。这个应该不存在,K8S会把缺失的字段添上,因此当前配置字段最全
    // 2. 新配置、当前配置都有的字段,且取值不一样,增量为新配置提供的值
    // 3. 当前配置有,新配置没有的字段,不体现为增量(IgnoreDeletions: true)
    deltaMapDiffOptions := DiffOptions{
        IgnoreDeletions: true,
        SetElementOrder: true,
    }
    deltaMap, err := diffMaps(currentMap, modifiedMap, schema, deltaMapDiffOptions)
    if err != nil {
        return nil, err
    }
 
    // 得到新配置删除字段,相对于旧配置引发的增量
    // 使用$patch: delete这样的键值标记删除操作
    deletionsMapDiffOptions := DiffOptions{
        SetElementOrder:           true,
        IgnoreChangesAndAdditions: true,
    }
    // 合并两个Patch
    deletionsMap, err := diffMaps(originalMap, modifiedMap, schema, deletionsMapDiffOptions)
    if err != nil {
        return nil, err
    }
 
    mergeOptions := MergeOptions{}
    patchMap, err := mergeMap(deletionsMap, deltaMap, schema, mergeOptions)
    if err != nil {
        return nil, err
    }
 
    return json.Marshal(patchMap)
}

以Nginx为例, 修改副本数、禁用持久化支持后。

其Deployment会生成如下Patch:

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
---
spec:
  # 修改
  replicas: 3
  template:
    spec:
      # 排序
      "$setElementOrder/containers":
      - name: nginx
      "$setElementOrder/volumes":
      - name: lt-config
      - name: tz-config
      containers:
      - "$setElementOrder/volumeMounts":
        - mountPath: "/etc/localtime"
        - mountPath: "/etc/timezone"
        name: nginx
        volumeMounts:
        # 删除
        - "$patch": delete
          mountPath: "/usr/share/nginx/html"
      volumes:
      # 删除
      - "$patch": delete
        name: wwwdata-volume

其Service则会生成如下Patch:

YAML
1
2
spec:
  clusterIP: ''

此Patch尝试修改不可变字段clusterIP,则是无法成功的,将收到错误:spec.clusterIP: Invalid value: "": field is immutable。 

这种尝试设置不可变字段为空值的Chart,在Helm 2中可能正常工作,因为Helm 2仅仅比对新旧配置。只要你没有后续设置clusterIP为非空就不会出问题。但是到了Helm 3中则肯定会出现问题,因为三方合并的关系,Helm 3认为需要将clusterIP从一个IP地址设置为空。

强制更新的行为

 指定--force标记时,可以在升级时,进行必要的强制更新:

  1. 对于Helm 2,当PATCH操作失败时,会删除、再重建目标资源
  2. 对于Helm 3,会用PUT操作来替换(replace/overwrite)目标资源

如果三方合并出现问题,有可能通过强制更新解决。对于Helm 3来说,更多情况下无济于事,主要是K8S限制某些字段一旦创建即不可变更。

在Helm 3中,即使强制更新,你也可能遇到类似下面的错误:

  1. ersistentVolumeClaim "ng" is invalid: spec: Forbidden: is immutable after creation except resources.requests for bound claims
  2. failed to replace object: Service "ng" is invalid: spec.clusterIP: Invalid value: "": field is immutable

PUT操作解决不了不可变字段的问题,然而Helm 2删除后再创建,则规避了不可变字段问题,但会引发其它问题:

  1. PVC删除,PV级联删除么?数据怎么办
  2. Service删除,会导致暂时的服务不可用么?
Chart变更
  1. Chart的apiVersion从v1变更为v2。Helm 3仍然支持v1格式的Chart,但是Helm 2则不支持v2格式的Chart
  2. 模板变量校验:支持基于JSON Schema对模板变量值进行校验,校验规则存放在values.schema.json中
  3. 依赖声明方式变更:Helm 2中在requirements.yaml文件中声明依赖。现在直接放到Chart.yaml中
  4. 工具库Chart(Library charts):通用、助手类Chart用于提供所有Chart都会反复用到的助手函数、模板片段。现在Chart被分为application、library两种类型,前者产生正常的应用程序,后者仅仅用作application的依赖,管理方式和其它依赖一样,但是不会被安装
  5. 支持中心化的Chart仓库Helm Hub
Helm3 Go库

Helm 3的编程接口更加简单,下面是一个例子:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import (
        "log"
 
        "helm.sh/helm/v3/pkg/action"
        "helm.sh/helm/v3/pkg/cli"
)
 
func main() {
        settings = cli.New()
        // 配置信息
        actionConfig := new(action.Configuration)
        err := actionConfig.Init(settings.RESTClientGetter(), namespace, os.Getenv("HELM_DRIVER"), log.Printf)
        if err != nil {
               log.Fatalf("Failed to get Helm action config: %v", err)
        }
        // 获取Get客户端
        client := action.NewGet(actionConfig)
        // Get一个Release
        rel, err := client.Run("mysql")
        if err != nil {
               log.Fatalf("Failed to get run command: %v", err)
        }
        log.Printf("%+v", rel)
}

Helm 3 使用Go Modules进行依赖管理, 导入路径从k8s.io/helm改为helm.sh/helm。

OCI包分发

Helm 3支持基于OCI的包分发,你可以将Chart存储在基于OCI的Registry(例如registry镜像)中。目前这个支持尚属试验特性,需要显式开启:

Shell
1
export HELM_EXPERIMENTAL_OCI=1
登陆

使用如下命令:

Shell
1
helm registry login -u alex docker.gmem.cc

要登出,可以使用: helm registry logout

保存Chart

下面的命令将目录中的Chart保存到本地缓存:

Shell
1
helm chart save mychart/ docker.gmem.cc/myrepo/mychart:1.0.0
删除Chart

从本地缓存删除Chart:

Shell
1
helm chart remove localhost:5000/myrepo/mychart:1.0.0
列出Chart

下面的命令列出所有存储在OCI注册表中的Chart:

Shell
1
helm chart list
导出Chart

从OCI注册表下载Chart到目录:

Shell
1
helm chart export docker.gmem.cc/myrepo/mychart:1.0.0
推送Chart 

下面的命令推送本地缓存中的Chart到OCI仓库:

Shell
1
helm chart push docker.gmem.cc/myrepo/mychart:1.0.0
拉取Chart

下面的命令将OCI仓库中的Chart下载到本地缓存:

Shell
1
helm chart pull localhost:5000/myrepo/mychart:1.0.0
从Helm2仓库迁移 

要从Helm2基于index.yaml的仓库迁移到Helm3,可以使用如下命令序列:

Shell
1
2
3
helm fetch
helm chart save
helm chart push
Helm3 CLI

所有命令都可以通过 -n指定命名空间,主要列出和Helm2差异的地方:

子命令 说明
uninstall

helm uninstall RELEASE_NAME [...] [flags]

Helm3删除默认不保留历史,没有 --purge标记。如果要保留历史,需要显式指定 --keep-history

install helm install [NAME] [CHART] [flags]
upgrade helm upgrade [RELEASE] [CHART] [flags]
rollback helm rollback <RELEASE> [REVISION] [flags]
list

helm list [flags]

和Helm2不同,Helm3默认仅仅列出default命名空间中安装的release,要查看所有命名空间的release,使用标记 --all-namespaces。

此外,某些状态下的Release默认不显示,使用标记 -a, --all可以全部显示

ChartMuseum

这是一个开源的Helm Chart仓库,支持多种云存储作为后端,也可以使用本地存储。

API

CM提供很简单的RESTful API,你可以通过helm命令调用这些API:

API 说明
GET /index.yaml

添加仓库时Helm访问此API:

helm repo add gmem https://chartmuseum.gmem.cc

GET /charts/mychart-0.1.0.tgz

安装一个Chart到K8S时,Helm访问此API:

helm install chartmuseum/mychart

GET /charts/mychart-0.1.0.tgz.prov 使用--verify选项安装一个Chart到K8S时,Helm访问此API
POST /api/charts 上传一个Chart版本
POST /api/prov 上传一个Provenance文件
DELETE /api/charts/mychart/0.1.0 删除一个Chart版本
GET /api/charts/mychart/0.1.0 描述一个Chart版本的详细信息
GET /api/charts 列出所有Chart
GET /api/charts/mychart 列出Chart的所有版本
GET /health 服务器健康状态探针
安装
Docker
Dockerfile
Shell
1
2
3
4
5
6
7
8
9
10
11
12
FROM chartmuseum/chartmuseum:latest
 
ADD /etc /etc
 
ENV PORT 443
ENV STORAGE local
ENV STORAGE_LOCAL_ROOTDIR /chartstorage
ENV BASE_AUTH_USER admin
ENV BASE_AUTH_PASS admin
ENV AUTH_ANONYMOUS_GET true
ENV TLS_CERT /etc/domain.crt
ENV TLS_KEY  /etc/domain.key

Shell
1
2
3
4
#!/bin/bash
docker stop chartmuseum && docker rm chartmuseum
docker run --name chartmuseum -h chartmuseum --network local --ip 172.21.0.10 --dns 172.21.0.1  \
           --restart=always  -d docker.gmem.cc/chartmuseum
Monocular

Monocular是管理K8S应用程序(仅限于Helm Chart打包格式)的Web GUI工具。

安装
获取源码
Shell
1
2
3
4
5
helm repo add monocular https://kubernetes-helm.github.io/monocular
helm install monocular/monocular --name monocular --namespace kube-system
helm fetch monocular/monocular --untar
helm repo remove monocular
cd monocular
定义覆盖值

我直接修改了Chart源码,推荐使用helm -f values-overrides.yaml进行覆盖:

values-overrides.yaml
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
api:
  replicaCount: 1
  image:
    # 镜像拉取策略全部改为:
    pullPolicy: IfNotPresent
  config:
    repos:
      # 私有仓库
      - name: gmem
        url: https://chartmuseum.gmem.cc
        source: https://github.com/gmemcc/charts
# 测试环境,减少副本
ui:
  replicaCount: 1
prerender:
  replicaCount: 1
# 基于HTTPS的入口路由规则
ingress:
  enabled: true
  hosts:
  - monocular.k8s.gmem.cc
  annotations:
    # 官网配置的 ingress.kubernetes.io/rewrite-target: / 在我的环境下无效
    nginx.ingress.kubernetes.io/rewrite-target: /
  tls:
    secretName: gmemk8scert
重新打包
Shell
1
2
3
4
helm package --save monocular
curl -X DELETE https://chartmuseum.gmem.cc/api/charts/monocular/0.5.0
curl --data-binary "@monocular-0.5.0.tgz" https://chartmuseum.gmem.cc/api/charts
helm search gmem/
部署到K8S
Shell
1
2
3
helm delete --purge monocular
helm repo update gmem
helm install gmem/monocular --name monocular  --namespace=kube-system
Kubeapps

Kubeapps是一个用于部署、管理K8S应用程序的Web前端。特性包括:

  1. 浏览、部署Helm Chart
  2. 查看、升级、删除基于Helm的应用陈故乡
  3. 添加自定义的、私有的Chart仓库。支持Chartmuseum、JFrog Artifactory两类仓库
  4. 浏览并且Provision外部服务(基于K8S的Service Catalog机制)
  5. 利用Service Catalog Binding将Helm应用连接到外部服务
  6. 基于RBAC的身份验证和访问控制
对比Monocular

Monocular项目的设计目标是,开发一个Helm Chart仓库的搜索、发现网站 —— 类似于Docker Hub那样。0.x版本的Monocular支持简单的应用发布,从1.0开始这些功能被移除了。

安装
Shell
1
2
3
4
kubectl delete crd apprepositories.kubeapps.com
kubectl -n devops delete secrets kubeapps-mongodb
# gmem仓库地址:https://chartmuseum.gmem.cc/public
helm install --name kubeapps --namespace devops gmem/kubeapps -f kubeapps/overrides/development.yaml
组件列表
dashboard

提供WebUI,可以方便的浏览Chart,部署Release。还提供和Kubernetes Service Catalog的集成。

访问仪表盘时需要提供Kubernetes API Token:

Shell
1
2
kubectl -n kube-system get secret $(kubectl -n kube-system  get serviceaccount admin \
    -o jsonpath='{.secrets[].name}') -o jsonpath='{.data.token}' | base64 --decode 
Tiller proxy

保证和Tiller之间的通信的安全性,Dashboard通过此代理连接到Tiller。

Apprepository

这是一个CRD + 它的控制器。 Apprepository表示被Kubeapps管理的Chart仓库。

chart-repo

一个负责扫描Chart仓库、抓取Chart元数据并且存储到MongoDB中的工具。作为Monocular项目的一部分来维护。

chartsvc

读取上述MongoDB数据库,对外提供Chart元数据查询服务。

常见问题
零散问题
Error: failed to download

尝试helm install时出现此错误,可能原因是,本地仓库的索引太旧了。

Error: jobs.batch already exists

删除Release时报此错误,或者卡死,可以使用--no-hooks:

Shell
1
helm delete yellow-vulture --purge --no-hooks 

注意:已经存在的K8S资源需要手工清理。

UPGRADE FAILED: cannot re-use a name that is still in use

去掉参数:--reuse-values即可。可能是Bug。

模板填充后数字输出为科学计数法

要禁用,可以添加Tag:

Shell
1
journal max write bytes: !!string 1073714824

使用命令行传递值时,可以用 --set-string代替 --set,也可以规避此问题。 

unable to do port forwarding socat not found

需要在运行Tiller的节点上安装socat:

Shell
1
yum install -y socat
Jenkins相关
命令行传递Values带双引号导致的失败

相同的命令直接在Agent的终端上执行,没有任何问题,但是在Jenkins流水线中执行,就会报错:

Error: UPGRADE FAILED: cannot re-use a name that is still in use

Error: YAML parse error on  error converting YAML to JSON: yaml: line 39: did not find expected key

最终没有找到原因,但是把Value中的双引号去掉就可以了:

Shell
1
2
3
/helm upgrade kube-system-health-controller health-controller --namespace=kube-system \
                                                                            # 去掉双引号
    --install --force --wait --timeout=1800 --set replicas=2,image.registry="registry.k8s.eb.mid/ao",args.cniCheckPeriod=15,args.dnsCheckPeriod=15,args.dnsCheckDomains="kubernetes.default\,e.session.api\,e.login.api",args.nodeSshPswd=dell1950,args.verboseLevel=1

 

← Kubernetes集群部署记录
通过自定义资源扩展Kubernetes →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Kata Containers学习笔记
  • Kustomize学习笔记
  • 基于Rook的Kubernetes存储方案
  • Kubernetes学习笔记
  • 限制Pod磁盘空间用量

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2