build docker image in docker

我们在设计 docker 打包服务的时候遇到一个问题:如何在 docker 中 build docker image

这篇文章 中介绍使用

--privileged

参数可以在启动的 docker 中运行新的 docker,但是我发现 systemctl start docker 的时候报错:Failed to get D-Bus connection: No connection to service manager。

更简单的办法是把宿主机的 /var/run/docker.sock 挂载到 docker 的 /var/run/docker.sock,而且此时 docker 和宿主机共享 image,使得在 docker 中 build image 可以大大减小网络传输。

举个例子:

# docker run -t -v /var/run/docker.sock:/var/run/docker.sock -i 0d2e243b2977 /bin/bash
[root@b8bd7326f448 /]# mkdir test
[root@b8bd7326f448 /]# cd test
[root@b8bd7326f448 test]# cat Dockerfile
FROM 18573b5f8de7
ADD xxx /xxx
[root@b8bd7326f448 test]# touch xxx
[root@b8bd7326f448 test]# docker build .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon
Step 0 : FROM 18573b5f8de7
—> 18573b5f8de7
Step 1 : ADD xxx /xxx
—> fa7718637b8a
Removing intermediate container 7d4615157279
Successfully built fa7718637b8a
[root@b8bd7326f448 test]# docker tag fa7718637b8a docker-in-docker-test
[root@b8bd7326f448 test]# docker push docker-in-docker-test
The push refers to a repository [10.19.26.15:5000/docker-in-docker-test] (len: 0)
fa7718637b8a: Image already exists
18573b5f8de7: Image successfully pushed
0f88a20aa712: Image successfully pushed
Digest:sha256:37eeddc4483dbad69136832dee304eb49cb186a1d80a20bb8a19431176340d9f

另外,我没找到如何在 Dockerfile 中定义「把宿主机的 /var/run/docker.sock 的挂载到 docker 的 /var/run/docker.sock」,但是如果使用 kubernetes,可以在 pod 配置文件中定义,比如:

apiVersion: v1
kind: Pod
metadata:
name: docker-in-docker
  labels:
name: docker-in-docker
spec:
containers:
  – name: docker-in-docker
    image: 10.19.26.15:5000/docker-in-docker
    ports:
    – containerPort: 80
      hostPort: 80
    volumeMounts:
      – mountPath: "/var/run/docker.sock"
        name: "docker-socket"
        volumes:
        – name: "docker-socket"
          hostPath:
          path: "/var/run/docker.sock"

参考链接

理解 VXLAN

VXLAN (Virtual eXtensible LAN) 是一种通过三层网络来传输二层网络数据的一种隧道技术,可以解决很多问题,我们主要用它来解决不同宿主机的 docker 实例通信问题。

不同的 VXLAN 有一个 ID,叫做 VXLAN Network Identifier 或者 VNI,有 24 位( VLAN 只有 12 位),能提供 16 million 个 ID。

使用 VXLAN 可以隔离每个 VNI 之间的网络,但是我们目前只使用一个 VNI,也就是所有 docker 宿主机都有相同的 VNI。

VXLAN 还有个名词叫做 VXLAN Tunnel End Point (VTEP),在我们的环境中 VTEP 在每台宿主机上,它有两个接口,一个是普通的对外的接口,一个是面向 docker 的接口,后者通过虚拟交换机和里面的 docker 相连,docker 的 IP 是私有网段。

 

当一台宿主机的 docker 访问另一台宿主机的 docker 时,原始的二层报文包括:

Inner Ethernet header  + Inner IP header + Payload

然后,VTEP 会对其进行封装,在原始的二层报文前面封装如下:

Outer UDP header + VXLAN header  —>  Outer IP header  —>  Outer Ethernet header

D4B1FE0D-F1A2-4C1F-8BF6-9C916F4DDFF8

解释:

| VXLAN 头封装
– Flags:8 比特,取值为 00001000。
– VNI:VXLAN 网络标识,24 比特,用于区分 VXLAN 段。
– Reserved:24 比特和 8 比特,必须设置为 0。

l 外层 UDP 头封装
目的 UDP 端口号是 4789 。源端口号是内层以太报文头通过哈希算法计算后的值。

