首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Docker--DockerFile与镜像

Docker--DockerFile与镜像

作者头像
洋仔聊编程
发布2019-01-15 17:11:56
8540
发布2019-01-15 17:11:56
举报

一:Dockerfile介绍

    Dockerfile是一个用于引导docker镜像生成过程的文件,遵循其特定的语法,我们便可以创建一个自己的镜像。

    Docker在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile ,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile 。并依据该文件的内容来创建定制镜像。镜像的定制实际上就是定制每一层所添加的配置、文件,在Dockerfile 中每一个指令都会建立一层镜像,最后成为一个总的镜像。

    Dockerfile 是一个文本文件,用来配置 image,Docker 根据 该文件生成二进制的 image 文件。


二:Dockerfile语法

2.1:FROM

  • 用法: FROM <image>
  • 说明:定制镜像,那一定是以一个镜像为基础,在其上进行定制。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。(在 Docker Hub 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如nginx 、 redis 、 mongo 、mysql 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 node 、 openjdk 、 python 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。 如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。)
  • 例如
FROM ubuntu15:10

2.2:MAINTAINER

  • 用法: MAINTAINER name
  • 说明:指定镜像创建者的姓名
  • 例如:
MAINTAINER liyangyang

2.3:RUN

  • 用法:RUN  command param1 param2 ...
  • 说明:用来执行命令行命令的指令, 执行完成之后会成为一个新的镜像。一句RUN就是一层,也相当于一个版本。这就是之前说的缓存的原理。我们知道docker是镜像层是只读的,所以你如果第一句安装了软件,用完在后面一句删除是不可能的。所以这种情况要在一句RUN命令中完成,可以通过&符号连接多个RUN语句。
  • 注意:
    • Dockerfile 中每一个指令都会建立一层,就比如每一个 RUN 命令执行后,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像,这样一来很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
    • 我们最好在一组命令的最后添加清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉
    • command是不会调用shell的,所以也不会继承相应变量,要查看变量输入RUN bash -c echo $HOME,而不是RUN echo $HOME
  • 例如:
    • 安装tomcat初始版本
RUN mkdir /var/tmp/tomcat  

RUN wget -P  /var/tmp/tomcat http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz

RUN tar xzf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz -C /var/tmp/tomcat && rm -rf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz
  • 优化:上述这几部完全可以在一层中,没有必要分层,则优化后
RUN mkdir /var/tmp/tomcat \  

    && wget -P  /var/tmp/tomcat http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz \

    && tar -xzf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz -C /var/tmp/tomcat && rm -rf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz

2.4:CMD

  • 用法:
    • shell 格式: CMD <命令>
    • exec 格式: CMD ["可执行文件", "参数1", "参数2"...]
    • 参数列表格式: CMD ["参数1", "参数2"...] 。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
  • 说明:CMD 指令用于指定默认的容器主进程的启动命令的,其作用是在启动容器的时候提供一个默认的命令项。
  • 注意:
    • 在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 " ,而不要使用单引号。
    • 如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:CMD echo $HOME,在实际执行中,会将其变更为:CMD [ "sh", "-c", "echo $HOME" ]。这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。
    • CMD在Dockerfile中只能出现一次,如果有多个,只有最后一个会有效。通常放在dockerfile的最后面。
  • 例如:
    • 设置tomcat随容器的启动而启动
CMD [ "/home/apache-tomcat-7.0.91/bin/catalina.sh", "run" ]

2.5:EXPOSE

  • 用法:EXPOSE <port> [<port>...]
  • 说明:声明运行时容器提供服务端口(容器暴露的端口), 在docker run -p的时候生效。
  • 注意:
    • 在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
    • 要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。 -p ,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
  • 例如:
    • 暴露容器的8080端口给宿主机
EXPOSE 8080

2.6:ENV

  • 用法:
    • ENV <key> <value>
    • ENV <key1>=<value1> <key2>=<value2>...
  • 说明: 设置镜像的环境变量
  • 例如:
    • 定义jdk环境变量
ENV JAVA_HOME /home/jdk1.8.0_181

ENV JRE_HOME $JAVA_HOME/jre

ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib

ENV PATH $PATH:$JAVA_HOME/bin
  • 定义变量,在后面的语句中使用
ENV JDK_VERSION  8u-91   //定义了JDK_VERSION变量

RUN echo JDK_VERSION  //使用

2.7:COPY

  • 用法: COPY <源路径> <目标路径>
  • 说明: 复制本机文件或目录或远程文件,添加到指定的容器目录,支持GO的正则模糊匹配。路径是绝对路径,不存在会自动创建。如果源是一个目录,只会复制目录下的内容,目录本身不会复制。
  • 注意:
    • <源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则。 <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
    • 使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git进行管理的时候。
  • 例如:
COPY    hom*        /mydir/

COPY    hom?.txt  /mydir/

COPY    hom*       ./mydir/

