深入解析Docker 架构原理

Docker使用了C/S体系架构,Docker客户端与Docker守护进程通信,Docker守护进程负责构建,运行和分发Docker容器。Docker客户端和守护进程可以在同一个系统上运行,也可以将Docker客户端连接到远程Docker守护进程。Docker客户端和守护进程使用REST API通过UNIX套接字或网络接口进行通信。

深入解析Docker 架构原理

Docker

2019年08月10日

一、Docker 简介

什么是Docker?

Docker的英文翻译是”搬运工“的意思,他搬运的东西就是我们常说的集装箱Container,Container 里面装的是任意类型的App,我们的开发人员可以通过Docker 将App变成一种标准化的、可移植的、自管理的组件,我们可以在任何主流的操作系统中开发、调试和运行。

从概念上来看Docker和我们传统的虚拟机比较类似,只是更加轻量级,更加方便使用

Docker和虚拟机主要的区别有一下几点:

1. 虚拟化技术依赖的是物理CPU和内存,是硬件级别的;而我们的Docker是构建在操作系统层面的,利用操作系统的容器化技术,所以Docker同样的可以运行在虚拟机上面

2. 虚拟机中的系统就是我们常说的操作系统镜像,比较复杂;而Docker比较轻量级,我们可以使用Docker部署一个独立的Redis,就像类似于在虚拟机当中安装一个Redis应用,但是我们用Docker部署的应用是完全隔离的。
3. 在传统的虚拟化技术是通过快照来保存的;而Docker引用了类似于源码的管理机制,将容器的快照历史版本一一记录下来,切换成本之低
4. 传统的虚拟化技术在构建系统的时候非常复杂;而Docker可以通过一个简单的Dockerfile文件来构建整个容器,更重要的是Dockerfile可以手动编写,这样应用开发人员可以通过发布Dockerfile来定义应用的环境和依赖,这样对于持续交付非常有利

image_1dhr8o7qrg2p1bhji21ber1at113.png-55.5kB


Docker Engine

Docker Engine是一个C/S架构的应用程序,主要包含下面几个组件;

  • 常驻后台进程Dockerd
  • 一个用来和Dockerd交互的REST API Server
  • 命令行CLI接口,通过和REST API进行交互

image_1dhq84mqt1i0g143f185s1843f7gm.png-34kB

Docker 架构

Docker使用了C/S体系架构,Docker客户端与Docker守护进程通信,Docker守护进程负责构建,运行和分发Docker容器。Docker客户端和守护进程可以在同一个系统上运行,也可以将Docker客户端连接到远程Docker守护进程。Docker客户端和守护进程使用REST API通过UNIX套接字或网络接口进行通信。

image_1dhr90pg31hd71ck876gsju1bgh1g.png-66.2kB

Docker Damon DockerD用来监听Docker API的请求和管理Docker对象,比如镜像、容器、网络和Volume

Docker Client docker client是我们和Docker进行交互的最主要的方式方法,比如可以通过docker run来运行一个容器,然后我们的这个client会把命令发送给上面的Docker

Docker Registry 用来存储Docker镜像的仓库,Docker Hub是Docker官方提供的一个公共仓库,而且Docker默认也是从Docker Hub上查找镜像的,当然你也可以很方便的运行一个私有仓库,当我们使用docker pull或者docker run命令时,就会从我们配置的Docker镜像仓库中去拉取镜像,使用docker push命令时,会将我们构建的镜像推送到对应的镜像仓库中

Images 镜像,镜像是一个制度模板,带有Docker容器的说明,一般来说的,镜像会基于另外的一些基础镜像上面安装一个Nginx服务器,这样就可以构建一个属于我们自己的镜像了

Containers 容器,容器是一个镜像的可运行的实例,可以使用Docker REST API或者CLI来操作容器,容器的实质是进程,但与直接在宿主执行的实例进程不同,容器进程属于自己的独立的命名空间。因此容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间、甚至自己的用户ID。容器内的经常是运行在一个隔离的环境里,使用起来,就好像在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全


二、Docker核心概念

①Docker namespace

Linux 内核2.4.19中开始陆续引用了namespace概念。目的是将某个特定的全局系统资源(global system resource)通过抽象方法使得namespace中的进程看起来拥有它们自己的隔离的全局系统资源实例

