(第3章)Docker核心原理解读(续)

1.Docker数据卷

  • 类似于mount操作,用户将一个文件夹作为volume挂载到容器上,可以将数据添加到容器中,以供其中的进程使用。
    多个容器可以共享同一个volume
  • 创建volume,挂载volume,以及使用Dockerfile添加volume,共享volume,珊瑚volume,备份恢复或迁移volume
1)创建volume
创建了一个指定名字的volume
docker volume creat --name vol_simle

挂载到容器的/data目录下
docker run -d -v /data ubuntu /bin/bash

创建一个指定名字的volume,并挂载到容器中的/data目录
docker run -d -v vol_simple: /data ubuntu /bin/bash

获取该volume在宿主机中该文件夹的位置等信息
docker volume inspect vol_simple

(2)挂载volume
创建一个指定名字的volume,并挂载到容器中的/data目录
docker run -d -v vol_simple: /data ubuntu /bin/bash

创建一个随机ID的volume,并将其挂载到/data下
docker run -d -v  /data ubuntu /bin/bash

将宿主机上的目录挂载到容器中
在/host/dir文件夹中的所有文件或文件夹都可以在容器的/container/dir文件夹下被访问,若镜像中的/container/dir文件夹下
有内容,将会被隐藏
docker run -v /host/dir:/container/dir ubuntu /bin/bash

将单个文件作为volume挂载到容器中
文件必须使用绝对路径
docker run -it --name vol_file -v /host/file:/container/file ubuntu /bin/bash

为容器添加多个volume
docker run -it --name vol_mult -v /data1 -v /data2 -v /host/dir:/container/dir ubuntu /bin/bash

(2)使用Dockerfile添加volume
若镜像中存在/data文件夹,该文件夹中的内容将全部被复制到宿主机中对应的文件夹中,并且依据容器中的文件
来设置合适的权限和所有者
VOLUME /data


Dockerfile中除了FROM指令的每一行都是基于上一行生成的临时镜像,运行一个容器,
执行一条指令并执行类似docker commit的命令得到一个新的镜像,docker commit的命令不会对挂载的volume进行保存
FROM ubuntu
RUN useradd foo
RUN mkdir /data && touch /data/file
RUN chown -R foo:foo /data
VOLUME /data

(3)共享volume
新创建的容器vol_use与之前创建的容器vol_simple共享volume,这个volume目的目录也是/data;
若被共享的容器有多个volume(eg:上面的vol_mult),新容器也将有多个volume,并且其挂载的目的目录也与vol_mult
docker run --rm -it --name vol_use --volumes-from vol_simple ubuntu /bin/bash

容器与多个已有容器共享volume
docker run --rm -it --name vol_use_mult --volumes-from vol_1 --volumes-from vol_2 ubuntu /bin/bash


创建一个挂载了volume的数据容器vol_data,该容器仅仅输出了一条提示后就停止运行以避免浪费资源。
接下来的两个容器vol_share1和vol_share2与这个数据容器共享了这个volume
docker run --name vol_data -v /data ubuntu echo "This is a data-only container"
docker run -it --name vol_share1 --volumes-from vol_data ubuntu /bin/bash
docker run -it --name vol_share2 --volumes-from vol_data ubuntu /bin/bash


(4)删除volume
只要在创建容器时挂载了volume,在/var/lib/docker/volumes下就会生成与volume对应的目录,
使用docker rm删除容器并不会删除与volume对应的目录。


只有当没有任何容器使用该volume时,该volume才能成功删除
docker volume rm <volume_name> 删除volume

下面的2种方法:只会对挂载在该容器上的未命名的volume进行删除,而对用户指定名字的volume进行保留
docker rm -v <container_name> 删除容器
docker run --rm ,--rm标签会在容器停止时,删除容器以及容器所挂载的volume

(5)备份恢复或迁移volume
备份volume方法:
方法1:利用docker inspect命令查找到/data在宿主机上对于的文件夹位置,复制其内容,利用tar打包
方法2:
vol_simple容器中包含了需要备份的volume
该命令启动了一个临时容器,该容器挂载了2个volume,第一个volume来自vol_simple容器的共享,即需要备份的volume
第二个volume将宿主机的当前目录挂载到容器的/backup下。
容器运行后,将要备份的内容(/data文件夹)备份到/backup/data.tar,
然后删除容器,备份后data.tar就留在了当前的目录
docker run --rm --volumes-from vol_simple -v $(pwd):/backup ubuntu tar cvf /backup/data.tar /data