l 外层 IP 头封装
源IP地址为发送报文的虚拟机所属VTEP的IP地址;目的 IP 地址是目的虚拟机所属
VTEP的 IP 地址。

l 外层 Ethernet 头封装
– SA:发送报文的虚拟机所属 VTEP 的 MAC 地址。
– DA:目的虚拟机所属 VTEP 上路由表中直连的下一跳 MAC 地址。
– VLAN Type:可选字段,当报文中携带 VLAN Tag 时,该字段取值为 0x8100。
– Ethernet Type:以太报文类型,IP协议报文该字段取值为 0x0800。

 

在 VTEP 进行封装之前,事实上源 docker ( 起名 docker1 )需要知道目的 docker ( 起名 docker2 ) 的 MAC 和 IP,而且源 VTEP ( 起名 VTEP1 ) 需要知道目的 VTEP ( 起名 VTEP2 ) 的 IP。

如何知道呢,过程如下(假设 VNI = 864):

1、docker1 发送 ARP 请求包,请求 192.168.0.101 [docker2 IP] 的 MAC 地址;
2、ARP 请求包被 VTEP1 封装成多播包,发给 VNI = 864 的多播组;
3、所有的 VTEP 接收此多播包,并添加 ( VNI – VTEP1– docker1_MAC Address) 映射关系到自己的 VXLAN 表中;
4、目的主机上的 VTEP2 接收到多播包后将其解开,并向本主机上 VNI = 864 的所有虚拟机发送广播包;
5、docker2 看到了 ARP 包后,回应了自己的 MAC 地址;
6、VTEP2 再次封装回应的单播包,通过路由发给 VTEP1;
7、VTEP1 解包,并将包传给 docker1,则最终获取了 docker2 的 MAC 地址;
8、VTEP1 将 ( VNI – VTEP2 – docker2_MAC Address)映射关系添加到自己的 VXLAN 表中。

下面看 docker1 到 docer2 的过程 ( 偷的图,把docker1 换成了 VM1,docker2 换成了 VM2 ):

5B172701-B5A7-46F8-8FDA-35E26D6FA085

1、 发送 IP 数据包到 VM2,即 192.168.0.100 到 192.168.0.101;
2、 VTEP1 查找自己的 VXLAN 表知道要发给 VTEP2,然后依次封装以下数据包头;
a) VXLAN 包头,VNI = 864;
b) 标准 UDP 包头,校验和 checksum 为 0x0000,目标端口号 4789;
c) 标准 IP 包头,目标地址为VTEP2的IP地址,协议号设为 0x11 表面为 UDP 包。
d) 标准 MAC 数据包,目标地址为下一跳设备的MAC地址 00:10:11:FE:D8:D2,可路由到目标隧道端 VTEP2。
3、 VTEP2 接收数据包,根据 UDP 的 destination 端口找到 VXLAN 数据包。接着查找所有所在 VXLAN 的 VNI 为 864 的端口组,找到 VM2;
4、 VM2 接收并处理数据包,拿到 Payload 数据。

 

 

https://sites.google.com/site/amitsciscozone/home/data-center/vxlan

http://blog.sina.com.cn/s/blog_6de3aa8a0101oisp.html

http://e.huawei.com/cn/marketing-material/cn/products/enterprise_network/ce_switches/hw_376141

 

VLAN 与 端口模式 Access、Hybrid、Trunk

VLAN(Virtual Local Area Network)的中文名叫”虚拟局域网”,是一种将局域网设备从逻辑上划分成一个个网段的技术。

 

1 交换机端口工作模式

交换机端口有三种工作模式,分别是 Access,Hybrid,Trunk。

Access 类型的端口只能属于 1 个 VLAN,一般用于连接计算机的端口;

Trunk 类型的端口可以允许多个VLAN通过,可以接收和发送多个 VLAN 的报文,一般用于交换机之间连接的端口;

Hybrid 类型的端口可以允许多个 VLAN 通过,可以接收和发送多个 VLAN 的报文,可以用于交换机之间连接,也可以用于连接用户的计算机。

