0%

  • Ceph中的RBD备份分为全量备份和增量备份,恢复也有增全量之分。
  • 备份和恢复的操作都是基于快照来操作的。
  • RBD的format必须为2 (–image-format 2)

备份

备份操作流程,先做一个全量备份,然后每隔一段时间做一个快照,并将快照导出。

准备

创建一个RBD image

1
$ rbd create --image-format 2 --size 1G crbd_cls17_1

全量

创建一个快照,然后备份从image创建一直到创建快照之前的全量数据

1
2
$ rbd snap create crbd_cls17_1@s1
$ rbd export-diff crbd_cls17_1@s1 ./crbd_base

增量

继续写入一段数据后,再次做快照,并导出增量数据

1
2
$ rbd snap create crbd_cls17_1@s2
$ rbd export-diff crbd_cls17_1@s2 --from-snap s1 ./crbd_s1_s2

至此,全量和增量备份数据都以导出到文件crbd_basecrbd_s1_s2两个文件中。

恢复

准备

将之前导出的增全量备份数据crbd_basecrbd_s1_s2拷贝到需要导入的ceph集群上。创建一个rbd image用于导入备份数据。

1
$ rbd create --image-format 2 --size 1G crbd_cls26_1

全量

恢复全量数据

1
$ rbd import-diff ./crbd_base crbd_cls26_1

增量

恢复增量数据

1
$ rbd import-diff ./crbd_s1_s2 crbd_cls26_1

Over!

Todo…

Journal存储格式

  1. header
  2. Ring buffer

journal_1

header格式

journal_1

Ring buffer中entry header格式

journal_1

块设备驱动程序,linux内核中的一类驱动程序。通过传输固定大小的随机数据来访问设备。

初始化

注册块设备驱动

向内核注册设备驱动程序使用函数int register_blkdev(unsigned int major, const char *name);(linux/fs.h),参数是该设备使用的主设备号和名称(名字在/proc/devices中显示)。若传入的主设备号为0,内核将分配一个新的主设备号,并将该设备号返回给调用者;失败则返回负值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int __init sbull_init(void)
{
int i;
sbull_major = register_blkdev(sbull_major,"sbull"); // 注册块设备,第一个是设备号,0为动态
if(sbull_major <= 0){ //分配,第二个是设备名
printk(KERN_WARNING "sbull:unable to get major number\n");
return -EBUSY;
}
/* 为块核心数据结构 sbull_dev 分配空间*/
Devices = kmalloc(ndevices *sizeof(struct sbull_dev),GFP_KERNEL);
if(Devices == NULL)
goto out_unregister;
for(i = 0;i < ndevices;i++) /* 初始化 sbull_dev 核心数据结构 */
setup_device(Devices + i,i);
return 0;
out_unregister:
unregister_blkdev(sbull_major,"sbd");
return -ENOMEM;
}

对于register_blkdev的调用是可选的,该接口所做的事情是:如果需要的话分配一个动态的主设备号;在/proc/devices中创建一个入口项。

注册磁盘

内核使用gendisk结构(linux/genhd.h)来表示一个独立的磁盘设备。内核还使用该结构体表示分区。该结构中的许多成员必须由驱动程序进行初始化。

  • major
    int major;主设备号
  • first_minor
    int first_minor;一个驱动器至少使用一个次设备号,如果驱动器是可被分区的用户将要为每个可能的分区都分配一个次设备号。
  • minors
    int minors;常取16,一个完整的磁盘可以包含15个分区。
  • disk_name
    char disk_name[32];磁盘设备名字,该名字显示在/proc/partitionssysfs中。
  • fops
    struct block_device_operations *fops;块设备操作
  • queue
    struct request_queue *queue;设备I/O请求队列
  • flags
    int flags;驱动器状态标志。可移动介质将被设置为GENHD_FL_REMOVABLE;CD-ROM设备被设置为GENHD_FL_CD;若不希望在/proc/partitions中显示分区信息可设置为GENHD_FL_SUPPRESS_PARTITION_INFO
  • capacity
    sector_t capacity;以512字节为一个扇区,该驱动器可以包含的扇区数。驱动程序不能直接设置该成员,而要将扇区数传给set_capacity
  • private_data
    void *private_data;块设备驱动可以使用该成员保存指向其内部数据的指针

gendisk是一个动态分配的结构,驱动程序不能自己动态分配该结构,必须使用alloc_disk分配,使用del_gendisk回收。

  • alloc_disk
    struct gendisk *alloc_disk(int minors);参数minors是该磁盘使用的次设备号的数目。为了能正常工作,minors传入后就不能更改了。
  • add_disk
    void add_disk(struct gendisk *gd);使用alloc_disk分配的gendisk不能使磁盘对系统可用,还需要add_disk将磁盘设备激活,并随时准备调用它提供的方法。在驱动程序完全被初始化并且能够响应对磁盘的请求前,请不要调用add_disk
  • get_disk and put_disk
    gendisk是一个引用技术结构,get_diskput_disk函数负责处理引用计数。
  • del_gendisk
    void del_gendisk(struct gendisk *gd);调用del_gendisk会删除gendisk中的最终计数。当没有用户继续使用时,将真正删除该结构。此后,在系统中不会找到该设备信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static void setup_device(struct sbull_dev *dev,int which)
{
memset(dev,0,sizeof(struct sbull_dev)); /* 初始化 dev 所指内容为0*/
dev->size = nsectors * hardsect_size;
dev->data = vmalloc(dev->size);
if(dev->data == NULL)
{
printk(KERN_NOTICE "vmalloc failure.\n");
return ;
}
spin_lock_init(&dev->lock); /* 初始化自旋锁*/
/* 在分配请求队列前要先初始化自旋锁*/
/* The timer which "invalidates the device给内核定时器初始化 "*/
init_timer(&dev->timer); /*初始化定时器,实际将结构中的list成员初始化为空*/
dev->timer.data = (unsigned long)dev; /*被用作function函数的调用参数*/
dev->timer.function = sbull_invalidate; /* 当定时器到期时,就执行function指定的函数*/

/*
* The I/O queue, depending on whether we are using our own
* make_request function or not.
*/
switch(request_mode)
{
case RM_NOQUEUE:
dev->queue = blk_alloc_queue(GFP_KERNEL); /* 分配“请求队列” */
if(dev->queue == NULL)
goto out_vfree;
blk_queue_make_request(dev->queue,sbull_make_request); /*绑定"制造请求"函数 */
break;
case RM_FULL:
dev->queue = blk_init_queue(sbull_full_request,&dev->lock); /*请求队列初始化*/
if(dev->queue == NULL)
goto out_vfree;
break;
case RM_SIMPLE:
dev->queue = blk_init_queue(sbull_request,&dev->lock); /*请求队列初始化*/
if(dev->queue == NULL)
goto out_vfree;
break;
default:
printk(KERN_NOTICE "Bad request mode %d,using simple\n",request_mode);
}
blk_queue_hardsect_size(dev->queue,hardsect_size); /* 硬件扇区尺寸设置 */
dev->queue->queuedata = dev;
dev->gd = alloc_disk(SBULL_MINORS); /* 动态分配 gendisk 结构体*/
if(!dev->gd)
{
printk(KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
/* 初始化 gendisk */
dev->gd->major = sbull_major; /* 主设备号 */
dev->gd->first_minor = which * SBULL_MINORS; /* 第一个次设备号 */
dev->gd->fops = &sbull_ops; /* 块设备操作结构体 */
dev->gd->queue = dev->queue; /* 请求队列 */
dev->gd->private_data = dev; /* 私有数据 */
snprintf(dev->gd->disk_name,32,"sbull%c",which + 'a');
/* 每个请求的大小都是扇区大小的整数倍,内核总是认为扇区大小是512字节,因此必须进行转换*/
set_capacity(dev->gd,nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd); /* 完成以上初始化后,调用 add_disk 函数来注册这个磁盘设备 */
return ;

out_vfree:
if(dev->data)
vfree(dev->data); /* 释放用 vmalloc 申请的不连续空间*/
}

逆初始化

注销块设备驱动

register_blkdev对应的注销函数为int unregister_blkdev(unsigned int major, const char *name);,传入的参数与传递给register_blkdev的参数必须匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void sbull_exit(void)
{
int i;
for(i = 0; i < ndevices;i++)
{
struct sbull_dev *dev = Devices + i;
del_timer_sync(&dev->timer); /* 去掉 "介质移除" 定时器*/
if(dev->gd)
{
del_gendisk(dev->gd); /* 释放 gendisk 结构体*/
put_disk(dev->gd); /* 释放对 gendisk 的引用 */
}
if(dev->queue)
{
if(request_mode == RM_NOQUEUE)
blk_put_queue(dev->queue);
else
blk_cleanup_queue(dev->queue); // 清除请求队列
}
if(dev->data)
vfree(dev->data);
}
unregister_blkdev(sbull_major,"sbull");
kfree(Devices);
}

块设备操作

字符设备使用file_operations结构,块设备使用类似的结构block_device_operations(linux2.6在linux/fs.h中,linux4.4在linux/blkdev.h中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
long (*direct_access)(struct block_device *, sector_t, void __pmem **, unsigned long *pfn);
unsigned int (*check_events) (struct gendisk *disk, unsigned int clearing);
/* ->media_changed() is DEPRECATED, use ->check_events() instead */
int (*media_changed) (struct gendisk *);
void (*unlock_native_capacity) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
/* this callback is with swap_lock and sometimes page table lock held */
void (*swap_slot_free_notify) (struct block_device *, unsigned long);
struct module *owner;
const struct pr_ops *pr_ops;
};
  • open
    int (*open) (struct inode *inode, struct file *filp);当设备被打开时调用它。
  • release
    int (*release) (struct inode *inode, struct file *filp);当设备被关闭时调用它。
  • ioctl
    int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);实现ioctl系统调用的函数,块设备层会先截取大量的标准请求,因此大多数块设备的ioctl函数都十分短小。
  • media_changed
    int (*media_changed) (struct gendisk *gd);内核调用该函数检查用户是否更换了驱动器内的介质,如果更换返回一个非零值。该函数只适用于那些支持可移动介质。
  • revalidate_disk
    int (*revalidate_disk) (struct gendisk *gd);当介质被更换时被调用。
  • owner
    struct module *owner;指向拥有该结构的模块指针,通常被初始化为THIS_MODULE

open

1
2
3
4
5
6
7
8
9
10
11
12
static int sbull_open(struct inode *inode,struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
del_timer_sync(&dev->timer); //去掉"介质移除"定时器
filp->private_data = dev;
spin_lock(&dev->lock);
if(!dev->users)
check_disk_change(inode->i_bdev);
dev->users++; // 使用计数加 1
spin_unlock(&dev->lock);
return 0;
}

release

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int sbull_release(struct inode *inode,struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
spin_lock(&dev->lock);
dev->users--; // 使用计数减 1
if(!dev->users)
{
//30秒的定时器,如果这个时段内设备没有被打开则移除设备
dev->timer.expires = jiffies + INVALIDATE_DELAY;
add_timer(&dev->timer); //将定时器添加到定时器队列中
}
spin_unlock(&dev->lock);
return 0;
}

media_changed

open中调用check_disk_change函数触发media_changed检查介质是否被改变。如果介质改变则返回非零值。

1
2
3
4
5
int sbull_media_changed(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
return dev->media_change;
}

revalidate_disk

介质改变后,内核会调用revalidate_disk,调用完成后,内核将重新读取设备的分区表。

1
2
3
4
5
6
7
8
9
10
int sbull_revalidate(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
if(dev->media_change)
{
dev->media_change = 0;
memset(dev->data,0,dev->size);
}
return 0;
}

ioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int sbull_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
long size;
struct hd_geometry geo;
struct sbull_dev *dev = filp->private_data; // 通过 file->private 获得设备结构体
switch(cmd)
{
case HDIO_GETGEO:
/*
* Get geometry: since we are a virtual device, we have to make
* up something plausible. So we claim 16 sectors, four heads,
* and calculate the corresponding number of cylinders. We set the
* start of data at sector four.
*/
size = dev->size *(hardsect_size/KERNEL_SECTOR_SIZE);
/* 获得几何信息 */
geo.cylinders = (size & ~0x3f) >> 6;
geo.heads = 4;
geo.sectors = 16;
geo.start = 4;
if(copy_to_user((void __user *)arg,&geo,sizeof(geo)))
return -EFAULT;
return 0;
}
return -ENOTTY; // 不知道的命令
}