命名空间是Linux内核强大的特性。每个容器都有自己的命名空间,运行在其中的应用都是在独立操作系统中运行一样。命名空间保证了容器之间彼此互不影响

image_1dhrm4rc72l23bf8kvng411pu1t.png-175.5kB

②Docker CGroups

Docker容器使用Linux namespace来隔离其运行环境,使得容器中的进程看起来就像在一个独立的环境中运行。但是光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU以及内存等。关于其目的,是为了防止它占用了太多的资源而影响到其它进程;另一方面,在系统资源耗尽的时候,Linux内核会触发OOM (out of memory killer,OOM会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一些内存)这会让一些被杀掉的进程成了无辜的替死鬼,因此为了让容器中的进程更加可控,Docker使用Linux cgroups来限制容器中的进程允许使用的系统资源

Linux Cgroup可以让系统中所运行任务(进程)的用户定义组分配资源—比如CPU时间、系统内存、网络带宽

③Docker UnionFS

UnionFS顾名思义,可以把文件系统上多个目录(分支)内容联合挂载到同一个目录下,而目录的物理位置是分开的。

要理解unionFS,我们首先需要先了解bootfs和rootfs
1.boot file system (bootfs) 包含操作系统boot loader和kernel。用户不会修改这个文件系统,一旦启动成功后,整个Linux内核加载进内存,之后bootfs会被卸载掉,从而释放内存。同样的内核版本不同Linux发行版,其bootfs都是一直的
2.root file system (rootfs) 包含典型的目录结构(/dev/,/proc,/bin,/etc,/lib,/usr,/tmp)

Linux系统在启动时,rootfs首先会被挂载为只读模式,然后在启动完成后被修改为读写模式,随后它们就可以被修改了

假设Dockerfile内容如下

FROM ubuntu:14.04
ADD run.sh /
VOLUME /data
CMD ["./run.sh"]

联合文件系统对应的层次结构如下图所示

58C7C152-A413-4EA5-884E-C29472B38DBB.png-125.5kB

图中的顶上两层,是Docker为Docker容器新建的内容,而这两层属于容器范畴。这两层分别为Docker容器的初始层(init Layer)与可读写层(Read-write Layer)

初始层: 大多是初始化容器环境时,与容器相关的环境信息,如容器主机名,主机host信息以及域名服务文件等。

读写层: Docker容器内的进程只对可读可写层拥有写权限,其它层进程而言都是只读的(Read-Only)。关于VOLUME以及容器的host、hostname、resolv.conf文件都会挂载到这里

  • FROM ubuntu:14.04 设置基础镜像,此时会使用Ubuntu:14.04作为基础镜像
  • ADD run.sh / 将Dockerfile所在目录下的run.sh加至镜像的根目录,此时新一层的镜像只有一项内容,即根目录下的run.sh
  • VOLUME /data 设置镜像的存储,此VOLUME在容器内部的路径为/data。此时并未在新一层的镜像中添加任何文件,但是更新了镜像的json文件,以便通过此镜像启动容器时获取这方面的信息(下面会有详细的介绍)
  • CMD [“./run.sh”] 设置镜像的默认执行入口,此命令同样不会在新建镜像中添加任何文件,仅仅在上一层镜像json文件的基础上更新新的镜像的json文件

三、Docker 存储驱动

Docker最开始采用AUFS作为文件系统,也得益于AUFS分层的概念,实现了多个Container可以共享一个image。但是由于AUFS未并入Linux内核,且只支持Ubuntu,考虑到兼容性问题,在Docker 0.7 版本中引入了存储驱动,目前,Docker支持AUFS、Btrfs、Devicemapper、OverlayFS、ZFS五种存储驱动。

原理说明

写时复制 (CoW)

所有驱动都用到的技术————写时复制,Cow全称copy-on-write,表示只是在需要写时才去复制,这个是针对已有文件的修改场景。比如基于一个image启动多个Container,如果每个Container都去分配一个image一样的文件系统,那么将会占用大量的磁盘空间。而CoW技术可以让所有的容器共享image的文件系统,所有数据都从image中读取,只有当要对文件进行写操作时,才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享一个image,所做的写操作都是对从image中复制到自己的文件系统的副本上进行,并不会修改image的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个副本,每个容器修改的都是自己的副本,互相隔离,互不影响。使用CoW可以有效的提高磁盘的利用率。

