同一个POD中默认共享哪些名称空间

若是经过POD的形式来启动多个容器那么它们的名称空间会是共享的么,因此我这里讨论是在默认状况下同一个POD的不一样容器的哪些名称空间是打通的。这里先说一下结论,共享的是UTS、IPC、NET、USER。html

UTS名称空间

主机名名称空间,保存内核名称、版本以及主机名和域名。默认状况下同一个POD的不一样容器是共享UTS的,看下面的配置:node

apiVersion: apps/v1
kind: Deployment
metadata:
  name: centos-dep
  labels:
    app: centos
spec:
  replicas: 1
  selector:
    matchLabels:
      app: centos
  template:
    metadata:
      labels:
        app: centos
    spec:
      containers:
      - name: app1
        image: centos
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh", "-c"]
        args:
          - sleep 3600
      - name: app2
        image: centos
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh", "-c"]
        args:
          - sleep 3600

运行这个POD,而后分别登录到不一样容器去查看主机名,你会发现主机名同样,并且就是POD的名字,以下图:docker

另外你经过uname -a若是查看到的内容是一致的也说明是共享UTS名称空间的。json

实验证实,默认状况下同一个POD中的不一样容器的UTS名称空间是共享的。centos

IPC名称空间

进程间通讯名称空间,IPC的隔离就是阻断进程间通讯,主要是信号量、队列和共享内存。运行主机进程经过上面的机制进行通讯。api

下面经过一个实验来看看同一个POD的IPC名称空间是不是共享的,在app2中经过命令ipcmk --queue来建立一个队列,而后在app1中经过命令ipcs来查看,若是有这个队列就说明是共享的,以下图:安全

实验证实,默认状况下同一个POD中的不一样容器的IPC名称空间是共享的。bash

MNT名称空间

Mount名称空间,提供对磁盘挂载点和文件系统的隔离能力。同一主机上的不一样进程访问相同的路径会获得相同的内容,由于它们共享本地主机的磁盘和文件系统。网络

在同一POD内容器之间挂载点名称空间是隔离的,若是该POD的多个容器挂载一个POD级别的Volume,那么它们就能够实现挂载点的共享,但共享的也仅仅是这一个Volume并非整个文件系统。app

实验证实,默认状况下同一个POD中的不一样容器的MNT名称空间不是共享的。

NET名称空间

网络名称空间,同一主机上的不一样进程能够进行localhost或者本地unix socket通讯。在单独启动容器的时候不一样容器是隔离的,可是在POD中不一样容器经过一个Infra容器来进行共享网络名称空间,其原理是其余用户本身定义的容器都Join这个Infra容器的网络。这里我启动的就是一个Cetnos镜像,没法作本地通讯验证。不过它的确是经过Infra容器来共享的。

PID名称空间

进程ID名称空间,同一主机上的不一样进程在同一PID空间内能够看到其余进程的ID,而且同一PID空间的进程的ID不会重复。另外PID名称空间有层级关系,子空间看不到父空间的内容,可是父空间能够管理子空间,好比发送信号。

在POD中则对应为同一POD内的不一样容器能够看到对方的进程ID。默认不是共享的,能够设置POD的shareProcessNamespace这个值为true来进行共享,默认为false。我在App2中启动一个top命令,而后在App1中经过ps命令查看,看下面的测试:

实验证实,默认状况下同一个POD中的不一样容器的PID名称空间不是共享的。

USER名称空间

隔离用户、组以及相关用户能力的。也就是在不一样的User Namespace中,相同的用户能够有不一样的UID或者不一样的权限。另外还能够经过映射的方式把某个User Namespace的用户映射到另一个User Namespace的用户上,这样这两个名称可能不一样的用户就具备相同的权限。若是想要在本机进行验证须要查看一下这个文件:

cat /proc/sys/user/max_user_namespaces若是是0则表示没有开启,须要给它一个值echo "15000" > /proc/sys/user/max_user_namespaces,而后你再运行unshare -U或者unshare --user就不会报错了。

在Docker中默认并无开启user namespace。

能够看到当前Bash进程和Dockerd进程的名称空间都同样,由于它们都是在同一个名称空间上运行的。另外须要说明的是uip_map的输出,第一个数字是在当前名称里的用户ID,第二个数字是该用户ID在当前名称空间外部被映射到哪一个用户ID上,最后一个数字是映射范围。

而后咱们启动一个包含两个容器的POD来看一下,以下图:

容器的User namespace和容器外的是同样的,也就是说没有单独为容器建立User namespace,并且容器内的用户ID是0,映射到容器外也是0,这就是意味着容器内的root用户和容器外的root用户拥有相同的权限。说白了就是容器中的进程是以root用户权限运行的,而且这个容器中的root用户和宿主机上的root用户是同一个,看下图,这2个容器进程就是以root运行的:

若是你须要验证,那么你把宿主机上的一个只能由root打开的文件挂载容器中,你看看能不能打开就知道了。

就算你进入容器查看这个sleep 3600其实也是root运行的,简单来讲容器内UID为0的root用户就是容器外UID为0的root用户。为何会是这样呢?在整个系统共享一个内核,而内核只管理一套uid和gid,而且对内核来讲只识别uid不识别用户名,也就是说内核在作权限方面它经过uid来作,用户名只是对于用户来说方便辨认。

不要误认为你在容器中建立一个用户,而后在宿主机也能够看到,由于/etc/password这个文件在不一样的文件系统上,容器和宿主机的文件系统仍是隔离的。

但有些时候也不要被用户名所迷惑,你应该检查UID,查看容器进程的uid_map中的信息。

