docker镜像是否包含操作系统

转载至 docker镜像是否包含操作系统?

还可参考 并非每个容器内部都能包含一个操作系统

结论

操作系统是管理计算机硬件、软件资源,并为计算机程序提供通用服务的系统软件

管理硬件、软件资源,都是由内核实现的。docker镜像是不含内核的,容器与宿主机共享内核。docker镜像只包含一部分提供通用服务的用户态(userland),如各种lib、系统应用,还有一部分服务由内核提供。

docker镜像是否包含操作系统?我这里尝试一次过给大家整明白。可能内容稍微多点,烦请诸君给点耐心,应该不会让你失望的。

1. Docker镜像可以只包含应用本身或者部分操作系统内容

首先,这里先给出我认为正确的答案:完全不包含或者包含操作系统部分内容

  • 部分包含中的部分,指的是可能会包含操作系统如linux各个发行版的工具或者根文件系统,但是不会包含内核,因为容器和docker所在的Host共用kernel。
  • 而完全不包含,指的是这个镜像不会引入任何linux发行版的工具或者根文件系统。

2. 背景知识:操作系统分层

首先,这里要搞清楚操作系统是怎么组成的。操作系统实现原理往深说的话会涉及到操作系统很多的实现细节,往简单点来说的话,我们可以将操作系统简单理解为由两层嵌套的软件组成的。

  • 第一,内核层:即上面说的kernel,最核心的模块,比如CPU调度模块,内存管理模块,IPC通讯模块。一般大家常说的微内核就包含前面这些模块,而Linux作为宏内核,里面可能还会塞进其他一些东西,如设备驱动,文件系统模块等。
  • 第二,应用层:就是跑在我们应用空间的各种应用以及对这些应用提供支持的一些中间件及库之类的,比如linux上的bash,ls工具,ftp服务啊之类的。

而上面说的部分包含,说的就是包含这里应用层的部分工具或者rootfs,即/etc/之类的。

下面我们通过示例来证明这两种情况。

3. 证明:Docker镜像可以不包含任何操作系统内容

要证明一个docker镜像中不包含任何操作系统相关的东西,思路很简单,我们创建一个最简单的hello world应用,然后编写Dockerfile,文件不要引入任何其他层的文件系统,而是直接基于scratch这个空镜像,然后把我们的hello world应用打包进去,最后看它是否能跑起来就可以了。

3.1. 创建HelloWorld应用

我们这里用go语言来写个简单的代码

// main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

然后通过以下命令构建可执行程序

$ GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o hello
$ file hello
> hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked

3.2. 基于空镜像创建新镜像

什么是空镜像
跟着创建Dockerfile来打包镜像,注意这里我们FROM中填写的是上面说的scratch这个空镜像,而不是debian什么的。也就是说最终这个容器里面只有一个/文件夹,下面只有一个hello,且运行是直接执行命令/hello来在该容器内运行该应用

FROM scratch
COPY hello /
CMD ["/hello"]

3.3. 运行新容器并正常输出

构建hello镜像并运行容器

$ docker build -t hello .
$ docker run hello
> Hello World

从输出中我们可以看到应用如预期般运行起来并正常输出。那么下一步,我们就是去证明这个容器里面确实没有包含操作系统任何的东西。

3.4. 分析镜像文件系统证明不包含任何操作系统

思路很简单,我们通过第三方工具来查看hello这个镜像的分层文件系统。看下里面有没有包含操作系统rootfs之类的任何操作系统相关的文件夹或者文件。

这里我用的是dive这个工具,输出如下(执行dive hello的输出):

在这里插入图片描述

很明显,整个镜像里面只有一个文件hello,不包含任何其他内容,更别说包含任何操作系统了!不信的话我们可以试下在镜像中执行个bash。

在这里插入图片描述

file not found,证明完毕!

4. 证明:Docker镜像可以包含部分操作系统内容,但不包含kernel

其实证明了上面说的镜像可以不包含任何操作系统,依然能正常把应用在容器中运行起来,也从另一个侧边说明,我在镜像中可以添加任何内容,并不影响我的hello能正常运行。而事实上,有些流行的应用往往需要用到很多操作系统用户层提供的库才能正常运作,比如我们下面分析的nginx镜像,里面就用了debian的rootfs文件系统。

$ docker run -d -P --name nginx nginx:latest

4.1. 分析nginx镜像文件

我们同样通过上面的dive工具来对nginx镜像进行分析(dive nginx输出)

在这里插入图片描述

从上面的输出,我们第一感觉这里面就是某个linux发行版的根文件系统内容。我们去看下它的Dockerfile:

https://github.com/nginxinc/docker-nginx/blob/594ce7a8bc26c85af88495ac94d5cd0096b306f7/mainline/buster/Dockerfile

FROM debian:buster-slim

LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"

ENV NGINX_VERSION   1.17.10
ENV NJS_VERSION     0.3.9
ENV PKG_RELEASE     1~buster
...

看上去应该是基于debian的发行版。那么这里包含了debian的多少东西呢?

这了我们再去看下debian:buster-slim对应的Dockerfile:

https://github.com/debuerreotype/docker-debian-artifacts/blob/d6ff3e75eeae3ea012c30bce9054336d99d1a20a/buster/slim/Dockerfile

FROM scratch
ADD rootfs.tar.xz /
CMD ["bash"]

很明显,这里面也是基于scratch这个空镜像,但是在这基础上加入了debian的rootfs,注意这里并没有加入kernel等的内容,仅仅只是根文件系统而已。

这也就证明了我们上面说的Docker镜像可以报包含操作系统部分内容的说法,而在这里,部分内容指的就是debian发行版的根文件系统。

证明完毕!

5. 结案陈词

从上面的分析我们可以看到,一个Docker的镜像其实本来可以不包含任何东西的。Docker容器本身也就是一个独立进程(组)而已。我们一开始只是想通过Linux内核底层的namespaces把这个进程隔离开来以防影响其他进程或者被其他进程影响,通过cgroup来限制其资源的使用而已。

但是到了后来,大家发现可以把各种不同Linux发行版的rootfs加入到镜像中作为基础镜像,通过简单的docker run -it fedora bash 之类的命令就能愉快的玩耍不同的发行版。且有了这些基础设施后,如nginx这些应用就很容易的在容器上跑起来。

我想这也就是为什么后来出现的很多镜像都是基于一些Linux发行版rootfs的,从而让一些不明觉厉的朋友们误以为docker镜像是包含了各种linux操作系统的发行版了!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