活动与资讯

助力企业逐步实现自动化

活动与资讯

活动与资讯

助力企业逐步实现自动化

活动与资讯

关于嘉为

19年行业积淀,嘉为助力企业从运维走向运营

关于嘉为
搜索
搜索
搜索
/
/
/
Docker原理、架构与操作详解

Docker原理、架构与操作详解

2019-09-12 15:55

本文旨在用一篇长文让读者能够清晰的认识与使用docker。本文结合理论与实战,先是从进程隔离、文件隔离、namespace、cgroups、libcontainer展开介绍容器的本质与概念,然后分析docker的技术架构,最后演示docker的常用操作。

 

一、容器本质之进程隔离

 

1.1   容器本质

容器本质上是一种进程隔离的技术。容器为进程提供了一个隔离的环境,容器内的进程无法访问容器外的进程。

1.2   容器及容器中的进程在主机上的呈现

 

启动一个ubuntu的容器:

docker run -it ubuntu

在主机上可以看到启动了三个进程:

第一个是刚刚执行的命令

第二个是启动的容器,容器在系统上就是一个进程

第三个是在该容器父进程下的一个子进程:/bin/bash

在容器中运行top命令生成一个进程:

在主机上继续查看进程:

可以查看到主机上多了一个top命令的进程,该进程的父进程是上面启动容器时运行的/bin/bash

由此,我们可以得知,容器在主机上是一个进程,容器中的进程在主机上,是容器进程树下的子进程或子子进程。

容器中的进程有什么不同呢,直观上看,就是进程编号不一样了,我们使用docker run -it busybox /bin/sh再启动一个busybox的容器,并查看它的进程号:

/bin/sh的进程号为1

在主机上查看:

/bin/sh的进程号是容器父进程下的37508

结论1:容器是一个进程,在容器中启动进程,其实就是在容器这个父进程下启动一个子进程。并且使用“障眼法”对这个子进程的进程编号进行了重新编号,使得用户在容器中查看进程时,如同身处于一个OS环境中。

 

二、容器本质之文件隔离

 

2.1   使用chroot来实现文件隔离

容器的本质是进程隔离,那么容器与外部之间也会存在着文件的隔离。文件系统隔离,这也是容器概念的起始。

容器的概念始于1979 年的UNIX  chroot,它是一个UNIX 操作系统上的系统调用,用于将一个进程及其子进程的根目录改变到文件系统中的一个新位置,让这些进程只能访问到该目录。这个功能的想法是为每个进程提供独立的磁盘空间。

接下来我们使用chroot来设置一个隔离的文件系统,也就是将某个目录设置为根目录。

创建根目录/root/container ,然后运行chroot container进行设置:

报错,是因为chroot设置该目录时,会启动一个仅在该目录范围内操作的bash,而我们没有将bash命令文件拷贝进行来,拷贝相关的命令文件、动态链接库文件:

然后再运行chroot container设置其为根目录:

在上图中,我们运行了pwd,发现该目录已经是根目录了。我们在此命令行窗口下的所有操作都是在此目录范围内进行了。

基于chroot,我们还可以继续将一些常用命令添加进来、设置该目录的访问权限,让用户登录时就切到此目录下等等。

chroot为进程隔离打开了一扇大门,后面就有更多的进程隔离技术加进来,并逐步建立规范,最终形成了容器技术,诞生了家喻户晓的docker。

 

三、容器技术演进历史

 

3.1   容器技术演进历史

1979年,chroot技术的引进开启了进程隔离大门。

2000年,FreeBSD Jails 作为最早的容器技术之一,它由R&D Associates 公司的Derrick T. Woolworth 在2000 年为FreeBSD 引入。这是一个类似chroot 的操作系统级的系统调用,但是为文件系统、用户、网络等的隔离增加了进程沙盒功能。因此,它可以为每个jail 指定IP 地址、可以对软件的安装和配置进行定制,等等

2006 年,Process Containers 由Google 实现,用于对一组进程进行限制、记账、隔离资源使用(CPU、内存、磁盘I/O、网络等)。后来为了避免和Linux 内核上下文中的“容器”一词混淆而改名为Control Groups。Cgroups至今仍然作为容器技术的关键组成之一。

