博客 > 术业专攻> 云计算> docker> docker学习笔记–从docker容器时间问题探究到Namespace问题 2019年08月29日 11:23:44
在测试环境已经全面使用k8s部署了,今天突然有一个测试同学,因为特殊场景,希望更改一下服务对应的容器时间,当时我心想,这不是挺简单的。
于是就来到容器当中,执行如下命令进行更改:
[root@a-admin-f478dbd55-ddv7x /]# date
Tue Jun 18 14:01:34 CST 2019
[root@a-admin-f478dbd55-ddv7x /]# date -s 05/28
date: cannot set date: Operation not permitted
Tue May 28 00:00:00 CST 2019
[root@a-admin-f478dbd55-ddv7x /]# date
Tue Jun 18 14:01:48 CST 2019
报了一个date: cannot set date: Operation not permitted
的错误,而且也没有更改成功。当时还不知道一些深层次的原因,于是开始了搜索之旅。
最先搜索到的,是解决date命令报错的问题:需要在启动容器的时候,加上--cap-add SYS_TIME
的参数。
docker run -it --cap-add SYS_TIME --name centos centos /bin/bash
简单说明一下这里添加的参数,深入的后边探讨。
在添加这些特殊权限的时候,可以有如下几种操作:
--cap-add:
给容器添加某个权限。--cap-drop:
去掉容器的某个权限。--privileged:
将所有的权限添加给容器。--cap-drop ALL:
去掉所有权限。然后再进入到容器里,重复执行上边改时间的操作,就能成功了。
因为目前使用rancher进行k8s的管理,因此挂载的方式如下,可点击pod升级
,在高级选项
的最后,有增加内核
的选项,选中SYS_TIME
,然后点击升级
即可。
事实上可能问题到这里,就已经没有问题了,但是接着测试同学发现,时间会自动又变成现在的时间了,以至于无法进行测试,我当时心想,难道说,k8s自己有这样的时间修正机制么,,,
后来想到会不会是定时任务的因素,于是乎,来到了刚刚那个容器所在的主机之上,一看,果然有时间校准的定时任务,每分钟执行一次,于是注释掉,再次更改容器时间,这次才真正的生效,与此同时,发现了另外一个重大问题,那就是容器时间变更为5月28日之后,宿主机的时间也跟着变更了,让我一阵儿惶恐,尽管这是在测试环境,宿主机的时间更改可也不是闹着玩的呀
到后来才知道,原来上边操作的 --cap-add SYS_TIME
是为了将宿主机的内核时间挂载进来与容器共享,因此容器时间更改了,宿主机时间也会跟着更改。说到底,还是对docker理解不深的缘故,但是借着这个机缘,倒是静下心来,看了如下的一些资料,让自己对容器技术有了新的一层认识。
cgroup
和namespace
,分别对容器进行资源限制
和资源隔离
。
cgroup用来限制容器的资源使用量
,避免单个容器耗尽系统资源。namespace用来隔离容器与宿主机,以及不同的容器。
这里,就先针对namespace这个概念进行一下探析。
Namespace | 系统调用参数 | 隔离内容 | 内核版本 |
---|---|---|---|
UTS | CLONE_NEWUTS | 主机名与域名 | 2.6.19 |
IPC | CLONE_NEWIPC | 信号量,消息队列和共享内存 | 2.6.19 |
PID | CLONE_NEWPID | 进程编号 | 2.6.24 |
Network | CLONE_NEWNET | 网络设备,网络栈,端口等 | 2.6.29 |
Mount | CLONE_NEWNS | 挂载点(文件系统) | 2.4.19 |
User | CLONE_NEWUSER | 用户和用户组 | 3.8 |
如上列出的这六项,是目前构建一个容器所默认赋予的基础能力,可以有别于原宿主机而在容器当中单独存在(当然还是基于宿主机),这个时候就能够理解,上边改时间的操作之所以失败,就是因为默认情况下,容器是没有Time(时间)这个Namespace的,通过--cap-add SYS_TIME
挂载也是引用了系统的内核时间,因此改了容器时间之后,系统时间跟着变化,也就不稀奇了。
UTS(UNIX Time-sharing System)namespace提供了主机名与域名的隔离,这样每个docke容器就可以拥有独立的主机名和域名了,在网络上可以被视为一个独立的节点,而非宿主机上的一个进程。
docker中,每个镜像基本都以自身所提供的服务名称来命名镜像的hostname,且不会对宿主机产生任何影响,其原理就是使用了UTS namespace。
进程间通信(Inter-Process Communication,IPC)涉及的IPC资源包括常见的信号量、消息队列和共享内存。申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace中实际上包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,不同IPC namespace下的进程则互相不可见。
目前使用IPC namespace机制的系统不多,其中比较有名的有PostgreSQL。Docker当前也使用IPC namespace实现了容器与宿主机、容器与容器之间的IPC隔离。
PID namespace隔离非常实用,它对进程PID重新标号,即两个不同namespace下的进程可以有相同的PID。每个PID namespace都有自己的计数程序。内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,被称为root namespace,它创建的心PID namespace被称为child namespace(树的子节点),而原先的PID namespace就是新创建的PID namespace的parent namespace(树的父节点)。通过这种方式,不同的PID namespace会形成一个层级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。反过来,子节点却不能看到父节点PID namespace中的任何内容,由此产生如下结论。
mount namespace通过隔离文件系统挂载点对隔离文件系统提供支持,它是历史上第一个Linux namespace,所以标示位比较特殊,就是CLONE_NEWNS
。隔离后,不同的mount namespace中的文件结构发生变化也互不影响。也可以通过/proc/[pid]/mounts查看到所有挂载在当前namespace中的文件系统,还可以通过/proc/[pid]/mountstats看到mount namespace中文件设备的统计信息,包括挂载文件的名字、文件系统的类型、挂载位置等。
进程在创建mount namespace时,会把当前的文件结构复制给新的namespace。新namespace中的所有mount操作都只影响自身的文件系统,对外界不会产生任何影响。这种做法非常严格的实现了隔离,但对某些状况可能并不适用。比如父节点namespace中的进程挂载了一张CD-ROM,这时子节点namespace复制的目录结构是无法自动挂载上这张CD-ROM的,因为这种操作会影响到父节点的文件系统。
一个挂载状态可能为以下一种:
传播事件的挂载对象称为共享挂载;接收传播事件的挂载对象称为从属挂载;同时兼有前述两者特征的挂载对象为共享/从属挂载;既不传播也不接受事件的挂载对象称为私有挂载;另一种特殊的挂载对象称为不可绑定挂载,它们与私有挂载相似,但不允许执行绑定挂载,即创建mount namespace时这块文件对象不可被复制。
network namespace主要提供了关于网络资源的隔离,包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、socket等。一个物理的网络设备最多存在于一个network namespace中,可以通过创建veth pair(虚拟网络设备对:有两端,类似管道,如果数据从一端传入另一端也能接受,反之亦然)在不同的network namespace间创建通道,以达到通信目的。
也许你会好奇,在建立起veth pair之前,新旧namespace该如何通信呢?答案是pipe
(管道)。以Docker daemon启动容器的过程为例,假设容器内初始化的进程称为init。Docker daemon在宿主机上负责创建这个veth pair,把一段绑定到docker0网桥上,另一端介入新建的network namespace进程中。这个过程执行期间,Docker daemon和init就通过pipe进行通信。具体来说,就是在Docker deamon完成veth pair的创建之前,init在管道的另一端循环等待,直到管道另一端传来Docker daemon关于veth设备的信息,并关闭管道。init才结束等待的过程,并把它的“eth0”启动起来。
与其他namespace类似,对network namespace的使用其实就是在创建的时候添加CLONE_NEWNET标识符位。
user namespace主要隔离了安全相关的标识符(identifier)和属性(attribute),包括用户ID、用户组ID、root目录、key(指密钥)以及特殊权限。通俗地讲,一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是它创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。
user namespace时目前的6个namespace中最后一个支持的,并且直到linux内核3.8版本的时候还未完全实现(还有部分文件系统不支持)。user namespace实际上并不算完全成熟,很多发行版担心安全问题,在编译内核的时候并未开启USER_NS。Docker在1.10版本中对user namespace进行了支持。只要用户在启动Docker daemon的时候制定了–user-remap,那么当用户运行容器时,容器内部的root用户并不等于宿主机的root用户,而是映射到宿主机上的普通用户。
Docker不仅使用了user namespace,还使用了在user namespace中涉及的Capability机制。从内核2.2版本开始,Linux把原来和超级用户相关的高级权限分为不同的单元,称为Capability。这样管理员就可以独立的对特定的Capability进行使用或禁止。Docker同时使用namespace和Capability,这很大程度上加强了容器的安全性。
这些似乎都是docker相关的基础性概念,过去也许并非没有看过,但都一带而过没有留下印象,若非今日时间问题这个机缘,恐怕自己也还一直不懂得这些原理,有时候学习就是如此,要看缘分。重要的是,如果缘分来了,不要错失!
© 2018 www.qingketang.net 鄂ICP备18027844号-1
武汉快勤科技有限公司 13554402156 武汉市东湖新技术开发区关山二路特一号国际企业中心6幢4层7号
扫码关注,全站教程免费播放
订单金额:
支付金额:
支付方式: