docker的实现与进程

容器落地中的问题:
什么容器里只能跑“一个进程”?
为什么我原先一直在用的某个 JVM 参数,在容器里就不好使了?
为什么 Kubernetes 就不能固定 IP 地址?容器网络连不通又该如何去 Debug?
Kubernetes 中 StatefulSet 和 Operator 到底什么区别?
PV 和 PVC 这些概念又该怎么用?

原因在于:
从过去以物理机和虚拟机为主体的开发运维环境,向以容器为核心的基础设施的转变过程,并不是一次温和的改革,而是涵盖了对网络、存储、调度、操作系统、分布式原理等各个方面的容器化理解和改造。

初出茅庐的docker
13年Cloud  Foundry 项目已经基本度过了最艰难的概念普及和用户教育阶段,吸引了大批技术厂商。 
此时docker公司微不足道,无人问津,最终决定开源容器项目。
Paas时代似乎到来了,因为提供了应用托管的能力,最核心的组件就是一套应用的打包和分发机制。
核心是调用操作系统的 Cgroups 和 Namespace 机制为每一个应用单独创建一个称作“沙盒”的隔离环境,然后在“沙盒”中启动这些应用进程。

PAAS存在打包过程很麻烦的问题,而 Docker 镜像是直接由一个完整操作系统的所有文件和目录构成的,所以这个压缩包里的内容跟你本地开发和测试环境用的操作系统是完全一样的。
根本上解决问题。

Docker 项目固然解决了应用打包的难题,但它并不能代替 PaaS 完成大规模部署应用的职责。
而只有那些能够为用户提供平台层能力的工具,才会成为开发者们关心和愿意付费的产品。

所以需要容器编排产品,
“编排”在云计算行业里不算是新词汇,它主要是指用户如何通过某些工具或者配置来完成一组虚拟机以及关联资源的定义、配置、创建、删除等工作,然后由云计算平台按照这些指定的逻辑来完成的过程。

所以docker发布了swarm项目,以一个完整的整体来对外提供集群管理功能。

Google则宣告了一个名叫 Kubernetes 项目的诞生。
Google、RedHat 等开源基础设施领域玩家们,牵头发起了一个名为 CNCF的基金会,来对抗以 Docker 公司为核心的容器商业生态。
Swarm 擅长的是跟 Docker 生态的无缝集成,而 Mesos 擅长的则是大规模集群的调度与管理。
那k8s有什么在编排领域的竞争力呢?
Pod、Sidecar 等功能和设计模式。

 Kubernetes 的应对策略则是反其道而行之,开始在整个社区推进“民主化”架构,即:从 API 到容器运行时的每一层,Kubernetes项目都为开发者暴露出了可以扩展的插件机制,鼓励用户通过代码的方式介入 Kubernetes 项目的每一个阶段。

 很快在整个容器社区中催生出了大量的、基于 Kubernetes API 和扩展接口的二次创新工作,比如:
 目前热度极高的微服务治理项目 Istio;
 被广泛采用的有状态应用部署框架 Operator;

容器基础 之 进程
容器其实是一种沙盒技术。顾名思义,沙盒就是能够像一个集装箱一样,把你的应用“装”起来的技术。
一旦“程序”被执行起来,它就从磁盘上的二进制文件,变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合。像这样一个程序运行起来后的计算机执行环境的总和,就是我们今天的主角:进程

对于进程来说,它的静态表现就是程序,平常都安安静静地待在磁盘上;而一旦运行起来,它就变成了计算机里的数据和状态的总和,这就是它的动态表现。

容器,其实是一种特殊的进程而已。
容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。

Cgroups 技术是用来制造约束的主要手段,而Namespace技术则是用来修改进程视图的主要方法。

namespace修改进程的视图:
Docker 在创建容器进程时,指定了这个进程所需要启用的一组Namespace参数,主要包括:
PID, UTS, network, user, mount, IPC, cgroup
效果为:
docker进程就会觉得自己是各自 PID Namespace 里的第 1 号进程
只能看到各自 Mount Namespace 里挂载的目录和文件
只能访问到各自NetworkNamespace里的网络设备,就仿佛运行在一个个“容器”里面,与世隔绝。