2008 年,LXC作为第一个最完善的Linux 容器管理器的实现方案,是通过cgroups 和Linux 名字空间(namespace)实现的。LXC提供了各种编程语言的API 实现,包括Python3、Python2、Lua、Go、Ruby 和Haskell。与其它容器技术不同的是,LXC 可以工作在普通的Linux 内核上,而不需要增加补丁。

2013年,LMCTFY出现,lmctfy 的意思是“让我为你容器化(Let Me Contain That For You)”。这是一个Google 容器技术的开源版本,提供Linux 应用容器。Google 启动这个项目旨在提供性能可保证的、高资源利用率的、资源共享的、可超售的、接近零消耗的容器。现在为Kubernetes 所用的cAdvisor 工具就是从lmctfy 项目的成果开始的。lmctfy 首次发布于2013 年10月,在2015 年Google 决定贡献核心的lmctfy 概念,并抽象成libcontainer。libcontainer 项目最初由  Docker 发起,现在已经被移交给了开放容器基金会(Open Container Foundation)。Libcontainer目前仍然是docker的重要组成之一。Docker最初使用LXC,后面替换为libcontainer。

2013年,Docker面世。Docker实际上是一家公司,在2013年这家公司还叫做DotCloud,Docker是他们公司的一个容器管理产品,2013年初,DotCloud决定将Docker开源,Docker在短短几个月间风靡全球,DotCloud公司也更名为Docker。

3.2   Docker的发展状况

从goole热度上可以获取到docker的热度如下:

四、容器本质之Namespace

 

4.1   Namespace的类别

在后面的容器技术中,实现上都离不开linux的Namespace技术。Namespace 是Linux 提供的一种内核级别环境隔离的方法。Namespace的隔离有6大分类:

每个进程都有自己的namespace的id。可以通过ls -l 来查看。

关于6种Namespace的简介如下:

  • Mount namespace 让容器看上去拥有整个文件系统,它与chroot相比,具有更好的安全性和扩展性。

  • IPC 全称Inter-Process Communication,是Unix/Linux 下进程间通信的一种方式,IPC 有共享内存、信号量、消息队列等方法。所以,为了隔离,我们也需要把IPC给隔离开来,这样,只有在同一个Namespace 下的进程才能相互通信。

  • Network namespace 让容器拥有自己独立的网卡、IP、路由等资源。

  • UTS namespace 让容器有自己的hostname。 默认情况下,容器的hostname 是它的短ID,可以通过-h 或--hostname 参数设置

  • 容器拥有自己独立的一套PID,同一个主机上,不同容器的进程的编号可以是相同的。容器中的进程的编号也可以与主机的进程编号相同。这就是PID namespace 提供的功能。

  • User namespace 让容器能够管理自己的用户,host 不能看到容器中创建的用户。

4.2   主机中与容器中的进程在Namespace上的呈现

在主机上运行top和ping命令,可以查看到这两个进程的namespace是一致的:

查看top进程的namespace

对比top进程和ping进程的namespace

可以发现,主机上的这两个进程的namespace是一致的。

运行一个容器”centos1”,并在容器中运行top命令,查看容器中的top进程与主机上的top进程的namespace,是不同的:

运行容器,并在容器中运行top命令

在主机上查看容器中的top命令的进程id

查看容器中的top进程的namespace

对比主机上的top进程的namespace

可以发现,主机上的top进程与容器上的top进程的namespace是不同的。

运行第二个centos容器”centos2”,并在容器中运行top进程,与第一个容器”centos1”中的top进程的namespace进行对比:

查看该容器中的top进程的id

查看该容器中top进程的namespace:

与第一个容器”centos1”中的top进程的namespace对比:

可以发现是不一致的。

结论:docker容器,利用namespace实现了文件系统、网络、主机名、进程方面的隔离。

 

五、容器核心技术之cgroups

 

5.1   cgroups定义及作用

cgroups是Linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。

本质上来说,cgroups是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。

cgroups主要有以下几个功能:

资源限制:cgroups可以对任务使用的资源总额进行限制,比如内存大小限制;