用时分配 (allocate-on-demand)

写是分配是用在原本没有这个文件的场景,只有在要新写入一个文件时才分配空间,这样可以提高存储资源的利用率。比如启动一个容器,并不会因为这个容器分配一些磁盘空间,而是当有新文件写入时,才按需分配新空间。

存储驱动介绍

AUFS

AUFS (AnotherUnionFS)是一种UnionFS,是文件级的存储驱动。AUFS能透明覆盖一或多个现有文件系统的层状文件系统,把多层合并成文件系统的单层表示。简单来说就是支持将不同目录挂载到同一个虚拟文件下的文件系统。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS创建该文件的一个副本,使用CoW将文件从只读层复制到可写层进行修改,结果也保存在科协层。在Docker中,只读层就是image,可写层就是Container。

image_1dhrpna9ppp0psca7k1cu216od2v.png-238.7kB

OverlayFS

OverlayFS是一种和AUFS很类似的文件系统,与AUFS相比,OverlayFS有以下特性;

1) 更简单地设计;

2) 从Linux 3.18开始,就加入了Linux内核主线;

3) 速度更快

因此,OverlayFS在Docker社区关注提高很快,被很多人认为是AUFS的继承者。Docker的overlay存储驱动利用了很多OverlayFS特性来构建和管理镜像与容器的磁盘结构

从Docker1.12起,Docker也支持overlay2存储驱动,相比于overlay来说,overlay2在inode优化上更加高效,但overlay2驱动只兼容Linux kernel4.0以上的版本

注意: 自从OverlayFS加入kernel主线后,它的kernel模块中的名称就从overlayfs改为overlay了

OverlayFS使用两个目录,把一个目录置放于另一个智商,并且对外提供单个统一的视角。这两个目录通常被称作层,这个分层的技术被称作union mount。术语上,下层的目录叫做lowerdir,上层的叫做upperdir。对外展示的统一视图称作merged

image_1dhrq9chmjlug3s1uavu7srqh3c.png-306.8kB

注意镜像层和容器层是如何处理相同文件的: 容器层(upperdir)的文件是显性的,会隐藏镜像层(lowerdir)相同文件的存在。并在容器映射(merged)显示出统一视图

overlay驱动只能工作在两层之上,也就是说多层镜像不能用多层OverlayFS实现。替代的,每个镜像层在/var/lib/docker/overlay中用自己的目录来实现,使用硬链接这种有效利用空间的方法,来引用底层分享的数据。

注意: Docker1.10之后,镜像层ID和/var/lib/docker中的目录名不再一一对应

创建一个容器,overlay驱动联合镜像层和一个新目录给容器。镜像顶层中的overlay是只读lowerdir,容器的新目录是可写的upperdir

OverlayFS (overlay2)镜像分层与共享

overlay驱动只工作在一个lower OverlayFS层之上,因此需要硬链接来实现多层镜像,但overlay2驱动原生地支持多层lower OverlayFS镜像(最多128层)。因此overlay2驱动在合层相关的命令(如build何commit)中提供了更好的性能,与overlay驱动对比,减少了inode消耗

容器overlay读写

有三种场景,容器会通过overlay只读访问文件

容器层不存在的文件 如果容器只读打开一个文件,但该容器不在容器层(upperdir),就要从镜像层(lowerdir)中读取。这会引起很小的性能消耗

只存在于容器层的文件 如果容器只读权限打开一个文件,并且容器只存在于容器层(upperdir)而不是镜像层(lowerdir),那么直接从镜像层读取文件,无额外的性能损耗

文件同时存在于容器层和镜像层 那么会读取容器层的文件,因为容器层(upperdir)隐层了镜像层(lowerdir)的同名文件,因此,也没有额外的性能损耗

有以下场景容器修改文件

第一次写一个文件,容器第一次写一个已经存在的文件,容器层不存在这个文件。overlay/overlay2驱动执行copy-up操作,将文件从镜像层拷贝到容器层。然后容器修改容器层新拷贝的文件

  • copy-up 操作只发生在第一次写文件时,后续的对同一个文件的鞋操作都是直接针对拷贝到容器层的文件
  • OverlayFS只工作在两层中。这比AUFS要在多层镜像中查找时性能要好

