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

Kuberentes客户端编程

25
Jul
2018

Kuberentes客户端编程

By Alex
/ in Go
/ tags K8S
0 Comments
客户端基础
常用包
Go
1
2
3
4
5
6
7
8
9
import (
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/kubernetes"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/labels"
    "k8s.io/apimachinery/pkg/watch"
    "k8s.io/apimachinery/pkg/api/errors"
)
配置和连接
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var (
    masterURL  string
    kubeConfig string
)
 
func main() {
    flag.StringVar(&masterURL, "master-url", "", "URL of kubernetes master")
    flag.StringVar(&kubeConfig, "kube-config", "", "Kubernetes configuration file location")
    flag.Parse()
    // 构建配置信息
    cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeConfig)
    if err != nil {
        log.Fatalf("Invalid arguments: %s", err.Error())
    }
    // 创建API集
    clientset, err := kubernetes.NewForConfig(cfg)
    if err != nil {
        log.Fatalf("Failed to connect to api server: %s", err.Error())
    }
}
集群内连接

在Pod中访问API Server,可以使用集群授予的ServiceAccount作为凭证。参考如下代码:

Go
1
2
3
"k8s.io/client-go/rest"
cfg, err = rest.InClusterConfig()
// 不在K8S内部调用此方法会报错
查询
get
Go
1
2
3
4
// 获取一个Deployment
dep, _ := clientset.ExtensionsV1beta1().Deployments(namespace).Get("mysql", metav1.GetOptions{})
// 获取一个Pod
pod, _ := clientset.CoreV1().Pods(namespace).Get("mysql-54fb6b686b-sv8ql", metav1.GetOptions{})
list

通过标签选择器列出:

Go
1
2
3
4
5
6
7
// labels.Set是map[string]string的typedef,进行cast以调用其方法
labelset := labels.Set(dep.Spec.Selector.MatchLabels)
// 使用标签选择器列出Deployment下的Pods
pods, _ := clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{
// String方法的返回值就是ParseSelector支持的格式,示意:application=mysql,tier=middleware
LabelSelector: labelset.String(),
})
watch

可以持续监控目标资源的变化:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
podApi := clientset.CoreV1().Pods(namespace)
watchApi, _ := podApi.Watch(metav1.ListOptions{ // 和list操作使用一个Options结构
    // 被监控资源的选择器
    LabelSelector: labelset.String(),
    // 持续监控被监控资源的变化,包括add,update,remove事件
    Watch: true,
    // 持续监控进行的最大时间
    TimeoutSeconds: &timeout,
    // 持续监控进行的最大次数
    Limit: 1024,
})
var debuggee string
for e := range watchApi.ResultChan() {
    pod := e.Object.(*v1.Pod)
    if e.Type == watch.Added {
        debuggee = pod.Name
    } else if pod.Name == debuggee && e.Type == watch.Modified && pod.Status.Phase == v1.PodRunning {
        os.Exit(0)
    }
} 
更新

下面是修改Deployment环境变量定义的例子:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 修改K8S资源时一定要注意赋值引用而非拷贝
container := &dep.Spec.Template.Spec.Containers[0]
debugEnvDefined := false
for idx, _ := range container.Env {
    envar := &container.Env[idx]
    if envar.Name == debugVar {
        debugEnvDefined = true
        envar.Value = debugMode
        break
    }
}
if !debugEnvDefined {
    container.Env = append(container.Env, v1.EnvVar{debugVar, debugMode, nil})
}
json, _ := json.MarshalIndent(container.Env, "", "\t")
fmt.Printf("Updated envrioment variables of container %v:\n%v", container.Name, string(json))
depApi.Update(dep) // 更新之后,Deployment管理的Pod会自动重新创建
创建

