这里是普通文章模块栏目内容页
Docker image 存储路径 解析

     在生产环境中,经常遇到docker image 在资源池中的主机上存留的数据,由于随着业务系统的升级,旧的image 需要进行清理。这里梳理下,docker image的在linux 系统上的存储目录,以针对性的进行数据清理。

在3.10内核上 docker基于aufs管理存储

下面的命令可以看到所有pull到的images

cat /var/lib/docker/repositories-aufs | python -m json.tool


该命令的结果与docker images看到的images数目相同

cat /var/lib/docker/repositories-aufs | python -m json.tool
{
    "Repositories": {
        "172.30.30.241:5000/centos1": {
            "latest": "e099197b794f459b777cc82ba53f2ecdcfb52c0a3245a9b010ca239b50fd72ad"
        },
        "centos": {
            "6": "b9aeeaeb5e17b5414e5caa9a6b2f99e9ccef50561bdfe137cd05956961f1cec6",
            "latest": "fd44297e2ddb050ec4fa9752b7a4e3a8439061991886e2091e7c1f007c906d75",
            "new": "8390535f8e613861a3715cf1af4a082ac80108c1d098944def5aa1391207e33a",
            "new2": "e099197b794f459b777cc82ba53f2ecdcfb52c0a3245a9b010ca239b50fd72ad"
        },
        "hello-world": {
            "latest": "91c95931e552b11604fea91c2f537284149ec32fff0f700a4769cfd31d7696ae"
        },
        "quay.io/coreos/etcd": {
            "v2.0.11": "c02fd8670851ce85ace68db5cff8694a3ed3656bedd9fa8054de8aff2f39e631"
        },
        "registry": {
            "latest": "204704ce31375bcf4afecf672563b4881bbef0d59135c68d273235bb7254fb4b"
        },
        "ubuntu": {
            "14.04": "07f8e8c5e66084bef8f848877857537ffe1c47edd01a93af27e7161672ad0e95"
        }
    }
}


上述出现的image的ID全局唯一和image hub上相同

可以看到通过docker images看到的IMAGE ID实际就是上面的ID的前几位

docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
quay.io/coreos/etcd          v2.0.11             c02fd8670851        11 days ago         12.83 MB
172.30.30.241:5000/centos1   latest              e099197b794f        12 days ago         306.1 MB
centos                       new2                e099197b794f        12 days ago         306.1 MB
centos                       new                 8390535f8e61        12 days ago         306.1 MB
registry                      latest              204704ce3137        13 days ago         413.9 MB
ubuntu                       14.04               07f8e8c5e660        3 weeks ago         188.3 MB
centos                       6                   b9aeeaeb5e17        5 weeks ago         202.6 MB
centos                       latest              fd44297e2ddb        5 weeks ago         215.7 MB
hello-world                latest              91c95931e552        5 weeks ago         910 B

docker images -tree可以看到image的层级结构, docker 的image是一层层叠加的, -tree参数可以看到具体的叠加方式。
通过-tree看到的image的数目比上面的要多很多,出现了很多没有见过的imageID,
但是tree中第0层的image ID和 docker image命令的中的ID相符

docker images -tree
Warning: '-tree' is deprecated, it will be removed soon. See usage.
?..8093db4276d5 Virtual Size: 0 B
?.?..f9c3a06edd7a Virtual Size: 6.642 MB
?.  ?..546a4b0d3153 Virtual Size: 12.83 MB
?.    ?..9caa77989e25 Virtual Size: 12.83 MB
?.      ?..c02fd8670851 Virtual Size: 12.83 MB Tags: quay.io/coreos/etcd:v2.0.11
?..e9e06b06e14c Virtual Size: 188.1 MB
?.?..a82efea989f9 Virtual Size: 188.3 MB
?.  ?..37bea4ee0c81 Virtual Size: 188.3 MB
?.    ?..07f8e8c5e660 Virtual Size: 188.3 MB Tags: ubuntu:14.04
?.      ?..1f4ab7282e19 Virtual Size: 375.1 MB
?.        ?..0e4483abe66b Virtual Size: 377.5 MB
?.          ?..c6153b5d8f1f Virtual Size: 377.5 MB
?.            ?..2bc4611f2ed7 Virtual Size: 389.1 MB
?.              ?..30887473610f Virtual Size: 413.9 MB
?.                ?..3f8e22c413b1 Virtual Size: 413.9 MB
?.                  ?..22b1c756fa19 Virtual Size: 413.9 MB
?.                    ?..90607d8d09d1 Virtual Size: 413.9 MB
?.                      ?..4f4a5acb19eb Virtual Size: 413.9 MB
?.                        ?..204704ce3137 Virtual Size: 413.9 MB Tags: registry:latest
?..f1b10cd84249 Virtual Size: 0 B
?.?..b9aeeaeb5e17 Virtual Size: 202.6 MB Tags: centos:6
?.  ?..8390535f8e61 Virtual Size: 306.1 MB Tags: centos:new
?.    ?..0570b3aa38fb Virtual Size: 306.1 MB
?.      ?..e099197b794f Virtual Size: 306.1 MB Tags: 172.30.30.241:5000/centos1:latest, centos:new2
?..6941bfcbbfca Virtual Size: 0 B
?.?..41459f052977 Virtual Size: 215.7 MB
?.  ?..fd44297e2ddb Virtual Size: 215.7 MB Tags: centos:latest
?..a8219747be10 Virtual Size: 910 B
  ?..91c95931e552 Virtual Size: 910 B Tags: hello-world:latest我们到graph目录