删除文件和目录 删除文件时,容器会在镜像层创建一个whiteout文件,而镜像层的文件并没有删除,但是whiteout文件会隐藏它。容器中删除一个目录,容器层会创建一个不透明目录,这和whiteout文件隐藏镜像层的文件类似

重命名目录 只有在源文件和目的路径都在顶层容器层时,才允许执行rename操作,否则返回EXDEV。因此,应用需要能够处理EXDEV,并且回滚操作,执行替代的”拷贝和删除”策略

在Docker中配置overlay2 存储驱动

为了给Docker配置overlay存储驱动,你的Docker host必须在Linux kernel3.18版本之上,并且加载了overlay内核驱动。对于overlay2驱动,kernel版本必须在4.0或以上。OverlayFS可以运行在大多数Linux文件系统之上。

注意: 在开始配置之前,如果你已经在使用Docker daemon,并且有一些想保留的镜像,请将他们push到镜像仓库中

我这里使用Centos7.6内核4.18演示

1.停止Docker
[root@i4t ~]# systemctl stop docker

2.检查kernel版本,确定overlay的内核模块是否加载
[root@i4t ~]# uname -r
4.18.9-1.el7.elrepo.x86_64
[root@i4t ~]# lsmod |grep overlay
overlay                90112  0

#如果没有过滤出overlay模块,说明驱动没有加载,使用下面方法进行加载
[root@i4t ~]# modprobe overlay

3.使用verlay2存储来启动docker
#配置方法有2种

(1) 在Docker的启动配置文件中添加--storage-driver=overlay2的标志到DOCKER_OPTS中,这样可以持久化配置

(2) 或者将配置持久化到配置文件/etc/docker/daemon.json中
  "storage-driver": "overlay2"

image_1dhrt4093hak1ilk1amn1c4j11kn3p.png-63.2kB

接下来可以检查Docker是否使用overlay2作为存储引擎

image_1dhrt6fo3aun1e7gqct10in1q3f46.png-67.5kB


Devicemapper

Device mapper是Linux内核2.6.9后支持的,提供的一种从逻辑设备到物理设备的映射框架机制,在该机制下,用户可以很方便的根据自己需要制定实现存储资源的管理策略。AUFS和OverlayFS都是文件级存储,而Devicemapper是块级存储,所有的操作都是直接对块进行操作,而不是文件。Devicemapper驱动会先在块设备上创建一个资源池,然后在资源池上创建一个带有文件系统的基本设备,所有镜像都是这个基本设备的快照,而容器则是镜像的快照。所以在容器看到文件系统是资源池上基本设备的文件系统的快照,并不是为容器分配空间。当要写入一个新文件时妹子容器的镜像内为其分配新的块并写入数据,这个叫做用时分配,上面也介绍了。当要修改已有文件时,再使用CoW为容器快照分配块空间,将要修改的数据复制到容器快照中的新快里再进行修改。

Devicemapper驱动默认会创建一个100G的文件包含镜像和容器,每个容器被限制在10G大小的卷内,可以自己配置调整

image_1dhrtkjoq1uh6k5vc74s1f6t44j.png-279.9kB

Btrfs

Btrfs被称为下一代写时复制的文件系统,并入Linux内核,也是文件级存储,但可以像Devicemapper一直被操作底层设备。Btrfs把文件系统的一部分配置为一个完整的子文件系统,称之为subvolume。采用subvolume,一个大的文件系统可以被划分多个子文件系统,这些子文件系统共享底层的设备空间,在需要磁盘空间时便从底层设备中分配,比如Btrfs支持动态添加设备。用户在系统中新增加硬盘后,可以使用Btrfs的命令将该设备添加到文件系统中。Btrfs把一个大的文件系统当成一个资源池,配置成多个完整的子文件系统,还可以往资源池里加新的子文件系统,而基础镜像则是子文件系统的快照,每个子镜像和容器都有自己的快照,这些快照都是subvolume的快照

image_1dhru0qkf1eogfgk17jsm6a13j550.png-102.1kB

当写入一个新文件时,在容器的快照里为其分配了一个新的数据块,文件写在这个空间里,叫做用时分配。而当修改已有文件时,使用CoW复制分配一个新的原始数据和快照,在这个新分配的空间变更数据,等结束后再进行相关的数据结构指引到新子文件系统和快照,原来的原始数据和快照没有指针指示,被覆盖。

