月度归档:2015年01月

Docker容器和镜像存储机制

Docker的存储机制采用了非常灵活的模块化设计,目前支持5种存储引擎,分别为aufs、btrfs、device mapper、vfs和overlay。它们的共同特点都是实现了graphdriver.Driver接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type ProtoDriver interface {
  String() string
  //创建layer
  Create(id, parent string) error 
  //删除layer
  Remove(id string) error 
  //mount, 获取容器挂载点
  Get(id, mountLabel string) (dir string, err error) 
  //umount文件系统
  Put(id string) 
  Exists(id string) bool
  Status() [][2]string
  Cleanup() error
}
 
type Driver interface {
  ProtoDriver
  Diff(id, parent string) (archive.Archive, error)
  Changes(id, parent string) ([]archive.Change, error) 
  ApplyDiff(id, parent string, diff archive.ArchiveReader)(size int64,err error)
  DiffSize(id, parent string) (size int64, err error)
}

所以,只要实现了存储驱动接口定义的方法,就可以扩展出一种存储引擎。想要更换存储引擎有两种方法:
1. docker daemon进程启动时指定-s参数:docker –s aufs。
2. 修改配置文件:DOCKER_OPTS="--storage-driver=aufs"。

在Docker存储模型中,bootfs同宿主机,rootfs则是由多个镜像层和一个容器层构成,其中镜像层只读,容器层可读写,多个镜像层之间有父子关系,下层作为上层镜像的父镜像,最下面的镜像称为base images(基础镜像),相关定义可以参考官网解释
docker image layer

aufs是Docker最早支持的一种存储引擎,它的工作机制和优缺点在前文『aufs文件系统』中已有介绍,aufs能将不同的目录挂载到某一目录下,将各个源目录下的内容联合到目标目录下,并可对不同目录进行权限控制。这个特性非常契合Docker的存储模型:
1. 镜像分层模型对应aufs中的分支(源目录)。
2. 镜像层对应aufs的ro分支,只读;容器层对应aufs的rw分支,可读写。

默认配置下,Docker镜像和容器存储路径($DOCKROOT)位于/var/lib/docker,如果选择的是aufs文件系统作为存储引擎,那么它的子目录树结构(基于docker 1.4.1)应该如下:

1
2
3
4
5
6
7
8
# tree .
├── aufs
│   ├── diff   镜像和容器每层的差异内容
│   ├── layers   镜像和容器每层的继承关系
│   └── mnt  容器挂载点
├── containers  容器配置文件,环境变量和日志文件
├── graph 镜像详情、大小等
└── repositories-aufs  镜像摘要

举例说明,当前本地镜像库有一个ubuntu:14.04的镜像,它的层级关系如下:

1
2
3
4
5
6
# docker images -t
└─511136ea3c5a Virtual Size: 0 B
  └─3b363fd9d7da Virtual Size: 192.5 MB
    └─607c5d1cca71 Virtual Size: 192.7 MB
      └─f62feddc05dc Virtual Size: 192.7 MB
        └─8eaa4ff06b53 Virtual Size: 192.7 MB Tags: ubuntu:14.04

那么在aufs/diff目录(相对于$DOCKROOT,下同)下会有以各个层级id命名的目录,每个目录存储着与它父镜像之间的差异:

1
2
3
4
5
6
7
# ls -l aufs/diff/
total 20
drwxr-xr-x 21 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a
drwxr-xr-x  2 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
drwxr-xr-x  6 607c5d1cca71dd3b6c04327c3903363079b72ab3e5e4289d74fb00a9ac7ec2aa
drwxr-xr-x  2 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a
drwxr-xr-x  3 f62feddc05dc67da9b725361f97d7ae72a32e355ce1585f9a60d090289120f73

aufs/layers目录下有以各个层级id命名的文件,文件内容为该层所有的祖先镜像id。例如

1
2
3
4
5
# cat aufs/layers/8eaa4ff06b53ff7730c4d7a7e21b4426a4b...
f62feddc05dc67da9b725361f97d7ae72a32e355ce1585f9a60d090289120f73
607c5d1cca71dd3b6c04327c3903363079b72ab3e5e4289d74fb00a9ac7ec2aa
3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a
511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158

