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

如何在Pod中执行宿主机上的命令

24
Mar
2020

如何在Pod中执行宿主机上的命令

By Alex
/ in PaaS
/ tags K8S
0 Comments
基础知识回顾

要回答标题中的疑问,我们首先要清楚,Pod是什么?

Pod的翻译叫容器组,顾名思义,是一组容器。叫做“组”是因为这些容器:

  1. 总是被同时调度,调度到同一节点
  2. 共享网络,具有相同的IP地址和端口空间,可以通过localhost相互访问
  3. 可以基于SystemV信号量、POSIX消息队列等方式,进行进程间通信
  4. 共享存储卷(需要各自分别挂载)

从效果上看,容器组运行在一个虚拟的“主机”中。这个“主机”基于Linux命名空间、cgroups等机制和宿主机相互隔离。

虽说容器具有隔离性,但是这种隔离程度远远不如虚拟机,容器本质上就是进程。内核也提供了接口,允许你切换命名空间。只需要切换到宿主机的初始(Initial)命名空间,理论上就可以运行宿主机文件系统中的任何程序,并保证程序的行为正常。

Linux命名空间

参考Linux知识集锦

K8S共享命名空间
相关Pod字段

在K8S的Pod的Spec中,和命名空间有关的编排配置包括:

Go
1
2
3
4
5
6
7
8
9
10
11
type PodSpec struct {
    // 使用宿主机的网络命名空间
    HostNetwork bool `json:"hostNetwork,omitempty" protobuf:"varint,11,opt,name=hostNetwork"`
    // 使用宿主机的PID命名空间
    HostPID bool `json:"hostPID,omitempty" protobuf:"varint,12,opt,name=hostPID"`
    // 使用宿主机的IPC命名空间
    HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,13,opt,name=hostIPC"`
    // 让所有容器共享同一个PID命名空间,此选项不能和HostPID同时设置
    // 启用该选项后,容器的第一个进程不会赋予PID 1
    ShareProcessNamespace *bool `json:"shareProcessNamespace,omitempty" protobuf:"varint,27,opt,name=shareProcessNamespace"`
}

创建一个启用上述配置,和宿主机共享网络、PID、IPC命名空间的Pod后,通过 kubectl exec执行 ps aux,你会看到宿主机上的进程。

其它User、Mount、Cgroup等几种命名空间,没有相应的配置字段。需要强调的是,Mount命名空间无法共享,容器需要独立的文件系统树来挂载镜像。仅仅通过K8S提供的编排配置,无法实现我们的目标 —— 因为看不到和宿主机一样的文件系统树。

K8S安全配置
特权模式

上文提到过,内核允许切换到某个命名空间,然后执行应用程序。系统调用setns、unshare、clone等提供了切换命名空间的接口,命令行工具nsenter、unshare也可以实现相同的功能。

如果我们在启用上述配置的容器中执行 nsenter,尝试切换到初始Mount命名空间,会提示Permission denied错误:

Shell
1
2
nsenter -m -t 1                                                                                                                                                      
nsenter: cannot open /proc/1/ns/mnt: Permission denied

这说明容器没有足够的权限进行操作。

Kubernets允许容器以特权模式运行,你只需要配置安全上下文即可。安全上下文包括Pod、Container两个级别。

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
31
32
type PodSpec struct {
    // 提供Pod级别的安全属性,并为容器安全属性提供默认值
    SecurityContext *PodSecurityContext `json:"securityContext,omitempty" protobuf:"bytes,14,opt,name=securityContext"`
}
 
 
type PodSecurityContext struct {
    // 应用到所有容器的SELinux上下文,如果不指定,则容器运行时为每个容器指定随机的SELinux上下文
    SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty" protobuf:"bytes,1,opt,name=seLinuxOptions"`
    // 运行容器进程入口点使用的UID,默认从镜像元数据中获取UID
    RunAsUser *int64 `json:"runAsUser,omitempty" protobuf:"varint,2,opt,name=runAsUser"`
    // 运行容器进程入口点使用的GID,默认从使用容器运行时的默认值
    RunAsGroup *int64 `json:"runAsGroup,omitempty" protobuf:"varint,6,opt,name=runAsGroup"`
    // 提示容器必须以非Root身份运行,如果设置为true,则Kubelet会在运行时校验镜像
    // 确保它不以UID 0运行,如果发现镜像以UID 0 进行则导致启动失败
    RunAsNonRoot *bool `json:"runAsNonRoot,omitempty" protobuf:"varint,3,opt,name=runAsNonRoot"`
    // 额外的补充组,赋予容器的第一个进程,作为组GID的补充
    SupplementalGroups []int64 `json:"supplementalGroups,omitempty" protobuf:"varint,4,rep,name=supplementalGroups"`
    // 一个特殊的、应用到所有容器的补充组
    // 某些类型的卷,允许Kubelet修改卷的所有者,这可以确保容器有权访问卷的内容
    // 该选项导致:
    // 1. 卷的所有者GID设置为FSGroup
    // 2. setgid位被启用,这导致卷中新创建的文件的所有者为FSGroup
    // 3. 卷中文件的模式和rw-rw----进行或操作,也就是启用所有者、所在组的读写权限
 
    // 如果不配置,kubelet不会修改任何卷的所有者和文件模式
 
    FSGroup *int64 `json:"fsGroup,omitempty" protobuf:"varint,5,opt,name=fsGroup"`
    // 指定一系列命名空间化的Sysctl键值
    // 如果容器运行时不支持某个Sysctl则可能导致启动失败
    Sysctls []Sysctl `json:"sysctls,omitempty" protobuf:"bytes,7,rep,name=sysctls"`
}
容器安全上下文