ZFS

ZFS文件系统是一个革命性的全新的文件系统,它从根本上改变了文件系统的管理方式,ZFS完全抛弃了”卷管理”,不再创建虚拟的卷,而是把所有设备集中到一个存储池中来进行管理,用”存储池”的概念来管理物理存储空间。过去,文件系统都是构建在物理设备之上的,并为数据提供冗余,”卷管理”的概念提供了一个单设备的映像。而ZFS创建在虚拟的,被称为”zpools”的存储池上。每个存储池由若干虚拟设备(virtual devices,vdevs)组成。这些虚拟设备可以是原始磁盘,也可以是一个RAID的镜像设备,或者是非标准RAID等级的多磁盘组。这样zpool上的文件系统可以使用这些虚拟设备的总存储容量。

image_1dhrug9rp1b8bqpe5udpcj1ndl5d.png-89kB

在Docker中使用ZFS,首先从zpool里分配一个ZFS文件系统给镜像的基础层,而其它镜像层则是这个ZFS文件系统快照的克隆,快照只是只读的,而克隆是可写的,当容器启动时则在镜像的最顶层生成一个可写成

image_1dhruj4ld15ao14781n65a7h1eia5q.png-101kB

当要写一个新文件时,使用按需分配,一个新的数据块从zpool里生成,新的数据写入这个块,而这个新的空间存储于容器(ZFS的克隆)里。当要修改一个已存在的文件时,使用写时复制,分配一个新空间并把原始数据复制到新空间完成修改

存储驱动的对比及适应场景

image_1dhs0bli8m8bu2qqpa1rsl4ve67.png-339kB

一般来说,overlay2驱动更快一些,几乎肯定比AUFS和devicemapper更快,在某些情况下,可能比Btrfs也快。在使用overlay2存储驱动时,需要注意以下几点

  • Page Caching 页缓存
  • OverlayFS支持页缓存,也就是说如果多个容器访问同一个文件,可以共享一个或多个页缓存选项。这使得overlay2驱动高效地利用了内存,是Pass平台或者高密度场景很好的选择
  • copy_up 和AUFS一样,在容器第一次修改文件时,OverlayFS都需要执行copy_up操作,这会给操作带来一些延迟————尤其这个拷贝很大的文件时,不过一旦文件已经执行了这个向上拷贝的操作后,所有后续对这个文件的操作都只针对这份容器层的拷贝而已
  • Inode limits 使用overlay存储驱动可能导致过多的inode消耗,尤其是Docker host上镜像和容器的数目增长时。大量的镜像或者很多容器启停,会迅速消耗该Docker host的inode。但是overlay2 存储驱动不存在这个问题

针对overlay2小结

overlay2存储驱动已经成为了Docker首选存储驱动,并且性能优于AUFS和devicemapper。不过,也带来了一些与其他文件系统不兼容性,如对open和rename操作的支持,另外,overlay和overlay2相比,overlay2支持了多层镜像,优化了inode的使用。


五、Docker安装

本文使用Centos安装为例,更多系统版本安装请参考官方文档

本次的环境版本如下