Hybrid 端口和 Trunk 端口在接收数据时,处理方法是一样的,唯一不同之处在于发送数据时:Hybrid 端口可以允许多个 VLAN 的报文发送时不打标签,而 Trunk 端口只允许缺省 VLAN 的报文发送时不打标签。

 

2 基本概念(tag、untag、802.1Q)

untag 就是普通的 ethernet 报文,普通 PC 机的网卡是可以识别这样的报文进行通讯;
tag 报文结构的变化是在「源 mac」地址和「目的mac」地址之后,加上了 4bytes 的 vlan 信息,也就是 vlan tag头;一般来说这样的报文普通 PC 机的网卡是不能识别的

下图说明了802.1Q封装tag报文帧结构:

20141014102020507

带 802.1Q 的帧是在标准以太网帧上插入了 4 个字节的标识。其中包含:

1). 2 个字节的协议标识符(TPID),当前置 0x8100 的固定值,表明该帧带有 802.1Q 的标记信息;

2). 2个字节的标记控制信息(TCI)。

标记控制信息(TCI)又包含了三个域:

1). Priority域,占3bits,表示报文的优先级,取值0到7,7为最高优先级,0为最低优先级。该域被 802.1p 采用;

2). 规范格式指示符(CFI)域,占 1bit,0 表示规范格式,应用于以太网;1表示非规范格式,应用于 Token Ring;

3). VLAN ID域,占12bit,用于标示 VLAN 的归属。

 

3 交换机接口出入数据处理过程

Acess 端口收报文:

收到一个报文,判断是否有 VLAN 信息:如果没有则打上端口的 PVID,并进行交换转发,如果有则直接丢弃(缺省)。

trunk 端口收报文:

收到一个报文,判断是否有VLAN信息:如果没有则打上端口的PVID,并进行交换转发,如果有判断该trunk端口是否允许该 VLAN的数据进入:如果允许则报文携带原有VLAN标记进行转发,否则丢弃该报文。

hybrid 端口收报文:

收到一个报文,判断是否有VLAN信息:如果没有则打上端口的 PVID,并进行交换转发,如果有则判断该 hybrid 端口是否允许该 VLAN 的数据进入:如果可以则转发,否则丢弃。

 
4. 端口发送报文时的处理

Acess 端口发报文:

将报文的 VLAN 信息剥离,直接发送出去。

trunk 端口发报文:

比较端口的PVID和将要发送报文的VLAN信息,如果两者相等则剥离VLAN信息,再发送,否则报文将携带原有的VLAN标记进行转发。

hybrid 端口发报文:

1). 判断该 VLAN 在本端口的属性;

2). 如果是 untag 则剥离 VLAN信息,再发送,如果是 tag 则比较端口的 PVID 和将要发送报文的 VLAN 信息,如果两者相等则剥离 VLAN 信息,再发送,否则报文将携带原有的  VLAN 标记进行转发。

 

参考:

http://blog.csdn.net/jesseyoung/article/details/40047749

 

使用分区表的方式复制虚拟机

本文通过分区表的方式来复制虚拟机(宿主机和虚拟机都是 Centos6),我们的虚拟机有两块盘,系统盘和数据盘,系统盘分为三个部分,分别是 / 分区、swap 分区和一个 LVM,LVM 里面包含 root (或者 usr、tmp、var 等) lv。

 

原材料如下:

17h3925bj8_mbr   分区表信息,通过 dd 命令生成;

17h3925bj8_os   系统盘数据,tar 包。

17h3925bj8_lvmheader   系统盘中的 LVM header 信息(系统盘是一个 LVM);

17h3925bj8_data   数据盘镜像,qcow2 格式,是一个 LVM,只包括 /home 分区;

17h3925bj8_boot   /boot 分区镜像,通过 dd 命令生成,这里使用拷贝 boot 分区数据的方式不 work,我猜测是因为 MBR 中的 Boot loader( MBR 512 字节中的前 446 字节)会根据位置查找 boot 分区的 grub 引导文件来继续引导。

这些文件都放在 /mfs/wmi 目录下。

另外,还有 swap uuid (通过 blkid 命令获得),这里假设是 59e1e82b-7220-4093-9a98-19ce4d78c46a,创建新 swap 时用到。

 

复制的目标虚拟机名称是 vm6,所在宿主机存储池名称是 vm_storage_pool,格式是 raw。