优先级分配:通过分配的CPU时间片数量及磁盘IO带宽大小,实际上就相当于控制了任务运行的优先级。

资源统计:cgroups可以统计系统的资源使用量,如CPUwetjfta\mwdhetjgtffu,p个功能非常适用计费。

任务控制:cgroups可以对任务执行挂起、恢复等操作。

5.2   Docker中如何使用cgroups

当我们运行:docker run -it --rm --cpu-period=100000 --cpu-quota=200000 centos /bin/bash

--cpu-period和–cpu-quota 表示在每100毫秒的时间里,运行进程使用的cpu时间最多为200毫秒(也就是要占用两个cpu)

进入容器的cgroups目录,查看启动容器时的cpu配置是否已经生效:

可以看到该配置已经生效,那么接下来容器就在这个cpu限额范围内运行了。

 

六、容器核心技术之Libcontainer

 

6.1   其他的进程隔离技术

前面的内容中,详细介绍了进程隔离的两个最主要技术,namespace和cgroups,除了他们之外,比较重要的进程隔离技术还有selinux、apparmor、capability、netlink。

Selinx与apparmor可以增加对容器的访问控制,capability实现了将超级用户root的权限分割为多种不同的capability权限,从而实现对容器的更细颗粒度的权限控制,netlink技术则可实现对docker容器网络环境的配置与创建。

6.2   LXC、Libcontainer与docker

基于各种进程隔离技术,我们就可以创建一个个满足应用运行要求的容器。但这个过程非常复杂。需要有一个简易的容器管理工具。Docker就是最流行的容器管理工具。但docker并没有直接操作namespace等这些linux内核技术,而是基于lxc或libcontainer之上再进行一次封装。通过lxc\libcontainer来操作这些linux内核技术。

docker1.8之后废弃了LXC(Linux Container,即linux虚拟容器技术),引入了基于Go构建的libcontainer的execution driver. 有了libcontainer这个项目, Docker不再需要依赖于Linux部件( LXC, libvirt, systemd-nspawn... )就可以处理namespaces, control groups, capabilities, apparmor profiles, network interfaces。

LXC是第一个真正意义的容器管理工具,主要是提供了方便的命令行接口供用户操作以调用底层的namespace等进程隔离进行。而libcontainer与lxc相比,实际上是反向定义容器了实现标准,将底层实现都抽象化到Libcontainer的接口。这就意味着,底层容器的实现方式变成了一种可变的方案,无论是使用namespace、cgroups技术抑或是使用systemd等其他方案,只要可以与上层的Libcontainer定义的接口对接,那么libcontainer上层的Docker也就可以运行。这也为Docker实现全面的跨平台带来了可能。

从上面的架构图中可以看到,docker daemon通过docker的execdriver和networkriver完成对容器及容器网络的创建。

6.3   Libcontainer的工作机制

Open Container Initiative(OCI)组织成立以后,libcontainer进化为runC,因此从技术上说,未来licontainer/runC创建的将是符合Open Container Format(OCF)标准的容器。

当使用docker run命令时,将会在主机上创建一个容器,Docker daemon调用libcontainer创建容器的过程如下:

创建逻辑容器LinuxContainer和逻辑进程Process

  • 在执行docker run的时候,docker中的execdriver调用libcontainer创建一个逻辑容器LinuxContainer和逻辑进程Process,逻辑容器位于/var/lib/docker/containers之下。

  • Execdriver在调用libcontainer时,会将从docker run命令接收到的参数(如cpu-quota等)进行处理之后以libcontainer所能接收的格式传输给libcontainer。而逻辑进程Process,则是容器中所要运行的指令及其参数和环境变量等配置信息。

启动逻辑容器LinuxContainer

  • 使用Container.start(Process)方法来启动逻辑容器和创建物理容器。

  • 启动逻辑容器,实际上是在libcontainer中创建一个initProcess的对象,包含了逻辑容器配置、逻辑进程配置,以及进程与外部的交互管道(pipes)等。这个initProcess对象用于创建真正的物理容器。

