package docker import ( "get-container/utils" "strconv" "context" "errors" "fmt" "os" "regexp" "strings" "sync" "time" "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" ) func init() { _ = initContainerInfo() } /** 有两种方法获取进程属于哪个容器 1. 通过查询pid命名空间,仅在没有指定--pid参数时有效 2. 通过查询进程的cgroup 3. 使用docker top 匹配 */ type FindCIDMethod string const ( ByCgroup FindCIDMethod = "byCGroup" ByPidNS FindCIDMethod = "byPidNS" ByTop FindCIDMethod = "byTop" ) 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 topInfo map[string]container.TopResponse } type ContainerPsInfo struct { Pid int32 Ppid int32 Uid string Cmd string } func ParsePsInfo(topInfo map[string]container.TopResponse) (map[string][]ContainerPsInfo, error) { if topInfo == nil { return nil, errors.New("topInfo is nil") } result := make(map[string][]ContainerPsInfo) for cid, topResp := range topInfo { indexMap, t := make(map[string]int), 0 result[cid] = make([]ContainerPsInfo, 0) for index, key := range topResp.Titles { switch strings.TrimSpace(strings.ToLower(key)) { case "pid": indexMap["pid"] = index t++ case "ppid": indexMap["ppid"] = index t++ case "uid": indexMap["uid"] = index t++ case "cmd": indexMap["cmd"] = index t++ default: } if t >= 4 { break } } for _, fields := range topResp.Processes { item := ContainerPsInfo{} if v, ok := indexMap["pid"]; ok { pid, err := strconv.ParseInt(fields[v], 10, 64) if err != nil { return nil, err } item.Pid = int32(pid) } if v, ok := indexMap["ppid"]; ok { ppid, err := strconv.ParseInt(fields[v], 10, 64) if err != nil { return nil, err } item.Ppid = int32(ppid) } if v, ok := indexMap["uid"]; ok { item.Uid = fields[v] } if v, ok := indexMap["cmd"]; ok { item.Cmd = fields[v] } result[cid] = append(result[cid], item) } } return result, nil } func (info *ContainersInfo) Update() error { info.lock.Lock() defer info.lock.Unlock() i, s, t, err := getContainerInfo() if err != nil { return err } info.inspectInfo = i info.listInfo = s info.topInfo = t 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 initContainerInfo() error { inspect, lists, tops, err := getContainerInfo() if err != nil { return err } ContainerInfo = &ContainersInfo{ lock: sync.RWMutex{}, time: time.Now(), inspectInfo: inspect, listInfo: lists, topInfo: tops, } return nil } // GetProcessIdInDocker 获取所用容器的进程信息 func (info *ContainersInfo) GetProcessIdInDocker(update bool) (map[int32]bool, error) { result := make(map[int32]bool) if update { err := info.Update() if err != nil { return result, err } } rl := info.lock.RLocker() rl.Lock() i, err := ParsePsInfo(info.topInfo) rl.Unlock() rl = nil if err != nil { return result, err } for _, v := range i { for _, k := range v { result[k.Pid] = true } } return result, 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 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, map[string]container.TopResponse, error) { cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return nil, nil, nil, err } defer func() { _ = cli.Close() }() containerSum, err := cli.ContainerList(context.Background(), client.ContainerListOptions{All: false}) if err != nil { return nil, nil, nil, err } inspects := make(map[string]container.InspectResponse) lists := make(map[string]container.Summary) tops := make(map[string]container.TopResponse) for _, c := range containerSum { inspect, innerErr := cli.ContainerInspect(context.Background(), c.ID) if innerErr != nil { return nil, nil, nil, innerErr } inspects[c.ID] = inspect lists[c.ID] = c topInfo, innerErr := cli.ContainerTop(context.Background(), c.ID, nil) if innerErr != nil { return nil, nil, nil, innerErr } tops[c.ID] = topInfo } return inspects, lists, tops, nil }