恢复volume方法:
首先运行一个新容器作为数据恢复的目标
第二个指令:挂载了两个volume,第一个volume与要恢复的volume共享,第二个volume将宿主机的当前目录挂载到容器的/backup下
容器启动后,将这个存档文件中的/data恢复到根目录下,然后删除容器,恢复后的数据就在vol_bck的volume中
docker run -it --name vol_bck -v /data ubuntu /bin/bash
docker run -rm --volumes-from vol_bck -v $(pwd):/backup ubunttu tar xvf /backup/data.tar -C /

  • Docker的volume的本质是容器中的一个特殊的目录
    Docker会将宿主机上的指定目录(一个volume ID为名称的目录,或指定的宿主机的目录)挂载到容器中指定的目录上,挂载完成胡的宿主机目录和容器内的目录表现一致。
docker run -v /data busybox /bin/bash
指定容器里的/data目录为一个volume
将宿主机上的volume_id目录绑定挂载到rootfs中指定的挂载点/data上
mount("/var/lib/docker/volumes/volume_id/_data","rootfd/data","none",MS_BIND,NULL)

docker run -v /var/log:/data busybox /bin/bash
将宿主机上的/var/log目录绑定挂载到rootfs中指定的挂载点/data上
mount("/var/log","rootfs/data","none",MS_BIND,NULL)

容器内部进行只能看见以rootfs为跟的文件内容以及被mount到rootfs之下的目录
下面的data目录就是生成出来的volume挂载点
root@0a4b299bb8e8:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  test.txt  tmp  usr  var
  • 创建volume原理
    (1)容器创建阶段
    (2)容器的启动阶段,libcontainer使用组装好的挂载点列表进行mount操作,完成volume的创建

  • volume相关的配置文件:该容器所使用的volumeID以及他们的可写情况
    位置:每个容器在/var/lib/docker/containers/容器ID/config.json

2.Docker网络基础

  • Docker公司在libnetwork中使用了CNM(Container Network Model),CNM定义了构建容器虚拟化网络的模型,还提供了可以用于开发多种网络驱动的标准化接口和组件
    (1)CNM的3个核心组件:
    沙盒:包含一个容器网络栈的信息,对容器的接口,路由和DNS设置进行管理
    端点:veth pair,Open vSwitch内部的端口或者相似设备
    网络:Linux bridge,VLAN等
    (2)CNM的5种内置驱动:bridge驱动,host驱动,overlay驱动,remote驱动,null驱动
    在这里插入图片描述
  • 在单一主机上使用Docker默认的bridge驱动进行演示
    在这里插入图片描述
上图中:backend network为后端网络,frontend network则为前端网络

(1)创建backend和frontend网络
docker network creat backend
docker network creat frontend

查看主机上的所有Docker网络;
docker network ls
。。。  bridge   bridge
。。。   none    null
。。。   host    host
上面3个是Docker内置的网络,无法使用docker network rm进行删除

(2)将container1和container2的容器加入到backend网络
将container3的容器加入到frontend网络
docker run -it -name container1 --net backend busybox
docker run -it -name container2 --net backend busybox
docker run -it -name container3 --net frontend busybox

(3)将container2加入到frontend网络
socker network connect frontend container2

(4)在container2中使用ifconfig可以发现:有两块网卡eth0和eth1,
两个ip分别在不同的ip段
  • bridge驱动实现机制分析:(1)Docker0网桥
(1)在宿主机上使用ifconfig命令:可以看到多了一块名为docker0的网卡,
其IP默认为172.17.0.1/16,之后创建的Docker容器都会在docker0子网
的范围内选取一个未占用的ip来使用,并连接到docker0网桥上。
在主机上输入:route -n
...
172.17.0.0   0.0.0.0     255.255.0.0     U     0   0   0 docker0
表示:所有目的IP地址的为172.17.0.0/16的数据包从docker0网卡发出

使用docker run创建一个容器名为con1的容器,在con1容器中可以看到他有两块
网卡:
网卡lo:容器的回环网卡;
网卡eth0:容器与外界通信的网卡,eth0的IP为172.17.0.2/16,和宿主机上的
网桥docker0在同一个网段;

(2)查看容器con1的路由表,发现con1的默认网关是宿主机的docker0网卡。
con1的eth0网卡与宿主机的docker0网卡是互相连通的。

(3)由于veth pair总是成对出现的,veth pair通常用来连接两个network namespace
查看宿主机的网络设备发现会有一块以veth开头的网卡,
另一个是Docker容器con1中的eth0.
所以docker0就是一个网桥