容器是一个“单进程”模型的理解
在容器内,除了pid=1的进程,其他进程是不受docker控制的。
线程是最小的执行单元,进程至少需要一个线程作为执行体。进程是最小的资源分配的单元。
容器是一个单进程,那比如我有一个镜像里面集成了jdk, netstat, ping等,虽然这个容器启动时里面是一个java进程,但是我可以进到容器里面执行各种命令,比如netstat等,那这些命令在容器的运行过程中是在运行的吗?
是在运行的。但它们不受docker的控制,就像野孩子。所以单进程意思不是只能运行一个进程,而是只有一个进程是可控的。
一个容器的本质就是一个进程,用户的应用进程实际上就是容器里 PID=1 的进程,也是其他后续创建的所有进程的父进程。这就意味着,在一个容器中,你没办法同时运行两个不同的应用,除非你能事先找到一个公共的 PID=1 的程序来充当两个不同应用的父进程,这也是为什么很多人都会用 systemd 或者 supervisord 这样的软件来代替应用本身作为容器的启动进程。
因为容器本身的设计,就是希望容器和应用能够同生命周期,这个概念对后续的容器编排非常重要。否则,一旦出现类似于“容器是正常运行的,但是里面的应用早已经挂了”的情况,编排系统处理起来就非常麻烦了。


容器基础2:隔离与限制
用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过这些被隔离的进程拥有额外设置过的 Namespace 参数,只能“看到”某些指定的内容。

Linux Cgroups:Linux Cgroups的全称是LinuxControlGroup。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。此外,Cgroups还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。

它就是一个子系统目录加上一组资源限制文件的组合。如CPU路径: /sys/fs/cgroup/cpu

一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。