内核对块设备的物理信息并不感兴趣,它只把设备看成是线性的扇区数组。但一些用户空间的应用程序依然需要查询磁盘的物理信息。特别是fdisk工具。

请求处理

块设备中没有字符设备中的read、write函数,那么块设备是如何处理I/O请求的呢?早在sbull_init中初始化queue时需要传入一个queue处理函数,待内核接收到磁盘I/O时,会调用queue处理函数来处理。

request queue

一个请求队列就是一个动态的数据结构,该结构必须由块设备的I/O子系统创建。

创建删除

  • 创建初始化
    request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);参数request是处理这个队列的函数指针,lock是控制访问队列权限的自旋锁。由于创建过程会分配内存,因此会有失败的可能,所以在使用队列前一定要检查返回值。
  • 删除
    void blk_cleanup_queue(request_queue_t *);调用该函数后,驱动程序将不会再得到这个队列中的请求,也不能再引用这个队列了。

队列元素

  • 从队列中获取请求
    struct request *elv_next_request(request_queue_t *queue);返回一个需要处理的请求指针,该指针由I/O调度器决定,如果没有请求需要处理返回NULL。该函数被调用后,请求依然保存在队列中,但是为其做了活动标记,该标记保证了当开始执行该请求时I/O调度器不再将该请求与其他请求合并。
  • 从队列中删除请求
    void blkdev_dequeue_request(struct request *req);将请求从队列中删除。
  • 请求返回队列
    void elv_requeue_request(request_queue_t *queue, struct request *req);将拿出队列的请求再返回给队列。当驱动需要同时处理同一队列中的多个请求时,一般多用blkdev_dequeue_requestelv_requeue_request

request

一个块请求队列可以包含那些实际并不向磁盘读写数据的请求,生产商信息、底层诊断操作、与特殊设备模式相关指令、介质写模式设定等。每个request结构都代表一个块设备的I/O请求,这个I/O请求可以通过对多个独立请求的合并而来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void sbull_request(request_queue_t *q)
{
struct request *req; //定义请求结构体
while((req = elv_next_request(q)) != NULL)//elv_next_request()获得队列中第一个未完成请求
{
struct sbull_dev *dev = req->rq_disk->private_data;
if(!blk_fs_request(req)) //判断是否为文件系统请求
{
printk(KERN_NOTICE "Skip non-fs request\n");
end_request(req,0); //通知请求处理失败,0为失败,1为成功
continue;
}
sbull_transfer(dev,req->sector,req->current_nr_sectors,req->buffer,rq_data_dir(req));
end_request(req,1);
}
}
  • elv_next_request
    用来获取队列中第一个未完成的请求,当没有请求需要处理时,返回NULL。请求被获取后并不从队列中删除。
  • blk_fs_request
    用来判断该请求是否是一个文件系统请求。
  • end_request
    void end_request(struct request *req, int succeeded);传递当前请求的指针和完成结果(0表示不成功,非0表示成功)。
  • req->sector
    在设备上开始扇区的索引号
  • req->buffer
    传输或者接收数据的缓冲区指针
  • rq_data_dir
    传输方向,返回0表示从设备读数据,非0表示向设备写入数据

如果多个请求都是对磁盘中相邻扇区进行操作,则内核将合并它们,内核不会合并在单独request结构中的读写操作,如果合并的结果会打破对请求队列的限制,则内核也不会对请求进行合并。

bio

一个request结构是作为一个bio结构的链表实现的,保证在执行请求的时候驱动程序能知道执行的位置。当内核以文件系统、虚拟内存子系统或者系统调用的形式决定从块I/O设备输入、输出块数据时,它将再结合一个bio结构,用来描述这个操作。该结构被传递给I/O代码,代码会把它合并到一个已存在的request结构中,或者根据需要再创建一个新的request结构。bio结构包含了驱动程序执行请求的全部信息,而不必与初始化这个请求的用户空间的进程相关联。

block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct bio {
struct bio *bi_next; /* request queue link */
struct block_device *bi_bdev;
unsigned int bi_flags; /* status, command, etc */
int bi_error;
unsigned long bi_rw; /* bottom bits READ/WRITE, top bits priority */
struct bvec_iter bi_iter;
/* Number of segments in this BIO after
* physical address coalescing is performed.
*/
unsigned int bi_phys_segments;
/*
* To keep track of the max segment size, we account for the
* sizes of the first and last mergeable segments in this bio.
*/
unsigned int bi_seg_front_size;
unsigned int bi_seg_back_size;
atomic_t __bi_remaining;
bio_end_io_t *bi_end_io;
void *bi_private;
#ifdef CONFIG_BLK_CGROUP
/*
* Optional ioc and css associated with this bio. Put on bio
* release. Read comment on top of bio_associate_current().
*/
struct io_context *bi_ioc;
struct cgroup_subsys_state *bi_css;
#endif
union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
};
unsigned short bi_vcnt; /* how many bio_vec's */
/*
* Everything starting with bi_max_vecs will be preserved by bio_reset()
*/
unsigned short bi_max_vecs; /* max bvl_vecs we can hold */
atomic_t __bi_cnt; /* pin count */
struct bio_vec *bi_io_vec; /* the actual vec list */
struct bio_set *bi_pool;
/*
* We can inline a number of vecs at the end of the bio, to avoid
* double allocations for a small number of bio_vecs. This member
* MUST obviously be kept at the very end of the bio.
*/
struct bio_vec bi_inline_vecs[0];
};

bio的核心是一个名为bi_io_vec的数组

1
2
3
4
5
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};

遍历request中的bio

1
2
3
4
5
6
7
8
9
10
11
static int sbull_xfer_request(struct sbull_dev *dev,struct request *req)
{
struct bio *bio;
int nsect = 0;
rq_for_each_bio(bio,req)//此宏遍历请求中的每个bio,传递用于sbull_xfer_bio()传输的指针
{
sbull_xfer_bio(dev,bio); //调用 bio 处理函数
nsect += bio->bi_size/KERNEL_SECTOR_SIZE; //传递的字节数/扇区大小等于扇区数
}
return nsect;
}

