层级存储仓库(LayerStore)存储docker容器镜像的基础单位,一个镜像是由多个Layer组成,各个存储体之间有重叠的部分,这两个部分之间存储有差别化信息,这些来自于用户的修改。下面我将从Layer的构造函数和NewStoreFromOptions函数的角度来了解LayerStore的初始化。
一、LayerStore构造函数
layerStore结构体记录一个hots主机上面存储着的所有的layer层信息,包括只读layer和读写layer。
其工作目录是/var/lib/docker/image/${graphDriverName}/layerdb
,其构造函数如下:
type layerStore struct {
store MetadataStore
driver graphdriver.Driver //文件系统的驱动
layerMap map[ChainID]*roLayer //存放镜像的只读layer信息,/var/lib/docker/image/overlay/layerdb/sha256/
layerL sync.Mutex
mounts map[string]*mountedLayer //存放可读写层信息,/var/lib/docker/image/overlay/layerdb/mounts/
mountL sync.Mutex
}
1、ChainID和DiffID申明
//ChainID 是一个用来寻找一个layer层内容的ID编号
type ChainID digest.Digest
//DiffID是一个layer.tar的hash,计算方法 sha256sum ./layer.tar
type DiffID digest.Digest
2、MetadataStore接口函数
MetadataStore定义操作layer及其元数据的接口
// MetadataStore 表示一个后端,用于持久保存有关层级的元数据并提供用于还原存储的元数据。
type MetadataStore interface {
//StartTransaction 会启动新元数据的更新,这些元数据将用于在提交时表示 ID。
StartTransaction() (MetadataTransaction, error)
GetSize(ChainID) (int64, error) //获取层级数据大小
GetParent(ChainID) (ChainID, error)
GetDiffID(ChainID) (DiffID, error)
GetCacheID(ChainID) (string, error) //获取缓存大小
GetDescriptor(ChainID) (distribution.Descriptor, error) //获取描述信息
TarSplitReader(ChainID) (io.ReadCloser, error)
SetMountID(string, string) error //设置挂载ID
SetInitID(string, string) error //设置初始化ID
SetMountParent(string, ChainID) error
GetMountID(string) (string, error) //获取挂载ID
GetInitID(string) (string, error)
GetMountParent(string) (ChainID, error)
//返回所有的只读layer和读写layer
List() ([]ChainID, []string, error)
Remove(ChainID) error //移除ChainID
RemoveMount(string) error //移除挂载
}
3、Driver接口
下面我介绍驱动接口的函数
//下面设计驱动接口的定义
type Driver interface {
ProtoDriver
DiffDriver
}
type ProtoDriver interface {
// 返回驱动程序的字符串表示形式。
String() string
// CreateReadWrite创建了一个新的空文件系统层,可以用作容器的存储层。 额外的选项可以在选项中传递。 Parent可以是""而opts可以是nil。
CreateReadWrite(id, parent string, opts *CreateOpts) error
//Create创建一个新的、空的文件系统层,使用指定的id和父级以及传递给opts的选项。 Parent可以是""而opts可以是nil。
Create(id, parent string, opts *CreateOpts) error
// Remove attempts to remove the filesystem layer with this id.
Remove(id string) error
// Get返回这个id所引用的分层文件系统的挂载点。您可以选择指定一个mountLabel或""。返回安装的分层文件系统的绝对路径。
Get(id, mountLabel string) (dir string, err error)
// 释放指定id的系统资源
Put(id string) error
// Exists返回驱动器上是否存在指定ID的文件系统层
Exists(id string) bool
// Status返回一组键值对,这些键值对给出关于这个驱动程序的低级诊断状态。
Status() [][2]string
// 返回一组键值对,这些键值对给出映像/容器驱动程序正在管理的低层信息。
GetMetadata(id string) (map[string]string, error)
// 清理执行必要的任务来释放驱动程序所拥有的资源,例如,卸载这个驱动程序所知道的所有分层文件系统。
Cleanup() error
}
// DiffDriver接口是继承于graph diffs
type DiffDriver interface {
// Diff生成指定层与父层(可能是“”)之间更改的存档。
Diff(id, parent string) (io.ReadCloser, error)
// Changes在指定的层和它的父层之间产生一个变化列表。如果parent是"",那么所有的更改都将是ADD更改。
Changes(id, parent string) ([]archive.Change, error)
// ApplyDiff从给定的diff提取变更集到具有指定id和父层的层中,以字节为单位返回新层的大小。存档。读取器必须是未压缩的流。
ApplyDiff(id, parent string, diff io.Reader) (size int64, err error)
// DiffSize计算指定id与其父目录之间的更改,并返回相对于其基文件系统目录的更改的大小(以字节为单位)。
DiffSize(id, parent string) (size int64, err error)
}
二、NewStoreFromOptions函数
创建daemon的过程中,正是调用此方法。其流程如下:
1、新建一个Driver
2、新建一个MetadataStore fms,这个比较简单,就是建立文件夹 /var/lib/docker/image/overlay/layerdb
,同时提供操作接口。
3、 基于type fileMetadataStore struct和graph driver构建一个type layerStore struct对象
// NewStoreFromOptions函数创建了新的Store接口
func NewStoreFromOptions(options StoreOptions) (Store, error) {
driver, err := graphdriver.New(options.GraphDriver, options.PluginGetter, graphdriver.Options{
Root: options.StorePath,
DriverOptions: options.GraphDriverOptions,
UIDMaps: options.UIDMaps,
GIDMaps: options.GIDMaps,
ExperimentalEnabled: options.ExperimentalEnabled,
})
if err != nil {
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
}
logrus.Debugf("Using graph driver %s", driver)
//建立文件夹 /var/lib/docker/image/overlay/layerdb,同时提供操作接口(实现type MetadataStore interface)
fms, err := NewFSMetadataStore(fmt.Sprintf(options.MetadataStorePathTemplate, driver))
if err != nil {
return nil, err
}
//基于type fileMetadataStore struct和graph driver构建一个type layerStore struct对象
return NewStoreFromGraphDriver(fms, driver)
}
1、 创建一个graphdriver
根据优先级priority来得到文件系统驱动,然后调用已经注册好的文件系统驱动初始化函数initFunc来初始化驱动
// New函数在指定的根目录下创建驱动程序并初始化它。
func New(name string, pg plugingetter.PluginGetter, config Options) (Driver, error) {
if name != "" {
/*用户自行自定了graphdriver*/
logrus.Debugf("[graphdriver] trying provided driver: %s", name) //日志显示了指定的驱动程序
return GetDriver(name, pg, config)
}
//获取文件系统驱动,首先根据优先级priority来得到文件系统驱动,然后调用已经注册好的文件系统驱动初始化函数initFunc来初始化驱动。driver和initFunc映射关系的构建是通过各个驱动初始化的时候,自行调用func Register()来注册
driversMap := scanPriorDrivers(config.Root)
for _, name := range priority {
// of the state found from prior drivers, check in order of our priority which we would prefer
driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.UIDMaps, config.GIDMaps)
...
...
}
...
...
}
各个文件系统会在初始化的时候调用func Register,把自身的func Init()注册到drivers中
//Register为驱动程序注册一个InitFunc。
func Register(name string, initFunc InitFunc) error {
if _, exists := drivers[name]; exists {
return fmt.Errorf("Name already registered %s", name)
}
drivers[name] = initFunc
return nil
}
以overlay为例子,/daemon/graphdriver/overlay/overlay.go
func init() {
graphdriver.Register("overlay", Init)
}
func NewStoreFromGraphDriver
目录/var/lib/docker/image/overlay/layerdb下,有着sha256目录、mounts目录
// NewStoreFromGraphDriver creates a new Store instance using the provided
// metadata store and graph driver. The metadata store will be used to restore
// the Store.
func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (Store, error) {
ls := &layerStore{
store: store,
driver: driver,
layerMap: map[ChainID]*roLayer{},
mounts: map[string]*mountedLayer{},
}
/*
/layer/filestore.go
==>func (fms *fileMetadataStore) List() ([]ChainID, []string, error)
根据fileMetadataStore中的root目录(即/var/lib/docker/image/overlay/layerdb)加载
其下的sha256目录(ids)和mounts目录(mounts)中的信息
*/
ids, mounts, err := store.List()
if err != nil {
return nil, err
}
/*
开始遍历sha256目录(ids),所有的只读layer
*/
for _, id := range ids {
/*
根据sha256下的id信息加载镜像的只读layer信息:
包括diff(所有镜像层diff是根据镜像内容使用sha256算法得到),size,cacheID,parent,descriptor。
然后存放到ls.layerMap[ChainID]中去。
*/
l, err := ls.loadLayer(id)
if err != nil {
logrus.Debugf("Failed to load layer %s: %s", id, err)
continue
}
if l.parent != nil {
l.parent.referenceCount++
}
}
/*
同理,
开始遍历mounts目录(mounts),所有的读写layer
目录名一般为容器id
目录下有三份文件,分别是init-id,mounts-id,parent
分别对应可读写层初始化层id,可读写层id以及父镜像层的ChainID
通过init-id,mounts-id,能在 /var/lib/docker/overlay/ 下找到对应的目录
通过parent能在 /var/lib/docker/image/overlay/layerdb/sha256/ 下找到对应目录
*/
for _, mount := range mounts {
if err := ls.loadMount(mount); err != nil {
logrus.Debugf("Failed to load mount %s: %s", mount, err)
}
}
return ls, nil
}
简单看一下loadLayer()和loadMount()
func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
cl, ok := ls.layerMap[layer]
if ok {
//如果该层layer已经被记录到ls.layerMap中,直接return即可
return cl, nil
}
diff, err := ls.store.GetDiffID(layer)
if err != nil {
return nil, fmt.Errorf("failed to get diff id for %s: %s", layer, err)
}
size, err := ls.store.GetSize(layer)
if err != nil {
return nil, fmt.Errorf("failed to get size for %s: %s", layer, err)
}
cacheID, err := ls.store.GetCacheID(layer)
if err != nil {
return nil, fmt.Errorf("failed to get cache id for %s: %s", layer, err)
}
parent, err := ls.store.GetParent(layer)
if err != nil {
return nil, fmt.Errorf("failed to get parent for %s: %s", layer, err)
}
descriptor, err := ls.store.GetDescriptor(layer)
if err != nil {
return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
}
cl = &roLayer{
chainID: layer,
diffID: diff,
size: size,
cacheID: cacheID,
layerStore: ls,
references: map[Layer]struct{}{},
descriptor: descriptor,
}
if parent != "" {
p, err := ls.loadLayer(parent)
if err != nil {
return nil, err
}
cl.parent = p
}
/*
把新的一层layer记录到ls.layerMap[chainID]中
*/
ls.layerMap[cl.chainID] = cl
return cl, nil
}
func (ls *layerStore) loadMount(mount string) error {
if _, ok := ls.mounts[mount]; ok {
return nil
}
mountID, err := ls.store.GetMountID(mount)
if err != nil {
return err
}
initID, err := ls.store.GetInitID(mount)
if err != nil {
return err
}
parent, err := ls.store.GetMountParent(mount)
if err != nil {
return err
}
ml := &mountedLayer{
name: mount,
mountID: mountID,
initID: initID,
layerStore: ls,
references: map[RWLayer]*referencedRWLayer{},
}
if parent != "" {
p, err := ls.loadLayer(parent)
if err != nil {
return err
}
ml.parent = p
p.referenceCount++
}
/*
加载一层读写layer到ls.mounts[container_id]
*/
ls.mounts[ml.name] = ml
return nil
}