1. 先创建系统盘和数据盘,大小和源虚拟机一样(创建之后目录是/dev/vm_storage_pool_vg/)。

# virsh vol-create-as –pool vm_storage_pool –name vm6 –capacity 18G
Vol vm6 created

# virsh vol-create-as –pool vm_storage_pool –name vm6_data –capacity 120G
Vol vm6_data created

 

2. 复制 mbr 到目标盘。

# dd if=/mfs/wmi/17h3925bj8_mbr of=/dev/vm_storage_pool_vg/vm6 bs=512 count=1 conv=notrunc

查看分区表。

# fdisk -ul /dev/vm_storage_pool_vg/vm6

Disk /dev/vm_storage_pool_vg/vm6: 19.3 GB, 19327352832 bytes
255 heads, 63 sectors/track, 2349 cylinders, total 37748736 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00081440

Device Boot Start End Blocks Id System
/dev/vm_storage_pool_vg/vm6p1 * 2048 1050623 524288 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/vm_storage_pool_vg/vm6p2 1050624 9439231 4194304 82 Linux swap / Solaris
Partition 2 does not end on cylinder boundary.
/dev/vm_storage_pool_vg/vm6p3 9439232 37748735 14154752 8e Linux LVM

 

3. 恢复 boot 分区、创建 swap、格式化 LVM 中的分区。

查看系统中已经有的 vg。

# vgdisplay | grep “VG UUID” | awk ‘{print $NF}’
DUobji-ikiZ-lrVE-OLmV-cOSY-VjSb-xNzYEF
pAmmcX-BNqu-tOdE-6cLa-s4Rn-JPaR-cojNBY
xWKyUR-qn3g-8uIo-FAQO-LFKp-VDeV-fR7ihu

映射 /dev/vm_storage_pool_vg/vm6 中的设备。

# kpartx -av /dev/vm_storage_pool_vg/vm6
add map vm_storage_pool_vg-vm6p1 (253:14): 0 1048576 linear /dev/vm_storage_pool_vg/vm6 2048
add map vm_storage_pool_vg-vm6p2 (253:15): 0 8388608 linear /dev/vm_storage_pool_vg/vm6 1050624
add map vm_storage_pool_vg-vm6p3 (253:16): 0 28309504 linear /dev/vm_storage_pool_vg/vm6 9439232

# ls /dev/mapper/vm_storage_pool_vg-vm6p*
/dev/mapper/vm_storage_pool_vg-vm6p1 /dev/mapper/vm_storage_pool_vg-vm6p2 /dev/mapper/vm_storage_pool_vg-vm6p3

# dd if=/mfs/wmi/17h3925bj8_lvmheader of=/dev/mapper/vm_storage_pool_vg-vm6p3  bs=512 count=24 conv=notrunc

boot 和 swap 好搞定,先处理。

# dd if=/mfs/wmi/17h3925bj8_boot of=/dev/mapper/vm_storage_pool_vg-vm6p1 bs=10M

# mkswap -f -U  59e1e82b-7220-4093-9a98-19ce4d78c46a /dev/mapper/vm_storage_pool_vg-vm6p2

然后处理 LVM,此时看下系统中的 vg。

# vgdisplay | grep “VG UUID” | awk ‘{print $NF}’
2tcMyv-KIqc-bfMx-7Ups-e8zT-7sV2-I35BmT
DUobji-ikiZ-lrVE-OLmV-cOSY-VjSb-xNzYEF
pAmmcX-BNqu-tOdE-6cLa-s4Rn-JPaR-cojNBY
xWKyUR-qn3g-8uIo-FAQO-LFKp-VDeV-fR7ihu

可以看出 2tcMyv-KIqc-bfMx-7Ups-e8zT-7sV2-I35BmT 是 /dev/vm_storage_pool_vg/vm6 中的 LVM vg,为了防止重名,我们修改一下 vg name(也可以使用 vgimportclone 命令,会修改 uuid 和 vg name),使用随机字符串。

# vgrename 2tcMyv-KIqc-bfMx-7Ups-e8zT-7sV2-I35BmT 82915cddce9b_vg
Volume group “vm6_vg” successfully renamed to “82915cddce9b_vg”