graph目录中存储的是每一层镜像的详细信息和大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# tree graph/
graph/
├── 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a
│   ├── json
│   └── layersize
├── 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
│   ├── json
│   └── layersize
├── 607c5d1cca71dd3b6c04327c3903363079b72ab3e5e4289d74fb00a9ac7ec2aa
│   ├── json
│   └── layersize
├── 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a
│   ├── json
│   └── layersize
└── f62feddc05dc67da9b725361f97d7ae72a32e355ce1585f9a60d090289120f73
    ├── json
    └── layersize

其中,json为该层元信息,layersize为该层大小。

此时用ubuntu:14.04镜像起一个容器:

1
2
# docker run -it -d ubuntu:14.04 /bin/bash
4262b0311350933de15936136b4c9142635782f8fd1a015d2fd2d6c54ed05efb

新建容器的操作会在aufs下三个子目录中分别新建两个以容器id为名的文件/目录,例如4262b031135...和4262b031135...-init,其中4262b031135...-init表示容器的初始层,它记录的信息和ubuntu:14.04镜像的最上层一致。所有在新建容器中的文件操作最终都会记录到aufs/diff/4262b031135...目录中,比如:
1. 新建文件,修改文件。
2. 删除文件和目录,以.wh.{obj}标记。在联合文件系统中被称为除白(Whiteout)对象。
3. 删除目录后新建,以.wh..wh..opq标记。在联合文件系统中被称为不透明(Opaque directory)对象。

aufs/mnt目录是容器的挂载点,通过df命令和mount -v命令进行确认,另外容器的操作日志、环境变量、元信息等也会在containers目录以容器id命名的目录中。容器运行后,可以在sysfs目录中找到对应的从上到下的镜像层次结构,读写权限一目了然(si可以通过mount -v查询):

1
2
3
4
5
6
7
8
# cat /sys/fs/aufs/si_13ac476258e8c5e8/br*
/var/lib/docker/aufs/diff/4262b031135...=rw
/var/lib/docker/aufs/diff/4262b031135...-init=ro
/var/lib/docker/aufs/diff/8eaa4ff06b5...=ro
/var/lib/docker/aufs/diff/f62feddc05d...=ro
/var/lib/docker/aufs/diff/607c5d1cca7...=ro
/var/lib/docker/aufs/diff/3b363fd9d7d...=ro
/var/lib/docker/aufs/diff/511136ea3c5...=ro

aufs为Docker镜像存储带来了可重用性、权限分明、层次清晰等优点后,也带来了它的固有缺陷:
1. 写时复制(Copy On Write),性能不够好。
2. 最大层数限制(127层)。

关于绕开最大层数限制,在『aufs文件系统』中已有讨论,这里针对Docker的使用场景再进行一次归纳:
1. 更换docker存储驱动类型:device mapper, btrfs ...
2. 重新编译aufs: CONFIG_AUFS_BRANCH_MAX_32767=y
3. 精简Dockerfile指令:RUN指令合并,脚本化
4. docker export & docker import

--EOF--

aufs文件系统

aufs是一种实现了联合挂载(union mount)的文件系统,同unionfs类似,它能够将不同类型的文件系统透明地层叠在一起,实现一个高效的分层文件系统。说白了aufs就是能将不同的目录挂载到某一目录下,并将各个源目录下的内容联合到目标目录下,这里每个源目录对应aufs中的一层,用户在目标目录读写时,感觉不到此目录是联合而来的。aufs中的每一层都可以有不同的权限(只读,读写),这个特性使得它在很多开源项目中有应用,比如Knoppix,Live CD, Docker等等。

Linux上unionfs的实现依托了VFS设计的灵活性,从架构上看,它在VFS的下层,在具体文件系统(如ext3, ext4等)的上层。系统调用read()/write()落在VFS上,VFS找到待操作文件的inode(unionfs inode),经过unionfs的路由后找到真实文件的inode,执行操作。这是一个hook的过程。具体实现可以参考这两篇关于unionfs实现的文章:Part1, Part2

0x00: 环境准备

aufs默认没有合入内核,据说被拒绝多次,此生无望。debian下可以通过apt安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# apt-get install -y aufs-tools
# dpkg -l | grep aufs-tools
ii  aufs-tools    1:3.0+20120411-2    amd64    Tools to manage aufs filesystems
# pwd
/root/test
# mkdir a b uniondir
# echo 1 > a/1.txt
# echo 2 > b/2.txt
# tree .
.
├── a
│   └── 1.txt
├── b
│   └── 2.txt
└── uniondir