让容器进程使用root帐号显然不安全,由于它的root就是宿主机的root,因此一般咱们会给dockerd进程创建单独的帐号或者使用User Namespace。不过推荐使用User Namesapce,由于有些使用容器进程必须以root来运行,若是使用User Namespace的话,咱们就能够把宿主机的一个普通用户映射到容器中的root用户,这样容器进程觉得本身是root而且在它所在的名称空间内有各类权限,可是在宿主机上它仍是普通用户。

如何开启User Namespace呢:

cat /boot/config-3.10.0-957.el7.x86_64 | grep _NS,先检查一下你的内核是否开启了User Namespace

检查一下是否有下面的文件,若是没有就手动创建:

你可使用系统中有的用户而后添加到这里,最后在docker的启动参数中加入这个帐号,也可让dockerd本身来创建,若是让dockerd本身来完成,在dockerd的启动docker-daemon.json中加入下面的内容,default表示使用dockerd去创建帐号,它使用的名字为dockermap,若是你使用本身的就替换dufault:

{
    "userns-remap": "default",
}

在RHEL 7.5版本,上面的配置在dockerd启动的时候会报错"Can't create ID mappings: %!v(MISSING): No subuid ranges found for user "dockremap"",查询以后判断应该是系统BUG,能够看看Redhat官网的Bug说明Bug-1546870,它会在系统中创建dockremap帐号而后使用usermod -v参数来设置dockermap用户的ID范围,可是在Centos 7.5版本上的usermod命里没有-v参数。这就意味着RHEL 7.5不支持动态添加subid。因此咱们只能手动来作,不过听说其余发行版能够支持好比Ubantu或者Fedora。

向从属用户和组文件中添加范围(若是你使用dockremap帐号,那么你无须手动创建,由于dockerd启动的时候就会创建,若是上面的配置是default):

echo "dockremap:10000:65536" > /etc/subuid
echo "dockremap:10000:65536" > /etc/subgid

一共三个字段:

  • 第一个字段dockremap,这个一个宿主机上的用户名

  • 第二个字段10000,表示子User Namespace中用户ID从哪里开始

  • 第三个字段65536,表示子User Namespace中能够有多少个用户ID

总体含义是宿主机的dockremap帐号一共有65536个从属用户,用户ID从10000-165535。这个从事用户的ID不是真实的,只是用来分配,它会从这个范围里拿一个ID映射到容器进程里的用户,好比容器进程仍是用root用户,其UID实0,那么咱们就能够从dockremap这个从属ID中拿一个来映射容器进程中的root。这样容器中看起来是root且具备root权限,可是在宿主机上它就是一个普通帐号dockremap的权限。配置好后重启dockerd进程。配置好从新启动POD,以下:

同一个POD中的User Namespace是共享的,但此时它与宿主机的进程就已经不共享User Namespace了。再看一下uid_map

容器中的UID0映射到容器外的从属ID 10000。

不过这样虽然安全可是有些容器进程不管在容器内仍是在容器外都须要root帐号,好比prometheus的node_explorer,它是以DaemonSet形式运行的须要共享宿主机的网络名称空间,若是以上的用户来运行则会启动失败,以下图:

其实这个和DaemonSet不要紧,主要是在docker上启用User Namspace后会有一些限制,userns-remap ,也就是说启用了User Namespace后容器将不能共享宿主机的PID和NET名称空间。因此我想由于有一些限制因此docker默认才不开启User Namespace。不过若是直接经过docker来启动容器能够指定--usens=host来为某个容器禁用User Namespace,不过在Kubernetes中目前没找到配置POD那个参数能够起到这个效果,有人知道请留言。

实验证实:在默认状况下同一个POD是共享User Namespace的。

最简单的办法来验证一下

在宿主机上找到该POD中的2个容器的容器ID,

经过docker inspect CONTINER_ID --format {{.State.Pid}}查看两个容器在宿主机上的进程号

经过进程ID查看每一个进程的ns状况,左侧红色的是被查看进程名称空间文件,右侧则是该文件指向的具体的Namespace文件,中括号里面的是具体Namespace文件号,若是两个进程的指向的Namespace文件号相同,则说明它们处在同一名称空间。

红色箭头编号相同的就是当前POD中2个容器所共享的名称空间。不过在这里我也有些不明白,uts是共享的但是上图中看到的编号确不同。由于在宿主机的当前终端运行unshare --uts /bin/bash命令将会在一个新的uts名称空间打开一个bash程序,这个bash进程和以前那个就是在不一样的uts中,看下图:

进行namespace的api操做

对Namespace的API操做包括clone()、setns()和unshare()。它们有一些不一样:

  • clone():建立新进场的同时能够建立namespace,经过在这个函数中加入不一样的名称空间标志来完成。

  • setns():它是加入一个已经存在的namespace,须要给它传递具体的namespace文件描述符。一般是在调用该函数以后调用clone(),其目的就是让一个新进程在一个已经存在的namespace中运行。docker exec就是利用这种机制让你指定的命令在容器中运行。

  • unshare():对当前的进程进行namespace隔离,换句话说它不启动新进程,而是让当前进程或者调用它的进程进入到一个新的namespace中。系统命令unshare就是利用这个调用来实现的。

注意在使用unshare系统调用或者命令或者setns系统调用的时候当涉及到PID Namespace的时候它的处理有些特殊,并非让调用者进入新的PID Namespace,而是让子进程进入,成为该PID Namespace的1号进程。为何为这样呢?由于一个进程的PID在系统中是常量,一但一个进程运行它的PID就肯定了从而它的父子进程也会被肯定,因此不能让它在调用setns或者unshare的时候发生变化,一但变化系统就没法维护这个进程表。

Namespace的资源隔离

Docker背后的内核知识1

Linux Namespace User

理解Docker容器的UID和GID

隔离Docker容器中的用户

相关文章
相关标签/搜索