容器技术基础

容器技术基础,第1张

什么是容器技术的本质

程序运行起来就是进程。所谓的容器技术,其实就是对进程做一个限制,让他有边界。专业的说法是约束和修改进程的动态表现。

具体来说就是

1. 启用 Linux Namespace 配置;
2. 设置指定的 Cgroups 参数;
3. 切换进程的根目录(Change Root)。
看不懂也没关系,后面会说。
隔离与限制

约束进程依靠的是cgroup技术,隔离,或者叫修改进程的动态表现,依靠的是namespace技术。

隔离

修改进程的动态表现指的是它的“视线”受到了限制,看不到自己“不该看到的”进程、硬盘设备等。

进程通过clone系统调用创建。

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

如果我们在创建的时候给定参数,进程自身就会看不见其他的进程,认为自己是1号进程。
而对OS来说,这个新创建的进程该是多少号,就是多少号。
除了pid,Linux *** 作系统还提供了 Mount、UTS、 IPC、Network 和 User 这些 Namespace。

约束

隔离主要是强调让进程看不见不该看的东西。但事实上这种“看不见”是能力很弱的。容器中的进程终究是进程,有很大地可能影响宿主机。比如吃掉大量资源啊、修改系统时间啊,等等。

为了不让容器中的进程肆意妄为,我们需要Linux 控制组。它能限制进程使用的资源的上限。英文名叫Cgroups。

Cgroups会表现成文件系统的样子。我们想限制进程能使用的资源,其实修改对应的文件就好了。

容器还需要什么?

容器还需要自己的文件系统。
从之前的namespace知识可以了解到,我们可以为进程单独的mount一个文件系统。只有它自己能使用这个文件系统。

注意,只通过mount namespace修改挂载点,进程看到的文件系统是不会变化的。需要重新做一下挂载才行。docker linux帮我们自动做了这些事情。

我们单独mount的文件系统,应该是 *** 作系统原有的文件系统格式一样,这样看起来才像沙箱。我们把这种文件系统叫做rootfs。

rootfs很小,也就20M左右。

但是我们有的时候希望对文件系统做些修改,并进行保留。比如我们想在文件系统里搭一个golang环境,并且希望其他朋友可以复用,这就要求我们保存我们的文件系统,这怎么办呢?

答案是通过layer结构解决这个问题。我们每一组修改都会在原有的文件系统上多一个层。这种增量更新方式让我们不需要更改原有文件系统,就能保存更新。


如图所示,只读层是不能变的。(wh是white out,一会说)
可读写层会保存我们的 *** 作(惰性的)。初始时为空,增加修改删除文件会反映在这个层上。

删除怎么反映呢?其实是通过创建一个.wh.xxx文件去shadow只读层的文件这种方式来反映的。
修改的话是通过cooy on write。修改文件系统会把文件复制到可读写层进行修改。由于查找文件是自上而下的,所以这些修改会被看到。

init层也是可以修改的,通常保存一些与当前容器相关的内容,不希望被保存。

rootfs太麻烦了,怎么办?

制作Dockerfile会比较容易。
下面介绍一下什么是docker file。

准备阶段

先给一个需求,我们要把一个python项目打成镜像:

这个代码放在一个叫app.py的文件中。
它的依赖放在名叫requirements.txt的文件里,该文件与代码同级,且文件里其实只有一行。

Flask

下面举一个docker file的例子,看看如何写docker file。

这些大写字母叫做原语。原语是按顺序执行的。
原语不都是容器内的 *** 作,比如ADD就是从宿主机的角度去看的。

我们把上述内容保存在名为Dockerfile的文件里。

制作镜像
docker build -t helloworld .

docker build会自动加载当前目录下的Dockerfile文件,并按顺序执行里面写的原语。-t是给镜像打个tag,就像起名。

**需要注意的是,每条原语都会生成一个对应的层。**即使原语没有修改什么文件(比如ENV),也会生成对应的层,只不过层是空的。

制作好就可以通过命令查看了

docker image ls

还可以通过命令启动

docker run -p 4000:80 helloworld

这里就是映射一下端口,然后指定一下要启动的镜像名。

复用镜像

当你做了个牛逼的镜像,你当然想把它上传了。

docker tag helloworld zhangsan/helloworld:v1

这里的tag是起完整名。zhangsan是你自己的仓库名,helloworld是镜像名,v1是版本号。

