作者:荣幸
为什么是容器
如果问你现在最热门的服务器端技术什么?想必很多人会不假思索的说是容器!
容器技术实际上并不是一个新鲜的名词,现在大家一提到容器马上想到的就是Docker,但是容器这个词并不是Docker公司发明的,早期的Pass项目如CloudFoundry,其底层就是基于namespace和cgroups的容器技术。
Docker在当时实际上是个小弟,并没有引起大家的注意,但是现在Docker已经成为容器事实上的标准,是什么让Docker发展成现在这样的程度呢?
这个功能就是Docker镜像。
早期的Pass平台最为人诟病的一个软肋就是应用的打包部署问题,一个应用包在本地部署的好好的,接入Pass平台后却问题重重,而Docker镜像解决了这个根本性问题。
容器到底是怎么回事
前面我们说了容器实际上是很早以前就有的技术,主要用到的是Linux的namespace,cgroups和rootfs。
我们经常说沙盒或者集装箱,他们里面装的是货物,那容器这个沙盒装的又是什么呢?是进程!
我们把进程装进一个沙盒(容器)里面,给他制造边界,和盒子外面的世界隔离,所以我们会说容器实际上就是加了围墙的一个进程。
Namespace
为进程制造边界就需要用到namespace技术,我们先运行一个docker进程看一下:
docker run -it busybox /bin/sh
# -it 是提供一个tty的输入输出环境
# -d 后台运行程序
# -v 挂载外部存储
# -p 端口映射
# -e 参数变量
# busybox 轻量级的容器镜像
我们执行ps命令,可以看到有趣的现象,PID为1的就是我们的启动进程。
而实际上在宿主机中也会同步启动一个进程,其PID在宿主机中是22035。
这就是PID namespace实现的障眼法,它在Linux进程启动的时候(clone函数),会添加CLONE_NEWPID的参数,进程就会看到一个新的命名空间,所以进程ID就会变成1,实际上进程在宿主机上面还是22035。
除了PID namespace之外,还有很多的namespace,比如Network、Mount、User等,通过这些namespace对进程进行网络、储存、文件等进行限制,使这个进程看不到宿主机的真实情况,装在了盒子里,这就是容器的核心原理了。
Cgroups
我们通过 Linux 的命名空间为新创建的进程隔离了文件系统、网络、宿主机器上的其他进程,但是命名空间并不能够为我们提供物理资源上的隔离,比如 CPU 或者内存。在同一台机器上可能运行着多个对彼此以及宿主机器一无所知的『容器』,但这些容器却共同占用了宿主机器的物理资源。
如果其中的某一个容器正在执行 CPU 密集型的任务,那么它就会影响其他容器的任务执行效率,导致多个容器相互影响并且抢占资源。如何对多个容器的资源使用进行限制就成了解决进程虚拟资源隔离之后的主要问题,而 Control Group(简称 cgroups)就能隔离宿主机器上的物理资源,例如 CPU、内存、磁盘 I/O 和网络带宽。cgroups介绍、应用实例及原理描述
容器是一个单进程的模型
通过namespace和cgroups的学习我们知道了容器就是一个启用了多个namespace的应用进程,而这个进程能够使用的资源受到cgroups的限制。这里面有个很重要的概念:容器是一个单进程的模型。
由于容器本质上面是一个进程,即PID=1的进程,他是后续其他进程的父进程,这就意味着在一个容器内,你没有办法同时运行两个应用,除非找到一个公共的PID=1的父进程,并使用像systemd或者supervisor这样的软件替代PID=1的进程来做为容器的启动进程。
但是我们还是希望容器和应用是同生命周期的,因为如果容器是好的,而里面的进程却已经挂了,这样处理起来就会非常麻烦了。
通过上面对容器原理的了解,我们能不能分析出容器和虚拟机的区别?
- 虚拟机需要hypervisor层,在上面创建虚拟机是一个完整的OS
- 容器是Linux上的一个进程
- 虚拟机的OS资源消耗比容器大的多
- 容器使用的是宿主机上相同的内核
- 容器隔离不了时间等资源
镜像
前面说了Docker能够成为容器现在的事实标准,主要是因为Docker创新了镜像这个东西,那么镜像在Linux系统里面是怎么存在的呢?
我们Docker的工作目录是/app/docker/docker/ ,其中有个overlay2子目录,它就是我们的镜像目录。
我们在这个目录下有三个目录和一个l的目录,如下:
我们可以进入其中一个目录,并查看该目录下diff子目录的内容:
到这里我们可以知道,镜像是由多个层组织并定义的,这些层本质上是文件,这些文件是只读的,每层具体的文件存放在层标识符下的diff目录下。
所以我们在制作镜像的时候就需要理解层的概念,提高镜像制作的效率和重复使用性。
以我们最常使用的Dockerfile制作镜像举例:
FROM node:8.16.0
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" >> /etc/timezone
RUN date -R
RUN mkdir -p /opt/app/
WORKDIR /opt/app
RUN rm -rf /opt/app/node_modules/
COPY package.json ./
RUN npm install --registry=https://registry.npm.taobao.org
Dockerfile 中的每一条命令在最终生成的镜像中都会产生一层。Docker为了提高镜像分发效率,给镜像赋予了复用层的能力,在拉取,推送,build不同镜像时,不同镜像中内容相同的层可以被复用从而节省大量操作。
所以我们应该尽量把不变的命令放到 Dockerfile 的上层,这样会显著提高镜像的使用效率。
如果希望了解更多关于Docker或运维技术的知识,请关注徐徐运维的公众号