0x01: 挂载,文件操作

1
# mount -t aufs -o br=/root/test/a:/root/test/b none /root/test/uniondir

aufs里每个源目录都是一个branch,挂载过程就是对不同branch进行联合操作,当不同分支内容相同时,上层覆盖下层。mount命令中,-t表示目标文件系统类型,-o表示挂载参数, none表示挂载的不是设备文件。这些都是mount命令的参数。br是aufs的参数,表示分支,多个分支之间用冒号(:)分隔,由于分支之间在逻辑上是层叠的,因此声明在前的表示逻辑上层,声明在后的表示逻辑上层,示例中a目录覆盖b目录。mount命令执行后,当前文件系统状态如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# mount -v
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
......
none on /root/test/uniondir type aufs (rw,relatime,si=73990447ced17254)
# tree .
.
├── a
│   └── 1.txt
├── b
│   └── 2.txt
└── uniondir
    ├── 1.txt
    └── 2.txt

可见,a、b目录的内容都被联合到uniondir目录下,此时uniondir目录就是一个aufs文件系统类型的挂载点,从df命令也可以验证。

因为在mount br参数里没加读写权限,默认情况下最上层分支可读写,其他层只读,所以,在uniondir目录下创建3.txt,实际上会创建到最上层分支里(a目录):

1
2
3
4
5
6
7
8
9
10
11
12
# echo 3 > /root/test/uniondir/3.txt
# tree .
.
├── a
│   ├── 1.txt
│   └── 3.txt
├── b
│   └── 2.txt
└── uniondir
    ├── 1.txt
    ├── 2.txt
    └── 3.txt

因为读写权限的不同,修改1.txt和2.txt内容对a、b的影响也不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# echo 'add sth' >> /root/test/uniondir/1.txt
# echo 'add sth' >> /root/test/uniondir/2.txt
# tree .
.
├── a
│   ├── 1.txt
│   ├── 2.txt
│   └── 3.txt
├── b
│   └── 2.txt
└── uniondir
    ├── 1.txt
    ├── 2.txt
    └── 3.txt
# cat /root/test/a/1.txt
1
add sth
# cat /root/test/a/2.txt
2
add sth
# cat /root/test/b/2.txt
2

可见,对可读写分支(目录a),修改直接作用到分支上;而对于只读分支(目录b),修改操作会触发一次写时复制(COW, Copy On Write),即先将待修改文件从目录b复制到目录a,然后在目录a修改相应文件。注意写时复制机制可能在修改大文件时带来性能损耗,虽然每个文件最多只会复制一次。

最后验证下aufs文件系统的删除操作:

1
2
3
4
5
6
7
# rm /root/test/uniondir/*
# tree .
.
├── a
├── b
│   └── 2.txt
└── uniondir

所有的删除操作也都是在最上层的可读写层进行,目录b因为是只读层,所以不受影响。但是为什么最终的uniondir目录中不见2.txt文件呢?因为最上层对它进行了遮挡:

1
2
3
4
5
# ls -al /root/test/a
drwxr-xr-x 4 root root 4096 Jan 17 20:08 .
drwxr-xr-x 5 root root 4096 Jan 17 19:20 ..
-r--r--r-- 2 root root    0 Jan 17 19:27 .wh.2.txt
......

aufs通过在上层加“.wh.+文件/目录名”的特殊标记来遮挡下层的文件。

0x02: 高级参数

1. aufs在mount的时候可以指定每个分支的读写权限。比如:

1
# mount -t aufs -o br=a=rw:b=rw:c=ro none /root/test/uniondir

这个挂载选项指定了a分支和b分支为可读写,c目录为只读。每个分支的读写权限记录在/sys/fs/aufs/si_XXX/brNNN文件里,这里,XXX可从mount -v命令中得到,NNN从0开始,表示第一层,往上递增,依次类推。

1
2
3
4
5
6
# ls /sys/fs/aufs/si_73990447c8980254/
br0  br1  br2  xi_path
# cat /sys/fs/aufs/si_73990447c8980254/br*
/root/test/a=rw
/root/test/b=rw
/root/test/c=ro

当有两个及以上的读写层时,在aufs挂载目录中新建文件就会涉及到源目录的选择策略问题,aufs提供了create配置参数,可以指定各种策略(round-robin(rr), most−free−space(mfs), etc),默认情况下只会写到第一层可写层。另外,经过测试发现,aufs文件系统最上层一定是要可读写的,否则会造成此文件系统只读。