[root@i4t ~]# uname -r
4.18.9-1.el7.elrepo.x86_64
[root@i4t ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

#关于内核升级可以参考k8s 1.13安装 https://i4t.com/4087.html

docker官方提供了安装脚本,我们确认好版本就可以直接安装

#我这里使用18.06为Docker版本
export VERSION=18.06
curl -fsSL "https://get.docker.com/" | bash -s -- --mirror Aliyun

#这里说明一下,如果想使用yum list --showduplicates 'docker-ce'查询可用的docker版本。需要先使用docker官方脚本安装了一个docker,才可以list到其他版本

设置overlay2为默认存储驱动,并配置加速器

mkdir -p /etc/docker/
cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "registry-mirrors": ["https://hjvrgh7a.mirror.aliyuncs.com"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

#这里配置当时镜像加速器,可以不进行配置,但是建议配置
要添加我们harbor仓库需要在添加下面一行
  "insecure-registries": ["harbor.i4t.com"],
默认docker hub需要https协议,使用上面配置不需要配置https

设置docker开机启动,CentOS安装完成后docker需要手动设置docker命令补全

yum install -y epel-release bash-completion && cp /usr/share/bash-completion/completions/docker /etc/bash_completion.d/
systemctl enable --now docker

六、Docker 基础操作

关于Docker常用命令可以参考

Docker 常用命令总结 [五]

15333

获取镜像

Docker官方提供了一个公共的镜像仓库: Docker Hub,我们可以从上面获取镜像

#获取镜像的格式
$ docker pull [选项] [Docker Registry 地址[:端口]/仓库名[:标签/版本]]
  • Docker镜像仓库地址: 这里的地址格式一般为[域名/IP]:[端口号],默认是Docker Hub
  • 仓库名: 这里的仓库名称是分两个字段,即<用户名>/<软件名>。对于Docker Hub,比如不给出用户名,默认为library,也就是官方镜像

这里使用Nginx镜像举例

[root@i4t ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
f5d23c7fed46: Pull complete
918b255d86e5: Pull complete
8c0120a6f561: Pull complete
Digest: sha256:eb3320e2f9ca409b7c0aa71aea3cf7ce7d018f03a372564dbdb023646958770b
Status: Downloaded newer image for nginx:latest

上面的命令中没有给出Docker镜像仓库地址,因此将会从Docker Hub获取镜像,而镜像名称为nginx,因此将会获取官方镜像library/nginx仓库中标签为latest也就是最新版,当然也可以选择镜像版本,但是我们要提前去Docker Hub仓库查看对应的Tag版本。从下来过程中也可以看到我们上面说的分层存储概念,镜像由多个存储层所构建。也是一层层的去下载,并非简单的单一下载。下载过程中给出了每一层的ID的前12位,并在下载结束后,给出该镜像完整的sha256的摘要,以确保下载一致性

更多的基础命令可以看https://i4t.com/1730.html 我这里介绍一下前面常用命令没有将的内容

Docker 数据持共享与持久化

目前在Docker容器中管理数据主要有两种方式:

  • 数据卷 (Data Volumes)
  • 挂载主机目录 (Bind Mounts)

数据卷

数据卷是一个可供容器或多个容器使用的特殊目录,它绕过UFS,可以提供很多有用的特性 (相当于NFS)

1. 数据卷可以在容器之间共享和重用
2. 对数据卷的修改会马上生效
3. 对数据卷的更新,不会影响镜像
4. 数据卷默认会一直存在,即使容器被删除

#数据卷的使用,类似于NFS挂载,镜像中的被指定为挂载点的目录中会隐藏掉,能显示看 的是挂载的数据卷

首先我们先创建一个数据卷

$ docker volume create mysql-volume

查看所有的数据卷

$ docker volume ls
DRIVER              VOLUME NAME
local              mysql-volume

#如果使用的Docker有容器在运行,这里可能会不止一个。但是如果不添加-v参数,当容器停止或者删除时,volume同时也会被删除

我们可以使用inspect命令查看指定数据卷的信息

$ docker volume inspect mysql-volume
[
    {
        "CreatedAt": "2019-08-10T05:18:55+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/mysql-volume/_data",
        "Name": "mysql-volume",
        "Options": {},
        "Scope": "local"
    }
]

启动一个挂载数据卷的容器,使用docker run命令时,使用--mount标记来将数据卷挂载到容器里。在一次docker run中可以挂载多个数据卷

#这里演示使用mysql镜像,创建一个名为abcdocker的容器,并加载mysql-volume数据卷到容器的/var/lib/mysql目录(因为这个目录就是mysql默认的存储目录)

$ docker run -d --name abcdocker -v mysql-volume:/var/lib/mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql:5.7

#参数说明
run 启动并创建容器
-d 后台运行
--name 设置名词
-v 挂载数据卷
-e 设置命令(因为mysql默认需要设置密码,使用这个变量是可以不设置密码的)
mysql:5.7 为mysql的镜像版本

因为我本地并没有下载mysql的镜像,所以在运行容器的时候会自动帮我们拉取镜像

image_1dhs46och16jbp518j214noqda71.png-249.2kB

我们可以通过inspect查看web容器的详细信息

$ docker inspect abcdocker
...省略号...
        "Mounts": [
            {
                "Type": "volume",
                "Name": "mysql-volume",
                "Source": "/var/lib/docker/volumes/mysql-volume/_data",
                "Destination": "/var/lib/mysql",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],
...省略号...

#从配置中我们可以看到我们容器挂载了一个名词为mysql-volume的存储卷,并且挂载到/var/lib/mysql目录下

接下来我们可以进入到容器查看

$ docker exec  -it abcdocker /bin/bash

登陆mysql创建并创建一个数据库名称为abcdocker数据库

image_1dhs4bs5re4sbj240t1ppo1t7b7e.png-222.4kB

接下来我们可以将容器删除,并从新创建一个,查看是否有为abcdocker

#删除abcdocker的数据库容器
$ docker rm -f abcdocker
#并创建一个新的
$ docker run -d --name abcdocker -v mysql-volume:/var/lib/mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql:5.7

#查看是否有abcdocker数据库
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| abcdocker          |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

image_1dhs4ioefcs14a1c2v1qm717pu7r.png-280.7kB

数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要删除在删除容器同时移除数据卷。可以在删除容器时使用docker rm -v命令。无主的数据卷可能会占用很多空间

#删除数据卷名称为mysql-volume命令如下
docker volume rm mysql-volume

#删除所有数据卷(请谨慎操作)
docker volume prune

挂载主机目录

Docker持久化存储除了有逻辑卷还有一个是挂载目录

#挂载一个主机目录作为数据卷,可以使用--mount或者使用-v 指定目录(-v也可以指定数据卷) -p参数为端口映射后面会说

[root@i4t ~]# docker run -d --name abcdocker-nginx  -p 80:80 -v /data:/data nginx
630f0d194583b5e3b547572c018bae1ac78f9341364a2c9eebf4ba898c9bf23e
[root@i4t ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
630f0d194583        nginx               "nginx -g 'daemon of…"   3 seconds ago       Up 2 seconds        0.0.0.0:80->80/tcp   abcdocker-nginx
[root@i4t ~]# docker exec -it abcdocker-nginx /bin/bash
root@630f0d194583:/# cd /data/
root@630f0d194583:/data# ls
root@630f0d194583:/data# echo "i4t.com" >abcdocker.txt
root@630f0d194583:/data# exit
exit
[root@i4t ~]# cat /data/abcdocker.txt
i4t.com

上面的测试我们是将宿主机的/data目录挂载到容器的/data目录,本地目录的路径必须是绝对路径,如果使用-v参数本地目录不存在Docker会自动创建一个文件夹。这样宿主机和Docker的/data目录数据就同步,相当于NFS挂载

查看数据卷的具体信息,可以在宿主机使用inspect查看容器信息

$ docker inspect abcdocker-nginx
...省略号...
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/data",
                "Destination": "/data",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...省略号...

七、Docker 网络模式

首先Docker网络模式有Bridge模式、Host模式、Container模式、None模式,一共四种

Bridge

当Docker进程启动时,会在主机上创建一个名为Docker0的虚拟网桥,此主机上启动的Docker容器默认会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机相似,这样主机上的所有容器就通过交换机连在了一个二层网络中。从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器内部网卡),另一端在放在主机中,以vethxxx这样类似的名称命名,并将这个网络设备加入到docker0网桥中。可以使用brctl show命令查看

image_1dhs62lvdrvesevj8bf0e1dpl88.png-39.1kB

如果有多个容器之间需要互相通信,推荐使用Docker Compose或者使用k8s编排工具

Host

如果启动容器的时候使用host模式,那么这个容器将不会获取一个独立的Network Namespace,而是和宿主机共用一个Network Namespace(这里和我们平常使用的虚拟机的仅主机模式相似)。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。但是,容器的其他方便,如文件系统、系统进程等还是和宿主机隔离的

image_1dhs6d500roc1tqj3p1t0tbfd8l.png-24.9kB

Container

这个模式指定新创建的容器和已经存在的容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器也不会自己创建网卡,IP等。而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的还都是属于隔离。两个容器的进程可以通过宿主机的lo网卡设备进行通信

image_1dhs6tndi1aq3qtmeqh1mu1dtl92.png-27.9kB

None

使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等

image_1dhs72eab17igbqq81q1gfi1ka69f.png-18kB

以上几种网络模式配置只需要在docker run的时候使用--net参数就可以指定,默认情况下是使用Bridge模式,也是常见的模式

#例如配置一个host仅主机模式
$ docker run -itd --net=host --name abbcdocker_host1 nginx

八、Docker 图形化管理和监控

Portainer

Portainer (基于Go开发)是一个轻量级的管理界面,可以轻松的管理Docker或者Docker相关的集群

Portainer的使用意图是简单部署,它包含可以在任何Docker引擎上运行的单个容器(Dockerfor Linux && Docker for Windows)

Portainer允许管理Docker容器、image、volume、network等。它与独立的Docker引擎和Docker Swarm兼容

安装Docker 图形化界面

#其他平台安装Portainer可以参考官方文档https://www.portainer.io/installation/
$ docker volume create portainer_data
$ docker run -d -p 8000:8000 -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

#使用portainer需要映射8000和9000端口,同时需要将docker.sock映射到容器中

接下来使用浏览器访问ip:9000端口,打开页面我们就需要创建一个密码,用户名默认是admin (密码需要是8位数)

image_1dhs7pbo015ng1orc1c2rbgp1la29s.png-103.5kB

这里的连接方式有几种,常见的就是本地直接连接,就是宿主机本身连接,使用宿主机连接自己就需要-v参数,将/var/run/docker.sock映射到容器。 大家的步骤和我一样,就按照我的步骤操作即可

image_1dhs7tp9e9a016mrtipmmneava9.png-121.8kB

这里就是主页面,我们在这可以镜像、network、以及容器的相关信息

image_1dhs7ujnscve16kq163k1ame1ojlam.png-320.9kB

这里显示的也比较全,在里面也可以创建容器,设置网络相关。 我就不一一演示了,喜欢的可以自己看一下

image_1dhs80esdq4d1n601fdg9691u4db3.png-140.6kB

除了portainer还有一个Rancher

Rancher是一个开源的企业级容器管理平台,通过Rancher,企业不必自己使用一系列的开源软件去从头搭建容器服务平台。Rancher提供了在生产环境中使用管理Docker和Kubernetes的全栈化容器部署与管理平台 (关注abcdocker,后面我会讲)

image_1dhs8che7b1upfbnsj19d214f7bg.png-324.8kB


cAdvisor

cAvisorGoogle开发的容器监控工具,在前面Prometheus我也介绍cAdvisor,有兴趣的可以点开下面链接看一下

Prometheus 原理介绍

01156
  • 监控Docker Host cAdvisor会显示当前host的资源使用情况,包括CPU、内存、网络、文件系统等。
  • 监控容器 点击Docker Containers链接,显示容器列表。可以详细的打印出每个容器的监控页面

由于cAvisor提供的操作界面略显简陋,而且需要在不同页面之间跳转,并且只能监控一个主机,不支持集群。但是cAdvisor的一个亮点是它可以将监控到的数据导出给第三方工具,由这些工具进行进一步处理

这里可以将cAdvisor定位为一个监控数据收集器,收集和导出数据是它的强项,而并非展示数据,cAdvisor支持很多第三方工具,其中就包括prometheus

这里我们演示一下如何通过docker运行cAvisor

#由于cAvisor是国外的镜像,这里我们使用微软的代理拉取镜像启动容器
$ docker pull gcr.azk8s.cn/google_containers/cadvisor:latest
$ docker run \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:rw \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  gcr.azk8s.cn/google_containers/cadvisor:latest

我这里已经将端口映射到宿主机的8080端口,我们检查docker容器启动成功后,就可以直接访问ip:8080端口

image_1dhs94pbhen1af1157012hjv6vbt.png-236.2kB

image_1dhs9bl318bt14h11mb31q9abi7cq.png-147.9kB

这里我们可以看到宿主机的一些信息

image_1dhs9cga81p4p14495hg1k331q8hd7.png-171.7kB

同时也可以访问http://ip:8080/docker/ 查看容器的运行状态

image_1dhs9hqpa1atl1gqj5al17i2m58dk.png-236.5kB

同时aAdvisor还提供了一个Rest API

https://github.com/google/cadvisor/blob/master/docs/api.md

cAdvisor通过该REST API暴露监控数据(metrics),格式如下

http://:/api//

image_1dhs9nhc3s881hi31q2f1q7a15n2e1.png-1520.6kB

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
abcdocker运维博客
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论
网站搭建
加入我们
  • 站长QQ:381493251一键联系
  • abcdocker 微信公众号
    abcdocker QQ群