创建物理容器Container

  • 使用initProcess.start()方法创建物理容器Container。

  • 这个过程中会配置容器的cgroup,创建容器内部的网络设备,配置容器内部的信息,如hostname、ip地址等

  • 最后启动容器中需要运行的进程Endtrypoint

 

七、Docker版本

Docker在1.13之后,采用时间线作为版本号标识。Docker的版本分为社区版(CE)和企业版(EE)。企业版会提供额外的收费服务,比如包括一些经过官方认证的插件、容器等。

社区版又分为三种版本:

稳定版本:stable。一般我们使用此版本。

预发布版:test

待发布版:nightly

 

八、Docker安装

 

8.1   卸载docker

卸载主机上已有的docker版本

8.2   安装前准备

 

配置yum

安装yum工具及devicemapper存储驱动

配置yum

yum makecache的过程中,可以看到可以安装使用的包是dockerr-ce-stable版本,也就是repo中默认只开启stable仓库。

此时如果我们需要安装test或nightly版本,可以使用yum-config-manger –enable docker-ce-test开启此仓库。

安装docker

可以使用yum list查看仓库中docker的版本:

安装docker

启动docker

systemctl start docker

systemctl enable docker

查看docker版本

 

九、Docker镜像下载加速器的配置

可配置阿里镜像加速器,配置方法参考链接:

https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

针对docker客户端版本大于1.10.0的,可通过修改/etc/docker/daemon.json文件来使用加速器。

如果该文件不存在,则直接创建。

操作命令:

sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'

{

  "registry-mirrors": ["镜像加速器地址(可从上面阿里云链接地址中获取)"]

}

EOF

sudo systemctl daemon-reload

sudo systemctl restart docker

 

十、Docker架构

Docker采用了传统的client-server架构模式,总架构图如下:

用户通过docker client与docker daemon建立通信,并将请求发送给后者。Docker daemon中的模块是松耦合结构,各模块各司其职并有机组合,完成用户的请求。

API:用于接收docker client的请求,然后根据不同的请求分发给daemon的不同模块执行相关的工作。

Docker client:是一个泛称,用来向指定的docker daemon发起请求。可以是docker命令行工具,也可以是任何遵循了docker api的客户端。

Graph:作为容器镜像的保管者。不论是docker下载的镜像,还是docker构建的镜像,都由graph统一管理。

Execdriver:是对linux操作系统的namespace、cgroups、apparmor、SELinux等容器运行所需的系统操作进行的一层二次封装,其本质作用类似于LXC,但是功能要更全面。这也就是为什么LXC会作为execdriver的一种实现而存在。不过目前,execdriver最主要的实现也是默认的实现是libcontainer库

Networkdriver:对容器网络环境操作所进行的封装。对于容器来说,网络设备的配置相对比较独立,并且应该允许用户进行更多的配置,所以在docker中,这一部分是单独作为一个driverr来设计和实现的。这些操作具体包括创建容器通信所需的网络,这个网络所需的虚拟网上,分配通信所需的IP,服务访问的端口和容器与宿该机之间的端口映射,设置hosts、resolv.conf、iptables等。

Graphdriver:是所有与容器镜像相关操作的最终执行者。包括从远程docker registry上下载镜像并进行存储,也包括本地构建完镜像后的存储。当用户下载指定的容器镜像时,graphdriver将容器镜像分层存储在本地的指定目录下;同时当用户需要使用指定的容器镜像来创建容器时,graphdriver从本地镜像存储目录中获取指定的容器铝合金,并按特定规则为容器准备rootfs;另外,当用户需要通过指定dockerfile构建全新镜像时,graphdriver会负责新镜像的存储管理。目前graphdriver支持对接四种不同的文件存储:aufs、btrfs、vfs、devmapper。

 

十一、Docker VS 虚拟机

 

11.1Docker与虚拟机的架构对比

Docker架构与虚拟机架构对比情况如下图。最直观的差别在于docker容器中是没有独立的os,而是与宿主机共用同一个内核。然后通过隔离技术,让我们在进入每一个容器时,所看到的内容和操作的环境就像是在一个虚拟机中一样。

11.2容器与虚拟机的优劣对比