容器安全上下文中,有一部分字段和Pod安全上下文一样,它们会覆盖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
type Container struct {
    SecurityContext *SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"`
}
 
 
type SecurityContext struct {
    // 需要给容器添加/删除的能力列表,默认能力取决于容器运行时
    Capabilities *Capabilities `json:"capabilities,omitempty" protobuf:"bytes,1,opt,name=capabilities"`
    // 以特权模式运行容器。这种模式下,容器中进程的身份等价于宿主机的root
    Privileged *bool `json:"privileged,omitempty" protobuf:"varint,2,opt,name=privileged"`
    // 覆盖Pod上下文设置
    SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty" protobuf:"bytes,3,opt,name=seLinuxOptions"`
    // 覆盖Pod上下文设置
    RunAsUser *int64 `json:"runAsUser,omitempty" protobuf:"varint,4,opt,name=runAsUser"`
    // 覆盖Pod上下文设置
    RunAsGroup *int64 `json:"runAsGroup,omitempty" protobuf:"varint,8,opt,name=runAsGroup"`
    // 覆盖Pod上下文设置
    RunAsNonRoot *bool `json:"runAsNonRoot,omitempty" protobuf:"varint,5,opt,name=runAsNonRoot"`
    // 容器的根文件系统是否设置为只读
    ReadOnlyRootFilesystem *bool `json:"readOnlyRootFilesystem,omitempty" protobuf:"varint,6,opt,name=readOnlyRootFilesystem"`
    // 是否允许子进程获得比父进程更多的特权,控制容器进程的no_new_privs标记是否被设置
    // 如果容器是运行在特权模式,或者具有CAP_SYS_ADMIN能力,则该配置自动为true
    AllowPrivilegeEscalation *bool `json:"allowPrivilegeEscalation,omitempty" protobuf:"varint,7,opt,name=allowPrivilegeEscalation"`
    // 指定该容器的proc挂载类型,默认的
    ProcMount *ProcMountType `json:"procMount,omitempty" protobuf:"bytes,9,opt,name=procMount"`
}
Privileged 

要满足我们的需求,只需要设置容器安全上下文的Privileged为True就足够了。这样你就可以通过nsenter进入宿主机的Mount命名空间,并且随意的运行命令了,例如通过systemctl判断某些服务是否正常运行。

K8S完整配置样例

这个样例允许我们在容器中访问宿主机的日志、控制宿主机的systemd,而不需要切换整个Mount命名空间。我们目前项目的一个需求就是,能够读取节点的内核日志环、Journald日志,可以用下面这种卷挂载的方式满足:

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
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
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: centos
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: centos
  template:
    metadata:
      labels:
        name: centos
    spec:
      # 加入宿主机网络命名空间
      hostNetwork: true
      # 加入宿主机PID命名空间
      hostPID: true
      # 加入宿主机IPC命名空间
      hostIPC: true
      containers:
      - image: docker.gmem.cc/centos:7.6
        imagePullPolicy: Always
        name: centos
        securityContext:
          # 设置PID为root
          runAsUser: 0
          # 特权模式
          privileged: true
        volumeMounts:
        # 这个挂载允许容器中的systemctl和宿主机的systemd通信
        - name: dbus
          mountPath: /var/run/dbus
        - name: run-systemd
          mountPath: /run/systemd
        # 这个挂载允许查看宿主机的systemd配置
        - name: etc-systemd
          mountPath: /etc/systemd
        # 这个挂载允许容器读取非journald管理的日志
        - name: var-log
          mountPath: /var/log
        - name: var-run
          mountPath: /var/run
        - name: run
          mountPath: /run
        - name: usr-lib-systemd
          mountPath: /usr/lib/systemd
      volumes:
      - name: dbus
        hostPath:
          path: /var/run/dbus
          type: Directory
      - name: run-systemd
        hostPath:
          path: /run/systemd
          type: Directory
      - name: etc-systemd
        hostPath:
          path: /etc/systemd
          type: Directory
      - name: var-log
        hostPath:
          path: /var/log
          type: Directory
      - name: var-run
        hostPath:
          path: /var/run
          type: Directory
      # /var/run 是 /run的符号链接
      - name: run
        hostPath:
          path: /run
          type: Directory
      - name: usr-lib-systemd
        hostPath:
          path: /usr/lib/systemd
          type: Directory