下面代码片断示意了如何创建一个完整的Deployment对象:

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
primaryDep = &appsv1.Deployment{
        // 对象元数据部分
    ObjectMeta: metav1.ObjectMeta{
        Name:      primaryName,
        Labels:    canaryDep.Labels,
        Namespace: cd.Namespace,
                // 指向其OwnerReference,也就是管理此对象的对象,在这个例子中是cd,一个CRD
        OwnerReferences: []metav1.OwnerReference{
            *metav1.NewControllerRef(cd, schema.GroupVersionKind{
                Group:   flaggerv1.SchemeGroupVersion.Group,
                Version: flaggerv1.SchemeGroupVersion.Version,
                Kind:    flaggerv1.CanaryKind,
            }),
        },
    },
        // 对象规格部分
    Spec: appsv1.DeploymentSpec{
        ProgressDeadlineSeconds: canaryDep.Spec.ProgressDeadlineSeconds,
        MinReadySeconds:         canaryDep.Spec.MinReadySeconds,
        RevisionHistoryLimit:    canaryDep.Spec.RevisionHistoryLimit,
        Replicas:                canaryDep.Spec.Replicas,
        Strategy:                canaryDep.Spec.Strategy,
        Selector: &metav1.LabelSelector{
            MatchLabels: map[string]string{
                "app": primaryName,
            },
        },
        Template: corev1.PodTemplateSpec{
            ObjectMeta: metav1.ObjectMeta{
                Labels:      map[string]string{"app": primaryName},
                Annotations: annotations,
            },
            Spec: ...,
        },
    },
}
扩容
Go
1
2
3
4
5
6
// 获得*v1beta1.Scale
scale, _ := depApi.GetScale("mysql", metav1.GetOptions{})
replicas := scale.Spec.Replicas
// 缩容为0
scale.Spec.Replicas = 0
depApi.UpdateScale("mysql", scale)
动态客户端
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
package main
import (
    "fmt"
 
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    _ "k8s.io/client-go/plugin/pkg/client/auth"
    "k8s.io/client-go/tools/clientcmd"
)
 
var (
    context = ""
)
 
func main() {
    //  连接到默认Kubernetes Context
    config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
        clientcmd.NewDefaultClientConfigLoadingRules(),
        &clientcmd.ConfigOverrides{CurrentContext: context}).ClientConfig()
    if err != nil {
        panic(err.Error())
    }
 
    // 创建动态客户端
    dynamicClient, err := dynamic.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }
 
    //  创建目标资源的GroupVersionResource GVR
    virtualServiceGVR := schema.GroupVersionResource{
        Group:    "networking.istio.io",
        Version:  "v1alpha3",
        Resource: "virtualservices",
    }
 
    //  获取默认命名空间的,对应GVR的资源列表
    virtualServices, err := dynamicClient.Resource(virtualServiceGVR).Namespace("default").List(metav1.ListOptions{})
    if err != nil {
        panic(err.Error())
    }
 
        // 遍历
    for index, virtualService := range virtualServices.Items {
        fmt.Printf("VirtualService %d: %s\n", index+1, virtualService.GetName())
    }
}
转换为静态类型

动态客户端的CRUD接口如下:

Go
1
2
3
4
5
6
7
8
9
10
11
type ResourceInterface interface {
    Create(obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error)
    Update(obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error)
    UpdateStatus(obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error)
    Delete(name string, options *metav1.DeleteOptions, subresources ...string) error
    DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
    Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error)
    List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
    Watch(opts metav1.ListOptions) (watch.Interface, error)
    Patch(name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error)
}

可以看到动态资源被封装在 unstructured.Unstructured结构中。你可以使用类似下面的代码将其转换为静态类型:

Go
1
2
3
4
5
6
import "k8s.io/apimachinery/pkg/runtime"
 
var obj runtime.Unstructured
 
pod = new(corev1api.Pod)
runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod) 
发现客户端

发现客户端用于获取API Server能支持的API组、版本、资源列表:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig())
arrayOfApiList, err := discoveryClient.ServerResources()
if err != nil {
    panic(err)
}
r.apiVersionKind = make(map[string]struct{}, 0)
for _, apiList := range arrayOfApiList {
    for _, api := range apiList.APIResources {
        // 是否命名空间内资源
        if api.Namespaced {
            continue
        }
        // GVK
        apiVersionKind[fmt.Sprintf("%s/%s", apiList.GroupVersion, api.Kind)] = struct{}{}
    }
}
ObservedGeneration

某些控制器具有Status.ObservedGeneration字段,来表示控制器最后一次操作所针对的资源的版本。

