虽然docker化是大势所趋,但是由于docker的缺陷,容器化过程中还是有不少坑要填。

cpu核数获取

很多情况下我们会用根据cpu核数设置线程数,最后调用的一般都是sysconf(_SC_NPROCESSORS_CONF) / get_nprocs(void) / get_nprocs_conf(void) 等系统调用,最后这些系统调用基本是从 /sys/devices/system/cpu/sys/devices/system/cpu/online 这两个地方获取到系统核数。

docker 并没有虚拟化 /sys 目录,所以在 docker 里面读到的数据是宿主系统的核数。

因为这些数据都是以文件的形式存的,所以可以通过 fuse hook 对此类文件的 read() 调用,通过 cgroup 计算出实际分配给 docker 的核数并返回。

内存

内存通过 sysconf(_SC_PHYS_PAGES) 或者 sysinfo 的方法获取的数据是从内存获取的,所以没法通过 fuse hook 读写的。但是可以通过劫持sysinfosysconf等系统调用实现,不然 jvm 会因为根据宿主系统内存扩大自己的内存,超过 docker 限制后会被 docker 杀掉。

宿主机系统与容器内部的用户权限

Linux 内核负责管理 uid 和 gid,内核级系统调用时,用于确定是否授予该请求权限。例如当进程尝试写入文件时,内核会检查创建进程的 uid 和 gid,以确定它是否有足够的权限来修改文件。此时没有使用用户名,使用了uid。

当在服务器上运行 Docker 容器时,仍然使用同一个内核内核,uids 和 gids 都由单个内核控制。

linux 中显示的用户名(和组名)不是内核的一部分,而是由外部工具 /etc/passwdldapkerberos 等管理的。所以实际用用户名去授权的话,并不能在宿主机和容器通用。

在一个新的镜像中通过 useradd 创建的用户一般是从 1000 开始递增的。这个时候容器内部 uid 1000 的用户对应的是宿主机 uid 1000 的用户(即使他们拥有不同的用户名),造成即使在用容器中用了不同的用户名,映射到宿主系统中都是uid为1000的用户,对用户的权限做不到比较好的隔离。

比较好的做法应该是预先分配好每个用户的 uid,在 dockerfile 中创建用户的时候也指定 uid,正确的把用户 uid 联系在一起。

时区问题

网上一般的做法推荐是 docker run 的时候加上 -v /etc/localtime:/etc/localtime:ro 或者在构建镜像的时候加上 RUN echo "Asia/shanghai" > /etc/timezone

这两种方法其实都存在点问题。

第一种方法挂载 /etc/localtime/etc/localtime 仍然是链到 /usr/share/zoneinfo/UTC。java的话,会判断 /etc/localtime 是不是软链,如果是的话,直接读取链接的内容,从而导致 -v /etc/localtime:/etc/localtime:ro 不起作用。

vm

第二种方法的话,写死了时区,对于存在不同时区的机房的话,也是不适用的。

目前这边采取的做法是构建基础景镜像的时候删除/etc/localtime, docker run 的时候 -v /etc/localtime:/etc/localtime:ro

k8s ulimit 问题

docker提供了设置ulimit的方法,但是k8s并没有提供。docker的默认值如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@c6a187e7349f dev]# ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14963
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65536
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

这个值中open files 只有65535 并没有具有普遍的适用性,既然没办法设置k8s的ulimit,设置一个比较合理的默认值就比较重要了。

可以通过修改docker 的systemd的service 文件 追加 -d --default-ulimit nproc=1024:2048 --default-ulimit nofile=20480:40960等参数达到设置默认值的效果。

docker init进程问题

docker中,如果entrypoint的进程挂掉的话,这个容器就会停止掉,因为docker中没有类似操作系统的initd进程,所以docker无法支持多进程。
解决思路的话就是把entrypoint的进程模拟成init进程,目前类似dumb-init之类的开源项目已经实现了类似的功能的,但是dumb-init 还是无法完美支持多进程,需要定制下,具体的问题请阅读代码并实践下,发现其中的坑 2333333