/var/lib/docker/graph


可以看到graph目录的imageID数目和 docker images -tree看到的相同
进入到每个image ID的目录,每个目录下有两个文件
json  layersize
其中json文件描述了image的元数据,layersize只有一个值,表示了这个层级的image的大小,可以为0

ls /var/lib/docker/graph/
0570b3aa38fbdd1defba6929282656a39cfabcf70dc39e68848139d649d8921b  41459f052977938b824dd011e1f2bec2cb4d133dfc7e1aa0e90f7c5d337ca9c4  a82efea989f94b1d9fac76e26e37b0bbde11047a3afcaa47064949dfa3b3209b
07f8e8c5e66084bef8f848877857537ffe1c47edd01a93af27e7161672ad0e95  9caa77989e25e788a5a75faff4e77b011e68d2fd5975a0bd20f5d14f61154bc0  fd44297e2ddb050ec4fa9752b7a4e3a8439061991886e2091e7c1f007c906d75
3f8e22c413b1783145e785a4729c4d5f98f9baca025b74d73774ed438ac82ba2  a8219747be10611d65b7c693f48e7222c0bf54b5df8467d3f99003611afa1fd8  _tmp

/var/lib/docker/graph/0570b3aa38fbdd1defba6929282656a39cfabcf70dc39e68848139d649d8921b# ls
json  layersize

/var/lib/docker/aufs/diff
可以看到这个目录的所有文件都是ImageID并且与graph目录相对应
根据image tree,进入到某个imageID的0层目录,可能没有数据,再往上,可以看到文件系统的目录,就是我们操作docker生成的文件

进过简单的测试,我们可以发现,
当我们pull 一个images时, /var/lib/docker/aufs/diff 目录下会多一个image ID
如果此时再根据该image创建文件会出现一个容器ID前缀的目录,以及ID相同带有-init结尾的目录
/var/lib/docker/aufs/mnt/这一层是多层diff的view