2.8:ADD

  • 用法:ADD <源路径> <目标路径>
  • 说明:ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。比如 <源路径> 可以是一个 URL ,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600 ,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整
  • 注意:
    • 如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip , bzip2 以及 xz 的情况下, ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
    • 在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY ,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
  • 例如:
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /

2.9:ENTRYPOINT

  • 用法: ENTRYPOINT command param1 param2 ...
  • 说明:ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 –entrypoint 来指定。
  • 注意:
    • 当指定了 ENTRYPOINT 后, CMD 的含义就发生了改变,不再是直接的运行其命令,而是将CMD 的内容作为参数传给 ENTRYPOINT 指令。
  • 例如:
    • 启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 )作为命令,在脚本最后执行。比如官方镜像 redis 中就是这么做的:
FROM alpine:3.4 
... 
RUN addgroup -S redis && adduser -S -G redis redis 
... 
ENTRYPOINT ["docker-entrypoint.sh"] 
EXPOSE 6379 
CMD [ "redis-server" ]   #此处的CMD的作为参数传入ENTRYPOINT所执行的脚本中

2.10:VOLUME

  • 用法:
    • VOLUME ["<路径1>", "<路径2>"...]
    • VOLUME <路径>
  • 说明:定义匿名卷,在主机上创建一个挂载,挂载到容器的指定路径。容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
  • 案例:
    • 将/data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。
VOLUME /data
  • 当然,运行时可以覆盖这个挂载设置。比如:
docker run -d -v mydata:/data xxxx
  • 在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

2.11:USER

  • 用法:USER <用户名>
  • 说明:USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。 WORKDIR 是改变工作目录, USER 则是改变之后层的执行 RUN , CMD 以及 ENTRYPOINT 这类命令的身份。
  • 注意:
    • USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
  • 案例:
    • 切换用户为lyy
USER lyy

2.12:WORKDIR

  • 用法:WORKDIR <工作目录路径> 
  • 说明:用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在, WORKDIR 会帮你建立目录。
  • 注意:
    • RUN cd /app 并不会将当前目录切换到app目录下,原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。
    • 如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。
  • 案例:
    • 切换当前目录到/home/lyy下,使后面的命令都在该目录下执行
WORKDIR /home/lyy

2.13:ONBUILD 

  • 用法:ONBUILD <其它指令>
  • 说明:ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
  • 注意:
    • 当基础镜像变化后,使用ONBUILD的指令在各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新,就不必去更改所有的子镜像了。
  • 案例:
    • dockerfile部分:
FROM node:slim 

RUN mkdir /app 

WORKDIR /app 

ONBUILD COPY ./package.json /app 
ONBUILD RUN [ "npm", "install" ] 
ONBUILD COPY . /app/ 

CMD [ "npm", "start" ]
  • 在构建基础镜像的时候,ONBUILD这三行并不会被执行,只有以该镜像为基础镜像创建镜像时才会执行。

三:构建镜像

  • 命令:docker build [OPTIONS] PATH | URL
  • 常用参数:
-f :指定要使用的Dockerfile路径;
-m :设置内存最大值;
--no-cache :创建镜像的过程不使用缓存;
--pull :尝试去更新镜像的新版本;
--quiet, -q :安静模式,成功后只输出镜像 ID,不会输出其log在显示屏;
--rm :设置镜像成功后删除中间容器;
-tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
--network: 默认 default。在构建期间设置RUN指令的网络模式
  • 说明:根据PATH | URL 中获取Dockerfile来创建镜像,当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、 ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。
  • 注意:
    • docker build 的工作原理:Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 DockerRemote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
    • docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。
    • 当构建的时候,用户会指定构建镜像上下文的路径也就是PATH | URL  部分, docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。所以, 一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore ,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
  • 镜像创建过程:
    • 容器镜像包括元数据和文件系统,其中文件系统是指对基础镜像的文件系统的修改,元数据不影响文件系统,只是会影响容器的配置
    • 每个步骤都会生成一个新的镜像,新的镜像与上一次的镜像相比,要么元数据有了变化,要么文件系统有了变化而多加了一层
    • Docker 在需要执行指令时通过创建临时镜像,运行指定的命令,再通过 docker commit 来生成新的镜像
    • Docker 会将中间镜像都保存在缓存中,这样将来如果能直接使用的话就不需要再从头创建了。
  • 镜像分层与容器层:
    • 一个 Docker 镜像是基于基础镜像的多层叠加,最终构成和容器的 rootfs (根文件系统)。
    • 当 Docker 创建一个容器时,它会在基础镜像的容器层之上添加一层新的薄薄的可写容器层。接下来,所有对容器的变化,比如写新的文件,修改已有文件和删除文件,都只会作用在这个容器层之中。因此,通过不拷贝完整的 rootfs,Docker 减少了容器所占用的空间,以及减少了容器启动所需时间。
    • 镜像与容器图如下:
  • COW 和镜像大小
    • COW,copy-on-write 技术,一方面带来了容器启动的快捷,另一方也造成了容器镜像大小的增加。
    • 每一次 RUN 命令都会在镜像上增加一层,每一层都会占用磁盘空间。举个例子,在 Ubuntu 14.04 基础镜像中运行 RUN apt-get upgrade 会在保留基础层的同时再创建一个新层来放所有新的文件,而不是修改老的文件,因此,新的镜像大小会超过直接在老的文件系统上做更新时的文件大小。
    • 因此,为了减少镜像大小起见,所有文件相关的操作,比如删除,释放和移动等,都需要尽可能地放在一个 RUN 指令中进行。
  • 例如:
    • 使用当前目录下的 Dockerfile 创建镜像,标签为 xcardata/ubuntu:v1.0