遍历bio中的segment

遍历bio中的segment,也就是遍历bio结构中的bi_io_vec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int sbull_xfer_bio(struct sbull_dev *dev,struct bio *bio)
{
int i;
struct bio_vec *bvec; //定义实际的 vec 列表
sector_t sector = bio->bi_sector; //定义要传输的第一个扇区
//下面的宏遍历bio的每一段,获得一个内核虚拟地址来存取缓冲
bio_for_each_segment(bvec,bio,i)
{
char *buffer = __bio_kmap_atomic(bio,i,KM_USER0);//通过kmap_atomic()函数获得返
//回bio的第i个缓冲区的虚拟地址
sbull_transfer(dev,
sector, // 开始扇区的索引号
bio_cur_sectors(bio), // 需要传输的扇区数
buffer, // 传输数据的缓冲区指针
bio_data_dir(bio)== WRITE); // 传输方向,0表述从设备读,非0从设备写
sector += bio_cur_sectors(bio); //返回扇区数
__bio_kunmap_atomic(bio,KM_USER0); //返回由 __bio_kmap_atomic()获得的内核虚拟地址
}
return 0;
}

参考&鸣谢

  • 《设备驱动程序》

Perf tools是由内核维护人员Ingo Molnar等人开发的linux内核的综合性能该要分析工具。通过它,应用程序可以利用 PMU,tracepoint 和内核中的特殊计数器来进行性能统计。它不但可以分析指定应用程序的性能问题 (per thread),也可以用来分析内核的性能问题,当然也可以同时分析应用代码和内核,从而全面理解应用程序中的性能瓶颈。

环境

** OS **

ubuntu 16.04.5 LTS

** Kernel **

1
2
$ uname -a
Linux ubuntu-linux 4.4.0-138-generic #164-Ubuntu SMP Tue Oct 2 17:16:02 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

安装

perf的安装可以分成两种,一种是package安装法;

1
sudo apt install linux-tools-common linux-tools-4.4.0-138-generic

另一种安装方法是源码安装,这种方法需要先下载当前内核对应的源代码,

1
2
# 下载 4.4.0-138 源码
sudo apt-get install linux-source=4.4.0.138.144

然后进入/usr/src目录中的源代码目录下,找到tools/perf目录,并在该目录下执行make编译perf源码,再执行make install安装perf

perf使用姿势

姿势一

选择分析的事件

通过perf list列出可以选择的分析事件

概要分析数据

