虽然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 读写的。但是可以通过劫持sysinfo
和sysconf
等系统调用实现,不然 jvm 会因为根据宿主系统内存扩大自己的内存,超过 docker 限制后会被 docker 杀掉。
宿主机系统与容器内部的用户权限
Linux 内核负责管理 uid 和 gid,内核级系统调用时,用于确定是否授予该请求权限。例如当进程尝试写入文件时,内核会检查创建进程的 uid 和 gid,以确定它是否有足够的权限来修改文件。此时没有使用用户名,使用了uid。
当在服务器上运行 Docker 容器时,仍然使用同一个内核内核,uids 和 gids 都由单个内核控制。
linux 中显示的用户名(和组名)不是内核的一部分,而是由外部工具 /etc/passwd
、ldap
、kerberos
等管理的。所以实际用用户名去授权的话,并不能在宿主机和容器通用。
在一个新的镜像中通过 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
不起作用。
第二种方法的话,写死了时区,对于存在不同时区的机房的话,也是不适用的。
目前这边采取的做法是构建基础景镜像的时候删除/etc/localtime
, docker run
的时候 -v /etc/localtime:/etc/localtime:ro
k8s ulimit 问题
docker提供了设置ulimit的方法,但是k8s并没有提供。docker的默认值如下
|
|
这个值中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