(4)下图3-18是:创建了docker0网桥,并以veth pair连接各容器的网络,容器
中的数据通过docker0网桥转发给eth0的网卡上.
这里的网桥相当于是交换机,工作在二层,所以不需要配置ip地址。

(5)Docker0作为二层设备,为什么要配置IP地址?
docker0是普通的Linux网桥,可认为其内部有一个可以用于配置IP信息的网卡接口。
docker0的IP地址可以作为连于之上的容器的默认网管地址存在!

(6)创建网桥可以使用--bridge+BRIDGE参数指定,也可以
添加一个名为br0的网桥,并且为其配置ip
brctl addbr br0
ifconfig br0 188.18.0.1

查看本机的Linux网桥及其端口
brctl show

在这里插入图片描述

  • bridge驱动实现机制分析:(2)iptables规则
(1)iptables规则主要用于Docker容器和容器之间以及和外界的通信
在宿主机上查看:
iptables-save
。。。
-A POSTROUTING -s 172.17.0.0/16 !-o docker0 -j MASQUERAED
。。。
上面的这条规则表示:Docker容器与外界通信时,将源地址为172.17.0.0/16的
数据包(从Docker容器发出的数据),当不是从docker0网卡发出时做SNAT
(源地址转换,将IP包的源地址替换为相应网卡的地址)
这样,玩不就感觉不到Docker容器的存在。

(2)eg:目的地址转换DNAT
docker run -d -p 5000:5000 training/webapp python app.py
在主机上输入:iptables-save
...
*nat
-A DOCKER | -i docker0 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.17.0.4:5000
...
*filter
-A DOCKER -d 172.17.0.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT

上述的规则将访问宿主机5000端口的流量转发到172.1.0.45000端口上
(真正提供服务的Docker容器的IP端口),所以外界访问Docker容器都是
通过iptables做DNAT

(3)Docker容器之间的通信也受到iptables规则限制。同一台宿主机上的Docker容器默认都连在docker0网桥上,它们属于一个子网。

在Docker容器和外界通信的过程中,还涉及到docker0网卡到宿主机eth0的
转发,还需内核将ip-forward功能打开。
  • bridge驱动实现机制分析:(3)Docker容器的DNS和主机名
1)同一个Docker镜像可以启动很多个Docker容器,通过查看,发现他们
的主机名不一样,即:主机名并非是被写入镜像中的。

(2/etc/hostname,/etc/hosts,/etc/resolv.conf这三个文件,
实际会被容器启动后的虚拟文件覆盖掉的。
在主机输入mount,也可以看到:
...
/dev/disk............on /etc/hostname type ext4
/dev/disk............on /etc/hosts type ext4
/dev/disk............on /etc/resolv.conf type ext4
...

  • Docker网络现在是一个单独的库:libnetwork

  • 传统link:若Docker使用默认的bridge模式网络,则会默认使用传统的link系统
    Link特点:Link是一种比端口映射更亲密的Docker容器间的通信方式,提供了更安全,高效的服务,通过环境变量和/etc/hosts文件的设置,提供了别名到具体通信地址的发现,适合于一些需要各组件间通信的应用。

1)新建一个含有数据库服务的Docker容器,取名为db。
新建一个包含web应用的Docker容器,取名为web,并将web连接到db
web容器称之为接收容器,db容器称之为源容器
docker run -d --name db training/postgres
docker run -d -P --name web --link db:webdb training/webapp python app.py

Docker将连接信息以下面(2)(3)的两种方式保存在接收容器中。

(2)设置接收容器的环境变量
当两个容器通过--link建立了连接后,会在接收容器中额外设置一些环境变量,以保存源容器的一些信息

(3)更新接收容器的/etc/hosts文件
link操作除了将link信息保存在接收容器中,还在/etc/hosts中添加了一项————
源容器的IP和别名(--link),以用来解析源容器,以用来解析源容器的IP地址。

当源容器重启后,会自动更新接收容器的/etc/hosts文件,需要注意的是:这里用
的是别名,而不是源容器的主机名(实际上,主机名对外界是不可见的)。

(4)建立iptables规则(防火墙规则)进行通信
在接收容器上设置了环境变量和更改了/etc/hosts文件之后,接收容器仅仅是得到
了源容器的相关信息(环境变量,IP地址),但是不代表源容器和接收容器在网络上
可以互相通信。