当我们在容器中操作时,创建的数据会保存在该容器ID的目录中,容器退出时,该目录不会删除。每次 根据image运行一次都会创建新的容器ID的两个目录。docker ps -a可以看到退出状态的容器,执行删除后,这两个目录消失。当我们commit修改后的容器时,又会生成一个新的目录。
docker images tree看到的结构 image从0层到最后一层或者tag,都是直接从hub上pull下来的各种imageID层次。
当容器创建时有VOLUME,或者-v启动,会在目录/var/lib/docker/volumes下创建一个随机ID的目录,并将这个随机ID添加到容器的元数据中
容器启动时,会绑定/var/lib/docker/volumes/*/到指定的路径中,volumes不是运行时绑定的,只是挂载的目录。
在新版本的docker中,docker使用vfs driver存储数据 /var/lib/docker/volumes/只存储元数据,实际数据存储在/var/lib/docker/vfs/dir/<volume id>

每当docker创建一个数据卷时,就会在目录/var/lib/docker/vfs/dir/* 下创建一个随机ID的目录,表示这个数据卷,如果数据卷不是和host共享的,写入数据卷中的数据会在这个目录。 元数据信息在/var/lib/docker/volumes/*中。


总结: 
每当创建一个pull一个新的image或者容器时会在/var/lib/docker/graph/*生成对应ID的目录,存储元数据,/var/lib/docker/aufs/diff/*目录生成对应ID的目录,存储数据。
当容器被删除或者image被移除时,对应的目录也会被移除。

每当docker创建一个数据卷时,就会在目录/var/lib/docker/vfs/dir/* 下创建一个随机ID的目录,表示这个数据卷,如果数据卷不是和host共享的,写入数据卷中的数据会在这个目录。 元数据信息在/var/lib/docker/volumes/*中。
 docker rm -v containerid  当指定-v时,如果卷没有关联容器,会删除该卷的数据。

可能有些情况下,一些异常或者docker rm -v潜在的bug,导致存储空间有残留,这种情况就需要手动清理。

1 对于image或者容器数据文件: docker image -a 以及docker ps -a 看到的ID是所有有效的文件ID,/var/lib/docker/aufs/diff/下的ID不在这个范围中的就是失效的文件。 这种情况情况其实比较少。
2 对于数据卷, 通过docker  inspect 查看所有的容器id会得到所有合法的volume的范围,/var/lib/docker/vfs/dir/不在这个范围内的的ID就是失效文件。这种情况比较多,因为删除容器时可能没有指定-v,在docker低版本中好像有bug,不能删除卷数据。
脚本2来源于:  https://github.com/chadoe/docker-cleanup-volumes/blob/master/docker-cleanup-volumes.sh

对应的脚本分别为:
cat docker_images_clean.sh

#!/bin/bash
dockerdir=/var/lib/docker
imagemetadir=${dockerdir}/aufs/diff
imagedatadir=${dockerdir}/graph
dryrun=false
allvolumes={}
 
allvolumes=`docker images -a | awk '{print $3}'`
allvolumes+=`docker ps -a | awk '{print $1}'`
 
function delete_imagefile() {
  targetdir=$1
  echo
  if [[ ! -d ${targetdir} ]]; then
        echo "Directory ${targetdir} does not exist, skipping."
        return
  fi
  echo "Delete unused image directories from $targetdir"
  echo $targetdir
  dirfile=`ls $targetdir`
  #for dir in $(ls -d ${targetdir}/* 2>/dev/null)
  for dir in $dirfile
  do
        dir=$(basename $dir)
     dir=${dir:0:12}
        if [[ "${dir}" =~ [0-9a-f]{12} ]]; then
                if [[ ${allvolumes[@]} =~ "${dir}" ]]; then
                        echo In use ${dir}
                else
                        if [ "${dryrun}" = false ]; then
                                echo Deleting ${dir}
                                rm -rf ${targetdir}/${dir}
                        else
                                echo Would have deleted ${dir}
                        fi
                fi
        else
                echo Not a volume ${dir}
        fi
  done
}
 
delete_imagefile $imagedatadir
delete_imagefile $imagemetadir

cat docker_volumes_clean.sh

#! /bin/bash
 
set -eo pipefail
 
#usage: sudo ./docker-cleanup-volumes.sh [--dry-run]
 
dockerdir=/var/lib/docker
volumesdir=${dockerdir}/volumes
vfsdir=${dockerdir}/vfs/dir
allvolumes=()
dryrun=false
 
function delete_volumes() {
  targetdir=$1
  echo
  if [[ ! -d ${targetdir} ]]; then
        echo "Directory ${targetdir} does not exist, skipping."
        return
  fi
  echo "Delete unused volume directories from $targetdir"
  for dir in $(ls -d ${targetdir}/* 2>/dev/null)
  do
        dir=$(basename $dir)
        if [[ "${dir}" =~ [0-9a-f]{64} ]]; then
                if [[ ${allvolumes[@]} =~ "${dir}" ]]; then
                        echo In use ${dir}
                else
                        if [ "${dryrun}" = false ]; then
                                echo Deleting ${dir}
                                rm -rf ${targetdir}/${dir}
                        else
                                echo Would have deleted ${dir}
                        fi
                fi
        else
                echo Not a volume ${dir}
        fi
  done
}
 
if [ $UID != 0 ]; then
    echo "You need to be root to use this script."
    exit 1
fi
 
docker_bin=$(which docker.io || which docker)
if [ -z "$docker_bin" ] ; then
    echo "Please install docker. You can install docker by running \"wget -qO- https://get.docker.io/ | sh\"."
    exit 1
fi
 
if [ "$1" = "--dry-run" ]; then
        dryrun=true
else if [ -n "$1" ]; then
        echo "Cleanup docker volumes: remove unused volumes."
        echo "Usage: ${0##*/} [--dry-run]"
        echo "   --dry-run: dry run: display what would get removed."
        exit 1
fi
fi
 
# Make sure that we can talk to docker daemon. If we cannot, we fail here.
docker info >/dev/null
 
#All volumes from all containers
for container in `${docker_bin} ps -a -q --no-trunc`; do
        #add container id to list of volumes, don't think these
        #ever exists in the volumesdir but just to be safe
        allvolumes+=${container}
        #add all volumes from this container to the list of volumes
        for vid in `${docker_bin} inspect --format='{{range $vol, $path := .Volumes}}{{$path}}{{"\n"}}{{end}}' ${container}`; do
                if [[ ${vid} == ${vfsdir}* && "${vid##*/}" =~ [0-9a-f]{64} ]]; then
                        allvolumes+=("${vid##*/}")
                else
                        #check if it's a bindmount, these have a config.json file in the ${volumesdir} but no files in ${vfsdir}
                        for bmv in `grep --include config.json -Rl "\"IsBindMount\":true" ${volumesdir} | xargs grep -l "\"Path\":\"${vid}\""`; do
                                bmv="$(basename "$(dirname "${bmv}")")"
                                allvolumes+=("${bmv}")
                                #there should be only one config for the bindmount, delete any duplicate for the same bindmount.
                                break
                        done
                fi
        done
done
 
delete_volumes ${volumesdir}
delete_volumes ${vfsdir}