Go命名空间编程
切换命名空间
nsenter

这是一个命令行工具,能够在指定的命名空间中执行命令。K8S的utils/nsenter包对该命令进行了封装,可以参考。

setns

要编程式的切换命名空间,可以利用这个系统调用。 

在Go语言下,你需要注意的一点是,setns调用可能需要单线程上下文。而Go运行时是多线程的,你必须在Go运行时启动之前,执行setns调用。要实现这种提前调用,可以利用cgo的constructor技巧,该技巧能够在Go运行时启动之前,执行一个任意的C函数:

Go
1
2
3
4
5
6
7
/*
__attribute__((constructor)) void init() {
    // 这里的代码会在Go运行时启动前执行
    // 它会在单线程的C上下文中运行
}
*/
import "C"

libcontainer提供了基于此技巧的例子。

在Go语言中,你可以这样设置NS:

Go
1
2
3
4
// 将当前线程的NS设置为ns.Fd()这个文件描述符所指向的网络命名空间
if err := unix.Setns(int(ns.Fd()), unix.CLONE_NEWNET); err != nil {
    return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
} 
在NS中执行函数
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
// 在ns所代表的网络命名空间(以网络命名空间的文件描述符锚定)
func (ns *netNS) Do(toRun func(NetNS) error) error {
    // 如果命名空间(文件描述符)已经关闭
    if err := ns.errorIfClosed(); err != nil {
        return err
    }
    // 设置当前调用者goroutine的网络NS为当前对象所代表的网络OS
    // 然后运行函数,完毕后重置为线程原先的网络命名空间
    // 为了防止goroutine底层的OS线程切换,需要锁定、回调执行完毕后解锁
    containedCall := func(hostNS NetNS) error {
        // 得到当前线程的NS
        threadNS, err := GetCurrentNS()
        if err != nil {
            return fmt.Errorf("failed to open current netns: %v", err)
        }
        // 关闭文件描述符
        defer threadNS.Close()
 
        // 设置NS
        if err = ns.Set(); err != nil {
            return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
        }
        // 结束回调后,恢复NS,解锁线程
        defer func() {
            err := threadNS.Set() // switch back
            if err == nil {
                // 仅当前NS切回成功,才解锁线程,否则
                // 保持锁定,这会导致gouroutine结束了销毁OS线程
                // 这种做法可能不是最优解,但是安全,不会出现NS混乱
                runtime.UnlockOSThread()
            }
        }()
        // 调用真实的回调
        return toRun(hostNS)
    }
 
    // 保存当前命名空间的句柄
    hostNS, err := GetCurrentNS()
    if err != nil {
        return fmt.Errorf("Failed to open current namespace: %v", err)
    }
    // 总是关闭(在回调goroutine完毕后)
    defer hostNS.Close()
 
    // 用于等待回调goroutine完毕
    var wg sync.WaitGroup
    wg.Add(1)
 
    // 关键之处:启用一个新的goroutine,在其中执行回调
    // 这样做的原因是,当前goroutine不切换NS,保证了安全性
    // 而新gorouinte会先切NS,再切回去,这过程万一失败,直接抛弃它的底层OS线程即可
    var innerError error
    go func() {
        defer wg.Done()
        // 锁定OS线程,重要
        runtime.LockOSThread()
        innerError = containedCall(hostNS)
    }()
    wg.Wait()
 
    return innerError
} 

调用上述函数的例子:

Go
1
2
3
4
5
6
err = networkNS.Do(func(ns.NetNS) error {
    var err error
    // 获取命名空间中的网络接口
    lo, err = net.InterfaceByName("lo")
    return err
})
获取命名空间
当前线程命名空间
Go
1
2
3
4
5
func getCurrentThreadNetNSPath() string {
    // /proc/self/ns/net 返回的是主线程的命名空间
    // 要获得当前goroutine的后备线程的命名空间,使用:
    return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
}
创建命名空间

下面的例子演示了如何创建一个不依赖于线程,可独立存在的网络命名空间:

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
107
108
109
import "golang.org/x/sys/unix"
 