要为连接的容器添加特定的iptables规则,才能保证两个容器间的通信。
eg:接着上面的web和db容器例子
源容器db容器将tcp/5432端口暴露给外界来为外界提供服务,web容器和db容器
需要在db容器的tcp/5432端口上进行通信。
假设:web容器的IP地址为172.17.0.2/16,db容器的IP地址为172.177.0.1/16
在主机上可以看到下面的iptables规则:
-A DOCKER -s 172.17.0.2/32 -d 172.17.0.1/32 -i docker0 ...... --dport 5432 -j ACCEPT
-A DOCKER -s 172.17.0.1/32 -d 172.17.0.2/32 -i docker0 ...... --dport 5432 -j ACCEPT

  • 新link:若用户使用自定义的网络(user-defined network),则会使用新的link系统
    (1)新旧link系统的一个重要区别是:新的link系统在创建一个link时,并不要求源容器已经创建或者启动。
使用bridge驱动创建一个自定义网络isolated_nw,再运行一个容器container1
加入到该网络,并链接另一个容器contianer2(contianer2不存在)

docker network creat isolated_nm
docker run --net=isolated_nm -it --name=container1 --link container2:c2 busybox
docker run --net=isolated_nw -itd --name=container2 busybox

测试:
在container1容器内执行:
ping c2
cat /etc/hosts发现并没有container2的相关信息。
实际上,Docker是通过DNS解析的方式提供名字和别名的解析,这很好地解决了在
传统link系统中由于容器重启造成注入的环境变量更新不及时的问题。

3.Docker与容器安全

  • Docker daemon安全
    (1)其默认是以Unix域套接字的方式来与客户端进行通信
    (2)Docker提供了TLS(Transport Layer Security)传输层安全协议,其安全认证主要是在服务器端设置,客户端可以对服务端进行验证。

  • 内核安全
    (1)容器的本质是进程,cgroups的存在就是为了限制宿主机上不同容器的资源使用量,避免单个容器耗尽宿主机资源而导致其它容器异常
    (2)namespace隔离容器,使容器与容器之间,容器与宿主机之间相互隔离。

  • 磁盘资源限制问题
    Docker通过镜像层叠的方式来构建其文件系统。
    当需要改写文件时,把改写的文件复制到最顶层的读写层,其本质还是在宿主机的文件系统的某一目录下存储这些信息。
    若一个容器把宿主机上所有的磁盘容量耗尽,节时,其它容器将无法进行文件存储的操作。

创建虚拟文件系统来对磁盘进行限额
首先创建虚拟文件系统,接着把Docker的rootfs构建于文件系统之上
(1)创建一个4G的文件
dd if=/dev/zeor of=/usr/dick-quota.ext3 count=4096 bs=1M

(2)在磁盘文件上创建文件系统
mkfs -t ext3 -F /usr/disk-quota.ext3

(3)挂载这个文件系统到指定的容器目录
mount -o loop,rw,usrquota,grpquota /usr/disk-quota.ext3 /path/to/image/top/lever

将创建的虚拟文件系统作为rootfs最顶层的layer
对于需要共享的数据,可以通过挂载volume方式进行磁盘数据存储
  • 容器逃逸问题
    在全虚拟化和半虚拟化中,每一个租户都独立运行一个内核。
    操作熊虚拟化指的是共享内核,内存,CPU以及磁盘等,所以容器的安全问题特别突出。
    解决办法:设置黑白名单

  • 容器DoSe攻击与流量限制问题
    实际上所有容器在共用一张物理网卡,如果在同一宿主机中的某一个容器抢占了大部分带宽,将会影响其它容器的使用,例如:大流量的容器内下载程序会影响其它交互式应用的访问。

  • 通过ulimit限制最大进程数目
    Docker无法使用ulimit来限制fork炸弹问题;
    fork炸弹:以极快的速度创建大量进程,并以此来消耗系统分配给进程的可用空间使得进程表包和,从而系统无法运行新程序



我们使用daemon用户启动4个容器,并设置运行的最大进程数为3
docker run -d -u daemon --ilimit nproc=3 busybox top
docker run -d -u daemon --ilimit nproc=3 busybox top
docker run -d -u daemon --ilimit nproc=3 busybox top
docker run -d -u daemon --ilimit nproc=3 busybox top##失败,资源不足

我们本想的是:在每个容器里,用户最多只能创建3个进程。现在在使用daemon用户
在启动4个容器的时候就失败了。
nproc调节的是属于一个用户UID的最大进程数之和。

<参考:Docker+容器与容器云>

发布了556 篇原创文章 · 获赞 140 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/u011436427/article/details/104248382