Generation字段表示资源最后一次被修改的版本,用于乐观并发控制。

当Status.ObservedGeneration比Generation小的时候,说明控制器还没有应用最新的资源,应当等待控制器完成后,执行更进一步操作:

Go
1
2
3
4
5
if deployment.Generation <= deployment.Status.ObservedGeneration {
    // do something
}else{
    return fmt.Errorf("waiting for rollout to finish: observed deployment generation less then desired generation")
}
ObjectReference
Go
1
2
3
import ref "k8s.io/client-go/tools/reference"
// 创建ObjectReference
ref, err := ref.GetReference(recorder.scheme, object)
Pod操作
执行命令
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
func (i *impl) ExecInPod(namespace, podName, container string, streamOptions remotecommand.StreamOptions, cmd string, args ...string) error {
    execReq := i.clientset.CoreV1().RESTClient().Post().
        Resource("pods").
        Name(podName).
        Namespace(namespace).
        SubResource("exec")
 
    command := []string{cmd}
    command = append(command, args...)
    execReq.VersionedParams(&corev1.PodExecOptions{
        Command:   command,
        Container: container,
        Stdin:     streamOptions.Stdin != nil,
        Stdout:    streamOptions.Stdout != nil,
        Stderr:    streamOptions.Stderr != nil,
        TTY:       streamOptions.Tty,
    }, scheme.ParameterCodec)
 
    exec, err := remotecommand.NewSPDYExecutor(i.restConfig, "POST", execReq.URL())
    if err != nil {
        return fmt.Errorf("error while creating executor: %v", err)
    }
 
    err = exec.Stream(streamOptions)
    if err != nil {
        return fmt.Errorf("error in stream: %v", err)
    } else {
        return nil
    }
}
操控事件
相关结构
Event

表示集群中某时某处发生的一个事件:

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
type Event struct {
    metav1.TypeMeta
    metav1.ObjectMeta
 
    // 此事件牵涉到的对象
    InvolvedObject ObjectReference
 
    // 简短的、机器可理解的,对象变为当前状态的原因
    Reason string
 
    // 人类可读的,对象变为当前状态的说明
    Message string
 
    // 报告此事件的K8S组件
    Source EventSource
 
    // 此事件首次被记录的时间
    FirstTimestamp metav1.Time
 
    // 此事件最后一次出现的时间
    LastTimestamp metav1.Time
 
    // 此事件发生的次数
    Count int32
 
    // 事件的类型Normal, Warning
    Type string
 
    // 事件第一次被观察的时间
    EventTime metav1.MicroTime
 
    // 此事件所属的事件序列,如果为单独事件则此字段为nil
    Series *EventSeries
 
    // 针对目标对象的什么操作被执行/失败
    Action string
 
    // 次要相关的对象
    Related *ObjectReference
 
    // 报告此事件的控制器
    ReportingController string
 
    // 报告此事件的控制器实例
    ReportingInstance string
}
EventSource

此结构表示事件的来源,什么组件(例如kubelet,某个operator)、什么节点产生了此事件:

Go
1
2
3
4
type EventSource struct {
    Component string
    Host string
}
EventSeries

表示一组连续发生的事件的集合:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type EventSeries struct {
    // 到最后一次心跳为止,事件发生的次数
    Count int32
    // 最后一次观察到事件的时间
    LastObservedTime metav1.MicroTime
    // 事件序列的状态
    State EventSeriesState
}
 
type EventSeriesState string
 
const (
        // 正在进行
    EventSeriesStateOngoing  EventSeriesState = "Ongoing"
        // 完成
    EventSeriesStateFinished EventSeriesState = "Finished"
        // 未知
    EventSeriesStateUnknown  EventSeriesState = "Unknown"
)  
发布事件
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import     "k8s.io/client-go/tools/record"
// 创建事件广播器
eventBroadcaster := record.NewBroadcaster()
// 将从广播器接收到的事件发送给日志记录器,进行日志记录
eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Debugf)
// 将事件持久化,默认实现是存放到K8S,可以通过kubectl describe查看事件
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{
    Interface: kubeClient.CoreV1().Events(""),
})
 