使用perf topperf stat进行该要分析系统性能,perf top类似于linux的top命令,按进程显示分析数据,可以使用-e参数指定分析事件,通过perf top找出系统中的问题进程。(更多关于perf top的使用精请见perf top -h

perf stat用于汇总综合性能,有人用此方法比较性能改善前后的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Performance counter stats for 'ls /':

0.827614 task-clock (msec) # 0.707 CPUs utilized
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
91 page-faults # 0.110 M/sec
<not supported> cycles
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
<not supported> instructions
<not supported> branches
<not supported> branch-misses

0.001169835 seconds time elapsed

(由于我使用的是VM,所有有很多不支持的)

  • task clock
    进程执行所花费的时间,CPU 利用率,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO。
  • context switches
    进程切换次数,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的。
  • cpu migrations
    进程运行过程中发生了多少次 CPU 迁移,即被调度器从一个 CPU 转移到另外一个 CPU 上运行。
  • page faults
    按需分页或交换等过程中使用的页面错误的发生次数和频率
  • cycles
    处理器时钟,一条机器指令可能需要多个 cycles
  • instructions
    机器指令数目
  • branches
    分支命令的数量和频率
  • branch misses
    分支预测错误的比例

分析记录的数据

记录并保存分析数据

通过perf record命令实现将分析数据保存到perf.data(默认)文件中,虽然perf top也能获取分析数据,但是perf top每隔2秒(默认)更新一次,而且对于一些特殊case不好抓取。perf record特别适合针对某个进程某个事件的分析非常好用。具体使用方法可以参考perf record -h

查看保存的分析数据

通过perf report查看perf.data(默认),perf report分析内容的查看与perf top的查看方式相同。

姿势二

perf script

一般情况下,我们可以通过perf record-e指定追踪事件方式来获取进程相关事件分析数据(perf.data),然后通过perf script(不加任何脚本情况下)只显示记录到的追踪事件数据。若perf script后指定脚本,那么会将记录到的追踪事件数据作为输入,传递给指定的脚本处理。

perf脚本处理数据分为在线处理和离线处理;

  • 在线处理
    在进行追踪的同时运行脚本,则不可避免地会增加负载
    eg: perf script sctop 统计系统调用次数
  • 离线处理
    进行离线处理时先追踪后处理,可以抑制处理对追踪对象的影响
    eg: perf script record syscall-counts ls / 先追踪,后处理;perf script report syscall-counts 查看统计系统调用次数结果

** 自定义脚本 **

用户可根据自己的需求编写自己的Python或Perl脚本处理追踪事件数据,二者分别需要安装libpython或libperl,并且需要重新build perf。

过程:

  • 先记录必要事件的数据文件
    使用perf record记录执行ls /过程中内存分配kmalloc()的相关事件数据
    eg: perf record -e kmem:kmalloc -e raw_syscalls:sys_enter ls /
  • 生成自定义脚本
    使用perf script -g perl(perl脚本) or perf script -g python(python脚本)生成自定义脚本,根据需要修改生成的脚本内容
  • 执行自定义脚本
    使用perf script+-s参数指定用户自定义的脚本执行追踪处理

姿势三

Todo…

perf tools这把瑞士军刀可以说功能齐全威力惊人,但能将其发挥几层还要看使用者的功力如何。

参考&鸣谢

** 概述 **

Ceph对象网关是一个构建在librados之上的对象存储接口,它为应用程序访问Ceph 存储集群提供了一个RESTful风格的网关。

Ceph 对象存储支持 2 种接口:

  1. 兼容S3: 提供了对象存储接口,兼容Amazon S3 RESTful接口的一个大子集。
  2. 兼容Swift: 提供了对象存储接口,兼容Openstack Swift接口的一个大子集。

Ceph对象存储使用Ceph对象网关守护进程(radosgw 以下简称RGW),它是个与Ceph存储集群交互的FastCGI模块。因为它提供了与OpenStack SwiftAmazon S3兼容的接口,RADOS要有它自己的用户管理。Ceph对象网关可与CephFS客户端或Ceph 块设备客户端共用一个存储集群。S3和Swift接口共用一个通用命名空间,所以你可以用一个接口写如数据、然后用另一个接口取出数据。

** 版本: Mimic **

radosgw-Frame.png

架构

RGW 主要由3部分组成。Frontend,用于接收回复外部客户端的http请求;REST,根据外部请求的http信息选择相应的REST、Handler、Op对请求进行分解处理;RGWRados,完成了对数据读写业务的封装。

Frontend

Frontend包括Frontend Config(RGWFrontendConfig)、Civetweb(RGWCivetWebFrontend)、beast(RGWAsioFrontend)、loadgen(RGWLoadGenFrontend)、fastcgi/fcgi(RGWFCGXFrontend)。用一个map管理所有RGWFrontendConfig配置信息对象;用一个list管理所有RGWFrontend对象。每个RGWFrontend实例使用与之对应的RGWFrontendConfig实例进行配置。

Frontend负责接收客户端的请求,然后回调process_request方法处理该请求,并将响应信息返回客户端。

  • Frontend Config
  • Civetweb
  • Beast
  • Loadgen
  • FastCGI/FCGI

REST

REST包括request process、S3 resource、Swift resource、Swift auth resource、Admin Usage resource、Admin User resource、Admin Metadata resource、Admin Realm resource、Admin Config resource、Admin Bucket resource、Admin OpState resource、Admin Log resource、 Admin Replica-log resource。每个resource包含一个RESTMgr、多个Handler、多个Op。

就在刚刚Frontend回调了process_requestprocess_request通过RESTMgr取得handler,handler再根据Http Method取得Op,Op再对数据进行读写操作。

REST Resource

S3

** 组成 **

  • RESTMgr
    有1个RGWRESTMgr_S3组成。
  • Handler
    有6个Handler组成,分别是:RGWHandler_REST_Service_S3RGWHandler_REST_Bucket_S3RGWHandler_REST_Obj_S3RGWHandler_REST_Service_S3WebsiteRGWHandler_REST_Bucket_S3WebsiteRGWHandler_REST_Obj_S3Website

** 功能 **

  • RGWHandler_REST_Service_S3, 完成如下功能:
    1. 获取Usage信息
    2. List Buckets
    3. Role信息操作
  • RGWHandler_REST_Bucket_S3,完成如下功能:
    1. Bucket ACL信息操作
    2. Bucket Core信息操作
    3. Bucket Payment信息操作
    4. Bucket LC信息操作
    5. Bucket Policy信息操作
    6. Bucket操作
  • RGWHandler_REST_Obj_S3,完成如下功能:
    1. Object ACL操作
    2. Object tagging操作
    3. Object Multipart上传操作
    4. Object 操作
    5. Object Copy操作

Swift

** 组成 **

  • RESTMgr
    RGWRESTMgr_SWIFTRGWRESTMgr_SWIFT_CrossDomainRGWRESTMgr_SWIFT_HealthCheckRGWRESTMgr_SWIFT_Info组成。
  • Handler
    RGWHandler_REST_Service_SWIFTRGWHandler_REST_Bucket_SWIFTRGWHandler_REST_Obj_SWIFTRGWHandler_REST_SWIFT_InfoRGWHandler_SWIFT_HealthCheckRGWHandler_SWIFT_CrossDomain组成。

Swift auth

** 组成 **

  • RESTMgr
    由1个RGWRESTMgr_SWIFT_Auth组成。
  • Handler
    由1个RGWHandler_SWIFT_Auth组成。

Admin

** 组成 **

  • RESTMgr
    RGWRESTMgr_UsageRGWRESTMgr_UserRGWRESTMgr_BucketRGWRESTMgr_MetadataRGWRESTMgr_LogRGWRESTMgr_OpstateRGWRESTMgr_ReplicaLogRGWRESTMgr_ConfigRGWRESTMgr_RealmRGWRESTMgr_Period组成。
  • Handler
    RGWHandler_UsageRGWHandler_UserRGWHandler_BucketRGWHandler_MetadataRGWHandler_LogRGWHandler_OpstateRGWHandler_ReplicaLogRGWHandler_ConfigRGWHandler_RealmRGWHandler_Period组成。

Op

RGWOp

  • Object Tags
    RGWGetObjTags RGWPutObjTags RGWDeleteObjTags实现对象属性user.rgw.x-amz-tagging的查询、设置、删除操作
  • Bulk
    RGWBulkDelete RGWBulkUploadOp Swift专享批量上传、删除操作
  • Usage
    RGWGetUsage 获取usage信息操作
  • Stat
    RGWStatAccount统计buckets的使用情况
  • Bucket
    RGWListBuckets列出所有buckets
    RGWGetBucketLocation获取bucket location
    RGWGetBucketVersioning获取bucket versioning、mfa-delete状态
    RGWSetBucketVersioning 设置bucket versioning
    RGWGetBucketWebsite获取bucket website信息
    RGWSetBucketWebsite设置bucket website
    RGWDeleteBucketWebsite删除bucket website
    RGWStatBucket获取bucket信息
    RGWCreateBucket创建bucket操作
    RGWDeleteBucket删除bucket操作
    RGWPutBucketPolicy设置bucket policy
    RGWGetBucketPolicy获取bucket policy
    RGWDeleteBucketPolicy删除bucket policy
  • Object
    RGWPutObj RGWPostObj RGWGetObj RGWDeleteObj RGWCopyObj实现对象的创建、修改、下载、删除、拷贝操作
    RGWListBucket列出bucket中的对象
  • Metadata
    RGWPutMetadataAccountSwift专享保存user所有属性信息(RGWUserInfo)
    RGWPutMetadataBucketSwift专享保存bucket所有属性信息
    RGWPutMetadataObjectSwift专享保存object所有属性信息
  • ACL
    RGWGetACLs RGWPutACLs 实现对bucket或object的访问控制策略属性(user.rgw.acl)的获取、设置操作
  • LC
    RGWGetLC RGWPutLC RGWDeleteLC实现对bucket的生命周期属性(user.rgw.lc)的获取、设置、删除操作
  • CORS
    RGWGetCORS RGWPutCORS RGWDeleteCORS RGWOptionsCORS实现对bucket的CORS属性(user.rgw.cors)的获取、设置、删除、Options操作
    RGWGetCrossDomainPolicySwift专享获取CrossDomain策略信息操作
  • Payment
    RGWGetRequestPayment RGWSetRequestPayment实现对bucket的payment属性的获取、设置操作
  • Multipart
    RGWInitMultipart RGWCompleteMultipart RGWAbortMultipart RGWListMultipart RGWListBucketMultiparts RGWDeleteMultiObj实现对Object的分片上传的操作
  • HealthCheck
    RGWGetHealthCheckSwift专享访问判断配置文件中的rgw_healthcheck_disabling_path是否可以被访问操作
  • Swift Info
    RGWInfoSwift专享获取Swift信息
  • Layout
    RGWGetObjLayout获取Object的layout信息
  • Bucket MateSearch Config
    RGWConfigBucketMetaSearch RGWGetBucketMetaSearch RGWDelBucketMetaSearch实现对Bucket的MetaSearch Config属性的配置、查询、删除操作
  • Cluster
    RGWGetClusterStat获取Cluster状态

AdminOp

  • Usage
    RGWOp_Usage_Get RGWOp_Usage_Delete获取usage信息操作
  • User
    RGWOp_User_Info RGWOp_User_Create RGWOp_User_Modify RGWOp_User_Removeuser信息的获取、创建、修改、删除操作
    RGWOp_Subuser_Create RGWOp_Subuser_Modify RGWOp_Subuser_Removeswift user的创建、修改、删除操作
    RGWOp_Key_Create RGWOp_Key_RemoveAccessKey、SecretKey的创建、删除操作
    RGWOp_Caps_Add RGWOp_Caps_RemoveCaps属性的增加、删除操作
    RGWOp_Quota_Info RGWOp_Quota_SetQuota的信息的获取、设置操作
  • Bucket
    RGWOp_Bucket_Info获取bucket信息操作
    RGWOp_Get_Policy获取bucket的policy信息操作
    RGWOp_Check_Bucket_Index检查bucket index操作
    RGWOp_Bucket_Link RGWOp_Bucket_Unlink关联、去关联bucket与user操作
    RGWOp_Bucket_Remove删除bucket操作
    RGWOp_Set_Bucket_Quota设置bucket quota属性操作
    RGWOp_Object_Remove删除bucket内的object操作
  • Metadata
    RGWOp_Metadata_List RGWOp_Metadata_Get RGWOp_Metadata_Put RGWOp_Metadata_Delete RGWOp_Metadata_Lock RGWOp_Metadata_Unlock实现对Metadata数据的管理操作
  • Log
    RGWOp_BILog_List RGWOp_BILog_Info RGWOp_BILog_Delete RGWOp_BILog_Status实现对bucket index log的管理
    RGWOp_MDLog_List RGWOp_MDLog_Info RGWOp_MDLog_ShardInfo RGWOp_MDLog_Lock RGWOp_MDLog_Unlock RGWOp_MDLog_Notify RGWOp_MDLog_Delete RGWOp_MDLog_Status实现对Metadata log的管理
    RGWOp_DATALog_List RGWOp_DATALog_Info RGWOp_DATALog_ShardInfo RGWOp_DATALog_Lock RGWOp_DATALog_Unlock RGWOp_DATALog_Notify RGWOp_DATALog_Delete RGWOp_DATALog_Status实现对Data log的管理
  • Opstate
    RGWOp_Opstate_List RGWOp_Opstate_Set RGWOp_Opstate_Renew RGWOp_Opstate_Delete获取远端object的操作日志管理
  • ReplicaLog
    RGWOp_OBJLog_GetBounds RGWOp_OBJLog_SetBounds RGWOp_OBJLog_DeleteBoundsObject同步日志管理
    RGWOp_BILog_GetBounds RGWOp_BILog_SetBounds RGWOp_BILog_DeleteBoundsBucket同步日志管理
  • Zone
    RGWOp_ZoneGroupMap_Get RGWOp_ZoneConfig_Get获取zonegroup map 和 zone config操作
  • Realm
    RGWOp_Realm_Get获取realm信息操作
    RGWOp_Period_Get RGWOp_Period_Post获取、设置Period信息操作

request process

RGW的所有请求都需要回调process_request这个函数来处理,所以process_request处理过程十分重要。

处理流程

  1. 获取RGWHandler_REST
    1.1. 获取RGWRESTMgr对象
    各种REST resource 都保存在一个map中,当请求到来时根据传入的frontend_prefix和uri信息在map中查找对应的RGWRESTMgr
    1.2. 通过RGWRESTMgrget_handler获取RGWHandler_REST对象
    1.3. 调用RGWHandler_REST对象的init方法进行初始化
  2. 调用RGWHandler_REST对象的get_op方法获取RGWOp对象
  3. 调用RGWOp对象的verify_request的方法,根据不同的操作进行不同的请求授权检查
  4. 调用RGWHandler_REST对象的postauth_init方法,进行bucket和tenant解析及验证等操作
  5. 调用RGWHandler_REST对象的init_permissions方法,进行初始化权限
    5.1. 如果是创建Bucket操作(RGW_OP_CREATE_BUCKET)直接忽略此操作
    5.2. 非创建Bucket操作,通过调用RGWHandler_REST对象的do_init_permissions方法进行初始化权限
  6. 调用RGWHandler_REST对象的read_permissions方法,获取Object的AccessControlPolicy,若处理的是Bucket直接忽略
  7. 调用RGWOp对象的init_processing方法,获取bucket和user的quota信息
  8. 调用RGWOp对象的verify_op_mask验证操作的种类是否为RGW_OP_TYPE_READRGW_OP_TYPE_WRITERGW_OP_TYPE_DELETE中的一个或多个
  9. 调用RGWOp对象的verify_permission检查当前的操作在之前的init_permissions获取的策略之下是否有权限
  10. 调用RGWOp对象的verify_params检查当前操作的参数
  11. 调用RGWOp对象的pre_exec执行预执行操作
  12. 调用RGWOp对象的execute执行操作
  13. 调用RGWOp对象的complete完成操作,并整理响应结果

RGWRados

  • Bucket Op
    使用librados完成Bucket创建、删除、设置、获取等相关操作
  • Object Op
    对Object的读写、属性获取设置等操作,所有操作都分为两类,一类是System Obj,包括metadata信息、realm信息、role信息、bucket信息等rgw内一些配置信息;另一类是Normal Obj,一般为用户上传的object
  • Cache
    对Bucket info信息和User info信息及所有Object的缓存。缓存失效采用LRU算法,采用超时失效+有效窗口方式判断缓存数据是否失效。
  • Watcher
    监听.rgw.controlpool中nodify的对象,当发生对Object(包括System Obj 和 Normal Obj)的增删改操作时,都会触发nodify的更新。Watcher再调用watch_cb去更新Object的缓存信息
  • Pools
    对root pool(.rgw)、control pool(.rgw.control)、gc pool(.rgw.gc)、lc pool(.rgw.lc)、objexp pool、reshard pool的IoCtx管理及操作
  • GC
    对象的删除操作不会真的将对象删除,而是在对象的属性中增加olh.,然后将对象存入.rgw.gc中。GC中的回收线程会每隔1小时处理一次,每次处理从.rgw.gc中获取对象并删除,每次处理的超时时间为1小时。
  • Obj Expirer(OE)
    对象的Version删除功能,通过设置对象的delete_at属性来等待OE清扫线程进行删除,OE清扫线程每隔10分钟处理一次。
  • LC
    bucket通过user.rgw.lc属性配置LC,开启了LC的bucket会被随机分配到.rgw.lcpool的lc.{index}对象上。LC处理线程会随机获取lc.{index}对象,对其上记录的bucket中的object进行处理,判断其是否过期失效,若失效则删除。
  • Quota
    bucket_stats_cacheuser_stats_cache分别缓存bucket和user的quota信息,并提供check_quota操作检查是否超出quota的限制。
  • Reshard
    bucket index用于索引bucket内的Object,这些index存在shard文件上。随着bucket内object的数量增加,整个shard文件也在不断增长,当object数量超过“bucket shard数*每个shard最大容纳object数量(默认值:100000)”时,触发reshard操作分配更多的shard文件用于存储index。
  • Sync
    根据metadata log 和 data log 同步 metadata 和 data 数据。当用户对metadata或data写入操作时,保存对应log信息,然后通过notifier模块唤醒SyncProcessor模块来同步log信息并处理。
  • Realm
    包括Realm、zonggroup、zone、period四部分;period用于管理realm配置信息,一个realm包括多个zonegroup,每个zonegroup包括多个zone;一个realm中只能有一个master zonegroup,这个zonegroup中只能有一个master zone。用户修改period时,通过.rgw.rootpool中的periods.{realm id}.control对象将通知Realm Watcher进行更改realm配置。

Nautilus

编译环境

硬件

采用KVM虚拟机编译Ceph源码,具体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 8
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 13
Model name: QEMU Virtual CPU version 2.5+
Stepping: 3
CPU MHz: 2394.454
BogoMIPS: 4788.90
Hypervisor vendor: KVM
Virtualization type: full
L1d cache: 32K
L1i cache: 32K
L2 cache: 4096K
NUMA node0 CPU(s): 0-7
Flags: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pse36 clflush mmx fxsr sse sse2 syscall nx lm rep_good nopl cpuid tsc_known_freq pni cx16 x2apic hypervisor lahf_lm pti
1
2
3
4
# free -g
total used free shared buff/cache available
Mem: 15 7 5 0 2 7
Swap: 0 0 0

软件

OS:

1
2
# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

kernel:

1
2
# uname -a
Linux host-10-100-13-111 4.18.20 #1 SMP Tue Sep 17 11:21:39 CST 2019 x86_64 x86_64 x86_64 GNU/Linux

gcc:

1
2
3
4
5
6
7
8
# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/opt/rh/devtoolset-7/root/usr/libexec/gcc/x86_64-redhat-linux/7/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,lto --prefix=/opt/rh/devtoolset-7/root/usr --mandir=/opt/rh/devtoolset-7/root/usr/share/man --infodir=/opt/rh/devtoolset-7/root/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-plugin --with-linker-hash-style=gnu --enable-initfini-array --with-default-libstdcxx-abi=gcc4-compatible --with-isl=/builddir/build/BUILD/gcc-7.3.1-20180303/obj-x86_64-redhat-linux/isl-install --enable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)

编译

pre-compile

执行install-deps.sh,安装编译ceph依赖的软件包。

cmake

安装官方文档的步骤可以正常编译,但默认的编译是含有调试信息的。所以编译后的bin文件比较大。

  1. 生成makefile文件
    使用do_cmake.sh脚本在build目录下生成Makefile文件
  2. 编译
    使用make命令编译,此过程时间较长。
  3. 瘦身
    编译后的binary文件包含有debug信息,所以文件较大。需要使用strip对其debug信息进行裁剪。裁剪有两个工具可以用一个是objcopy;另一个是strip
    eg:
    objcopy --strip-debug ./radosgw
    strip --strip-debug --strip-unneeded ./radosgw

rpmbuild

  1. 生成rpmbuild目录树
    使用rpmdev-setuptree命令创建rpmbuild目录树,该目录树会在$home目录下生成。可根据自己的需要将rpmbuildcopy到指定的目录,但后续build过程需要指定这个目录。本人将rpmbuild目录移动到/root/src目录下。
  2. 获取源码tar包到rpmbuild/SOURCES目录
    获取源码tar包可以到https://download.ceph.com/tarballs/ceph-14.2.3.tar.gz去下载。也可以将修改好的代码直接做成tar包。然后将tar包copy到rpmbuild/SOURCES目录下。
  3. 提取tar包中的ceph.spec
    从源码tar包中提取ceph.spec文件到rpmbuild/SPECS目录,用于后续rpmbuild使用。eg: tar --strip-components=1 -C /root/src/rpmbuild/SPECS/ --no-anchored -xvzf /root/src/rpmbuild/SOURCES/ceph-14.2.3.tar.gz "ceph.spec"
  4. build rpms
    rpmbuild具体使用-bb还是-ba根据个人需要而定吧。本人这里使用-bb只制作binary。eg: rpmbuild -D "_smp_mflags 4" -D "_topdir /root/src/rpmbuild" -bb /root/src/rpmbuild/SPECS/ceph.spec
    由于本人将rpmbuild移动到了/root/src目录下,所以需要使用-D "_topdir /root/src/rpmbuild"去指定rpmbuild目录。
    本人不希望rpmbuild自动推算使用几个核心去编译,需要指定4个核心编译需要指定-D "_smp_mflags 4"

此时,可以静静等待编译完成。

*** 编译的过程中可能会遇到BuildArch:noarch错误 ***

1
2
3
4
5
error: Arch dependent binaries in noarch package


RPM build errors:
Arch dependent binaries in noarch package

可以通过在spec文件中增加%define _binaries_in_noarch_packages_terminate_build 0来解决此问题。

Luminous

编译 Ceph luminous版本,luminous的改动还是蛮大的,而且改变了原有的configure为cmake。

编译环境

硬件

armv7l (Odroid XU4)

软件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ lsb_release  -a
LSB Version: 1.4
Distributor ID: Arch
Description: Arch Linux
Release: rolling
Codename: n/a

$ uname -a
Linux HomeCenter 4.14.29-1-ARCH #1 SMP PREEMPT Fri Mar 23 02:57:06 UTC 2018 armv7l GNU/Linux

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/armv7l-unknown-linux-gnueabihf/7.2.1/lto-wrapper
Target: armv7l-unknown-linux-gnueabihf
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://github.com/archlinuxarm/PKGBUILDs/issues --enable-languages=c,c++,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --disable-multilib --disable-werror --enable-checking=release --enable-default-pie --enable-default-ssp --host=armv7l-unknown-linux-gnueabihf --build=armv7l-unknown-linux-gnueabihf --with-arch=armv7-a --with-float=hard --with-fpu=vfpv3-d16
Thread model: posix
gcc version 7.2.1 20180116 (GCC)

编译

按照官方的文档,编译总共分为4步。

  1. 安装编译需要的依赖包 ./install-deps.sh
  2. 使用cmake生成Makefile ./do_cmake.sh
  3. 使用Makefile编译源代码
  4. 安装包制作

由于本人使用的是archlinux,简单查看了一下install-deps.sh这个脚本,发现没有与我使用os相匹配的处理;所以本人决定跳过第一步,并且我也不打算制作安装包,所以第四部也省略了。

接下来就只剩下第2、3步了,那么ceph编译依赖的其他软件包怎么解决,就只能放倒编译过程中出现报错再去究其原因了。闲言少叙,我们开始编译。

cmake过程

从github上clone下来ceph代码,不需要取得submodule的代码,因为在do_cmake.sh中首先会做。执行do_cmake.sh后,先取submodule代码,然后创建build目录,并在build目录中执行cmake,生成Makefile。

在获取submodule这个过程由于网络问题会出现不只一次的中断,这个时候需要先删除build目录,然后在重新执行do_cmake.sh

make过程

ceph编译需要用到boost包,之前的版本都是需要用户手动安装,L版改为取源码自行编译了。可以先执行make Boost或直接执行make操作,我在编译boost的时候遇到了找不到pyconfig.h的编译错误,此时需要根据错误提示的文件及行数,找到对应的cxxflags并增加-I/usr/include/python2.7/这样再次执行make操作boost就能正常编译了。

1
2
3
4
5
6
7
8
9
Scanning dependencies of target ceph-dencoder
[ 0%] Building CXX object src/CMakeFiles/ceph-dencoder.dir/test/encoding/ceph_dencoder.cc.o
c++: internal compiler error: Killed (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <https://github.com/archlinuxarm/PKGBUILDs/issues> for instructions.
make[2]: *** [src/CMakeFiles/ceph-dencoder.dir/build.make:64: src/CMakeFiles/ceph-dencoder.dir/test/encoding/ceph_dencoder.cc.o] Error 4
make[1]: *** [CMakeFiles/Makefile2:1609: src/CMakeFiles/ceph-dencoder.dir/all] Error 2
make: *** [Makefile:141: all] Error 2

由于我使用的是嵌入式设备,内存只有2g大小,并且没有配置swap,在编译过程中会遇到g++: internal compiler error: Killed (program cc1plus)错误,这种错误都是由于内存不足导致的。对于我的环境而言,只能通过增加swap来解决(关于增加swap的方法)。

问题解决了,就一路make下去吧。

参考&鸣谢

社区版XenServer是一款开源产品。那么拿到这款社区版首先想到的是怎么把它build出来,然后怎么把它部署起来,最后才是巴拉巴拉吧啦。。。

我们先来解决第一步,编译源码,编译之前需要先构建编译环境,构建好编译环境后以xapi为例进行编译。xapi是用OCaml这种语言写的,本人对这种语言一窍不通。这儿就不讨论了 ……

准备

本人打算使用容器进行编译xapi,所以你得先有个docker环境,然后呢编译的是xapi代码也得自己下好吧。

具体操作,巴拉巴拉吧啦。

构建编译环境

拉取xenserver-build-env

使用docker pull xenserver/xenserver-build-env命令拉取镜像,这是个漫长的过程还有可能失败,镜像还挺大。

喝杯茶,慢慢等吧。。。

启动编译容器

走到这里,说明你的镜像拉取成功了,那么我们需要启动镜像,本人推荐使用下列方法启动镜像

1
$ docker run -i -t -v /home/{xxxx}/labs/xen-api:/mnt/repos --name xapi-builder xenserver/xenserver-build-env /usr/local/bin/init-container.sh

别傻乎乎的copy直接用啊,里面的路径要根据实际情况自己调整,我懒了你不能懒。

网上坛子里有人推荐加参数-u builder,的确build的过程不推荐使用root用户;我用root是因为我要把它装到容器里,用builder会引入一些权限和环境问题。所以我用root了,您根据自己的情况而定。别盲从!!!

配置编译环境

你是否以为下载好了镜像,并且成功的启动的容器,就可以顺理成章的编译了?哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈,别做梦了。哪有那么简单,社区版不给你点儿坑你都觉得不过瘾。

安装dep包

1
# yum-buiddep xapi

我是root,不是root自己加sudo去。

初始化opam

卧槽,opam什么东西,一脸的懵B啊!?不用太深究,就是一个类似pip的包管理器;想深究的可以自己研究OPAM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# opam init

...

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

1. To configure OPAM in the current shell session, you need to run:

eval `opam config env`

2. To correctly configure OPAM for subsequent use, add the following
line to your profile file (for instance ~/.profile):

. /root/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true

3. To avoid issues related to non-system installations of `ocamlfind`
add the following lines to ~/.ocamlinit (create it if necessary):

let () =
try Topdirs.dir_directory (Sys.getenv "OCAML_TOPLEVEL_PATH")
with Not_found -> ()
;;

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
...
#

看到这段英文了吧,1、2、3,乖乖的照做,要不管保你编不过。

最后切记,将eval \opam config env`加到/.bashrc/.profile`中。

使用opam安装包ocamlfind, cmdliner, jbuilder

1
2
3
4
# opam depext conf-m4.1
...
# opam install ocamlfind cmdliner jbuilder
...

安装过程中有需要点确认的授权的,改给就给,千万别搞事情!

到此为止环境的搭建算是完成了,记住这个session不要退,再开一个session去做编译一会儿有惊喜。。。

编译xapi

使用docker exec -it xapi-builder /bin/bash登录进你的容器,像上文说到的你可以使用-u builder参数。

废话不多说直接编译

1
2
3
4
5
$ cd /mnt/repo/
$ ./configure
...
$ make
...

别急,会报错的,看到了吧。stack overflow,去网上搜吧,一搜返回一堆stack overflow的网站,哈哈哈!!!

别急,还记得之前保留的那个session吗,对,就是那个session。输入ulimit -a看看里面的stack size;再对比看看报错的那个session的stack size。是不是有一种恍然大悟的感觉。没错设置一下报错session的stack size就可以了ulimit -s 16384


你刚刚经历了一个华丽的分割线。。。


如果你使用xenserver-build-env创建编译环境,可以省去很多步骤。

创建编译容器

1
2
3
$ git clone git://github.com/xenserver/xenserver-build-env
$ cd xenserver-build-env
$ ./build.sh

编译xapi

1
2
3
4
5
6
7
8
$ ./run.py -p xapi --rm

# --- you are now inside the docker container ---

$ git clone git://github.com/xapi-project/xen-api
$ cd xen-api
$ ./configure
$ make

制作RPM Package

在Centos系统中制作RPM包,需要用到rpmbuild,所以你要用yum安装好。当然上面的编译容器已经将rpmbuild安装好了。那么的接下来的重点在于SPEC文件编写,SPEC用来告诉rpmbuild,制作的每一个过程需要做哪些动作。

SPEC文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Name:   xen-api     
Version: 1.60.2
Release: 1%{?dist}
Summary: rpm xen-api modify

Group: Application/test
License: Share
Source: $RPM_SOURCE_DIR/xen-api-1.60.2_m.tar.gz


%description
print xen-api


%prep
rm -rf $RPM_BUILD_DIR/xen-api-1.60.2
zcat $RPM_SOURCE_DIR/xen-api-1.60.2_m.tar.gz | tar xvf -

%build
cd $RPM_BUILD_DIR/xen-api-1.60.2
./configure
make %{?_smp_mflags}


%install
cd $RPM_BUILD_DIR/xen-api-1.60.2
make install DESTDIR=%{buildroot}


%files
/etc
/var
/usr
/opt
%doc

创建目录SOURCESPECSBUILDRPMSSRPMS,将SPEC文件放入到SPECS文件件中,然后将源代码做成xen-api-1.60.2_m.tar.gz拷贝到SOURCE目录。最后执行rpmbuild -ba {SPEC文件}。执行完毕后,在RPM目录中生成我们需要的RPM包

参考&鸣谢

Linux下编程时,为了方便编译,往往使用Makefile文件自动完成编译,但是Makefile文件本身的书写十分复杂,规则很多。好在Linux为我们提供了自动生成功能完善的Makefile文件的工具autoconf/automake。本文讲述如何使用它们生成Makefile文件。

环境

  • OS
    ubuntu 16.04
  • aclocal
    1.15
  • autoconf
    2.69-9
  • automake
    1.15

使用示例

准备

创建一个main.c文件

1
2
3
4
5
6
#include <stdio.h>

int main(){
printf("Hello automake\n");
return 0;
}
1
2
$ ls
main.c

创建configure.in文件

运行autoscan,自动创建两个文件autoscan.logconfigure.scan

1
2
3
4
5
$ autoscan
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/\${ <-- HERE [^\}]*}/ at /usr/bin/autoscan line 361.

$ ls
autoscan.log configure.scan main.c

修改configure.scan,AC_INIT里面的参数: AC_INIT(main,1.0, test@263.com);添加宏AM_INIT_AUTOMAKE, 它是automake所必备的宏,也同前面一样,PACKAGE是所要产生软件套件的名称,VERSION是版本编号;在AC_OUTPUT后添加输出文件Makefile。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT(main, 1.0, main@google.com)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE(main, 1.0)

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT([Makefile])

configure.scan重命名成configure.ac

1
2
$ ls
autoscan.log configure.ac main.c

生成aclocal相关文件

运行aclocal,生成文件aclocal.m4、目录autom4te.cache,处理本地的宏定义。

1
2
3
$ aclocal
$ ls
aclocal.m4 autom4te.cache autoscan.log configure.ac main.c

生成configure文件

运行autoconf

1
2
3
$ autoconf
$ ls
aclocal.m4 autom4te.cache autoscan.log configure configure.ac main.c

生成config.h.in文件

运行autoheader,生成config.h.in,该工具通常会从“acconfig.h”文件中复制用户附加的符号定义,因此此处没有附加符号定义,所以不需要创建“acconfig.h”文件。

1
2
3
$ autoheader
$ ls
aclocal.m4 autom4te.cache autoscan.log config.h.in configure configure.ac main.c

运行automake

运行automake之前先创建一个Makefile.am,这一步是创建Makefile很重要的一步,automake要用的脚本配置文件是Makefile.am,用户需要自己创建相应的文件。之后,automake工具转换成Makefile.in

Makefile.am

1
2
3
4
AUTOMAKE_OPTIONS=foreign

bin_PROGRAMS=main
main_SOURCES=main.c
  • AUTOMAKE_OPTIONS为设置automake的选项
    由于GNU对自己发布的软件有严格的规范,比如必须附带许可证声明文件COPYING等,否则automake执行时会报错。automake提供了三种软件等级:foreign、gnu和gnits,让用户选择采用,默认等级为gnu。在本例使用foreign等级,它只检测必须的文件。
  • bin_PROGRAMS定义要产生的执行文件名
    如果要产生多个执行文件,每个文件名用空格隔开。
  • main_SOURCES定义“main”这个执行程序所需要的原始文件
    如果”main”这个程序是由多个原始文件所产生的,则必须把它所用到的所有原始文件都列出来,并用空格隔开。例如:若目标体“main”需要“main.c”、“sunq.c”、“main.h”三个依赖文件,则定义main_SOURCES=main.c sunq.c main.h。要注意的是,如果要定义多个执行文件,则对每个执行程序都要定义相应的file_SOURCES。
1
2
3
4
$ automake --add-missing
configure.ac:8: warning: AM_INIT_AUTOMAKE: two- and three-arguments forms are deprecated. For more info, see:
configure.ac:8: http://www.gnu.org/software/automake/manual/automake.html#Modernize-AM_005fINIT_005fAUTOMAKE-invocation
Makefile.am: installing './depcomp'

使用automake对其生成“configure.in”文件,在这里使用选项“—adding-missing”可以让automake自动添加有一些必需的脚本文件。

1
2
$ ls
Makefile.am Makefile.in aclocal.m4 autom4te.cache autoscan.log compile config.h.in configure configure.ac depcomp install-sh main.c missing

运行configure生成Makefile

通过运行自动配置设置文件configure,把Makefile.in变成了最终的Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking whether gcc understands -c and -o together... yes
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands

$ ls
Makefile Makefile.am Makefile.in aclocal.m4 autom4te.cache autoscan.log compile config.h config.h.in config.log config.status configure configure.ac depcomp install-sh main.c missing stamp-h1

make编译工程

1
2
3
4
5
6
7
$ make
make all-am
make[1]: Entering directory '/root/automake'
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
gcc -g -O2 -o main main.o
make[1]: Leaving directory '/root/automake'
1
2
3
4
$ ls
Makefile Makefile.am Makefile.in aclocal.m4 autom4te.cache autoscan.log compile config.h config.h.in config.log config.status configure configure.ac depcomp install-sh main main.c main.o missing stamp-h1
$ ./main
Hello automake

参考鸣谢

Ceph官网有一篇“Getting started with the Docker RBD volume plugin”里面提到了一个驱动“github.com/yp-engineering/rbd-docker-plugin”,此驱动使用的是krbd(kernel RBD)。krbd与librbd相比很多功能被阉割了,如果你只想用Ceph作为backend,那你可以使用这个驱动。如果你不甘心只是简单的使用Ceph,还想体验他的很多特性,那你可以考虑下面这个方案。。。

原理

本方案的想法来自于Ceph的一个命令nbd-rbd,只要你的系统支持nbd,并且支持docker,nbd与rbd的通讯方式依然使用socket文件的形式,只是rbd client相关程序需要放置到容器中;因为有些操作系统没有包管理器,不能方便的安装软件包(如:CoreOS)。

docker_volplugin_nbd_rbd.png

图画的这么清晰,还需要再说明吗!好吧,还是再说点儿吧。

  • 每个nbd设备与rbd设备之间使用一个socket文件进行通讯
  • NBDServer用于管理所有nbd设备
  • RBDServer用于管理所有rbd设备

实现

*** Todo… ***

参考&鸣谢

docker中容器的运行离不开image,一个image中都存放有哪些东西呢?这些东西又是以何种方式组织在一起的呢?今天让我们来撕开这层面纱,看看image到底是什么样。

导出tar包

首先我们使用docker save,将一个docker image保存成tar包,我们以nginx官方镜像为例

1
2
3
4
5
6
7
8
9
10
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx 1.11.3 4efb2fcdb1ab 15 months ago 183MB

$ docker save nginx:1.11.3 -o ./nginx.tar
$ tar -xvf ./nginx.tar -C ./nginx
...
$ ls -l ./
drwxrwxr-x 5 zhoub zhoub 4096 11月 28 16:55 nginx
-rw------- 1 zhoub zhoub 191400960 11月 28 16:52 nginx.tar

走近nginx目录

现在nginx.tar已经被解开了,接下来就要进入nginx目录一探究竟了。首先tree一下nginx目录,看看都有什么文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ tree -L 2 ./nginx
./nginx
├── 01d69dac180439f10b5f2f06b71fa19c9f89fb85cb5fd543dba9bb2ca9b620ef
│   ├── json
│   ├── layer
│   ├── layer.tar
│   └── VERSION
├── 4efb2fcdb1ab05fb03c9435234343c1cc65289eeb016be86193e88d3a5d84f6b.json
├── 80cdba09b091f72276c031bd64488c3da5f82022812bc995f0859abe0855a872
│   ├── json
│   ├── layer
│   ├── layer.tar
│   └── VERSION
├── c575e5db41b92dbf9e1814b8ea9cf989ef650e37cfcef12fa402e630db6a581f
│   ├── json
│   ├── layer
│   ├── layer.tar
│   └── VERSION
├── manifest.json
└── repositories

从目录结构上来看,有三个总体的描述文件repositoriesmanifest.json4efb2fcdb1ab05fb03c9435234343c1cc65289eeb016be86193e88d3a5d84f6b.json,还有三个目录,这三个目录的结构基本相同都由jsonlayerVERSION组成。

通过这些名字,我们先简单猜测一下,image由很多layer组成,上层layer基于下层layer构建而成,下层layer基于基层layer构成。到底image是不是按我们的猜测构成的呢,让我们来看一看总体描述的三个文件。

manifest.json:

1
{"Config":"4efb2fcdb1ab05fb03c9435234343c1cc65289eeb016be86193e88d3a5d84f6b.json","RepoTags":["nginx:1.11.3"],"Layers":["80cdba09b091f72276c031bd64488c3da5f82022812bc995f0859abe0855a872/layer.tar","c575e5db41b92dbf9e1814b8ea9cf989ef650e37cfcef12fa402e630db6a581f/layer.tar","01d69dac180439f10b5f2f06b71fa19c9f89fb85cb5fd543dba9bb2ca9b620ef/layer.tar"]}]