激活 vg。

# vgchange -ay 82915cddce9b_vg
1 logical volume(s) in volume group “82915cddce9b_vg” now active

查看 vg 中的 lvm。

# ls /dev/mapper/82915cddce9b_vg*
/dev/mapper/82915cddce9b_vg-root

格式化 lvm。

# mkfs.ext4 /dev/mapper/82915cddce9b_vg-root

此时已经搞定了 LVM。

 

4. 拷贝系统盘中除了 boot 之外的数据。

首先挂载系统盘镜像,挂载目录 /mnt,使用随机数子目录。

# mkdir /mnt/4702113420fa/

开始挂载目标虚拟机的系统盘,需要先挂载 / 分区。

# mount /dev/mapper/82915cddce9b_vg-root /mnt/4702113420fa/

此处如果除了 / 和 boot 还有其他的分区,也要挂载。

由于我们已经有了系统盘数据,此时解包就可以了:

# tar xf /mfs/wmi/17h3925bj8_os -C /mnt/4702113420fa/

# umount /mnt/4702113420fa

# vgchange -an 82915cddce9b_vg

 

5. 事实上,我们上面修改过 vg 的 name,我们需要还原 vg name。

覆盖 lvm header 即可。

执行:

# dd if=/mfs/wmi/17h3925bj8_lvmheader of=/dev/mapper/vm_storage_pool_vg-vm6p3 bs=512 count=24 conv=notrunc

 

6. 取消系统盘的映射。

# kpartx -dv /dev/vm_storage_pool_vg/vm6

 

7. 删除临时挂载目录。

# rm -rf /mnt/4702113420fa

 

8. 对数据盘创建 LVM,创建分区和格式化文件系统,拷贝数据。

这里不写了,参考这里

 

9. 新虚拟机的镜像完全准备好了,此时可以创建虚拟机配置文件,修改虚拟机和启动虚拟机,此处略去。

 

 

http://manuel.kiessling.net/2013/03/19/converting-a-running-physical-machine-to-a-kvm-virtual-machine/

http://www.thegeekstuff.com/2011/02/linux-boot-process/

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/3/html/Reference_Guide/s1-boot-init-shutdown-process.html

https://zh.wikipedia.org/wiki/GNU_GRUB

关于硬盘的基础知识

12165621_1

Cylinder 柱面数 表示硬盘每面盘面上有几条磁道。

Head 磁头数 表示磁盘共有几个磁头,也就是几面盘面。

Sector/Track 扇区数 表示每条磁道上有几个扇区。

逻辑区块 Block:逻辑区块 是在 partition 进行 filesystem 的格式化时,所指定的最小存储单位。Block 的大小为 Sector 的 2 的次方倍数。磁头一次可以读取一个 block。

 

看 fdisk -l 的输出例子:

Disk /dev/vm_storage_pool_vg/vm4: 19.3 GB, 19327352832 bytes
255 heads, 63 sectors/track, 2349 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00014b4f

Device Boot Start End Blocks Id System
/dev/vm_storage_pool_vg/vm4p1 * 1 66 524288 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/vm_storage_pool_vg/vm4p2 66 588 4194304 82 Linux swap / Solaris
Partition 2 does not end on cylinder boundary.
/dev/vm_storage_pool_vg/vm4p3 588 2350 14154752 8e Linux LVM

这里有 255 个磁头,每个磁道有 63 个扇区,一个盘片有 2349 个磁道。

一个扇区的大小是 512 bytes。

计算一下,255 * 63 * 512 * 2349 = 19321182720 bytes,和 19327352832 有点差距(为什么?)。

看 Units 这一行,16065 = 255 * 63,是 Sector/Track 和 Head 的乘积,它再乘以 Sector 大小(512 bytes) 即是 Units,它表示一个垂直 Cylinder 的字节数。

 

另外,fdisk -l 使用 cylinders,加上 -u 表示使用 sectors。

-u     When listing partition tables, give sizes in sectors instead of cylinders.

 

http://www.vinidox.com/linux/disk/ouput-of-fdisk-explained/ 

https://en.wikipedia.org/wiki/Cylinder-head-sector#Blocks_and_clusters