打好tag之后,还要push到远端。

docker push zhangsan/helloworld:v1

这样就完成啦。

在线修改

我们起了一个容器之后,可能在容器之内做了一些修改,并且希望保留这些修改。
这时候,可以用commit命令进行保存。
commit命令会把可读写层和只读层打包起来,提交上去。

 docker commit imageId zhangsan/helloworld:v2

接着push到远端(类似于git commit和git push)

docker push zhangsan/helloworld:v2

企业内部也可以搭建一个大的镜像仓库。这种镜像仓库叫做harbor。

数据卷 Volume

有时我们希望在容器的文件系统和宿主机的文件系统上搭一个桥,这时就要依靠数据卷技术。

Volume技术可以让你把宿主机上的文件挂到容器里。具体地 *** 作方法是:

docker run -v /test
docker run -v /home:/test

第一种方法是新建一个文件夹,挂载到容器的test目录下。第二种方法是把home文件夹挂载到test目录下。
第一种方法新建的文件夹位置在/var/lib/docker/volumes/volumeId/_data

而这个命令背后是如何实现的呢?

实现原理

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

更重要的是,由于在挂载文件夹的时候,已经开启了Mount Namespace,所以这个挂载是宿主机不可见的。

这种机制在linux上叫做绑定挂载机制。主要是说这个挂载的文件夹会正确地被容器修改,且不会影响挂载点原有的文件夹。
这个机制背后的原理如图所示:

inode代表文件,目录是目录项。这种绑定挂载其实就是改变被挂载的目录项的指向。
这样在取消挂载的时候,恢复目录项指针,一切就恢复正常了。
同时,因为改变了test的指向,所以commit不会有影响。

k8s的本质

容器的运行时主要是namespace、cgroup技术去影响的。
而容器的镜像主要是通过chroot、rootfs等实现的。
容器真正的价值在镜像,而非容器的运行时。

因此,真正重要的是对容器镜像的组织和管理。而能够定义容器组织和管理规范的容器编排技术,成为了容器领域的头把交椅。

而容器编排领域的工具,现在最重要的自然是k8s。

k8s的设计理念源于google内部的Borg系统,起点非常高。当然,任何系统都会有一些缺陷,这些缺陷在开源出来后也得到了社区的维护和改善。

k8s的全局架构


node节点与容器高度相关,尤其是kubelet,直接与容器运行时、网络、存储等内容打交道。

master节点呢?
事实上,master节点是borg对k8s指导意义的直接体现。

关系部分

对一个大系统来说,处理不同任务之间错综复杂的关系是最难的部分。比如web应用与数据库之间的关系、负载均衡server与后端服务之间的关系,等等。

当然,像web应用与db之间的关系比较简单,通常给定ip、端口即可。在Compose项目里,我们可以给这样的容器之间定义link。

可关系复杂起来以后,只是定义link就麻烦了。k8s想提出一套统一的理念,使得复杂的关系也可以被k8s表达。

比如,对于一些“紧密联系”的容器,通常是指通过localhost或者本地文件进行通信的容器,会被划分为一个pod。一个pod里的容器共享同一个网络命名空间、同一组数据卷等等。

举例来说,比如一个应用程序进程、以及和他相关的监控进程、日志处理进程、等等。

而对于联系相对更松的容器,k8s提供了一种叫service的服务。比如web应用与数据库。

然而pod的ip是不固定的,像数据库与web应用这种,需要固定的ip,怎么解决呢?答案是给pod绑定一个service,service的ip是不变的,相当于一个代理。

这里只是举了一个例子,其实“关系”有很多。比如我们可能想扩展pod、想存储用户名和密码、等等。具体地内容可以看图。

定性部分

除了容器间的关系问题,我们还需要给容器定性。一个容器(进程、应用),可能是只希望运行一次的(比如大数据处理任务),这种容器我们定为Job。还有一些容器,我们希望它们长期运行,且只有一个实例,比如起守护作用的容器。为了定性这种,我们定义一个DaemonSet。对于定时任务,我们定性为Cronjob等等。

如何使用k8s项目

通过声明式API写yaml文件即可。声明式API的威力是很强大的!

总结

其实k8s的核心,还是容器编排。到底什么是编排呢?
就是按用户的意愿,自动化的管理好各种各样的容器,处理好他们之间的关系。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/990985.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存