docker build -txcardata/ubuntu:v1.0  .     #注意后面的空格和点,代表当前目录
#后台方式执行
nohup  docker build -t newxcardata/centos:v1.0 .  >  ./build.log  2>&1  &
docker build github.com/creack/docker-firefox
  • 通过 -f 指定Dockerfile 文件的位置创建镜像
docker build -f  /path/Dockerfile  . 

四:其他

4.1:容器中应用在前台执行和后台执行的问题?

    Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。

    初学者一般将 CMD 写为:  

CMD service nginx start

    然后发现容器执行后就立即退出了。甚至在容器内去使用 systemctl 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。

 对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。

 而使用 service nginx start 命令,则是希望 systemd 来以后台守护进程形式启动 nginx 服务。而刚才说了 CMD service nginx start 会被理解为 CMD [ “sh”, “-c”, “service nginxstart”] ,因此主进程实际上是 sh 。那么当 service nginx start 命令结束后, sh 也就结束了, sh 作为主进程退出了,自然就会令容器退出。

 正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:

CMD ["nginx", "-g", "daemon off;"]

4.2:Dockerfile案例(基于centos构建jdk+tomcat镜像)

#使用的基础镜像
FROM centos  
#不指定版本tag,则默认pull最新的

#创建者信息
MAINTAINER <liyangyang> <test@163.com>

#安装wget
RUN yum install -y wget

#安装JDK
RUN mkdir /var/tmp/jdk  \
    && wget --no-cookies --no-check-certificate --header "Cookie: oraclelicense=accept-securebackup-cookie" -P /var/tmp/jdk  http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz   \
    && tar -zxf /var/tmp/jdk/jdk-8u181-linux-x64.tar.gz -C /var/tmp/jdk \
    && rm -rf /var/tmp/jdk/jdk-8u111-linux-x64.tar.gz   

#配置JDK环境变量
ENV JAVA_HOME /var/tmp/jdk/jdk1.8.0_181
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib
ENV PATH $PATH:$JAVA_HOME/bin

#安装Tomcat
RUN mkdir /var/tmp/tomcat \  
     && wget -P  /var/tmp/tomcat http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz \
     && tar -xzf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz -C /var/tmp/tomcat  \
     && rm -rf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz

#配置Tomcat环境变量
ENV CATALINA_HOME /var/tmp/tomcat/apache-tomcat-7.0.91

#指定容器暴露端口
EXPOSE 8080

#添加项目
ADD xcar-index-web.war /var/tmp/tomcat/apache-tomcat-7.0.91/webapps/

#创建容器时默认启动tomcat
CMD ["/var/tmp/tomcat/apache-tomcat-7.0.91/bin/catalina.sh", "run"]

4.3:使用容器需要避免的一些做法

  • * 不要在容器中保存数据(Don’t store data in containers)
  • * 将应用打包到镜像再部署而不是更新到已有容器(Don’t ship your application in two pieces)
  • * 不要产生过大的镜像 (Don’t create large images)
  • * 不要使用单层镜像 (Don’t use a single layer image)
  • * 不要从运行着的容器上产生镜像 (Don’t create images from running containers )
  • * 不要只是使用 “latest”标签 (Don’t use only the “latest” tag)
  • * 不要在容器内运行超过一个的进程 (Don’t run more than one process in a single container )
  • * 不要在容器内保存 credentials,而是要从外面通过环境变量传入 ( Don’t store credentials in the image. Use environment variables)
  • * 不要使用 root 用户跑容器进程(Don’t run processes as a root user )
  • * 不要依赖于IP地址,而是要从外面通过环境变量传入 (Don’t rely on IP addresses )

4.4:docker镜像的组成

  • * docker 镜像中主要就是 tar 文件包和元数据 json 文件
  • * docker 镜像的打包过程,其实就是将每一层对应的文件打包过程,最后组成一个单一的 tar 文件
  • * docker 镜像的使用过程,其实就是将一层层的 tar 文件接包到文件系统的过程

参考:https://www.cnblogs.com/lighten/p/6900556.html

http://www.runoob.com/docker/docker-build-command.html

https://blog.csdn.net/wo18237095579/article/details/80540571

https://www.cnblogs.com/sammyliu/p/5877964.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年10月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器镜像服务
容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档