// 日志记录器,能够产生事件,并发送给广播器处理
eventRecorder := eventBroadcaster.NewRecorder(
                // 事件源,填写组件名称,例如flagger、kubelet
        scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
 
// 产生一个事件
c.eventRecorder.Event(r, corev1.EventTypeWarning, "Synced", fmt.Sprintf(template, args...))
工具包
clock

来自 k8s.io/apimachinery/pkg/util/clock的clock包,可以注入真实/仿冒的时钟到任何需要基于时间执行特定逻辑的代码中。

Go
1
2
3
4
5
6
7
8
type Clock interface {
    Now() time.Time  // 返回当前时间
    Since(time.Time) time.Duration  // 从指定时间点到现在,过了多久
    After(time.Duration) <-chan time.Time // 在指定时间之后,通道可读
    NewTimer(time.Duration) Timer // 返回一个定时器
    Sleep(time.Duration) // 导致当前线程睡眠
    NewTicker(time.Duration) Ticker // 返回一个重复定时器,每隔一段时间其通道可读
}

创建一个时钟:

Go
1
c := clock.RealClock{}

创建一个重复定时器:

Go
1
ticker := c.clock.NewTicker(updatePeriod)

每过一定间隔执行逻辑:

Go
1
2
3
4
5
6
7
defer ticker.Stop()
for {
    select {
    case <-ticker.C():
        // ...
    }
}
wait 

来自 k8s.io/apimachinery/pkg/util/wait的wait包,提供监听/轮询某个条件是否达成的功能。

PollImmediate

立即开始周期性的尝试调用条件函数,直到此函数返回true、出错,后者到达超时:

Go
1
2
3
4
5
6
                   // 尝试间隔 尝试(总计)超时
func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error {}
// 示例
wait.PollImmediate(npdo.APIServerWaitInterval, npdo.APIServerWaitTimeout, func() (done bool, err error) {
    return false, nil
})

此函数被调用时,不经等待即立即开始第一次调用条件函数。 

util
retry

执行一个调用,如果错误是冲突(API Server乐观并发控制),则重试:

Go
1
2
3
func RetryOnConflict(backoff wait.Backoff, fn func() error) error {
    return OnError(backoff, errors.IsConflict, fn)
}
常见问题 
乐观并发控制

有时候,更新资源会接收到错误:the object has been modified; please apply your changes to the latest version and try again。这是由于你提交的资源的resourceVersion过低导致 —— 在你获取资源修改、提交之前,资源已经被别人更新过。

需要注意:

  1. Scale、Status子资源的版本号,和主资源是一致的
  2. 单次API调用可能导致多次resourceVersion变更

下面是一段示意代码,说明如何处理这种错误:

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
depApi := clientset.ExtensionsV1beta1().Deployments(namespace)
 
// 第一次修改:缩容到0
scale, _ := depApi.GetScale(deployment, metav1.GetOptions{})
replicas := scale.Spec.Replicas
scale.Spec.Replicas = 0
// 从调用的返回值中可以获取最新的资源版本
scale, _ = depApi.UpdateScale(deployment, scale)
 
// 最低资源版本
minVer := scale.ResourceVersion
// 第二次修改:变更Deployment规格、扩容到原副本数
for {
    // 反复尝试直到成功
    dep, err := depApi.Get(deployment, metav1.GetOptions{
        // 要求获取的资源的版本必须大于等于指定的版本
        ResourceVersion: minVer,
    })
      
    dep.Spec.Replicas = &replicas
    _, err = depApi.Update(dep)
    if err != nil {
        serr := err.(*errors.StatusError)
        if serr.ErrStatus.Code == 409 {
            time.Sleep(time.Second)
            minVer = dep.ResourceVersion
            continue
        }
        log.Fatalf("Failed to update deployment %s/%s: %s", namespace, deployment, err.Error())
    }
    break
}

 

← 乡村夏日
2018年7月青岛 →

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

  • 通过自定义资源扩展Kubernetes
  • Kubernetes故障检测和自愈
  • Kata Containers学习笔记
  • CNI学习笔记
  • 基于Rook的Kubernetes存储方案

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