404频道

学习笔记

nsenter是一个命令行工具,用来进入到进程的linux namespace中。

docker提供了exec命令可以进入到容器中,nsenter具有跟docker exec差不多的执行效果,但是更底层,特别是docker daemon进程异常的时候,nsenter的作用就显示出来了,因此可以用于排查线上的docker问题。

CentOS用户可以直接使用yum install util-linux来进行安装。

启动要进入的容器:docker run -d ubuntu /bin/bash -c "sleep 1000"

获取容器的pid可以使用`

要进入容器执行如下命令:

1
2
3
4
# 获取容器的pid
docker inspect 9f7f7a7f0f26 -f '{{.State.Pid}}'
# 进入pid对应的namespace
sudo nsenter --target $PID --mount --uts --ipc --net --pid

ref

题图为北京城西部的潭柘寺,始建于西晋年间,有“先有潭柘寺,后有幽州城”的说法。

明朝燕王朱棣听取了重臣姚广孝的建议后,起兵“靖难”,并成功夺取皇位。朱棣继皇帝位后,姚广孝辞官到京西的潭柘寺隐居修行。据说当年修建北京城时,设计师就是姚广孝,他从潭柘寺的建筑和布局中获得了不少灵感。

资源

1.virtual-kubelet

很多公有云厂商都提供了弹性容器服务实例,比如阿里云的ECI(Elastic Container Instance)、AWS Fargate、Azure Container Instances等,但这些平台都提供了私有的API,与kubernetes的API不兼容。该项目将公有云厂商的的容器组虚拟为kubernetes集群中的一个超级node,以便支持kubernetes的API,与此同时失去了很多kubernetes的特性。

2.Kata Containers

容器在部署服务方面有得天独厚的优势,但受限于内核特性,在隔离性和安全性方面仍然较弱。虚拟机(VM)在隔离性和安全性方面都比较好,但启动速度和占用资源方面却不如容器。Kata Containers项目作为轻量级的虚拟机,但提供了快速的启动速度。同时支持Docker容器的OCI标准和kubernetes的CRI。目前华为公有云已经将此技术用于生产环境中。

3.Knative

在今年的Google Cloud Next大会上,Google发布了Knative, 这是由Google、Pivotal、Redhat和IBM等云厂商共同推出的Serverless开源工具组件,它与Istio,Kubernetes一起,形成了开源Serverless服务的三驾马车。

4.naftis

小米信息部武汉研发中心开源的istio的dashboard。

5.kubespy

用来查看kubernetes中资源实时变化的命令行工具。

6.Md2All

如果你已经习惯了markdown写作,在微信公账号发文时,可以使用该工具渲染后,将文章复制到微信公众号后台。

精彩文章

1.阿里云的这群疯子

从记者的视角来了解阿里云的历史。

2.王垠最近博客-更新一下

曾经以天才自居桀骜不驯傲视一切的垠神,突然变得温顺了许多,开始意识到自己的缺点,开始享受生活。

3.为何“秀恩爱,死得快”?我是认真的

用量子物理学的知识来解释为啥“秀恩爱,死得快”。

4.CTO、技术总监、首席架构师的区别

5.面对云厂商插管吸血,MongoDB使出绝杀

半年后看下MongoDB的修改开源协议的做法在国内奏效否。

6.终于明白了 K8S 亲和性调度

通过该文章,已经差不多可以了解kubernetes调度的亲和性、反亲和性、taint和toleration机制了。

7.微软资深工程师详解 K8S 容器运行时

图书

1.鸟哥的Linux私房菜:基础学习篇(第四版)

鸟哥的linux私房菜终于出新版了,最新版本是基于CentOS7的。

电影

1.嗝嗝老师

电影讲述了印度贫民窟中的孩子在学校上学总是遭受歧视不爱学习各种调皮捣蛋,在一位新老师来了后,将学生们带向正轨的故事。印度电影总能将平凡的电影演绎的很魔性,单就这些故事就已经足够了。偏偏这位老师还是抽动秽语综合征患者,在受到老师和学生们的双重歧视下,给故事情节增加了许多感人和励志色彩。

强迫症患者谨慎观看,看完电影后,总感觉得抽搐两下才舒服。

精彩句子

货币的贬值,是永恒的趋势。明天的物价,一定比今天的贵。
你想要赚钱,就一定要把今天的钱,换成明天的物。
而且时间越紧凑越好。

八年之后 房价多少?

今年以来,网络上一直在传言济南市要吞并莱芜市的消息,最近几天尤甚。市民们纷纷去市政府门前拍照留念,纪念莱芜市的最后一天,虽然到今天为止传言还未变成现实,但应该是迟早要到来的。

有趣的是,莱芜市是1993才从泰安市中独立出来,很多人都感慨道:出生是泰安人,长大是莱芜人,明天变成济南人。地理位置上而言,莱芜跟济南搭界,而且莱芜市是山东省17地市中面积最小的一个。

山东的发展策略一直是各地市全面发展,济南市作为省会,在中国城市中的存在感确实不够强,吞并莱芜后也一样不会变强太多,一个地市要想变强,要从多方面找原因。

资源

1.k8s-deployment-strategies

kubernetes内置的deployment和statefulset对象往往很难满足企业的部署需求,比如蓝绿发布、金丝雀发布等,Github上的项目介绍了其他部署方式在kubernetes上的具体实现方式。

2.Let’s Encrypt

https越来越普及,通常CA颁发的证书都是收费的,Let’s Encrypt是一家非盈利的CA机构,为广大的小型站点和博客博主提供了非常大的帮助。Github pages中也是采用了Let’s Encrypt来提供自定义域名的https服务支持。

3.openmetrics

监控领域存在多款开源软件,比如premetheus、influxdb、opentsdb等,每种软件的写入数据格式都不一致,该开源项目旨在定义监控数据的标准格式,目前支持premetheus的文本格式和protobuf两种格式。该项目目前还在起步阶段,已经加入CNCF,期待后续一统行业标准。

4.NATS

Go语言实现的消息队列,目前已经加入CNCF。

5.sequel fumpt

sql的在线格式化工具。

6.The Linux Audit Project

Linux下的日志审计工具,CentOS系统下默认安装,可以通过man auditd看到该工具的说明。

7.kafkabridge

360开源的kafka客户端库的封装,只需调用极少量的接口,就可完成消息的生产和消费。支持多种语言:c++/c、php、python、golang。

精彩文章

1.Keyhole,Google Maps发展史

文章为微信公众号余晟以为的系列文章,大部分素材来源于《Never Lost Again》一书,该书作者为Bill Kilday,Keyhole和Google Maps团队的核心成员。文章介绍了Google Maps的前身Keyhole的创业史,后被Google收购后,又推出了基于web的Google Maps产品,继而开发出了Google Earth产品。即使在Google内部,也存在团队之间的孤立及不信任问题。

2.Kubernetes 调度器介绍

文章对kubernetes的kube-scheduler的整体流程介绍的比较清晰。

3.A Brief History of Alibaba Founders

阿里巴巴的18罗汉介绍。

图片

1.电传打字机设备(Teletype)

tty

早期的计算机设备比较笨重,计算机放在单独的一个房间中,操作计算机的人坐在另外一个房间中,通过终端机设备来操作计算机。

早期的终端设备为电传打字机(Teletype),该设备价格比较低廉,通过键盘输入,并将输出内容打印出来。图中的设备为ASR-33,在YouTube上可以可以看到视频

有意思的是,实际上Teletype的出现要早于计算机,原本用于在电报线路上发送电报,但是后来计算机出现后直接拿来作为计算机的终端设备。

在linux操作系统中设计了tty子系统用于支持tty设备,并将具体的硬件设备放到/dev/tty*目录下,这里的tty设备即Teletype。不过后来随着其他终端设备的引入,tty这个名字仍旧保留了下来,tty目前已基本代表终端的总称。

玉渡山

题图为北京玉渡山风景区中的盘山公路,旁边有个观景台,在观景台上可以鸟瞰官厅水库。

资源

1.Intel RDT

Intel RDT(Resource Director Technology)资源调配技术框架,包括高速缓存监控技术(CMT)、高速缓存分配技术(CAT)、内存带宽监控(MBM)和代码和数据优先级(CDP),容器技术的runc项目中使用到了CAT技术来解决cgroup下的CPU的三级缓存隔离性问题。

在linux 4.10以上内核中通过资源控制文件系统的方式来提供给用户接口,类似cgroup的管理方式。

感兴趣的可以了解下runc项目源码

2.Thanos

Thanos

Prometheus作为Google内部监控系统Borgmon的开源实现版本,存在高可用和历史数据存储两个致命的缺点,Thanos利用Sidecar等技术来解决Prometheus的缺点。

3.netshoot

1
2
3
4
5
6
                    dP            dP                           dP
88 88 88
88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P
88' `88 88ooood8 88 Y8ooooo. 88' `88 88' `88 88' `88 88
88 88 88. ... 88 88 88 88 88. .88 88. .88 88
dP dP `88888P' dP `88888P' dP dP `88888P' `88888P' dP

用于排查docker网络问题的工具,以容器的方式运行在跟要排查问题的容器同一个网络命名空间中,该容器中已经具备了较为丰富的网络命令行工具,用于排查容器中的网络问题。

4.bat

用来替代cat的命令行工具,支持语法高亮、自动分页。mac下可直接使用brew install bat来安装。

image

5.asciiflow

asciiflow

写博客的往往都比较痛恨图片的存储问题,尤其是使用markdown语法写作的,图片往往需要图床来存储,常常跟文章不在一起存储。asciiflow是较为小众的一款ascii图形工具,可以应付较为简单的图形绘制,直接以文字的形式呈现简单图形,省去了存储图片的繁琐。

6.processon

processon

免费的在线图行绘制协作工具,支持流程图、思维导图等多种图形,有类似visio的使用体验,同时是web版的,支持多人协作。我目前在使用,不过免费版有使用限制。

精彩文章

1.手把手教你打造高效的 Kubernetes 命令行终端

文中汇总了各种可以取代kubernetes的命令行kubectl的工具,以便提供更方便的操作,比如更完善的自动补全。

2.Understand Container - Index Page

学习容器的cgroup和namespace的系列文章。

3.gVisor是什么?可以解决什么问题?

docker容器技术基于cgroup和namespace来实现,但系统调用仍是调用宿主机的系统调用,比如在其中一个容器中通过系统调用修改了当前系统时间,在其他容器中看到的时间也已经修改过了,这显然不是符合期望的,通常可以通过Seccomp来限制容器中的系统调用。

gVisor为Google开源的容器Runtime,通过pstrace技术来截获系统调用,从而保证系统的安全。目前还不成熟,单就凭Google的开源项目,该项目还是非常值得关注的。

4.Use multi-stage builds

Dockerfile的多阶段构建技术,对于解决编译型语言的发布非常有帮助,可以在其中一个image中编译源码,另外一个image用于将编译完成后的二进制文件复制过来后打包成单独的线上运行镜像。而这两部操作可以合并到一个Dockerfile中来完成。

5.唯品会Noah云平台实现内幕披露

唯品会内部云平台的实践,涉及到大量的干货,值的花时间一读。

App推荐

1.Nike Training

健身类app我用过keep、火辣健身、FitTime(以收费课程居多),偶然间在AppStore上看到了Nike Training,如果厌倦了国内的健身类app,不防尝试一下。

新奇

1.手机QQ扫一扫

用手机QQ扫一扫100元人民币正面,可以出现浮动的凤凰图案,并会跳转到人民币鉴别真伪的视频页面,视频效果确实不错,忍不住会多扫描几遍。

2.kubeadm

kubernetes的组件非常多,部署起来非常复杂,因此社区就推出了kubeadm工具来简化集群的部署,将除了kubelet外的其他组件都部署在容器中。令人惊奇的是,kubeadm几乎完全是一个芬兰高中生Lucas KaIdstrom的作品,是他在17岁时利用业余时间完成的一个社区项目。

传统的unix权限模型将进程分为root用户进程(有效用户id为0)和普通用户进程。普通用户需要root权限的某些功能,通常通过setuid系统调用实现。但普通用户并不需要root的所有权限,可能仅仅需要修改系统时间的权限而已。这种粗放的权限管理方式势必会带来一定的安全隐患。

linux内核中引入了capability,用于消除需要执行某些操作的程序对root权限的依赖。

capability用于分割root用户的权限,将root的权限分割为不同的能力,每一种能力代表一定的特权操作。例如,CAP_SYS_MODULE用于表示用户加载内核模块的特权操作。根据进程具有的能力来进行特权操作的访问控制。

只有进程和可执行文件才有能力,每个进程拥有以下几组能力集(set)。

  • cap_effective: 进程当前可用的能力集
  • cap_inheritable: 进程可以传递给子进程的能力集
  • cap_permitted: 进程可拥有的最大能力集
  • cap_ambient: Linux 4.3后引入的能力集,
  • cap_bounding: 用于进一步限制能力的获取

可以通过/proc/${pid}/status文件中的CapInh CapPrm CapEff CapBnd CapAmb来表示,每个字段为8个字节即64bit,每个比特表示一种能力,这几个字段存放在进程的内核数据结构task_struct中,由此可见capability的最小单位为线程,而不是进程。

example 1 设置进程能力

在执行下面程序之前需要安装yum install libcap-devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#undef _POSIX_SOURCE
#include <sys/capability.h>

extern int errno;

void whoami(void)
{
printf("uid=%i euid=%i gid=%i\n", getuid(), geteuid(), getgid());
}

void listCaps()
{
cap_t caps = cap_get_proc();
ssize_t y = 0;
printf("The process %d was give capabilities %s\n",(int) getpid(), cap_to_text(caps, &y));
fflush(0);
cap_free(caps);
}

int main(int argc, char **argv)
{
int stat;
whoami();
stat = setuid(geteuid());
pid_t parentPid = getpid();

if(!parentPid)
return 1;
cap_t caps = cap_init();

// 给进程增加5中能力
cap_value_t capList[5] ={ CAP_NET_RAW, CAP_NET_BIND_SERVICE , CAP_SETUID, CAP_SETGID,CAP_SETPCAP } ;
unsigned num_caps = 5;
cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET);
cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET);
cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET);

if (cap_set_proc(caps)) {
perror("capset()");

return EXIT_FAILURE;
}
listCaps();

// 将进程的能力清除
printf("dropping caps\n");
cap_clear(caps); // resetting caps storage
if (cap_set_proc(caps)) {
perror("capset()");
return EXIT_FAILURE;
}
listCaps();

cap_free(caps);
return 0;
}

并执行如下操作:

1
2
3
4
5
6
7
8
9
10
gcc capability.c -lcap -o capability

# 需要使用root执行,因为普通用户不能给进程设置能力
sudo ./capability

# 输出如下内容
uid=0 euid=0 gid=0
The process 5044 was give capabilities = cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw+eip
dropping caps
The process 5044 was give capabilities =

example 2 获取进程能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#undef _POSIX_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/capability.h>
#include <errno.h>

int main()
{
struct __user_cap_header_struct cap_header_data;
cap_user_header_t cap_header = &cap_header_data;

struct __user_cap_data_struct cap_data_data;
cap_user_data_t cap_data = &cap_data_data;

cap_header->pid = getpid();
cap_header->version = _LINUX_CAPABILITY_VERSION_1;

if (capget(cap_header, cap_data) < 0) {
perror("Failed capget");
exit(1);
}
printf("Cap data 0x%x, 0x%x, 0x%x\n", cap_data->effective,cap_data->permitted, cap_data->inheritable);
}

可以通过capget命令获取进程的能力

1
2
3
4
5
6
7
8
[vagrant@localhost tmp]$ gcc get_capability.c -lcap -o get_capability
# 普通用户默认情况下没有任何能力
[vagrant@localhost tmp]$ ./get_capability
Cap data 0x0, 0x0, 0x0

# root用户默认拥有所有的能力
[vagrant@localhost tmp]$ sudo ./get_capability
Cap data 0xffffffff, 0xffffffff, 0x0

工具

  1. getcap用于获取程序文件所具有的能力。
  2. getpcaps用于获取进程所具有的能力。
  3. setcap用于设置程序文件所具有的能力。
  4. capsh 查看和设置程序的能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# chown命令授权给普通用户也具备更改文件owner的能力
# 其中eip分别代表cap_effective(e) cap_inheritable(i) cap_permitted(p)
[vagrant@localhost tmp]$ sudo setcap cap_chown=eip /usr/bin/chown
[vagrant@localhost tmp]$ getcap /usr/bin/chown
/usr/bin/chown = cap_chown+eip

# 使用root创建测试文件
[vagrant@localhost tmp]$ sudo touch /tmp/aa
# 普通用户也可以修改root用户创建文件的owner了
[vagrant@localhost tmp]$ chown vagrant:vagrant /tmp/aa

# 清除chown的能力
[vagrant@localhost tmp]$ sudo setcap -r /usr/bin/chown
[vagrant@localhost tmp]$ getcap /usr/bin/chown

# 获取到进程的 capability
[vagrant@localhost tmp]$ cat /proc/95373/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000

# 对上述 capability 进行解码
[vagrant@localhost tmp]$ capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36,37

runc项目中的应用

runc的容器配置文件spec.Process.Capabilities可以定义各个能力集的能力,用来限制容器的能力。

docker中的应用

docker默认情况下给容器去掉了一些比较危险的capabilities,比如cap_sys_admin

例如在docker中使用gdb命令默认是不允许的,这是因为docker已经将SYS_PTRACE相关的能力给去掉了。

在docker中使用--cap-add--cap-drop命令来增加和删除capabilities,

可以使用--privileged赋予容器所有的capabilities,该操作谨慎使用。

ref

user namespace是所有namespace中实现最复杂的一个,也是最晚引入的一个,是在linux2.6版本中才引入。因为涉及到权限机制,跟capability有着比较密切的关系。

创建user namespace

在clone或者unshare系统调用使用CLONE_NEWUSER参数后,在子进程中看到的uid和gid跟父进程中的不一样,子进程中找不到uid时,会显示最大的uid 65534(在/proc/sys/kernel/overflowuid中设置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vagrant@ubuntu-xenial:/tmp$ id
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant)
vagrant@ubuntu-xenial:/tmp$ readlink /proc/$$/ns/user
user:[4026531837]

# 使用unshare命令创建新的user namespace
vagrant@ubuntu-xenial:/tmp$ unshare --user /bin/bash
nobody@ubuntu-xenial:/tmp$ readlink /proc/$$/ns/user
user:[4026532145]
# 新的user namespace没有映射关系,默认使用/proc/sys/kernel/overflowuid中定义的user id和/proc/sys/kernel/overflowgid中的group id
nobody@ubuntu-xenial:/tmp$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

#--------------------------第二个shell窗口----------------------
# 在父user namespace上创建文件夹,可以看到用户为vagrant
vagrant@ubuntu-xenial:/tmp$ mkdir test
vagrant@ubuntu-xenial:/tmp$ ll /tmp | grep test
drwxrwxr-x 2 vagrant vagrant 4096 Sep 23 17:11 test/

#--------------------------第一个shell窗口----------------------
# 在新创建的user namespace下用户显示为nobody
nobody@ubuntu-xenial:/tmp$ ll /tmp | grep test
drwxrwxr-x 2 nobody nogroup 4096 Sep 23 17:11 test/

映射user id和group id到新的user namespace

创建完新的user namespace后,通常会先映射user id和group id,方法为添加映射关系到/proc/${pid}/uid_map和/proc/${pid}/gid_map中。

user namespace被创建以后,第一个进程被赋予了该namespace的所有权限,但该进程并不拥有父namespace的任何权限。利用该机制可以做到一个用户在父user namespace中是普通用户,在子user namespace中是超级用户的功能。

为了将容器中的uid和父user namespace上的uid和gid进行 关联起来,可通过/proc//uid_map和/proc//gid_map来进行映射的。这两个文件的格式为:

1
ID-inside-ns ID-outside-ns length

第一个字段ID-inside-ns表示在容器显示的UID或GID,
第二个字段ID-outside-ns表示容器外映射的真实的UID或GID。
第三个字段表示映射的范围,一般填1,表示一一对应。

0 1000 256这个配置的含义为父user namespace的1000-1256映射到新user namespace的0-256.

创建子进程在没有指定CLONE_NEWUSER时文件内容如下,子进程跟父进程的用户完全一致:

1
2
3
# 表示把namespace内部从0开始的uid映射到外部从0开始的uid,其最大范围是无符号32位整形
[root@centos7 1325]# cat uid_map
0 0 4294967295

要想实现以普通用户运行程序,在子进程中以root用户执行,仅需要将uid_map文件修改为普通用户映射到子进程中的0即可,因为uid为0表示root用户。

那么谁拥有写入该文件的权限呢?

/proc/${pid}/[u|g]id的拥有者为创建新user namespace的用户,拥有map文件写入权限的仅有两个用户:和该用户在同一个user namespace中的root用户,创建新的user namespace的用户。创建新的user namespace有没有写入map文件的权限,还要取决于capability中的CAP_SETUID和CAP_SETGID两个权限。

为了方便写入/proc/${pid}/uid_map和/proc/${pid}/gid_map文件,可以使用newuidmap和newgidmap命令来完成。

继续上述例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#--------------------------第一个shell窗口----------------------
# 在新user namespace中获取当前的进程号
nobody@ubuntu-xenial:/tmp$ echo $$
13226

#--------------------------第二个shell窗口----------------------
vagrant@ubuntu-xenial:/tmp$ ll /proc/13226/uid_map /proc/13226/gid_map
-rw-r--r-- 1 vagrant vagrant 0 Sep 23 17:31 /proc/13226/gid_map
-rw-r--r-- 1 vagrant vagrant 0 Sep 23 17:31 /proc/13226/uid_map
# 提示当前进程没有权限写入
vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/uid_map
-bash: echo: write error: Operation not permitted

# 查看当前bash没有任何capability
vagrant@ubuntu-xenial:/tmp$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000

# 使用root权限给/bin/bash可执行文件增加cap_setgid和cap_setuid
vagrant@ubuntu-xenial:/tmp$ sudo setcap cap_setgid,cap_setuid+ep /bin/bash
# 启动新的bash后capability会生效
vagrant@ubuntu-xenial:/tmp$ bash
vagrant@ubuntu-xenial:/tmp$ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 00000000000000c0
CapEff: 00000000000000c0

# 重新写入
vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/uid_map
vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/gid_map

# 第二次再写入会失败,仅允许写入一次
vagrant@ubuntu-xenial:/tmp$ echo '0 1000 100' > /proc/13226/uid_map
bash: echo: write error: Operation not permitted

# 将刚才设置的capability取消
vagrant@ubuntu-xenial:/tmp$ sudo setcap cap_setgid,cap_setuid-ep /bin/bash
vagrant@ubuntu-xenial:/tmp$ getcap /bin/bash
/bin/bash =
vagrant@ubuntu-xenial:/tmp$ exit
exit

#--------------------------第一个shell窗口----------------------
# 第一个窗口中userid已经变更为0了
nobody@ubuntu-xenial:/tmp$ id
uid=0(root) gid=0(root) groups=0(root)
# 重新执行一个新的bash,会发现提示符已经变更为root了
nobody@ubuntu-xenial:/tmp$ bash
root@ubuntu-xenial:/tmp#

# 可以看到新的bash已经拥有的所有的capability,但也仅限于当前的user namespace中
root@ubuntu-xenial:/tmp# cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff

问题

user namespace在linux3.8内核版本上才实现,存在一定的安全问题。在redhat和centos系统下,user namespace作为了一个实验feature,默认情况下未开启。

执行如下命令sudo grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"并重启系统后可以就可以打开user namespace feature了。执行grubby --remove-args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"可关闭user namespace feature。

经实验,上述操作未生效,后续待查该问题。

ref

Mount Namespace 是 Linux 下的一种挂载隔离机制,可以实现不同的进程拥有独立的挂载视图,各挂载点之间相互不受影响。

mount namespace 基本用法

Linux 启动时会创建一个默认的 mount namespace,在使用 clone() 或者 unshare() 系统调用通过 CLONE_NEWNS 会创建出新的 mount namespace。因为是第一个加入到 Linux 内核的中的 namespace,CLONE_NEWNS 的取名并不是太合理。

每个进程的挂载信息保存在了 /proc/<pid>/{mountinfo,mounts,mountstats} 中。

使用如下命令可以看到一个进程对应的 mount namespace 名称。

1
2
#ll /proc/$$/ns/mnt
lrwxrwxrwx 1 root root 0 Aug 30 00:02 /proc/94832/ns/mnt -> mnt:[4026531840]

可以使用 unshare 命令来创建一个新的命名空间,执行如下命令:

1
2
3
4
5
6
7
# 创建新的命名空间,此时会继承当前 mount namespace 的挂载点信息
unshare --mount --fork --pid --mount-proc /bin/bash
# 创建新的挂载点并挂载,执行完成后可以看到新的挂载点存在
mkdir /mnt/new_mount
mount -t tmpfs tmpfs /mnt/new_mount
# 查看新的挂载点
df -h | grep new_mount

新打开一个终端,切换回原始的命名空间,可以看到挂载点并不存在。

mount namespace 的 shared subtree

mount namespace 实现了挂载点的隔离,但在很多创建下又希望能够在不同的 namespace 下共享挂载点。shared subtree 机制允许在 mount namespace 之间自动的或者受控的传播 mount 和 umount 事件。

注意:该处理解起来比较绕。在创建 mount namespace 和挂载目录的时候分别可以指定参数。

在做单个目录挂载的时候通过指定传播类型来指定的不同隔离行为,支持如下的值:

  • private:挂载点不会传播到其他命名空间,默认类型。
  • shared:挂载点会传播到共享这个挂载点的其他命名空间。
  • slave:挂载点不会传播到另一个命名空间,但其他命名空间的传播会影响到这个挂载点。
  • unbindable:挂载点不能被移动。
  • rprivate:递归地将挂载点及其子挂载点设置为 private。
  • rshared:递归地将挂载点及其子挂载点设置为 shared。
  • rslave:递归地将挂载点及其子挂载点设置为 slave。

mount 在挂载目录的时候可以指定如下参数:

  1. –make-rshared:将挂载方式设置为 rshared。
  2. –make-rslave:将挂载方式设置为 rslave。
  3. –make-rprivate:将挂载方式设置为 rprivate。
  4. –make-runbindable:将挂载方式设置为不可移动。

可以通过 findmnt -o TARGET,PROPAGATION 命令查看到当前 mount namespace 下的挂载点的 shared subtree 属性。但该命令无法区分出 shared 和 rshared 属性。

unshare 命令在创建新的 mount namespace 时通过选项 --propagation来指定新创建的 mount namespace 中所有挂载点的共享方式。支持如下值:

  1. private:新创建的 mount namespace 中的所有挂载点的 shared subtrees 属性全部为 private。默认值。
  2. shared:新创建的 mount namespace 中的挂载点的 shared subtrees 属性全部为 share。
  3. slave:新创建的 mount namespace 中的挂载点的 shared subtrees 属性全部为 slave。
  4. unchanged:新创建的 mount namespace 中的挂载点的 shared subtrees 属性保持不变。

在上面的例子中可以通过指定为 rshared 的模式来查看效果。

1
2
3
4
5
6
7
8
# 创建新的命名空间
unshare --mount --fork --pid --mount-proc /bin/bash

# 创建新的挂载点并挂载,执行完成后可以看到新的挂载点存在
mount --make-rshared /

# 查看新的挂载点
df -h | grep new_mount

mount namespace 在容器技术中的应用

在容器技术中,在容器中不应该看到宿主机上挂载的目录。在Linux中使用chroot技术来实现,在容器中将/挂载到指定目录,这样在容器中就看不到宿主机上的其他挂载项。在容器技术中,chroot所使用的目录即容器镜像的目录。容器镜像中并不包含操作系统的内核。

Docker Volume

为了解决容器中能够访问宿主机上文件的问题,docker引入了Volume机制,将宿主机上指定的文件或者目录挂载到容器中。而整个的docker Volume机制跟mount namespace的关系不太大。

Volume用到的技术为Linux的绑定挂载机制,该机制将一个指定的目录或者文件挂载到一个指定的目录上。

容器启动顺序如下:

  1. 创建新的mount namespace
  2. dockerinit根据容器镜像准备好rootfs
  3. dockerinit使用绑定挂载机制将一个指定的目录挂载到rootfs的某个目录上
  4. dockerinit调用chroot

容器启动时需要创建新的mount namespace,根据容器镜像准备好rootfs,调用chroot。docker volume的挂载时机是在rootfs准备好之后,调用chroot之前完成。

上文提到进入新的mount namespace后,mount namespace会继承父mount namespace的挂载, docker volume一定是在新的mount namespce中执行,否则会影响到宿主机上的mount。在调用chroot之后已经看不到宿主机上的文件系统,无法进行挂载。

执行这一操作的进程为docker的容器进程dockerinit,该进程会负责完成根目录的准备、挂载设备和目录、配置hostname等一系列需要在容器内进行的初始化操作。在初始化完成后,会调用execv()系统调用,用容器中的ENTRYPOINT进程取代,成为容器中的1号进程。

volume挂载的目录是挂载在读写层,由于使用了mount namespace,在宿主机上看不到挂载目录的信息,因此docker commit操作不会将挂载的目录提交。

下面使用例子来演示docker volume的用法

在宿主机上使用docker run -d -v /test ubuntu sleep 10000创建新的容器,并创建docker容器中的挂载点/test,该命令会自动在容器中创建目录,并将宿主机上指定目录下的随机目录挂载到容器中的/test目录下。

可在宿主机上通过如下命令查看到volume的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 列出当前docker在使用的所有volume
[root@localhost vagrant]# docker volume ls
DRIVER VOLUME NAME
local 4ad97e6356707b66cd1cacc4a2e223d9c79d11eca26fe12b1becc9dd664fc5c6

# 查看volume在宿主机上的挂载点
[root@localhost vagrant]# docker volume inspect 4ad97e6356707b66cd1cacc4a2e223d9c79d11eca26fe12b1becc9dd664fc5c6
[
{
"CreatedAt": "2018-09-15T23:36:09+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/4ad97e6356707b66cd1cacc4a2e223d9c79d11eca26fe12b1becc9dd664fc5c6/_data",
"Name": "4ad97e6356707b66cd1cacc4a2e223d9c79d11eca26fe12b1becc9dd664fc5c6",
"Options": {},
"Scope": "local"
}
]

docker文件系统

rootfs在最下层为docker镜像的只读层。

rootfs之上为dockerinit进程自己添加的init层,用来存放dockerinit添加或者修改的/etc/hostname等文件。

rootfs的最上层为可读写层,以Copy-On-Write的方式存放任何对只读层的修改,容器声明的volume挂载点也出现这一层。

mount namespace 在 k8s 中应用

在 k8s 中可以通过 volumeMounts 中的参数 mountPropagation 来指定挂载的参数。

引用

  • 极客时间-深入剖析Kubernetes-08

network namespace用来隔离Linux系统的网络设备、ip地址、端口号、路由表、防火墙等网络资源。用户可以随意将虚拟网络设备分配到自定义的networknamespace里,而连接真实硬件的物理设备则只能放在系统的根networknamesapce中。

一个物理的网络设备最多存在于一个network namespace,可以通过创建veth pair在不同的network namespace之间创建通道,来达到通讯的目的。

容器的bridge模式的实现思路为创建一个veth pair,一端放置在新的namespace,通常命名为eth0,另外一端放在原先的namespace中连接物理网络设备,以此实现网络通信。

docker daemon负责在宿主机上创建veth pair,把一端绑定到docker0网桥,另一端到新建的network namespace进程中。建立的过程中,docker daemon和dockerinit通过pipe进行通讯。

一、测试例子

测试network namespace的过程比较复杂。

docker默认采用的为bridge模式,在容器所在的宿主机上看到的网卡情况如下:

1
2
3
4
5
6
7
8
9
10
11
[root@localhost software]# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:6c:3e:95 brd ff:ff:ff:ff:ff:ff
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:a5:78:ca brd ff:ff:ff:ff:ff:ff
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:a3:75:00:16 brd ff:ff:ff:ff:ff:ff
18: veth71f2650@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether ca:05:f7:db:6f:4c brd ff:ff:ff:ff:ff:ff link-netnsid 0

其中的enp0s3和enp0s8可以忽略,为虚拟机使用的网卡。docker0和veth71f2650@if17是需要关注的网卡。

1
2
3
[root@localhost software]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242a3750016 no veth71f2650

下面的操作为在已经运行docker的虚拟机上的,以便于跟docker进行比较。

以下命令根据coolshell中的步骤进行配置,并对执行命令的顺序进行了调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# 增加network namespace ns1
[root@localhost software]# ip netns add ns1
[root@localhost software]# ip netns
ns1

# 激活namespace ns1中的lo设备
[root@localhost software]# ip netns exec ns1 ip link set dev lo up

# 创建veth pair
[root@localhost software]# ip link add veth-ns1 type veth peer name lxcbr0.1
# 多出了lxcbr0.1@veth-ns1和veth-ns1@lxcbr0.1两个设备
# 后面的操作步骤中将lxcbr0.1位于主网络命名空间中,veth-ns1位于ns1命名空间中
[root@localhost software]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:6c:3e:95 brd ff:ff:ff:ff:ff:ff
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:a5:78:ca brd ff:ff:ff:ff:ff:ff
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:a3:75:00:16 brd ff:ff:ff:ff:ff:ff
18: veth71f2650@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether ca:05:f7:db:6f:4c brd ff:ff:ff:ff:ff:ff link-netnsid 0
19: lxcbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether c6:b7:4d:7f:f8:90 brd ff:ff:ff:ff:ff:ff
20: lxcbr0.1@veth-ns1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether c6:8a:26:3d:ba:de brd ff:ff:ff:ff:ff:ff
21: veth-ns1@lxcbr0.1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether f2:03:22:93:d6:f4 brd ff:ff:ff:ff:ff:ff

# 将设备veth-ns1放入到ns1命名空间中
[root@localhost software]# ip link set veth-ns1 netns ns1
# 可以看到veth-ns1设备在当前命名空间消失了
[root@localhost software]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:6c:3e:95 brd ff:ff:ff:ff:ff:ff
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:a5:78:ca brd ff:ff:ff:ff:ff:ff
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:a3:75:00:16 brd ff:ff:ff:ff:ff:ff
18: veth71f2650@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether ca:05:f7:db:6f:4c brd ff:ff:ff:ff:ff:ff link-netnsid 0
19: lxcbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether c6:b7:4d:7f:f8:90 brd ff:ff:ff:ff:ff:ff
20: lxcbr0.1@if21: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether c6:8a:26:3d:ba:de brd ff:ff:ff:ff:ff:ff link-netnsid 1
# 同时在命名空间ns1中看到了设备veth-ns1,同时可以看到veth-ns1设备的状态为DOWN
[root@localhost software]# ip netns exec ns1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
21: veth-ns1@if20: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether f2:03:22:93:d6:f4 brd ff:ff:ff:ff:ff:ff link-netnsid 0

# 将ns1中的veth-ns1设备更名为eth0
[root@localhost software]# ip netns exec ns1 ip link set dev veth-ns1 name eth0
[root@localhost software]# ip netns exec ns1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
21: eth0@if20: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether f2:03:22:93:d6:f4 brd ff:ff:ff:ff:ff:ff link-netnsid 0

# 为容器中的网卡分配一个IP地址,并激活它
[root@localhost software]# ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up
# 可以看到eth0网卡上有ip地址
[root@localhost software]# ip netns exec ns1 ifconfig
eth0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.10.11 netmask 255.255.255.0 broadcast 192.168.10.255
ether f2:03:22:93:d6:f4 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

# 添加一个网桥lxcbr0,类似于docker中的docker0
[root@localhost software]# brctl addbr lxcbr0
[root@localhost software]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242a3750016 no veth71f2650
lxcbr0 8000.000000000000 no

# 关闭生成树协议,默认该协议为关闭状态
[root@localhost software]# brctl stp lxcbr0 off
[root@localhost software]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242a3750016 no veth71f2650
lxcbr0 8000.000000000000 no

# 为网桥配置ip地址
ifconfig lxcbr0 192.168.10.1/24 up
[root@localhost software]# ifconfig lxcbr0
lxcbr0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.10.1 netmask 255.255.255.0 broadcast 192.168.10.255
inet6 fe80::c4b7:4dff:fe7f:f890 prefixlen 64 scopeid 0x20<link>
ether c6:b7:4d:7f:f8:90 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 648 (648.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

# 将veth设备中的其中一个lxcbr0.1添加到网桥lxcbr0上
[root@localhost software]# brctl addif lxcbr0 lxcbr0.1
# 可以看到网桥lxcbr0中已经包含了设备lxcbr0.1
[root@localhost software]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242a3750016 no veth71f2650
lxcbr0 8000.c68a263dbade no lxcbr0.1

# 为网络空间ns1增加默认路由规则,出口为网桥ip地址
[root@localhost software]# ip netns exec ns1 ip route add default via 192.168.10.1
[root@localhost software]# ip netns exec ns1 ip route
default via 192.168.10.1 dev eth0
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.11

# 为ns1增加resolv.conf
[root@localhost software]# mkdir -p /etc/netns/ns1
[root@localhost software]# echo "nameserver 8.8.8.8" > /etc/netns/ns1/resolv.conf

二、常用命令

1. 列出当前的network namespace

1.1 使用lsns命令

lsns命令通过读取/proc/${pid}/ns目录下进程所属的命名空间来实现,如果是通过ip netns add场景的命名空间,但是没有使用该命名空间的进程,该命令是看不到的。

1
2
3
4
5
6
7
8
9
10
11
12
# lsns -t net
NS TYPE NPROCS PID USER COMMAND
4026531956 net 383 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 21
4026532490 net 1 1026 rtkit /usr/libexec/rtkit-daemon
4026532762 net 2 24872 root /pause
4026532866 net 20 25817 root /pause
4026532965 net 3 30763 root /pause
4026533059 net 3 2794 root /bin/sh -c python /usr/src/app/clean.py "${endpoints}" "${expire}"
4026533163 net 2 1122 102 /docker-java-home/jre/bin/java -Xms2g -Xmx2g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupan
4026533266 net 4 13920 root /pause
4026533371 net 2 1844 root /pause
4026533559 net 3 1067 root sleep 4

1.2 通过ip netns命令

该命令仅会列出有名字的namespace,对于未命名的不能显示。

  • ip netns identify ${pid} 可以找到进程所属的网络命名空间
  • ip netns list: 显示所有有名字的namespace

2. 通过pid进入具体的network namespace

2.1 通过nsenter命令

nsenter --target $PID --net可以进入到对应的命名空间

2.2 docker --net参数

docker提供了--net参数用于加入另一个容器的网络命名空间docker run -it --net container:7835490487c1 busybox ifconfig

2.3 setns系统调用

一个进程可以通过setns()系统调用来进入到另外一个namespace中。

编写setns.c程序,该程序会进入到进程id所在的网络命令空间,并使用gcc setns.c -o setns进行编译,编译完成后执行./setns /proc/4913/ns/net ifconfig可以看到网卡的信息为容器中的网卡信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
int fd = open(argv[1], O_RDONLY);
if (setns(fd, 0) == -1) {
perror("setns");
exit(-1);
}
execvp(argv[2], &argv[2]);
printf("execvp exit\n");
}

如果执行./setns /proc/4913/ns/net /bin/bash,在宿主机上查看docker进程和/bin/bash进程的网络命名空间/proc/${pid}/ns/net,会发现都指向lrwxrwxrwx 1 root root 0 Sep 14 14:42 net -> net:[4026532133]同一个位置。

3. pid的获取方式

最简单的方式上文第1点中的PID列

3.1 /proc/[pid]/ns

可以使用如下命令查看当前容器在宿主机上的进程id。

1
docker inspect --format '{{.State.Pid}}' a1bf0119d891

每个进程在/proc/${pid}/ns/目录下都会创建其对应的虚拟文件,并链接到一个真实的namespace文件上,如果两个进程下的链接文件链接到同一个地方,说明两个进程同属于一个namespace。

1
2
3
4
5
6
7
8
[root@localhost runc]# ls -l /proc/4913/ns/
total 0
lrwxrwxrwx 1 root root 0 Sep 11 00:21 ipc -> ipc:[4026532130]
lrwxrwxrwx 1 root root 0 Sep 11 00:21 mnt -> mnt:[4026532128]
lrwxrwxrwx 1 root root 0 Sep 11 00:18 net -> net:[4026532133]
lrwxrwxrwx 1 root root 0 Sep 11 00:21 pid -> pid:[4026532131]
lrwxrwxrwx 1 root root 0 Sep 11 00:21 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Sep 11 00:21 uts -> uts:[4026532129]

reference

北京灵山

题图为北京灵山主峰,海拔2303米,北京最高峰。登上主峰的时候,恰巧一头牛就在山顶悠闲,拍照的时候,牛哥把我带上去的枣、葡萄、花生米全部吃光了,甚至连橘子皮都没剩下,吃完后牛哥又悠闲的去吃草了,让我见识了啥叫吃葡萄不吐葡萄皮。

教程

  1. 《Kubernetes权威指南 企业级容器云实战》

kubernetes的书籍并不多,该书八月份刚初版,内容较新,并不是一本kubernetes的入门书籍,而是讲解kubernetes在企业落地为PASS平台时需要做的工作,建议对kubernetes有一定了解后再看。

书的内容为HPE的多名工程师拼凑而成,有些部分的内容明显是没有经过实践验证的理论派想法,但总体来看值得一读。

书中提到了很多kubernetes较新版本才有的特性、微服务、service mesh、lstio,对于补充自己已经掌握的知识点有一定帮助。

书的后半部分反而显的干货少了非常多,我仅草草的过了一遍。

  1. 《Go语言高级编程》

Golang中相对进阶的中文教程,我还没来得及看。

  1. Gorilla

facebook发表的分布式的时序数据库论文,如果英文看起来吃力,可以看一下小米运维公众号中的翻译版本。facebook并未提供开源的实现,但在github上能找到一些开源的实现。

  1. 《深入剖析Kubernetes》

极客时间app的专栏,本来购买之前没有报特别大的预期,但读完头几篇文章后被作者的文字功底折服,将PASS、容器的来龙去脉、docker的发展讲解的很到位,超出了我的预期。期待后面更新的专栏能够保持搞水准。

  1. SDN手册

一本介绍SDN相关知识的开源电子书。

资源

  1. M3DB

监控领域还是比较缺少特别好用的分布式时间序列存储数据库,性能特别优异的数据库往往都是单机版的,缺少高可用的方案,比如rrdtool、influxdb、graphite等。OpenTSDB、KairosDB、Druid等虽为分布式的时序数据库,但使用或者运维起来总有各种不方便的地方。uber开源的m3在分布式时序数据库领域又多了一个方案,并可作为prometheus的远程存储。

  1. cilium

使用BPF(Berkeley Packet Filter)和XDP(eXpress Data Path)内核技术来提供网络安全控制的高性能开源网络方案。

  1. kubeless

kubernetes平台上的Serverless项目,Faas(功能即服务)一定是云计算发展的一个趋势。目前CNCF中还没有Serverless项目,期待CNCF下能够孵化一个Serverless项目。

工具

  1. vagrant

还在使用virturalbox的你,是时候使用vagrant了。vagrant作为对虚拟机的管理,虽然引入了一些概念带来了更大的复杂性。但同时功能上也更强大,比如对box的管理,可以将box理解为docker image,便于将虚拟机的环境在不同的主机上分发。

公众账号推荐

  1. 小米运维

开通时间不算特别长,但文章的质量不错,都是比较接地气的干货,看得出确实是在工作中遇到的问题或者是总结经验,值的一读。

  1. 开柒

曾经公众号的名字为开八,江湖人称八姐,忘记为何更改为开柒了,曾经的搜狐记者。总能非常及时的爆料很多互联网的内幕,消息来源往往非常准确,可见八姐在圈内的人脉非同一般。

  1. 毕导

打发时间非常好的公众号,用理科男的思维方式进行恶搞,是不是拿出冗长的数学公式来证明日常生活中的小尝试,语言诙谐幽默,绝对是公众号中的一股清流。可惜每篇文章都很长,我没有太多时间把每一篇文章都看一遍。

晚上在工位上安静地看着技术文档,不知为何脑中突然闪现了小时候打场的场景,那段记忆并无特殊之处,但大概是那片金黄色的背景和村民脸上丰收的喜悦以及村民之间的和谐相处的画面突然浮现在了我的眼前,让我不由得想写点什么以便回忆那段近乎忘却的回忆。

说起“打场”这个词,可能很多人都不太熟悉,其实这个词也已经很多年我都没有接触过了,我甚至都不知道现在农村老家的村民们还是否会时长提起这个词语。我甚至不知道这个词是不是我们那地域性的方言,这些都不重要了。以下内容摘自百度百科:

打场[dǎ cháng],指把收割下来带壳的粮食平摊在场院里,用马拉磙子,或者用小型拖拉机,碾压这些粮食,使之脱去外壳,这一系列活动就叫打场。

我的家在山东,小的时候还没有那么多的农业经济作物,地里更多的农作物以小麦和玉米为主,一年两季。农作物的种植周期跟二十四节气是息息相关的,每年的二十四节气的芒种左右是小麦丰收的季节,时间大概在高考前后。比我大一点的应该还有麦假,就是在收麦子的时候不上学了回家帮着收麦子,我没怎么有印象我有过麦假。

小时候的机械化程度低,甚至谈不上机械化,收麦子这种活自然完全是人工完成的。在机械化程度还可以的今天,收麦子这种活在当时还是有着稍微繁琐的工序,也比较耗费人力,一到收麦子的时候,几乎家里所有的劳力都要出来干活了。

村里场地基本上都是挨着的,这应该是生产队时代的产物,那时候土地是公有的,场地自己要连成一片比较容易。后来土地开始分割给个人,这块场地仍然是保留的,只不过是场地被划分成了很多份,每家分上一点。

在打场那个年代,我是几乎很少参与过收麦子的活动,那时我还小,就是在场地里捣乱和瞎跑,还时不时让在场地里干活的村民调侃一番,毕竟小孩子活泼好动,村民们调侃一下解解乏也算是为他们做的一点点微不足道的贡献了。

收麦子的活第一步是割麦子,割麦子这个活我还正儿八经得干过几次,虽然每次干的时间都非常短。关于割麦子的画面,我脑海中浮现的是烈日当头,家人们带着帽子弓着背拿着镰刀一人一沟麦子往前赶,一会儿麦秸在怀里就抱不下了,然后将麦秸放下继续往前赶。那时候的帽子几乎全部是用草编织成的,两根绳子沿着帽檐搭下来系在下巴上,很是牢固,即使低着头帽子也不会掉下来。我干活的时候每次负责的那一沟麦子总是割的最慢的,几乎要慢一倍的样子,而且是干的最差的,背后总会留下一些麦子没有割下来,到现在我也仍然好奇,他们是怎么干的这么快的,我感觉当时已经尽最大的努力了,而这仅是他们的正常速度。

在麦子割完后,就需要将麦子放到集中的晒干了。之所以要晒干,是因为要是麦秸不晒干,麦粒是很难从麦秸上拖不下来的。而要晒干,那么多麦秸就需要一片地方来晾晒麦秸和麦粒。那时候还没怎么有柏油路,家里也很少有水泥的房顶,为了能够有晾晒的地方,村里有一片地就是场地,在收麦子的时候专门用来收麦子的,收完麦子后再种其他作物。之所以场地是集中式的,这还要从生产队时代说起,那时候土地都是公有的,自然场地就会划到一块比较容易,后来土地慢慢私有后,这些土地仍然是作为场地,只是被划的一小块一小块的。

割麦子的时候偶尔会碰到鹌鹑蛋,这也是一些额外的小收获,但遗憾的是从没看到过鹌鹑。

场地要想能晾晒麦秸和麦粒,自然就需要特别的平整,而要想土地特别的平整,在石器时代,仅有一个办法,那就是用轱辘一遍一遍的撵,一般至少需要两个人用绳子拉着轱辘满地里转,要想将土地撵的特别平整且不能有缝隙,哪怕一个麦粒掉在地上能够看的见捡的起来,想想都不是一件特别容易的事情,尤其是刚开始土地特别不平的时候。

并非所有的麦秸都是需要在场地里暴晒的,这个要看麦秸的干燥程度,有些麦秸割完后已经非常干燥了,就不需要再暴晒了。

干燥的麦秸在干燥后就需要将麦粒从麦秸上分离出来,毕竟农民们想要的是麦粒,那才是实打实的粮食。要想麦粒从麦秸上分离,这时候仍然是重量级的轱辘上场,这时候场地相对平整了,自然拉起来会省力气很多,我也曾经拉过一次,但每次都是拉一会就不知道去哪里玩了,小孩子干这么枯燥的事情自然没有耐心。

拖完粒的麦秸一般是就地放到的场地的地头上,为了省空间,会将麦秸垛起来,一个剁的高度差不多在两三米高的样子,差不多是农民拿着叉子往上扔麦秸扔不上去的高度,叉子的长度差不多在两米高的样子。

白天的时间是晒麦粒的最佳时机,到了傍晚时分就需要将晒的麦粒堆起来盖住,这个时候也是场上人最多的时候,几乎每家的地里都有人在忙着。我脑中闪现的场景就是在这个时候,村民们在场地上忙来忙去,边干活边聊着天,扯东扯西的。我在场地上跑来跑去,从这个地里跑到另外一家,时不时村民们还会那我来开个玩笑。累了靠着垛儿打个滚,不一会体力就恢复了,想来那时体力真是好。那时虽然很多事情还不懂,但我确实能从中体会村民们丰收的喜悦。

麦子收完后,用轱辘碾压了无数遍的场地也就失去了存在的价值,村民们在场地上该种点其他作物就种上其他作物了,总之场地不会空闲特别久。场地上总会落下一些麦粒,雨后空闲的场地上的缝隙处时长会生出新的绿油油的麦苗。

现在随着机械化的进步,收麦子这种事自然不需要手工割麦子了,晒麦子的地方也开始变多了,柏油路上,自家的房顶上,自家的院子里都是及其不错的晒麦子的地方,打场这种原始的方式也自然就退出了历史的舞台,而且再也不会回来。

但就是那幅金黄色的画面却在我的记忆中留下了一道抹不去的色彩,让我时长回忆起来那温馨的画面。纵科技的发展,村民们的亲情却在变淡,该进城的都进城了,该出去打工的都出去打工了,一年村民们都见不上几回。也许将来村落的概念会消失,也许人与人之间的关系会更淡,但我曾经经历过村落时代的美好,也许这就足够了。

0%