namespace是用来隔离应用进程的,给应用进程一个独立的运行环境,让彼此之间不受到干扰。
初识容器
cat Dockerfile
FROM ubuntu
ENV MY_SERVICE_PORT=80
ADD bin/amd64/httpserver /httpserver //将一个二进制文件加入到这个容器镜像里面
ENTRYPOINT /httpserver //容器启动要运行哪条命令
将Dockerfile打包成镜像
docker build -t cncamp/httpserver:${tag} .
docker push cncamp/httpserver:v1.0
运行容器
docker run -d cncamp/httpserver:v1.0
这样就完成了将源代码,编译成一个二进制文件,然后构建镜像并且推送到镜像仓库里面去。
有了这个,就可以在任何运行docker的机器上面,将这个镜像运行起来。
容器操作
- 查看容器细节:
docker inspect <containerid>
- 进入容器:
docker attach
docker exec
- 通过 nsenter:(所谓nsenter是要进入到哪种namespace下面,去看其资源)
查看容器对应宿主机上面的pid,容器技术的实质是进程,并没有完整的操作系统,就相当于在主机上面fork了一个子进程,通过docker daemon去fork一个子进程,这个子进程是可以在主机上面看到其pid的。
PID=$(docker inspect --format "{{ .State.Pid }}" <container>)
$ nsenter --target $PID --mount --uts --ipc --net --pid
#查看容器对应宿主机上面的pid,容器技术的实质是进程,并没有完整的操作系统,就相当于在主机上面fork了一个子进程,通过docker daemon去fork一个子进程,这个子进程是可以在主机上面看到其pid的。
[root@docker ~]# docker inspect 37d084d8e21b | grep -i pid
"Pid": 18496,
"PidMode": "",
"PidsLimit": null,
[root@docker ~]# ps -ef | grep 18496
root 18496 18476 0 15:34 pts/0 00:00:00 sh
-p 是pid namesapce -n是network namespace,在主机上面通过nsenter去敲ip a,ps命令和在容器内部敲的命令返回的结果是一样的。
[root@docker ~]# nsenter -t 18496 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
44: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
[root@docker ~]# nsenter -t 18496 -p ps
PID TTY TIME CMD
18648 pts/1 00:00:00 sh
20436 pts/1 00:00:00 bash
21368 pts/1 00:00:00 nsenter
21369 pts/1 00:00:00 bash
21529 pts/1 00:00:00 nsenter
21530 pts/1 00:00:00 ps
容器看到的是可以通过在主机上面nsenter看到的。这是日常去调试容器里面进程应用的一个法宝!
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4461cb8f8be5 nginx "/docker-entrypoint.?? 59 minutes ago Up About a minute 80/tcp pedantic_ganguly
[root@docker ~]# docker inspect 4461cb8f8be5 | grep -i pid
"Pid": 21903,
"PidMode": "",
"PidsLimit": null,
[root@docker ~]# nsenter -t 21903 -n netstat -tpln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 21903/nginx: master
tcp6 0 0 :::80 :::* LISTEN 21903/nginx: master
- 拷贝文件至容器内:
docker cp file1 <containerid>:/file-to-path
容器主要特性
虚拟机提供了哪些功能,这其实是docker需要去实现的一个目标,是用一种轻量级的手段来实现类似于虚拟机的业务目标。
隔离性:应用程序要跑,那么要有应用程序运行的沙箱,也就是整个运行环境。这个运行环境要和其他人隔离,需要一个封闭的运行环境,这样一个封闭的运行环境和其他人隔离有什么好处呢?
应用之间不会互相影响,其次是一个封闭的系统,所有的依赖都在封闭的环境里面存在,外部的依赖是使用不了的,你所需要的依赖必须在你的隔离环境里面。这样在不同的环境里面运行其实就是将容器镜像的整个环境还原出来。
Namespace
cgroup + namespace + unionfs 这些东西是保证容器技术可以承担和虚拟机一样的职责,但是更加轻量。
在当前命令行窗口查看一下当前进程的 Namespace 信息
[root@docker ~]# ls /proc/self/ns
cgroup ipc mnt net pid pid_for_children user uts
[root@docker ~]# ls -l /proc/self/ns
total 0
lrwxrwxrwx 1 root root 0 Feb 22 14:20 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Feb 22 14:20 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Feb 22 14:20 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Feb 22 14:20 net -> net:[4026531992]
lrwxrwxrwx 1 root root 0 Feb 22 14:20 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Feb 22 14:20 pid_for_children -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Feb 22 14:20 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Feb 22 14:20 uts -> uts:[4026531838]
root@e70a5541fa99:/# ls -l /proc/self/ns
total 0
lrwxrwxrwx 1 root root 0 Feb 22 06:20 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Feb 22 06:20 ipc -> 'ipc:[4026532261]'
lrwxrwxrwx 1 root root 0 Feb 22 06:20 mnt -> 'mnt:[4026532259]'
lrwxrwxrwx 1 root root 0 Feb 22 06:20 net -> 'net:[4026532264]'
lrwxrwxrwx 1 root root 0 Feb 22 06:20 pid -> 'pid:[4026532262]'
lrwxrwxrwx 1 root root 0 Feb 22 06:20 pid_for_children -> 'pid:[4026532262]'
lrwxrwxrwx 1 root root 0 Feb 22 06:20 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Feb 22 06:20 uts -> 'uts:[4026532260]'
Linux 内核里面对任何进程的数据结构描述叫做task_struct。这里面包含很多信息,如pid的信息,内存的信息,打开文件的信息。
这里面的nsproxy这个属性就是定义这个进程所在的namespace信息的。
再去看看nsproxy数据结构里面细节,可以看到这里面就有好几类。
一个经常在初创的时候,它就是有天然的namespace的,linux操作系统里面pid为1的进程是systemd,systemd这个进程本身有自己的namespace,那就是默认的namespace,也就是主机namespace,当其他的进程被init systemd拉起来之后,也就是fork其他进程的时候,它将自己的这些namespace复制到新的进程上面去了,默认情况下面,新的子进程会和主机公用一个namspace,所以默认情况下我们感受不到公用同一个pid的namspace,以及其他的namspace。
Linux 对 Namespace操作方法
任何进程都有namespace,即使是在host namespace这些进程。systemd要有自己的namespace。在Linux里面其他进程都是fork出来的,当默认情况下父进程fork子进程,会将自己的namespace赋予给子进程。但是Linux还支持下面这些操作。
- clone
在创建新进程的系统调用时,可以通过 flags 参数指定需要新建的 Namespace 类型:
// CLONE_NEWCGROUP / CLONE_NEWIPC / CLONE_NEWNET / CLONE_NEWNS /
CLONE_NEWPID / CLONE_NEWUSER / CLONE_NEWUTS
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)
- setns 先将进程fork出来,通过setns将其namespace改掉
Int setns(int fd, int nstype)
- unshare 要去fork一个子进程,fork子进程的时候告诉操作系统移动到新的namespace,不和父进程共享
int unshare(int flags)
上面这些就可以让父进程和子进程处于不同的namespace下面,这样就实现了隔离。
隔离性 – Linux Namespace
namespace不同类型如下: 可以看到不同的namespace在不同的内核版本的支持,所以现在的这些都能够支持。
上面可以看到每个namespace下面,进程都运行在自己的封闭的空间,有独立的网络,独立的pid,独立的文件系统,独立的用户,独立的主机名,它就是完全孤立的一个环境,和主机完全隔离开来的一个环境。
为什么之前讲微服务,微服务最终的目的是什么?就是要让进程跑到一个独立的环境里面,有一个独立的网路身份,并且控制资源。
支持的namespace主要有上面几种。
NameSpace PID类型
可以看到每个进程还有pid,
[root@master ~]# ps -ef | head -n 10
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:23 ? 00:00:07 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
root 2 0 0 13:23 ? 00:00:00 [kthreadd]
root 3 2 0 13:23 ? 00:00:04 [ksoftirqd/0]
root 5 2 0 13:23 ? 00:00:00 [kworker/0:0H]
现在运行一个容器,可以看到和外面的pid完全不一样了。容器在启动时候,启动容器进程的时候,启动了一个新的pid namespace,所以里面的进程看到的和外面看到的是不一样的。
[root@docker ~]# docker run -itd busybox
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
37d084d8e21b busybox "sh" 6 seconds ago Up 5 seconds hopeful_tereshkova
[root@docker ~]# docker exec -it 37d084d8e21b sh
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 sh
15 root 0:00 sh
22 root 0:00 ps -ef
bash1的ns信息
[root@docker ~]# ls -l /proc/self/ns
total 0
lrwxrwxrwx 1 root root 0 Feb 23 15:37 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 net -> net:[4026531992]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 pid_for_children -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 uts -> uts:[4026531838]
可以看到在bash2和bash1处于同一个ns下面
[root@docker ~]# ls -l /proc/self/ns/
total 0
lrwxrwxrwx 1 root root 0 Feb 23 15:37 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 net -> net:[4026531992]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 pid_for_children -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Feb 23 15:37 uts -> uts:[4026531838]
//容器当中pid:[4026532265]和bash pid:[4026531836]是不一样的
/ # ls -l /proc/self/ns/
total 0
lrwxrwxrwx 1 root root 0 Feb 23 07:38 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Feb 23 07:38 ipc -> ipc:[4026532264]
lrwxrwxrwx 1 root root 0 Feb 23 07:38 mnt -> mnt:[4026532262]
lrwxrwxrwx 1 root root 0 Feb 23 07:38 net -> net:[4026532267]
lrwxrwxrwx 1 root root 0 Feb 23 07:38 pid -> pid:[4026532265]
lrwxrwxrwx 1 root root 0 Feb 23 07:38 pid_for_children -> pid:[4026532265]
lrwxrwxrwx 1 root root 0 Feb 23 07:38 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Feb 23 07:38 uts -> uts:[4026532263]
NameSpace NetWork类型:
容器里面的网络配置和外面也是不一致的。
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
44: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
这就是namespace,当系统启动了新进程的时候,它开辟了一些新的namespace,这些新的namespace可以将用户的进程隔离,让容器里面的进程看不到外面的进程,让他有一个独立的网络IP地址,让其有独立的文件系统,让其有独立的用户,让其有独立的主机名。
这样就相当于在本机启动了一个进程,但是模拟成为了一个操作系统。
所以namespace是一种隔离的技术,就是将进程放到了不同的namespace下面。这些新的namespace里面可以创建新的网络,有新的进程管理,有新的文件系统,有新的用户等....
这就达到了作为微服务这样的一个平台,这个运行在容器里面的微服务就有独立的主机域名,ip地址,端口号,将进程塞到这个namespace下面就达到这个目的了。
隔离性-linux namespace
关于 namespace 的常用操作
- 查看当前系统的 namespace:
lsns –t <type>
罗列当前主机上所有的namespace,可以指定类型-t指定要看哪一类的namespace,因为我只跑了一个容器busybox,所以只能看到两个namespace。
[root@docker ~]# lsns -t pid
NS TYPE NPROCS PID USER COMMAND
4026531836 pid 100 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 21
4026532265 pid 2 18496 root sh
[root@docker ~]# lsns -t net
NS TYPE NPROCS PID USER COMMAND
4026531992 net 100 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 21
4026532267 net 2 18496 root sh
- 查看某进程的 namespace:
ls -la /proc/<pid>/ns/
- 进入某 namespace 运行命令:(这是日常去调试容器里面进程应用的一个法宝!)
nsenter -t <pid> -n ip addr
查看容器对应宿主机上面的pid,容器技术的实质是进程,并没有完整的操作系统,就相当于在主机上面fork了一个子进程,通过docker daemon去fork一个子进程,这个子进程是可以在主机上面看到其pid的。
PID=$(docker inspect --format "{{ .State.Pid }}" <container>)
$ nsenter --target $PID --mount --uts --ipc --net --pid
#查看容器对应宿主机上面的pid,容器技术的实质是进程,并没有完整的操作系统,就相当于在主机上面fork了一个子进程,通过docker daemon去fork一个子进程,这个子进程是可以在主机上面看到其pid的。
[root@docker ~]# docker inspect 37d084d8e21b | grep -i pid
"Pid": 18496,
"PidMode": "",
"PidsLimit": null,
[root@docker ~]# ps -ef | grep 18496
root 18496 18476 0 15:34 pts/0 00:00:00 sh
-p 是pid namesapce -n是network namespace,在主机上面通过nsenter去敲ip a,ps命令和在容器内部敲的命令返回结果是一样的。
[root@docker ~]# nsenter -t 18496 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
44: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
[root@docker ~]# nsenter -t 18496 -p ps
PID TTY TIME CMD
18648 pts/1 00:00:00 sh
20436 pts/1 00:00:00 bash
21368 pts/1 00:00:00 nsenter
21369 pts/1 00:00:00 bash
21529 pts/1 00:00:00 nsenter
21530 pts/1 00:00:00 ps
容器看到的是可以通过在主机上面nsenter看到的。这是日常去调试容器里面进程应用的一个法宝!,有些时候容器里面要做一些debug,缺一些工具,可以登入到主机上面进入到容器的namespace里面去看其网络配置,看其端口监听。这样就能够知道网络连接的状态是怎么样的。
Namespace 练习
- 在新 network namespace 执行 sleep 指令:unshare这个命令是说要fork一个进程,fork这个进程可以指定说不和父进程共用一个namespace。
这是告诉操作系统说要创建一个新的进程,这个新的子进程用新的namespace。下面就是unshare启动一个新的进程,并且加入了新的网络namespace。
其实docker就是这种技术,相当于启动了一个新的进程,然后set到了新的namespace里面,在新的namesapce里面可以配置自己的网络。
容器里面的网络设备是docker的网络驱动去配置的,kubernetes也是类似的,它会有自己的网络插件去配置网络。
unshare -fn sleep 60
-n, --net unshare network namespace
-f, --fork fork before launching <program>
[root@docker ~]# unshare -fn sleep 60
[root@docker ~]# lsns -t net
NS TYPE NPROCS PID USER COMMAND
4026532319 net 2 1817 root unshare -fn sleep 60
- 查看进程信息
ps -ef|grep sleep
root 32882 4935 0 10:00 pts/0 00:00:00 unshare -fn sleep 60
root 32883 32882 0 10:00 pts/0 00:00:00 sleep 60
- 查看网络 Namespace
lsns -t net
4026532508 net 2 32882 root unassigned unshare
- 进入改进程所在 Namespace 查看网络配置,与主机不一致
nsenter -t 32882 -n ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
容器分为两个部分,一个叫runtime service,一个叫image service。
runtime service其实就是容器启动时候需要做哪些事情,比如namespace,cgroup这些都是和runtime service紧密相关的,还有一个image service,这个是镜像,镜像更多是overylay fs。
总结
namespace其实就是将进程放到一个隔离的环境,这个隔离的环境和其他的namespace也就是主机是分离的。这样可以又额外的网络配置,域名,文件系统。
实现的主要目的是,有一个进程,这个进程里面跑着一个服务。这个服务希望给它封闭的运行环境,并且有独立的网络标识,这样别人可以来访问。