容器与虚拟机的对比情况如下:

在安全性上,虚拟机肯定是比容器要好的,显而易见,虚拟机毕竟拥有着独立的操作系统。

在计算资源开销和镜像大小对比上,容器优势更大,从而实现了硬件资源的节约。

在启动速度、快速扩展能力上,容器优势更大,从而实现了容器化应用的快速部署/扩容,从而提高业务的敏捷性

在跨平台迁移能力上,容器优势更大,这对于容器化应用在企业的不同云环境之间的迁移提供了非常大的便利性。

基于以上的种种优势,容器实现了对微服务架构和devops的更好的支持。

 

十二、一张图了解docker命令

下图摘自https://my.oschina.net/u/3115403/blog/834536

12.1 概念介绍

Images:docker镜像。可将容器转化为镜像,也可从镜像运行出一个个的容器实例。类似于虚拟机模板的概念。

Container:容器。有running\stopped\pause三种状态,类似于虚拟机的概念。

Tar files:可以将镜像打包为tar文件,也可以将打包后的tar文件重新load为镜像

Dockerfile:构建镜像的声明式配置文件。Docker技术的核心亮点之一。当我们从一个基础镜像(centos镜像)构建一个另一个基础镜像(如java镜像)时,一般不会使用的在centos镜像中直接安装java的方式,而是将java软件与centos镜像放置于一起,在相同目录下编写好dockerfile,dockerfile中定义了java镜像安装的命令和环境配置参数等安装信息,然后使用docker build命令就可以将这些软件及配置文件打包成一个java镜像。这样做的好处就是通过这种声明式的构建方式最终构建了一个纯净的镜像文件。

Registry:镜像仓库。用于存储镜像文件。

Engine:docker引擎

12.2 与Engine相关的命令

Docker version  查看docker版本

Docker info 显示Docker 系统信息,包括镜像和容器数.

Docker events  从服务器获取实时事件

12.3 与容器相关的命令

状态操作命令:

Docker start  从stop到running

Docker kill  从running到stop,直接kill容器进程

Docker stop  从running到stop,容器在停止前先完成一些保护性的动作之后再停止容器

Docker pause  暂停容器中所有的进程

Docker unpause  恢复容器中所有的进程

容器与镜像相关的操作命令:

Docker commit  将容器保存为一个新的镜像

Docker create  从镜像创建一个容器,该容器状态为stop

Docker run  从镜像创建一个容器并将该容器启动

Docker diff 检查自镜像运行之后,容器里文件结构的更改

容器与tar文件相关的操作命令:

Docker export  将容器导出为一个tar文件

容器自身的其他操作命令:

Docker inspect  获取容器/镜像的元数据

Docker attach  连接到正在运行中的容器

Docker port  列出指定的容器的端口映射

Docker ps 查看系统中的容器

Docker top 查看容器中运行的进程信息

Docker rm  删除容器

Docker logs  获取容器的日志

Docker wait  阻塞运行直到容器停止,然后打印出它的退出代码

Docker exec  在运行的容器中执行命令

Docker network 操作容器网络

12.4 与镜像相关的命令

容器与镜像相关的操作命令:

Docker commit  将容器保存为一个新的镜像

Docker create  从镜像创建一个容器,该容器状态为stop

Docker run  从镜像创建一个容器并将该容器启动

Docker diff 检查自镜像运行之后,容器里文件结构的更改

镜像与tar files相关的命令:

Docker import  将tar文件load为镜像,会丢失相关的元数据和历史记录

Docker load  将tar文件load为镜像

Docker save  将镜像保存为tar文件

镜像与dockerfile相关的命令:

Docker build  基于dockerfile构建镜像

镜像与仓库相关的命令:

Docker pull  从仓库下载镜像

Docker push  将镜像上传至仓库

镜像自身的命令:

Docker images 列举镜像

Docker rmi 删除镜像

Docker tag 为镜像打上标签

Docker inspect  列出镜像的详细信息

Docker history  列出镜像的构建历史

12.5 与tar files相关的命令

Docker export  将容器导出tar文件

Docker import  将tar文件load为镜像,会丢失相关的元数据和历史记录