2. aufs支持udba参数,用于配置是否监听源目录(branch)的变化。如果需要源目录的修改实时反应到挂载目录,设置挂载参数-o udba=reval,如果对源目录的修改不需反应到挂载目录,设置挂载参数-o udba=none。默认为reval。

0x03: 限制

aufs出于某些原因会限制层叠的分支数量,debian里默认是127层(早期是42层)。首先,层数越多,效率肯定越低。另一方面,由于/proc和sysfs文件系统中有单个文件4k大小的限制,所以依赖于此的的aufs元信息写入就会受影响,无论层次太多还是分支名称过长,都很容易达到这个限制(brs参数为0时,aufs会将所有的分支信息写入到/proc/mount文件里)。要突破这个限制,可以选择重新编译aufs模块,enable CONFIG_SYSFS和设置brs参数为1(貌似debian下的aufs包默认就是开启此功能的),并且设置合适的编译参数(kconfig中CONFIG_AUFS_BRANCH_MAX_32767=y),这种绕过的方法未经测试,理论上应该可行。

拓展阅读:
1. aufs manpage. http://aufs.sourceforge.net/aufs3/man.html
2. aufs changelog. http://aufs.sourceforge.net/
3. aufs tutorial. http://www.thegeekstuff.com/2013/05/linux-aufs/

--EOF--

SSH和Puppet的身份认证机制

互联网上最成熟的身份验证方式就是非对称加密和数字证书技术,它是互联网安全的基石,以此为基础的SSH和SSL/TLS也是不可信网络下进行安全通信的标准协议,可以有效避免通信数据被窃听、篡改和伪造。

这里简单总结下SSH远程登录和Puppet是如何进行通信双方的身份识别。

1. SSH远程登录

SSH的一个主要应用就是安全可靠地登录远程机器,所有数据经过加密传输。客户端和远程主机(服务端)在通信之前需要握手,进行身份验证。首先,服务端任性地向客户端发送自己的公钥PubKey,客户端没得选择,只能线下确认PubKey是否准确,然后接受。如果不进行确认就接受公钥,容易发生中间人攻击,严重的话会导致密码泄露。有了服务端公钥PubKey之后,客户端就要向对方表明自己的身份了,SSH提供了两种方式:第一种是密码登陆,客户端用PubKey加密密码后发送给服务端,服务端用私钥解密后进行身份验证;第二种是密钥登陆,客户端公钥事先存储在服务端,当用户选择密钥登陆时,服务端向客户端发送一个随机数,客户端用用户私钥对随机数加密后发回服务端,服务端将数据解密后得到的随机数如果与之前的一样,表示身份验证通过。密钥登陆能避免中间人攻击。

2. Puppet

Puppet是一个C/S架构的配置管理和部署工具。采用SSL证书对客户端和服务端之间的通信进行身份认证和数据加密传输。Puppet自己提供本地CA,用于签发客户端和服务端的证书,通常情况下,服务端进程会兼任本地CA的职责。当服务端初始化时,首先创建一对本地CA的公私钥,用于签发证书,然后为服务端创建一对公私钥和一张数字证书,这张证书包含了服务端的公钥和公钥签名(用CA私钥签),最后将这张证书分发到所有的客户端;当客户端初始化时,它生成一对公私钥,并且把公钥发送到本地CA(服务端),申请为这个公钥签发一张证书。本地CA(服务端)可以根据不同策略对客户端的申请请求进行确认,这样服务端就有了一张客户端的数字证书。此时,客户端持有了自己的私钥和服务端数字证书,服务端持有了自己的私钥和客户端数字证书,之后的通信就是SSL协议的内容了,双方首先通过本地CA的公钥认证对方数字证书和公钥的合法性,确认双方身份,然后通过非对称加密算法进行随机数的加密传送,推导出双方的会话密钥,最后通过这个会话密钥进行传输内容的对称加解密。

References:
[1]. 『Secure Shell』.
[2]. 『SSH原理与运用(一):远程登录』.
[3]. 『数字签名是什么?』.
[4]. 『SSL』.
[5]. 『SSL/TLS协议运行机制的概述』.
[6]. 『What's the difference between SSH and SSL/TLS?』.
[7]. 『Puppet Labs: Certificates and Security』.