通过manifest.json文件我们可以看出,nginx:1.11.3这个image的配置文件是4efb2fcdb1ab05fb03c9435234343c1cc65289eeb016be86193e88d3a5d84f6b.json,layer一共有三个分别是80cdba09b091f72276c031bd64488c3da5f82022812bc995f0859abe0855a872/layer.tarc575e5db41b92dbf9e1814b8ea9cf989ef650e37cfcef12fa402e630db6a581f/layer.tar01d69dac180439f10b5f2f06b71fa19c9f89fb85cb5fd543dba9bb2ca9b620ef/layer.tar

image的配置文件中都对image进行了哪些配置呢?让我们来看一看
4efb2fcdb1ab05fb03c9435234343c1cc65289eeb016be86193e88d3a5d84f6b.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
"architecture":"amd64",
"author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"",
"config":{
"Hostname":"2da0903ff372",
"Domainname":"",
"User":"",
"AttachStdin":false,
"AttachStdout":false,
"AttachStderr":false,
"ExposedPorts":{"443/tcp":{},"80/tcp":{}},
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.3-1~jessie"],
"Cmd":["nginx","-g","daemon off;"],
"ArgsEscaped":true,
"Image":"sha256:27ca9d70a764c6955e354f5fdc706b03ed47601213ac9fc638a8943fcc7680f8",
"Volumes":null,
"WorkingDir":"",
"Entrypoint":null,
"OnBuild":[],
"Labels":{}
},
"container":"25ab8b143580751e438246801e7ba720f6f84ee9faca3522532e7fbe1ed7021f",
"container_config":{
"Hostname":"2da0903ff372",
"Domainname":"",
"User":"",
"AttachStdin":false,
"AttachStdout":false,
"AttachStderr":false,
"ExposedPorts":{"443/tcp":{},"80/tcp":{}},
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.3-1~jessie"],
"Cmd":["/bin/sh","-c","#(nop) CMD [\"nginx\" \"-g\" \"daemon off;\"]"],
"ArgsEscaped":true,
"Image":"sha256:27ca9d70a764c6955e354f5fdc706b03ed47601213ac9fc638a8943fcc7680f8",
"Volumes":null,
"WorkingDir":"",
"Entrypoint":null,
"OnBuild":[],
"Labels":{}
},
"created":"2016-08-23T18:51:23.709520142Z",
"docker_version":"1.10.3",
"history":[
{"created":"2016-07-28T17:47:54.990622865Z","created_by":"/bin/sh -c #(nop) ADD file:0e0565652aa852f62033d99f84892216020d30f64521ded5e72d4940bc4c9697 in /"},
{"created":"2016-07-28T17:47:55.809686499Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true},
{"created":"2016-08-23T18:49:31.945397165Z","author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"","created_by":"/bin/sh -c #(nop) MAINTAINER NGINX Docker Maintainers \"docker-maint@nginx.com\"","empty_layer":true},
{"created":"2016-08-23T18:49:33.057946615Z","author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"","created_by":"/bin/sh -c #(nop) ENV NGINX_VERSION=1.11.3-1~jessie","empty_layer":true},
{"created":"2016-08-23T18:51:19.236946594Z","author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"","created_by":"/bin/sh -c apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 \t\u0026\u0026 echo \"deb http://nginx.org/packages/mainline/debian/ jessie nginx\" \u003e\u003e /etc/apt/sources.list \t\u0026\u0026 apt-get update \t\u0026\u0026 apt-get install --no-install-recommends --no-install-suggests -y \t\t\t\t\t\tca-certificates \t\t\t\t\t\tnginx=${NGINX_VERSION} \t\t\t\t\t\tnginx-module-xslt \t\t\t\t\t\tnginx-module-geoip \t\t\t\t\t\tnginx-module-image-filter \t\t\t\t\t\tnginx-module-perl \t\t\t\t\t\tnginx-module-njs \t\t\t\t\t\tgettext-base \t\u0026\u0026 rm -rf /var/lib/apt/lists/*"},
{"created":"2016-08-23T18:51:21.574094006Z","author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"","created_by":"/bin/sh -c ln -sf /dev/stdout /var/log/nginx/access.log \t\u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log"},
{"created":"2016-08-23T18:51:22.632895061Z","author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"","created_by":"/bin/sh -c #(nop) EXPOSE 443/tcp 80/tcp","empty_layer":true},
{"created":"2016-08-23T18:51:23.709520142Z","author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"","created_by":"/bin/sh -c #(nop) CMD [\"nginx\" \"-g\" \"daemon off;\"]","empty_layer":true}
],
"os":"linux",
"rootfs":{
"type":"layers",
"diff_ids":[
"sha256:2f71b45e4e254ddceb187b1467f5471f0e14d7124ac2dd7fdd7ddbc76e13f0e5",
"sha256:d7953e5e5bba5c637ce6eb81a4528a777c4265386cb12e9bff8124ef97538746",
"sha256:69ecf026ff94793ab573754612e6fa40b28331a47c3584de0b32d39a88959c37"
]
}
}

看完这个配置文件我惊到了,如此庞大的一个配置文件告诉我们dockerfile能配的我能配,dockerfile不能配的我也能配。。。这个配置文件具体就不分析了。。。

刚刚看完那个累心的配置文件,接下来让我们来说说这三个layer,哪一个layer是入口呢?

repositories:

1
{"nginx":{"1.11.3":"01d69dac180439f10b5f2f06b71fa19c9f89fb85cb5fd543dba9bb2ca9b620ef"}}

我们通过查看repositories可以看出1.11.3这个版本的layer入口是01d69dac180439f10b5f2f06b71fa19c9f89fb85cb5fd543dba9bb2ca9b620ef。那么三个layer之间的依赖关系怎样呢?让我们进入各个layer一探究竟吧。

layer 01d69dac180439f10b5f2f06b71fa19c9f89fb85cb5fd543dba9bb2ca9b620ef

该layer中的json文件描述的本layer的配置信息

json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
"id":"01d69dac180439f10b5f2f06b71fa19c9f89fb85cb5fd543dba9bb2ca9b620ef",
"parent":"c575e5db41b92dbf9e1814b8ea9cf989ef650e37cfcef12fa402e630db6a581f",
"created":"2016-08-23T18:51:23.709520142Z",
"container":"25ab8b143580751e438246801e7ba720f6f84ee9faca3522532e7fbe1ed7021f",
"container_config":{
"Hostname":"2da0903ff372",
"Domainname":"",
"User":"",
"AttachStdin":false,
"AttachStdout":false,
"AttachStderr":false,
"ExposedPorts":{"443/tcp":{},"80/tcp":{}},
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.3-1~jessie"],
"Cmd":["/bin/sh","-c","#(nop) CMD [\"nginx\" \"-g\" \"daemon off;\"]"],
"ArgsEscaped":true,
"Image":"sha256:27ca9d70a764c6955e354f5fdc706b03ed47601213ac9fc638a8943fcc7680f8",
"Volumes":null,
"WorkingDir":"",
"Entrypoint":null,
"OnBuild":[],
"Labels":{}
},
"docker_version":"1.10.3",
"author":"NGINX Docker Maintainers \"docker-maint@nginx.com\"",
"config":{
"Hostname":"2da0903ff372",
"Domainname":"",
"User":"",
"AttachStdin":false,
"AttachStdout":false,
"AttachStderr":false,
"ExposedPorts":{"443/tcp":{},"80/tcp":{}},
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.11.3-1~jessie"],
"Cmd":["nginx","-g","daemon off;"],
"ArgsEscaped":true,
"Image":"sha256:27ca9d70a764c6955e354f5fdc706b03ed47601213ac9fc638a8943fcc7680f8",
"Volumes":null,
"WorkingDir":"",
"Entrypoint":null,
"OnBuild":[],
"Labels":{}
},
"architecture":"amd64",
"os":"linux"
}

我们可以看到parent字段指出了当前layer的父layer是c575e5db41b92dbf9e1814b8ea9cf989ef650e37cfcef12fa402e630db6a581f
当前layer中数据内容请见layer.tar

layer.tar:

1
2
3
4
5
6
7
$ tree ./layer
./layer
└── var
└── log
└── nginx
├── access.log -> /dev/stdout
└── error.log -> /dev/stderr

layer c575e5db41b92dbf9e1814b8ea9cf989ef650e37cfcef12fa402e630db6a581f

该layer中的json文件描述的本layer的配置信息

json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"id":"c575e5db41b92dbf9e1814b8ea9cf989ef650e37cfcef12fa402e630db6a581f",
"parent":"80cdba09b091f72276c031bd64488c3da5f82022812bc995f0859abe0855a872",
"created":"1970-01-01T08:00:00+08:00",
"container_config":{
"Hostname":"",
"Domainname":"",
"User":"",
"AttachStdin":false,
"AttachStdout":false,
"AttachStderr":false,
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":null,
"Cmd":null,
"Image":"",
"Volumes":null,
"WorkingDir":"",
"Entrypoint":null,
"OnBuild":null,
"Labels":null
}
}

该layer中的父layer是80cdba09b091f72276c031bd64488c3da5f82022812bc995f0859abe0855a872
当前layer中的数据内容请见layer.tar

layer.tar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ tree -L 2 ./layer
./layer
├── etc
│   ├── alternatives
│   ├── apt
│   ├── ca-certificates
│   ├── ca-certificates.conf
│   ├── default
│   ├── fonts
│   ├── group
│   ├── group-
│   ├── gshadow
│   ├── gshadow-
│   ├── init.d
│   ├── ld.so.cache
│   ├── logrotate.d
│   ├── nginx
│   ├── passwd
│   ├── passwd-
│   ├── perl
│   ├── rc0.d
│   ├── rc1.d
│   ├── rc2.d
│   ├── rc3.d
│   ├── rc4.d
│   ├── rc5.d
│   ├── rc6.d
│   ├── shadow
│   ├── shadow-
│   ├── ssl
│   ├── subgid
│   ├── subgid-
│   ├── subuid
│   ├── subuid-
│   └── ucf.conf
├── lib
│   └── x86_64-linux-gnu
├── tmp
├── usr
│   ├── bin
│   ├── lib
│   ├── local
│   ├── sbin
│   └── share
└── var
├── cache
├── lib
└── log

layer 80cdba09b091f72276c031bd64488c3da5f82022812bc995f0859abe0855a872

该layer中的json文件描述的本layer的配置信息
json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"id":"80cdba09b091f72276c031bd64488c3da5f82022812bc995f0859abe0855a872",
"created":"1970-01-01T08:00:00+08:00",
"container_config":{
"Hostname":"",
"Domainname":"",
"User":"",
"AttachStdin":false,
"AttachStdout":false,
"AttachStderr":false,
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":null,
"Cmd":null,
"Image":"",
"Volumes":null,
"WorkingDir":"",
"Entrypoint":null,
"OnBuild":null,
"Labels":null
}
}

从配置中可以看出,没有parent字段了。说明这个layer应该就是base layer了。(我十分好奇nginx镜像是用哪个基础镜像做出来的?)
当前layer中的数据内容请见layer.tar
layer.tar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ tree -L 1 ./layer
./layer
├── bin
├── boot
├── dev
├── etc
├── home
├── lib
├── lib64
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
1
2
$ cat ./layer/etc/debian_version
8.5

噢,原来nginx镜像是基于debian 8.5做出来的。