Docker load  将tar文件load为镜像

Docker save  将镜像保存为tar文件

12.6 与dockerfile相关的命令

Docker build  基于dockerfile构建镜像

12.7 与Registy相关的命令

镜像与仓库相关的命令:

Docker pull  从仓库下载镜像

Docker push  将镜像上传至仓库

仓库自身的命令:

Docker search  在仓库中查找某个镜像

Docker login  登录仓库

Docker logout  登出仓库

 

十三、其他docker命令学习示意图

Docker命令学习示意图:https://blog.csdn.net/yuanfenger/article/details/73316481

Docker命令学习示意图:https://www.twblogs.net/a/5c290471bd9eee01606d2e41

 

十四、Docker run命令简介

 

14.1 Ducker run命令简介

Docker run命令的作用在于从一个容器镜像生成一个容器实例并将其启动。Docker run的主要命令参数如下:

 

十五、Docker run命令关键参数说明

 

15.1 -i -t -d

使用docker run -it [IMAGE_NAME] 可以启动一个容器,并进入命令行交互界面:

使用exit可退出此容器

使用-d可以让容器在后台运行:

15.2 --ip

--ip可以为容器指定ip地址,先使用docker network ls查看主机上的docker网络:

使用--ip参数启动容器:

此时会报错,如果要使用自定义ip,只能使用自定义的容器网络。先创建自定义的容器网络:

也可以在创建时手动指定容器的子网:

重新运行指定容器ip的命令:

执行成功,查看容器ip地址:

15.3 -h -name

-h指定容器中的主机名,--name指定容器的名字

15.4 -p

-p 可以将主机上的端口映射到容器中。-p 8088:80 表示将主机的8088端口映射到容器的80端口上:

15.5 -v

--privileged表示将对主机的文件更改的权限赋予此容器,-v 表示将主机的/host/v1 目录映射给容器上的/con/logs目录 ,如果容器中没有此目录,则会自动创建。执行命令docker run -it --privileged -v /host/v1:/con/logs centos

在主机上的相应目录可看到容器中对该目录写入的内容

为容器挂载主机上的卷并设置容器只有对此卷的只读权限:docker run -it –privileged -v /host/v1:/con/logs:ro centos

15.6 -m -c

-m用来指定容器内存,-c可用于指定容器的cpu配额

我们可以下载progrium/stress容器来测试容器的配额指定。使用以下命令查看该容器有哪些测试功能:

使用docker run -m 2000m可启动一个内存占用2000MB的容器,但该容器中未进行什么进程时,该容器并不会直接占用主机2000MB的内存。此时我们可以启动stress容器,并使用--vm 1和–vm-bytes 2000M表示启动stress容器时在其中启动一个占用内存2000M的进程:

如果我们在一个总限额为2000MB的stress容器中启动3个占用500MB的进程,总共1500MB,那么情况如下:

在容器中是运行超过容器内存限额的进程的:

关于cpu限制,--cpu-share表示主机上运行的容器的cpu使用权重的定义。由于主机上有4个cpu核,所有需--cpu 4参数在容器中启动4个进程以便将主机的cpu全部占满。

关于docker run中的--cpus 参数,则是指定cpu的个数。当主机上有4个cpu时,容器只会使用总共1个cpu,也就是4分之1。

此数值也可以为小数,如0.5 ,那么容器只会使用此主机8分之1的cpu。

当数值与主机的cpu核数相同时,将可使用此主机的所有cpu配额,可以看到4个cpu全满。

我们可以启动5个占满cpu的进程而不是4个,情况如下:

二维码
广州总部 电话:020-38851616
深圳嘉为 电话:0755-83668518
北京嘉为 电话:010-51705705
上海嘉为 电话:021-61269880
© Power by Tencent
© 2019 广州嘉为科技有限公司. All rights reserved. 粤ICP备19059200号
  • 嘉为
  • 嘉为
    嘉为
  • 嘉为

    服务热线:
    广州总部 020-38851616
    深圳嘉为 0755-83668518
    北京嘉为 010-88578622
    上海嘉为 021-61269880

  • 嘉为
  • 嘉为