之前在”Docker镜像存储分析”中已经介绍过Docker是如何组织镜像存储目录的。在Docker中,主要有三个store来负责对镜像进行管理:
- imageStore: 负责imagedb目录管理;
- layerStore: 负责content目录管理;
- referenceStore: 负责repositories.json文件管理。
本次分析将介绍imageStore相关的知识。
imagedb
Docker1.12.3把镜像相关的内容入在/var/lib/docker/image/aufs/imagedb目录下。主要有content和metadata两个目录,content目录存储镜像config文件,metadata目录存储镜像的parent文件。
fs
先来看fs。fs是直接和imagedb目录打交道的模块。fs定义在/image/fs.go中:
1 2 3 4 5
| type fs struct { sync.RWMutex root string }
|
fs的root字段表示imagedb目录,即/var/lib/docker/image/aufs/imagedb/。
fs实现了StorageBackend接口:
1 2 3 4 5 6 7 8 9 10
| type StoreBackend interface { Walk(f IDWalkFunc) error Get(id ID) ([]byte, error) Set(data []byte) (ID, error) Delete(id ID) error SetMetadata(id ID, key string, data []byte) error GetMetadata(id ID, key string) ([]byte, error) DeleteMetadata(id ID, key string) error }
|
NewFSStoreBackend()
NewFSStoreBackend()可以生成一个新的fs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func NewFSStoreBackend(root string) (StoreBackend, error) { return newFSStore(root) } func newFSStore(root string) (*fs, error) { s := &fs{ root: root, } if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0700); err != nil { return nil, err } if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil { return nil, err } return s, nil }
|
可以看出,NewFSStoreBackend()调用了newFSStore(),newFSStore()也只是创建了content和metadata目录。
路径组装方法
路径组装有两个方法:contentFile()和metadataDir(),分别可以获取content和metadata目录下的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13
| func (s *fs) contentFile(id ID) string { dgst := digest.Digest(id) return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex()) } func (s *fs) metadataDir(id ID) string { dgst := digest.Digest(id) return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Hex()) }
|
Walk()
Walk()该当实现了目录的遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func (s *fs) Walk(f IDWalkFunc) error { s.RLock() dir, err := ioutil.ReadDir(filepath.Join(s.root, contentDirName, string(digest.Canonical))) s.RUnlock() if err != nil { return err } for _, v := range dir { dgst := digest.NewDigestFromHex(string(digest.Canonical), v.Name()) if err := dgst.Validate(); err != nil { logrus.Debugf("Skipping invalid digest %s: %s", dgst, err) continue } if err := f(ID(dgst)); err != nil { return err } } return nil }
|
Get()
Get()可以通过id获取镜像的config文件。获取config文件的流程很简单,从content目录下读取对应id的文件即可。
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
| func (s *fs) Get(id ID) ([]byte, error) { s.RLock() defer s.RUnlock() return s.get(id) } func (s *fs) get(id ID) ([]byte, error) { content, err := ioutil.ReadFile(s.contentFile(id)) if err != nil { return nil, err } if ID(digest.FromBytes(content)) != id { return nil, fmt.Errorf("failed to verify image: %v", id) } return content, nil }
|
Set()
Set()可以设置镜像层的config文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func (s *fs) Set(data []byte) (ID, error) { s.Lock() defer s.Unlock() if len(data) == 0 { return "", fmt.Errorf("Invalid empty data") } id := ID(digest.FromBytes(data)) if err := ioutils.AtomicWriteFile(s.contentFile(id), data, 0600); err != nil { return "", err } return id, nil }
|
Delete()
Delete()可以依据id删除镜像层对应的文件或目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func (s *fs) Delete(id ID) error { s.Lock() defer s.Unlock() if err := os.RemoveAll(s.metadataDir(id)); err != nil { return err } if err := os.Remove(s.contentFile(id)); err != nil { return err } return nil }
|
SetMetadata()可以把内容写入到metadata目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func (s *fs) SetMetadata(id ID, key string, data []byte) error { s.Lock() defer s.Unlock() if _, err := s.get(id); err != nil { return err } baseDir := filepath.Join(s.metadataDir(id)) if err := os.MkdirAll(baseDir, 0700); err != nil { return err } return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(id), key), data, 0600) }
|
从metadata目录下获取key对应的内容。
1 2 3 4 5 6 7 8 9 10 11
| // GetMetadata returns metadata for a given ID. //***获取镜像的metadata***// func (s *fs) GetMetadata(id ID, key string) ([]byte, error) { s.RLock() defer s.RUnlock() if _, err := s.get(id); err != nil { return nil, err } return ioutil.ReadFile(filepath.Join(s.metadataDir(id), key)) }
|
DeleteMetadata()移除某id的metadata内容。
1 2 3 4 5 6 7 8
| func (s *fs) DeleteMetadata(id ID, key string) error { s.Lock() defer s.Unlock() return os.RemoveAll(filepath.Join(s.metadataDir(id), key)) }
|
image
接着来看image,image定义在/image/image.go中:
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
| type V1Image struct { ID string `json:"id,omitempty"` Parent string `json:"parent,omitempty"` Comment string `json:"comment,omitempty"` Created time.Time `json:"created"` Container string `json:"container,omitempty"` ContainerConfig container.Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` Author string `json:"author,omitempty"` Config *container.Config `json:"config,omitempty"` Architecture string `json:"architecture,omitempty"` OS string `json:"os,omitempty"` Size int64 `json:",omitempty"` } type Image struct { V1Image Parent ID `json:"parent,omitempty"` RootFS *RootFS `json:"rootfs,omitempty"` History []History `json:"history,omitempty"` OSVersion string `json:"os.version,omitempty"` OSFeatures []string `json:"os.features,omitempty"` rawJSON []byte computedID ID }
|
image的各字段可以和image的config对应起来。
RawJSON()
RawJSON()返回rawJSON。
1 2 3 4
| func (img *Image) RawJSON() []byte { return img.rawJSON }
|
ID()
1 2 3 4 5
| func (img *Image) ID() ID { return img.computedID }
|
ImageID()
返回镜像层的ID。
1 2 3 4
| func (img *Image) ImageID() string { return string(img.ID()) }
|
RunConfig()
1 2 3 4 5
| func (img *Image) RunConfig() *container.Config { return img.Config }
|
MarshalJSON()
MarshalJSON()把镜像层的config序列化成json。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func (img *Image) MarshalJSON() ([]byte, error) { type MarshalImage Image pass1, err := json.Marshal(MarshalImage(*img)) if err != nil { return nil, err } var c map[string]*json.RawMessage if err := json.Unmarshal(pass1, &c); err != nil { return nil, err } return json.Marshal(c) }
|
rootfs
rootfs表明Docker是如何组织镜像的,目前只有”layers”一种。rootfs定义在/image/rootfs_unix.go中:
1 2 3 4 5 6 7 8 9 10 11 12
| type RootFS struct { Type string `json:"type"` DiffIDs []layer.DiffID `json:"diff_ids,omitempty"` } func (r *RootFS) ChainID() layer.ChainID { return layer.CreateChainID(r.DiffIDs) }
|
可以看出,RootFS可以依据DiffIDs(由layer.tar哈希计算得出)计算layer的值。具体见镜像存储分析。
Store
Store可以操作整个imagedb。
Store定义在/image/store.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type Store interface { Create(config []byte) (ID, error) Get(id ID) (*Image, error) Delete(id ID) ([]layer.Metadata, error) Search(partialID string) (ID, error) SetParent(id ID, parent ID) error GetParent(id ID) (ID, error) Children(id ID) []ID Map() map[ID]*Image Heads() map[ID]*Image } type store struct { sync.Mutex ls LayerGetReleaser images map[ID]*imageMeta fs StoreBackend digestSet *digest.Set }
|
可以看出,store中有images map维护ID和imageMeta的关系。
imageMeta定义如下:
1 2 3 4 5
| type imageMeta struct { layer layer.Layer children map[ID]struct{} }
|
imageMeta中存有layer信息及该镜像层的children信息。
NewImageStore()
来看下store的生成函数NewImageStore()。
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
| func NewImageStore(fs StoreBackend, ls LayerGetReleaser) (Store, error) { is := &store{ ls: ls, images: make(map[ID]*imageMeta), fs: fs, digestSet: digest.NewSet(), } if err := is.restore(); err != nil { return nil, err } return is, nil } func (is *store) restore() error { err := is.fs.Walk(func(id ID) error { img, err := is.Get(id) if err != nil { logrus.Errorf("invalid image %v, %v", id, err) return nil } var l layer.Layer if chainID := img.RootFS.ChainID(); chainID != "" { l, err = is.ls.Get(chainID) if err != nil { return err } } if err := is.digestSet.Add(digest.Digest(id)); err != nil { return err } imageMeta := &imageMeta{ layer: l, children: make(map[ID]struct{}), } is.images[ID(id)] = imageMeta return nil }) if err != nil { return err } for id := range is.images { if parent, err := is.GetParent(id); err == nil { if parentMeta := is.images[parent]; parentMeta != nil { parentMeta.children[id] = struct{}{} } } } return nil }
|
其中restore()是直接从imagedb目录构建imageStore。
Create()
Create()可以从config生成一个镜像层。
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
| func (is *store) Create(config []byte) (ID, error) { var img Image err := json.Unmarshal(config, &img) if err != nil { return "", err } rootFSLayers := make(map[layer.DiffID]struct{}) for _, diffID := range img.RootFS.DiffIDs { rootFSLayers[diffID] = struct{}{} } layerCounter := 0 for _, h := range img.History { if !h.EmptyLayer { layerCounter++ } } if layerCounter > len(img.RootFS.DiffIDs) { return "", errors.New("too many non-empty layers in History section") } dgst, err := is.fs.Set(config) if err != nil { return "", err } imageID := ID(dgst) is.Lock() defer is.Unlock() if _, exists := is.images[imageID]; exists { return imageID, nil } layerID := img.RootFS.ChainID() var l layer.Layer if layerID != "" { l, err = is.ls.Get(layerID) if err != nil { return "", err } } imageMeta := &imageMeta{ layer: l, children: make(map[ID]struct{}), } is.images[imageID] = imageMeta if err := is.digestSet.Add(digest.Digest(imageID)); err != nil { delete(is.images, imageID) return "", err } return imageID, nil }
|
Get()
Get()先从fs中获取config,然后调用NewFromJSON(config)创建一个img,再设置img的computedID(就是该image的config的hash值)及Parent。
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
| func (is *store) Get(id ID) (*Image, error) { config, err := is.fs.Get(id) if err != nil { return nil, err } img, err := NewFromJSON(config) if err != nil { return nil, err } img.computedID = id img.Parent, err = is.GetParent(id) if err != nil { img.Parent = "" } return img, nil }
|
Delete()
Delete()可以删除一个镜像层。
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
| func (is *store) Delete(id ID) ([]layer.Metadata, error) { is.Lock() defer is.Unlock() imageMeta := is.images[id] if imageMeta == nil { return nil, fmt.Errorf("unrecognized image ID %s", id.String()) } for id := range imageMeta.children { is.fs.DeleteMetadata(id, "parent") } if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil { delete(is.images[parent].children, id) } if err := is.digestSet.Remove(digest.Digest(id)); err != nil { logrus.Errorf("error removing %s from digest set: %q", id, err) } delete(is.images, id) is.fs.Delete(id) if imageMeta.layer != nil { return is.ls.Release(imageMeta.layer) } return nil, nil }
|
SetParent()
Setparent()可以建立两个id之前的父子层关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func (is *store) SetParent(id, parent ID) error { is.Lock() defer is.Unlock() parentMeta := is.images[parent] if parentMeta == nil { return fmt.Errorf("unknown parent image ID %s", parent.String()) } if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil { delete(is.images[parent].children, id) } parentMeta.children[id] = struct{}{} return is.fs.SetMetadata(id, "parent", []byte(parent)) }
|
GetParent()
GetParent()可以获取镜像层的父层。
1 2 3 4 5 6 7 8 9
| func (is *store) GetParent(id ID) (ID, error) { d, err := is.fs.GetMetadata(id, "parent") if err != nil { return "", err } return ID(d), nil }
|
Children()
Children()可以获取镜像层的所有子层。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func (is *store) Children(id ID) []ID { is.Lock() defer is.Unlock() return is.children(id) } func (is *store) children(id ID) []ID { var ids []ID if is.images[id] != nil { for id := range is.images[id].children { ids = append(ids, id) } } return ids }
|
Map()
Map()返回所有的镜像层。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func (is *store) Map() map[ID]*Image { return is.imagesMap(true) } func (is *store) imagesMap(all bool) map[ID]*Image { is.Lock() defer is.Unlock() images := make(map[ID]*Image) for id := range is.images { if !all && len(is.children(id)) > 0 { continue } img, err := is.Get(id) if err != nil { logrus.Errorf("invalid image access: %q, error: %q", id, err) continue } images[id] = img } return images }
|