这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
看到这里您可能会有疑问:从用户视角来看,client-go已经提供了API,可以远程访问api-server获取完整对象,那不就够用了吗?这里用Indexer获取本地对象有什么意义呢?
从api-server视角来看还有另一层含义:走本地缓存后,来api-server查询的次数就少了,这也就降低了api-server的负载
接下来就通过编码实战的方式,和大家一起了解Indexer的基本用法,另外为了印证本地缓存的性能优势,还会写一段远程访问api-server查询对象的代码,最后用性能工具同时对比两者,看看性能差距是否存在
经过上述操作,相信咱们可以快速了解Indexer的实际用法,还能通过数据对其有更具体的认识,有了这些基础,在后面深入学习Indexer源码的时候,似乎可以轻松很多,每当您看到一段源码,对其设计原因和实际作用都有更多的认识,嗯,这也是欣宸一直推崇的学习方法:实战,不停的实战,拒绝凭空读代码
kubectl get pods -n indexer-tutorials -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysql-556b999fd8-22hqh 1/1 Running 0 8m4s 100.91.64.49 hedy <none> <none>
nginx-deployment-696cc4bc86-2rqcg 1/1 Running 0 8m1s 100.91.64.50 hedy <none> <none>
nginx-deployment-696cc4bc86-bkplx 1/1 Running 0 8m2s 100.91.64.46 hedy <none> <none>
nginx-deployment-696cc4bc86-m7wwh 1/1 Running 0 8m1s 100.91.64.47 hedy <none> <none>
tomcat-deployment-nautilus-7fcb47fcc4-vvdq6 1/1 Running 0 8m3s 100.91.64.48 hedy <none> <none>
pod | 语言类型(language) | 服务类型(business-service-type) |
---|---|---|
nginx | c | web |
tomcat | c | web |
mysql | java | storage |
type Indexer interface {
// 存储相关的,不在本章讨论
Store
// indexName表示分类方式,obj表示用来查询的对象,
// 例如indexName等于BY_LANGUAGE,obj等于nginx的pod对象,
// 那么Index方法就会根据BY_LANGUAGE去获取pod对象的语言类型,即c语言,再返回所有c语言类型的对象
// 简而言之就是:查找和obj同一个语言类型的所有对象
Index(indexName string, obj interface{}) ([]interface{}, error)
// indexName表示分类方式,indexedValue表示分类的值,
// 例如indexName等于BY_LANGUAGE,indexedValue等于c,
// 那么IndexKeys方法就会返回所有语言类型等于c的对象的key
IndexKeys(indexName, indexedValue string) ([]string, error)
// indexName表示分类方式,
// 例如indexName等于BY_LANGUAGE,
// ListIndexFuncValues返回的就是java和c
ListIndexFuncValues(indexName string) []string
// indexName表示分类方式,indexedValue表示分类的值,
// 例如indexName等于BY_LANGUAGE,indexedValue等于c,
// 那么ByIndex方法就会返回所有语言类型等于c的对象
ByIndex(indexName, indexedValue string) ([]interface{}, error)
// Indexers是个map,key是分类方式,
// 本文中key有两个,分别是BY_LANGUAGE和BY_SERVICE,
// value则是个方法,
// key等于BY_LANGUAGE的时候,该方法的入参是个对象pod,返回值是这个pod的语言,
// key等于BY_SERVICE的时候,该方法的入参是个对象pod,返回值是这个pod的服务类型,
GetIndexers() Indexers
// 添加Indexers
AddIndexers(newIndexers Indexers) error
}
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | [email protected]:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
go mod init client-go-indexer-tutorials
go get -u github.com/gin-gonic/gin
package main
import (
"client-go-indexer-tutorials/basic"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// kubernetes相关的初始化操作
basic.DoInit()
// 用于提供基本功能的路由组
basicGroup := r.Group("/basic")
// a. 查询指定语言的所有对象的key(演示2. IndexKeys方法)
basicGroup.GET("get_obj_keys_by_language_name", basic.GetObjKeysByLanguageName)
// b. 返回对象的key,返回对应的对象(演示Store.GetByKey方法)
basicGroup.GET("get_obj_by_obj_key", basic.GetObjByObjKey)
// c. 查询指定语言的所有对象(演示4. ByIndex方法)
basicGroup.GET("get_obj_by_language_name", basic.GetObjByLanguageName)
// d. 根据某个对象的key,获取同语言类型的所有对象(演示1. Index方法)
basicGroup.GET("get_all_obj_by_one_name", basic.GetAllObjByOneName)
// e. 返回所有语言类型(演示3. ListIndexFuncValues方法)
basicGroup.GET("get_all_languange", basic.GetAllLanguange)
// f. 返回所有分类方式,这里应该是按服务类型和按语言类型两种(演示5. GetIndexers方法)
basicGroup.GET("get_all_class_type", basic.GetAllClassType)
r.Run(":18080")
}
package basic
import (
"errors"
"flag"
"fmt"
"log"
"path/filepath"
"sync"
"github.com/gin-gonic/gin"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
const (
NAMESPACE = "indexer-tutorials"
PARAM_LANGUAGE = "language"
PARAM_OBJ_KEY = "obj_key"
LANGUAGE_C = "c"
INDEXER_LANGUAGE = "indexer_language"
INDEXER_BUSINESS_SERVICE_TYPE = "indexer_business_service_type"
LABEL_LANGUAGE = "language"
LABEL_BUSINESS_SERVICE_TYPE = "business-service-type"
)
var ClientSet *kubernetes.Clientset
var once sync.Once
var INDEXER cache.Indexer
// DoInit Indexer相关的初始化操作,这里确保只执行一次
func DoInit() {
once.Do(initIndexer)
}
// initIndexer 这里是真正的初始化逻辑
func initIndexer() {
log.Println("开始初始化Indexer")
var kubeconfig *string
// 试图取到当前账号的家目录
if home := homedir.HomeDir(); home != "" {
// 如果能取到,就把家目录下的.kube/config作为默认配置文件
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
// 如果取不到,就没有默认配置文件,必须通过kubeconfig参数来指定
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
// 加载配置文件
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err.Error())
}
// 用clientset类来执行后续的查询操作
ClientSet, err = kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
log.Println("kubernetes配置文件加载成功")
// 确定从apiserver订阅的类型
podListWatcher := cache.NewListWatchFromClient(ClientSet.CoreV1().RESTClient(), "pods", NAMESPACE, fields.Everything())
// Indexers对象的类型是map,key是自定义字符串,value是个function,用于根据业务逻辑返回一个对象的字符串
indexers := cache.Indexers{
INDEXER_LANGUAGE: func(obj interface{}) ([]string, error) {
var object metav1.Object
object, err = meta.Accessor(obj)
if err != nil {
return []string{}, nil
}
labelValue := object.GetLabels()[LABEL_LANGUAGE]
if labelValue == "" {
return []string{}, nil
}
return []string{labelValue}, nil
},
INDEXER_BUSINESS_SERVICE_TYPE: func(obj interface{}) ([]string, error) {
var object metav1.Object
object, err = meta.Accessor(obj)
if err != nil {
return []string{}, nil
}
labelValue := object.GetLabels()[LABEL_BUSINESS_SERVICE_TYPE]
if labelValue == "" {
return []string{}, nil
}
return []string{labelValue}, nil
},
}
var informer cache.Controller
INDEXER, informer = cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{}, indexers)
log.Println("Indexer初始化成功")
stopCh := make(chan struct{})
// informer的Run方法执行后,就开始接受apiserver推送的资源变更事件,并更新本地存储
go informer.Run(stopCh)
// 等待本地存储和apiserver完成同步
if !cache.WaitForCacheSync(stopCh, informer.HasSynced) {
err = errors.New("timed out waiting for caches to sync")
runtime.HandleError(err)
return
}
log.Println("pod加载完成")
}
// language 辅助方法,从请求参数中获取语言类型,默认返回c
func language(c *gin.Context) string {
return c.DefaultQuery(PARAM_LANGUAGE, LANGUAGE_C)
}
// objKey 辅助方法,从请求参数中获取对象key
func objKey(c *gin.Context) string {
return c.DefaultQuery(PARAM_OBJ_KEY, "")
}
// getObjKeysByLanguageName a. 查询指定语言的所有对象的key(演示2. IndexKeys方法)
func GetObjKeysByLanguageName(c *gin.Context) {
language := language(c)
v, err := INDEXER.IndexKeys(INDEXER_LANGUAGE, language)
if err != nil {
c.String(500, fmt.Sprintf("a. get pod failed, %v", err))
} else if nil == v || len(v) < 1 {
c.String(500, fmt.Sprintf("a. get empty pod, %v", err))
} else {
m := make(map[string][]string)
m["language"] = v
c.JSON(200, m)
}
}
// GetObjByObjKey b. 根据对象的key返回(演示Store.Get方法)
func GetObjByObjKey(c *gin.Context) {
rawObj, exists, err := INDEXER.GetByKey(objKey(c))
if err != nil {
c.String(500, fmt.Sprintf("b. get pod failed, %v", err))
} else if !exists {
c.String(500, fmt.Sprintf("b. get empty pod, %v", err))
} else {
if v, ok := rawObj.(*v1.Pod); ok {
c.JSON(200, v)
} else {
c.String(500, "b. convert interface to pod failed")
}
}
}
// getObjByLanguageName c. 查询指定语言的所有对象(演示4. ByIndex方法)
func GetObjByLanguageName(c *gin.Context) {
v, err := INDEXER.ByIndex(INDEXER_LANGUAGE, language(c))
if err != nil {
c.String(500, fmt.Sprintf("c. get pod failed, %v", err))
} else if v == nil {
c.String(500, fmt.Sprintf("c. get empty pod, %v", err))
} else {
m := make(map[string][]interface{})
m["language"] = v
c.JSON(200, m)
}
}
// getAllObjByOneName d. 根据某个对象的key,获取同语言类型的所有对象(演示1. Index方法)
func GetAllObjByOneName(c *gin.Context) {
// 注意,Index方法的第二个入参是对象,所以这里要先根据对象key查询到对象,然后再调用Index方法
rawObj, exists, err := INDEXER.GetByKey(objKey(c))
if err != nil {
c.String(500, fmt.Sprintf("d1. get pod failed, %v", err))
} else if !exists {
c.String(500, fmt.Sprintf("d1. get empty pod, %v", err))
} else {
// 先得到pod对象,再根据pod对象查询同类型的所有对象
if podObj, ok := rawObj.(*v1.Pod); ok {
rawArray, err := INDEXER.Index(INDEXER_LANGUAGE, podObj)
if err != nil {
c.String(500, fmt.Sprintf("d2. get pod failed, %v", err))
} else if len(rawArray) < 1 {
c.String(500, fmt.Sprintf("d2. get empty pod, %v", err))
} else {
m := make(map[string][]interface{})
m["language"] = rawArray
c.JSON(200, m)
}
} else {
c.String(500, "d1. convert interface to pod failed")
}
}
}
// getAllClassType e. 返回所有语言类型(演示3. ListIndexFuncValues方法)
func GetAllLanguange(c *gin.Context) {
languages := INDEXER.ListIndexFuncValues(INDEXER_LANGUAGE)
m := make(map[string][]string)
m["language"] = languages
c.JSON(200, m)
}
// getAllClassType f. 返回所有分类方式,这里应该是按服务类型和按语言类型两种(演示5. GetIndexers方法)
func GetAllClassType(c *gin.Context) {
indexers := INDEXER.GetIndexers()
// indexers是个map,其value是cache.IndexFunc类型,无法被序列化,所以这里只返回key
names := make([]string, 0)
for key, _ := range indexers {
names = append(names, key)
}
c.JSON(200, names)
}
public class CustomSort {
public static void main(String[] args) {
String[] arr = {"banana", "apple", "orange", "pear"};
// 使用自定义排序方法进行排序
Arrays.sort(arr, new CustomComparator());
// 输出排序后的数组
for (String str : arr) {
System.out.print(str + " ");
}
}
}
class CustomComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
// 按照字符串长度进行排序
return Integer.compare(s1.length(), s2.length());
}
}
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}"
}
]
}
### 变量,这是一个pod的对象key,和pod是一对一的关系
@obj_key=indexer-tutorials/nginx-deployment-87945df85-vzjgv
### 测试用例a. 查询指定语言的所有对象的key(演示2. IndexKeys方法)
GET http://192.168.50.76:18080/basic/get_obj_keys_by_language_name?language=c
### 测试用例b. 返回对象的key,返回对应的对象(演示Store.GetByKey方法)
GET http://192.168.50.76:18080/basic/get_obj_by_obj_key?obj_key={{obj_key}}
### 测试用例c. 查询指定语言的所有对象(演示4. ByIndex方法)
GET http://192.168.50.76:18080/basic/get_obj_by_language_name?language=c
### 测试用例d. 根据某个对象的key,获取同语言类型的所有对象(演示1. Index方法)
GET http://192.168.50.76:18080/basic/get_all_obj_by_one_name?obj_key={{obj_key}}
### 测试用例e. 返回所有语言类型(演示3. ListIndexFuncValues方法)
GET http://192.168.50.76:18080/basic/get_all_languange
### 测试用例f. 返回所有分类方式,这里应该是按服务类型和按语言类型两种(演示5. GetIndexers方法)
GET http://192.168.50.76:18080/basic/get_all_class_type
http://192.168.50.76:18080/basic/get_obj_keys_by_language_name?language=c
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 22:56:37 GMT
Content-Length: 219
Connection: close
{
"language": [
"indexer-tutorials/mysql-556b999fd8-22hqh",
"indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg",
"indexer-tutorials/nginx-deployment-696cc4bc86-bkplx",
"indexer-tutorials/nginx-deployment-696cc4bc86-m7wwh"
]
}
http://192.168.50.76:18080/basic/get_obj_by_obj_key?obj_key=indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:19:50 GMT
Connection: close
Transfer-Encoding: chunked
{
"metadata": {
"name": "nginx-deployment-696cc4bc86-2rqcg",
"generateName": "nginx-deployment-696cc4bc86-",
"namespace": "indexer-tutorials",
"uid": "f079b9e3-2b0f-4090-8d0f-5bab4fa2eeda",
"resourceVersion": "1341749",
"creationTimestamp": "2023-06-12T23:04:58Z",
"labels": {
"app": "nginx-app",
"business-service-type": "web",
"language": "c",
"pod-template-hash": "696cc4bc86",
"type": "front-end"
},
...
http://192.168.50.76:18080/basic/get_obj_by_language_name?language=c
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:25:00 GMT
Connection: close
Transfer-Encoding: chunked
{
"language": [
{
"metadata": {
"name": "mysql-556b999fd8-22hqh",
"generateName": "mysql-556b999fd8-",
"namespace": "indexer-tutorials",
"uid": "ac7ca6a2-f463-450d-848a-f3a2ea6a02df",
"resourceVersion": "1341711",
"creationTimestamp": "2023-06-12T23:04:55Z",
"labels": {
"app": "mysql",
"business-service-type": "storage",
"language": "c",
"pod-template-hash": "556b999fd8"
},
http://192.168.50.76:18080/basic/get_all_obj_by_one_name?obj_key=indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:30:11 GMT
Connection: close
Transfer-Encoding: chunked
{
"language": [
{
"metadata": {
"name": "nginx-deployment-696cc4bc86-bkplx",
"generateName": "nginx-deployment-696cc4bc86-",
"namespace": "indexer-tutorials",
"uid": "eba92053-bc35-4ef5-835a-1c2f054891d5",
"resourceVersion": "1341721",
"creationTimestamp": "2023-06-12T23:04:57Z",
"labels": {
"app": "nginx-app",
"business-service-type": "web",
"language": "c",
"pod-template-hash": "696cc4bc86",
"type": "front-end"
},
...
http://192.168.50.76:18080/basic/get_all_languange
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:32:37 GMT
Content-Length: 25
Connection: close
{
"language": [
"java",
"c"
]
}
http://192.168.50.76:18080/basic/get_all_class_type
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 14 Jun 2023 23:59:56 GMT
Content-Length: 52
Connection: close
[
"indexer_language",
"indexer_business_service_type"
]