问题:
/proc文件系统的问题我好像遇到过这个坑..当时在容器上运行的java应用,由于当时jvm参数没正确配置上,就用默认的,而容器设置的内存为4g,最后oom了,当时用命令查看容器的内存占用情况,竟然发现内存竟然有60多g。 那应该显示的是宿主机的内存了,jvm按照宿主机内存大小分配的默认内存应该大于4g 所以还没full gc 就oom了
解决:
把宿主机的 /var/lib/lxcfs/proc/* 文件挂载到容器的/proc/*。
top 是从 /prof/stats 目录下获取数据,所以道理上来讲,容器不挂载宿主机的该目录就可以了。lxcfs就是来实现这个功能的,做法是把宿主机的 /var/lib/lxcfs/proc/memoinfo 文件挂载到Docker容器的/proc/meminfo位置后。容器中进程读取相应文件内容时,LXCFS的FUSE实现会从容器对应的Cgroup中读取正确的内存限制。从而使得应用获得正确的资源约束设定。kubernetes环境下,也能用,以ds 方式运行 lxcfs ,自动给容器注入争取的 proc 信息。

cgroups除了限制资源上限,能否锁定下限?如果不能,那不是很容易被抢资源?
才需要kubernetes 来帮你做调度嘛。


Docker vs 虚拟机
虚拟机的工作原理。其中,名为 Hypervisor 的软件是虚拟机最主要的部分。它通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,比如 CPU、内存、I/O 设备等等。然后,它在这些虚拟的硬件上安装了一个新的操作系统,即 Guest OS。这样,用户的应用进程就可以运行在这个虚拟的机器中,它能看到的自然也只有 Guest OS 的文件和目录,以及这个机器里的虚拟设备。这就是为什么虚拟机也能起到将不同的应用进程相互隔离的作用。
namespace 和 cgroup 是内核特性,容器本质上就是一个加了限定参数的进程,所以说说容器的安全性,也就是隔离性,就是没办法达到虚拟机的级别
虚拟化技术作为应用沙盒,必须要Hypervisor来负责创建虚拟机,且运行GuestOS,会带来二外的资源占用
虚拟机中的应用对主机操作系统的调用会进过虚拟化软件的拦截和处理,又是一层性能损耗
但docker存在隔离的不彻底的问题,部分资源是不能被Namespace化的,最典型的例子就是:时间。你的容器中的程序使用settimeofday(2)系统调用修改了时间,整个宿主机的时间都会被随之修改


白话容器基础三:深入理解容器镜像
chroot: 改变进程的根目录到你指定的位置
chroot $HOME/test /bin/bash 使用 $HOME/test 目录作为 /bin/bash 进程的根目录

挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)。
注意:rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。
同一台机器上的所有容器,都共享宿主机操作系统的内核。所以你的应用程序如果对内核有什么操作,该操作影响该机器上的所有容器,牵一发而动全身。

Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。该想法用到了用到了一种叫作联合文件系统能力。
Union File System 也叫 UnionFS,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。

容器的 rootfs 由如下图所示的三部分组成:
只读层:基础层
Init 层:它是一个以“-init”结尾的层,夹在只读层和读写层之间。Init 层是 Docker 项目单独生成的一个内部层,专门用来存放 /etc/hosts、/etc/resolv.conf 等信息。
可读写层:专门用来存放你修改 rootfs 后产生的增量,无论是增、删、改,都发生在这里。

容器的本质是一种特殊的进程,Namespace的作用是“隔离”,Cgroups的作用是“限制”,rootfs切换进程的根目录。

对 Docker 项目来说,它最核心的原理实际上就是为待创建的用户进程:
1)启用 Linux Namespace 配置;
2)设置指定的 Cgroups 参数;
3)切换进程的根目录(Change Root)。

docker白话基础 重新认识docker
进入容器交互页面的方法:
docker exec -it containerId /bin/sh

获取docker容器在系统中的进程号 : 436
docker inspect --format '{{ .State.Pid }}'  4ddf4638572d

获取436进程的所有 Namespace 对应的文件:
ls -l  /proc/436/ns

一个进程的每种 Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。

 Linux Namespace 的文件,我们就可以对 Namespace 做一些很有意义事情了,比如:加入到一个已经存在的 Namespace 当中。这也就意味着:一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。


Docker 项目另一个重要的内容:Volume(数据卷)
容器技术使用了rootfs机制和MountNamespace,构建出了一个同宿主机完全隔离开的文件系统环境。那么:
容器里进程新建的文件,怎么才能让宿主机获取到?
宿主机上的文件和目录,怎么才能让容器里的进程访问到?
Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。

卷的挂载方式
$ docker run -v /test ...
Docker 就会默认在宿主机上创建一个临时目录 /var/lib/docker/volumes/[VOLUME_ID]/_data,然后把它挂载到容器的 /test 目录上

$ docker run -v /home:/test ... 
把宿主机的 /home 目录挂载到容器的 /test 目录上。

卷的实现方案:
当容器进程被创建之后,尽管开启了 Mount Namespace,但是在它执行 chroot(或者 pivot_root)之前,容器进程一直可以看到宿主机上的整个文件系统。

镜像的各个层,保存在/var/lib/docker/aufs/diff目录下,在容器进程启动后,它们会被联合挂载在/var/lib/docker/aufs/mnt/目录中,这样容器所需的rootfs就准备好了。

所以,我们只需要在 rootfs 准备好之后,在执行 chroot 之前,把 Volume 指定的宿主机目录(比如 /home 目录),挂载到指定的容器目录(比如 /test 目录)在宿主机上对应的目录(即 /var/lib/docker/aufs/mnt/[可读写层 ID]/test)上,这个 Volume 的挂载工作就完成了。

挂载技术,就是 Linux 的绑定挂载(bind mount)机制。它的主要作用就是,允许你将一个目录或者文件,而不是整个设备,挂载到一个指定的目录上。并且,这时你在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而原挂载点的内容则会被隐藏起来且不受影响。

绑定挂载实际上是一个 inode 替换的过程。在 Linux 操作系统中,inode 可以理解为存放文件内容的“对象”,而 dentry,也叫目录项,就是访问这个 inode 所使用的“指针”。

mount --bind /home /test,会将 /home 挂载到 /test 上。其实相当于将 /test 的 dentry,重定向到了 /home 的 inode。这样当我们修改 /test 目录时,实际修改的是 /home 目录的 inode。这也就是为何,一旦执行 umount 命令,/test 目录原先的内容就会恢复:因为修改真正发生在的,是 /home 目录里。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值