--EOF--

2014年度总结

2014年在准备Milestone评审的PPT中完全过去,又到了一年回首过去的时间。整体来看,今年是在一个快节奏高压力的状态中度过,无论是工作,学习还是生活,都碰到了一些困难,有些解决了,有些没解决,留给多事的2015。

这里不讲工作不讲学习,也不涉及摄影,只总结电影,音乐和阅读。

一、 电影

今年看过的电影有124部。相比去年数量上有了增加,并且院线片也跟得比较紧,但是巨幕体验并没有带来额外的观影快感,几部好片感觉也就那样,比如『驯龙高手2』,『银河护卫队』,『黄金时代』等。从地域角度统计的话,今年看过了较多的中国大陆和韩国片,以娄烨和朴赞郁为典型吧。从电影类型角度来看,文艺片可能会占一个很大的比重,主要还是想丰富一下观影体验,文艺片分两类,一类是比较沉闷的,例如基耶斯洛夫斯基的『蓝白红三部曲』,英格玛·伯格曼的『处女泉』、『假面』、『秋日奏鸣曲』,得强迫着自己看完;另一类是比较不闷的,例如『绝美之城』,『小武』,『孔雀』等,虽然节奏慢,但是故事性很强。以下关键词应该或多或少能代表我2014年的电影之路:新现实主义,娄烨,秦昊,韩国犯罪,文艺电影,朴赞郁,低碳馆。

个人今年看过的最“好看”电影Top10:
1. 克里斯托弗·诺兰 - 『星际穿越』
2. 李杨 - 『盲山』/『盲井』
3. 黄东赫 - 『熔炉』
4. 本·阿弗莱克 - 『逃出德黑兰』
5. 顾长卫 - 『立春』
6. 杨宇锡 - 『辩护人』
7. 贾樟柯 - 『天注定』
8. 张艺谋 - 『大红灯笼高高挂』
9. 奉俊昊 - 『雪国列车』
10. 娄烨 - 『浮城谜事』

二、 音乐

今年听过的音乐专辑有70张。相比去年数量降低不少,感觉质量也没有去年高,没有了像去年初听李志时的那份惊喜。如果非要给个今年最惊喜的发现,那只能是谢天笑了,喜欢他清脆的嗓音以及与古筝相融合的编曲。另外今年一个最大的变化可能是我听歌方式的转变。自虾米改版之后,我就渐渐摆脱了“虾米Web版+Last.FM”这种听歌模式,逐渐适应网易云音乐。在我看来网易云音乐推荐歌曲没有虾米和豆瓣精准,播放列表的组织形式会带来不便,乐评缺失,也没有任何个性化统计功能,但是它将跨平台做得极致,更重要的是应用本身很有质感。

个人今年听过的最“好听”专辑Top10:
1. 李志 - 『勾三搭四』
2. 谢天笑 - 『X.T.X』
3. Various Artists - 『你在红楼 我在西游』
4. Hans Zimmer - 『Interstellar: Original Motion Picture Soundtrack』
5. 阿鲲 - 『舌尖上的中国2: 民以食为天原声音乐大碟』
6. 宋冬野 - 『安和桥北』
7. 腰乐队 - 『相見恨晚』
8. Various Artists - 『醉拳II:电影原声带』
9. Pink Floyd - 『The Wall』
10. 万能青年旅店 - 『万能青年旅店』

三、 阅读

今年看过的书有17本。相比去年的阅读量也是降低的趋势,这是可以预见的,因为很难再有固定的睡前阅读时间了,更重要的是,少了一份沉静的心。在17本书里挑Top 10本身是一件很敷衍的事情,不求数量,但求质量吧。

个人今年看过的最佳图书Top10:
1. 东野圭吾 - 『白夜行』
2. 岸本齐史 - 『火影忍者』
3. 赵健伟 - 『崔健:在一无所有中呐喊』
4. 原研哉 - 『设计中的设计』
5. 东野圭吾 - 『嫌疑人X的献身』
6. Joe Armstrong - 『Erlang程序设计(第2版)』
7. 吴军 - 『数学之美』
8. 东野圭吾 - 『幻夜』
9. J.D. Salinger - 『The Catcher in the Rye』
10. 曾宪杰 - 『大型网站系统与Java中间件开发实践』

--EOF--