find.go 5.92 KB
Newer Older
liming6's avatar
liming6 committed
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
package docker

import (
	"get-container/utils"

	"context"
	"errors"
	"fmt"
	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/client"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"
)

/**
有两种方法获取进程属于哪个容器
1. 通过查询pid命令空间
2. 通过查询进程的cgroup
*/

type FindCIDMethod string

const (
	ByCgroup FindCIDMethod = "byCGroup"
	ByPidNS  FindCIDMethod = "byPidNS"
)

var (
	ReDocker                      = regexp.MustCompile(`^.*docker[-/]([0-9a-z]*)(?:|.*)`)
	ContainerInfo *ContainersInfo = nil
)

type ContainersInfo struct {
	lock        sync.RWMutex // 读写锁,防止对Info的并发写
	time        time.Time    // 记录写入Info的时间
	inspectInfo map[string]container.InspectResponse
	listInfo    map[string]container.Summary
}

func (info *ContainersInfo) Update() error {
	info.lock.Lock()
	defer info.lock.Unlock()
	i, s, err := getContainerInfo()
	if err != nil {
		return err
	}
	info.inspectInfo = i
	info.listInfo = s
	info.time = time.Now()
	return nil
}

func (info *ContainersInfo) Get() (map[string]container.InspectResponse, sync.Locker) {
	rl := info.lock.RLocker()
	rl.Lock()
	return info.inspectInfo, rl
}

func init() {
	_ = initContainerInfo()
}

func initContainerInfo() error {
	inspect, lists, err := getContainerInfo()
	if err != nil {
		return err
	}
	ContainerInfo = &ContainersInfo{
		lock:        sync.RWMutex{},
		time:        time.Now(),
		inspectInfo: inspect,
		listInfo:    lists,
	}
	return nil
}

// FindContainerIdByPid 根据pid获取该进程属于哪个docker容器,返回容器id,如果为nil,表示找不到容器id
func FindContainerIdByPid(pid uint64, method FindCIDMethod) (*string, error) {
	switch method {
	case ByPidNS:
		return findContainerIdByNS(pid)
	case ByCgroup:
		return findContainerIdByCgroup(pid)
	default:
		return nil, fmt.Errorf("unknown method: %s", method)
	}
}

func FindContainerIdByPidBatch(pids []uint64, method FindCIDMethod) (map[uint64]string, error) {
	if pids == nil || len(pids) == 0 {
		return nil, nil
	}
	switch method {
	case ByPidNS:
		return findContainerIdByNSBatch(pids)
	case ByCgroup:
		return findContainerIdByCgroupBatch(pids)
	default:
		return nil, fmt.Errorf("unknown method: %s", method)
	}
}

// findContainerIdByPidCgroup 通过cgroup查询docker容器id
func findContainerIdByCgroup(pid uint64) (*string, error) {
	content, err := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid))
	if err != nil {
		return nil, err
	}
	contentStr := strings.Trim(string(content), "\n")
	if len(contentStr) == 0 {
		return nil, errors.New("process's cgroup not found")
	}
	lines := strings.Split(contentStr, "\n")
	var target string
	if len(lines) > 1 {
		// 如果有多行,解析有pids的行
		for _, line := range lines {
			if strings.Contains(line, "pids") {
				target = strings.TrimSpace(line)
				break
			}
		}
		if target == "" {
			return nil, errors.New("process's cgroup not found pids line")
		}
	} else {
		// 如果是单行,直接解析
		target = strings.TrimSpace(lines[0])
	}
	target = strings.TrimSpace(target)
	if !strings.Contains(target, "docker") {
		return nil, errors.New("process's cgroup is not create by docker")
	}
	if ReDocker.MatchString(target) {
		fields := ReDocker.FindStringSubmatch(target)
		if len(fields) < 2 {
			return nil, errors.New("process's cgroup is not create by docker")
		}
		cid := fields[1]
		return &cid, nil
	} else {
		return nil, errors.New("process's cgroup is not create by docker")
	}
}

func findContainerIdByCgroupBatch(pids []uint64) (map[uint64]string, error) {
	results := make(map[uint64]string)
	for _, pid := range pids {
		str, err := findContainerIdByCgroup(pid)
		if err != nil {
			return nil, err
		}
		s := *str
		results[pid] = s
	}
	return results, nil
}

// findContainerIdByNS 通过pid命名空间查询docker容器id
func findContainerIdByNS(pid uint64) (*string, error) {
	ns, err := utils.GetPidNS(pid)
	if err != nil {
		return nil, err
	}
	if ContainerInfo == nil {
		innerErr := initContainerInfo()
		if innerErr != nil {
			return nil, innerErr
		}
	} else {
		if innerErr := ContainerInfo.Update(); innerErr != nil {
			return nil, innerErr
		}
	}
	info, lock := ContainerInfo.Get()
	defer lock.Unlock()
	for k, v := range info {
		containerNs, innerErr := utils.GetPidNS(uint64(v.State.Pid))
		if innerErr != nil {
			continue
		}
		if containerNs == ns {
			cid := k
			return &cid, nil
		}
	}
	return nil, nil
}

func findContainerIdByNSBatch(pids []uint64) (map[uint64]string, error) {
	if ContainerInfo == nil {
		innerErr := initContainerInfo()
		if innerErr != nil {
			return nil, innerErr
		}
	} else {
		if innerErr := ContainerInfo.Update(); innerErr != nil {
			return nil, innerErr
		}
	}
	info, lock := ContainerInfo.Get()
	defer lock.Unlock()
	results := make(map[uint64]string)
	ns2cid := make(map[uint64]string)
	for k, v := range info {
		containerNs, innerErr := utils.GetPidNS(uint64(v.State.Pid))
		if innerErr != nil {
			return nil, innerErr
		}
		ns2cid[containerNs] = k
	}
	for _, pid := range pids {
		ns, err := utils.GetPidNS(pid)
		if err != nil {
			continue
		}
		if cid, ok := ns2cid[ns]; ok {
			results[pid] = cid
		}
	}
	return results, nil
}

// getContainerInfo 获取所有正在运行的docker容器的详细信息
func getContainerInfo() (map[string]container.InspectResponse, map[string]container.Summary, error) {
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return nil, nil, err
	}
	defer func() {
		_ = cli.Close()
	}()
	containerSum, err := cli.ContainerList(context.Background(), client.ContainerListOptions{All: false})
	if err != nil {
		return nil, nil, err
	}
	inspects := make(map[string]container.InspectResponse)
	lists := make(map[string]container.Summary)
	for _, c := range containerSum {
		inspect, innerErr := cli.ContainerInspect(context.Background(), c.ID)
		if innerErr != nil {
			return nil, nil, innerErr
		}
		inspects[c.ID] = inspect
		lists[c.ID] = c
	}
	return inspects, lists, nil
}