404频道

学习笔记

题图为公司楼下公园的杨树林。时光易逝弹指间,又到一年叶落时。

资源

1.runV

基于 hypervisor 的 OCI runtime

2.operator-sdk

operator机制利用CRD机制增强了kubernetes的灵活性,但operator的编写代码很多模式都是固定的,该项目提供了更高层次的抽象。

3.orchestrator

用来管理mysql的集群拓扑和故障自动转移的工具。

4.Tars

https://github.com/TarsCloud/Tars/blob/master/docs/images/tars_jiaohu.png

腾讯开源的RPC框架,在腾讯内部已经有多年的使用历史,目前支持多种语言。

5.ngx_http_dyups_module

nginx module,可以提供Restful API的形式来动态修改upstream,而不用重新reload nginx。

6.Dragonfly

https://github.com/alibaba/Dragonfly/raw/master/docs/images/logo/dragonfly-linear.png

阿里巴巴开源的基于P2P的容器镜像分发系统。

7.SonarQube

开源的代码检查和扫描工具,支持多种语言,并提供了友好的web界面用来查看分析结果。

8.QUIC

QUIC是Google开发的基于UDP的传输层协议,提供了像TCP一样的数据可靠性,但降低了数据的传输延时,并具有灵活的拥塞控制和流量控制。

9.OpenMessaging

阿里巴巴发起的分布式消息的应用开发标准,目前github上的star数还较少。

10.nsenter

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

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

精彩文章

1.为何程序员永远是高薪行业

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

2.Harbor传奇(1)- Harbor前世

3.蚂蚁金服 Service Mesh 实践探索

4.美团容器平台架构及容器技术实践

美团内部的容器平台HULK已经从第一代的自研升级为第二代的基于kubernetes的容器管理平台。由此可以反映出kubernetes在容器管理领域的地位。

5.Serverless:后端小程序的未来

Serverless是未来软件架构的一个演进方向,包括BasS(Backend as a Service,后端即服务)和FaaS(Functions as a Service,函数即服务)两个组成部分。

BaaS包括对象存储、数据库、消息队列等服务,并以API的形式提供应用依赖的后端服务。

FaaS中的运行是通过事件触发的方式,代码执行完成后即运行结束,因此代码必须是无状态的。FaaS平台负责服务的自动扩容,并可做到按照服务的使用资源付费,以节省大量开支。

Serverless给开发人员带来了非常大的便利性,但同时也软件跟云平台绑定特别紧密。

图书

1.《奈飞文化手册:“硅谷重要文件”的深度解读

https://images-na.ssl-images-amazon.com/images/I/51UmLKXW9%2BL._SX366_BO1,204,203,200_.jpg

Netflix公司的技术文化一直非常被业界推崇,可以从Netflix OSS已经开源的软件项目,很多的开源项目在社区也有不错的影响力,本书值得每一位技术从业者一读。

精彩句子

我们要求大家做出的任何举动,出发点都是以对客户和公司最有利为出发点,而不是试图证明自己正确。

- 奈飞文化手册:“硅谷重要文件”的深度解读

golang中的pprof工具可以分析系统问题,开启pprof功能非常简单,即在import中增加_ "net/http/pprof"的导入即可,然后通过http调用/debug/pprof接口即可在web界面上看到pprof的相关信息。

golang 1.11版本中已经自带了火焰图功能,火焰图为性能分析的利器,可以快速找到程序性能的瓶颈。不再需要使用go-torch项目。

查看火焰图需要用到Graphviz工具,该工具需要单独安装。

Graphviz工具运行的服务器系统为CentOS,使用下载源码包的方式进行安装。

依次执行下面命令:

1
2
3
./configure
make
make install

例子

本文的使用环境:服务器程序为transfer,运行在linux系统中。执行go tool pprof命令运行在linux服务器10.103.17.184上。

程序运行后调用程序的/debug/pprof/profilehttp接口,可获取到cpu profile数据文件。例如,在服务器上可以执行运行wget http://10.103.34.138:3300/debug/pprof/profile命令,将profile文件下载到另外一台分析的服务器上。

在分析服务器上执行go tool pprof -http="10.103.17.184:20000" transfer profile

在浏览器中打开10.103.17.184:20000即可得到性能分析的结果。

调用关系图

火焰图

同样也可以在本机访问远程的程序暴露的pprof数据,使用命令如:go tool pprof -http :9090 http://10.66.161.43:10245/debug/pprof/heap

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

在使用CLONE_NEWNS来创建新的mount namespace时,子进程会共享父进程的文件系统,如果子进程执行了新的mount操作,仅会影响到子进程自身,不会对父进程造成影响。

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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
"/bin/bash",
NULL
};

int container_main(void* arg)
{
printf("Container [%5d] - inside the container!\n", getpid());
sethostname("container",10);
/* 重新mount proc文件系统到 /proc下 */
system("mount -t proc proc /proc");
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}

int main()
{
printf("Parent - start a container!\n");
int container_pid = clone(container_main, container_stack+STACK_SIZE,
CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}

执行效果:

1
2
3
4
5
6
7
8
9
[root@centos7 docker_learn]# ./mount
Parent - start a container!
Container [ 1] - inside the container!

# 由于ps是读取的/proc目录下的文件来显示当前系统系统的进程,新进程挂载/proc目录到新的proc文件系统,自然看不到父进程的/proc目录了
[root@container docker_learn]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 22:20 pts/3 00:00:00 /bin/bash
root 30 1 0 22:20 pts/3 00:00:00 ps -ef

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

mount namespace是Linux中第一个namespace,是基于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挂载点也出现这一层。

ref

极客时间-深入剖析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

0%