// 获取网络命名空间目录
func getNsRunDir() string {
    xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
 
    // 如果XDG_RUNTIME_DIR被设置,检查是否当前用户是/var/run的所有者
    // 如果不是,提示当前运行在一个User命名空间中
    // 运行时目录应该取:$XDG_RUNTIME_DIR/netns
    if xdgRuntimeDir != "" {
        if s, err := os.Stat("/var/run"); err == nil {
            // 发起系统调用,获取文件信息
            st, ok := s.Sys().(*syscall.Stat_t)
            if ok && int(st.Uid) != os.Geteuid() {
                return path.Join(xdgRuntimeDir, "netns")
            }
        }
    }
 
    return "/var/run/netns"
}
 
func NewNS() (ns.NetNS, error) {
    nsRunDir := getNsRunDir()
    // 随机目录名
    b := make([]byte, 16)
    _, err := rand.Reader.Read(b)
 
    // 创建目录,如果它被挂载到了其它命名空间,则必须改为共享挂载点
    err = os.MkdirAll(nsRunDir, 0755)
 
    // 重新挂载它,设置为共享挂载,MS_REC表示递归的处理子树中的挂载,都改为共享
    // 如果它尚不是一个挂载点,则调用会失败
    err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
    if err != nil {
        if err != unix.EINVAL {
            return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
        }
        // 重新Bind挂载到它自身,可以“升级”为挂载点,递归处理子树
        err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "")        
        if err != nil {
            return nil, fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err)
        }
        // 再次标记为共享挂载点
        err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
    }
 
    nsName := fmt.Sprintf("cnitest-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
 
    // 在挂载点下创建一个空白文件,获取它的文件描述符
    nsPath := path.Join(nsRunDir, nsName)
    mountPointFd, err := os.Create(nsPath)
    if err != nil {
        return nil, err
    }
    // 关闭文件描述符
    mountPointFd.Close()
 
    // 确保在出错时挂载点被清理掉
    // 如果命名空间已经成功挂载,则该调用没有任何作用,因为文件正在使用
    defer os.RemoveAll(nsPath)
 
    var wg sync.WaitGroup
    wg.Add(1)
 
    // 在专门的进程中进行命名空间相关的工作
    // 这样我们可以安全的Lock/Unlock OSThread而不会搞乱此函数调用者的lock/unlock状态
    go (func() {
        defer wg.Done()
        // 将当前协程绑到当前所在的OS线程上,确保它总是在此宿主机线程上执行
        // 在UnlockOSThread之前,其它协程不会在此OS线程上运行
        // 如果协程退出前没有UnlockOSThread则OS线程被关闭
        // 所有init函数在启动线程中运行,在其中调用LockOSThread会导致main函数在启动线程中执行
        // 在调用依赖于per-thread状态的OS服务或非Go库之前,应当LockOSThread
        runtime.LockOSThread()
        // 这里不去解锁,确保协程结束时OS线程会被杀死(1.10+)
 
        var origNS ns.NetNS
        // 获取当前线程的网络命名空间路径 /proc/2816046/task/2816057/ns/net
        origNS, err = ns.GetNS(getCurrentThreadNetNSPath())
        if err != nil {
            return
        }
        defer origNS.Close()
 
        // 在当前线程上创建新的网络命名空间
        err = unix.Unshare(unix.CLONE_NEWNET)
        if err != nil {
            return
        }
 
        // 恢复原来的网络命名空间
        defer origNS.Set()
 
        // 将当前线程的网络命名空间(/proc/..)绑定挂载到先前创建的挂载点上
        // 这会持久化网络命名空间,即使其中没有线程了(当前Goroutine的底层线程马上就退出了)
        err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "")
        if err != nil {
            err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err)
        }
    })()
    wg.Wait()
 
    if err != nil {
        return nil, fmt.Errorf("failed to create namespace: %v", err)
    }
    // 打开代表网络命名空间的文件描述符
    return ns.GetNS(nsPath)
}

当不需要后,关闭文件描述符、解除挂载即可:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
networkNS.Close()
testutils.UnmountNS(networkNS)
 
func UnmountNS(ns ns.NetNS) error {
    nsPath := ns.Path()
    // 仅当它是被bind挂载时才umount,不去触碰/proc中的命名空间
    if strings.HasPrefix(nsPath, getNsRunDir()) {
        if err := unix.Unmount(nsPath, 0); err != nil {
            return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err)
        }
 
        if err := os.Remove(nsPath); err != nil {
            return fmt.Errorf("failed to remove ns path %s: %v", nsPath, err)
        }
    }
 
    return nil
} 
参考
  1. 控制组详解
  2. Get a Shell to a Kubernetes Node
← 通过ExternalDNS集成外部DNS服务
服务网格的现状和未来 →

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

  • 基于Calico的CNI
  • Istio学习笔记
  • CSI学习笔记
  • Cilium学习笔记
  • 基于Helm的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