Golang 如何确定App是否运行在Docker内

背景

项目需要隔离部署,如果在docker环境需要使用一些特殊的配置,之前没有太好的判别方法,现在总结一下。

V1 使用/.dockerenv判断

如果是Docker容器内,根目录会生成一个可执行的/.dockerenv文件,判断此文件是否存在可以基本断定是否运行内容器内,注意是“基本”,极少数的发行版或者某些定制化较高的系统,可能会不存在此文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const DOCKERENV_FILE string = "/.dockerenv"
func pathExist(_path string) bool {
_, err := os.Stat(_path)
if err != nil && os.IsNotExist(err) {
return false
}
return true
}
func main() {
if pathExist(DOCKERENV_FILE) {
logs.Warn("Running in Docker, not check for now...")
}
}

V2 使用cgroup信息进行判断

Docker在Xnix下虚拟环境,使用了cgroup,容器内的group信息与物理机会有本质不同,可以根据Docker内特殊化的环境信息进行判断,这也是目前最靠谱的方案。

正常情况下物理机的cgroup:

1
2
3
4
5
6
7
8
9
10
$ cat /proc/1/cgroup
9:blkio:/
8:devices:/
7:freezer:/
6:cpuset:/
5:memory:/
4:cpu,cpuacct:/
3:perf_event:/
2:net_cls,net_prio:/
1:name=systemd:/

Docker内的cgroup(例子):

1
2
3
4
5
6
7
8
9
10
11
# cat /proc/1/cgroup
10:freezer:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
9:memory:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
8:cpuset:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
7:pids:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
6:devices:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c/init.scope
5:blkio:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
4:cpu,cpuacct:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
3:perf_event:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
2:net_cls,net_prio:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c
1:name=systemd:/kubepods/poda2804054-906f-11e7-9ae4-0cc47ad2a6d4/e0ad06c5691de850c976e780c867a254d71df81b315cdc0e2ace2c36983c946c/init.scope

判断响应内容即可,一般情况下,主要看name & devices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
Need Pkg:
cgroup "github.com/containerd/cgroups"
units "github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
*/
func main() {
paths, err := parseCgroupFile("/proc/1/cgroup")
if err != nil {
t.Fatal(err)
}
dp := strings.TrimPrefix(paths["devices"], "/")
path := PidPath(os.Getpid())
p, err := path("devices")
if err != nil {
t.Fatal(err)
}
if p != filepath.Join("/", dp) {
t.Fatalf("expected self path of %q but received %q", filepath.Join("/", dp), p)
}
}

V3 拓展

如果需要当前协程的父进程信息,会对我们定位很多关键信息起到很大的帮助

1
2
3
4
5
6
7
8
9
10
11
12
13
const processNameFormat = "/proc/%d/comm"
func main() {
fmt.Println(os.Getppid())
ppid := os.Getppid()
bs, err := ioutil.ReadFile(fmt.Sprintf(processNameFormat, ppid))
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(string(bs)) // bs is ppid info
}

EOF