代码改变世界

Docker学习3--镜像和仓库

2016-05-08 17:25  Qin奋  阅读(341)  评论(0)    收藏  举报

1.Docker的镜像概念

 Docker镜像是由文件系统叠加而成,最底端是一个引导文件系统,Docker镜像的第二层是root文件系统,即bootfs,位于引导文件系统之上。

   bootfs可以是一种或多种操作系统。这很像典型的Linux/Unix的引导文件系统。(实际上一个容器启动后,它会被移到内存中,而引导文件系统则会被卸载(unmount),以留出更多的内存供initrd磁盘镜像使用。)

   在传统的Linux引导过程中,root文件系统会最先以只读的方式加载,当引导结束并完成了完整性检查之后,它才会被切换为读写模式。

   但是在Docker里,root文件系统永远只能是只读状态,并且Docker利用联合加载 union mount 技术又会在root文件系统层上加载更多的只读文件系统。

   联合加载指的是一次同时加载多个文件系统,但是在外面看起来只能看到一个文件系统。联合加载会将各层文件系统叠加到一起,这样最终的文件系统会

   包含所有底层的文件和目录。

   Docker将这样的文件系统统称为镜像。一个镜像可以放到另一个镜像的顶部。位于下面的镜像叫父镜像parent image,以此类推直到镜像栈的最底部,最底部镜像成为base image.

   当一个镜像启动容器时,Docker会在该镜像的最顶层加载一个读写文件系统。我们在Docker中运行的应用程序就是在这个读写层中执行的。

   写时复制(copy on write) 是使Docker如此强大的技术之一。 要想理解Docker的镜像原理,需要对操作系统,和linux内核了解熟悉

  

2.Docker 镜像相关命令

    2.1. docker images

    列出镜像

    docker images ubuntu 按照名称显示

        本地镜像都保存在 宿主机的 /var/lib/docker目录下。每个镜像都保存在Docker所采用的存储驱动目录下面,如aufs或者devicemapper.

        所有的容器都保存在/var/lib/docker/container下面

     2.2. 镜像仓库 Registry。Docker公司运营的公共Registry服务叫 Docker Hub。镜像是保存在仓库中的,包括镜像,层以及镜像的metadata

         一个镜像在仓库中可以有多个版本(同一个镜像名称)。Docker提供tag来标记版本或者代号,我们docker images 能看到同一个image ID 但不同tag

        2.2.1.docker pull ubuntu: 按名称(从默认仓库Docker Hub)中拉取镜像。 结果是N个版本的镜像都被拉取下来了。

    docker run -i -t --name new_container ubuntu  /bin/bash  默认拉取latest版本的镜像

        2.2.2.docker pull ubuntu:12.04

           docker run -i -t --name new_container ubuntu:12.04 /bin/bash

           指定版本拉取镜像

        2.2.3.Docker Hub中有两种类型的仓库:用户仓库(user repository)和 顶层仓库(top repository)。用户仓库的镜像都是有Docker用户创建的,而顶层仓库则是由Docker内部的人来管理的

           用户仓库的命令由用户名和仓库名两部分组成,如 jack/puppet. 用户名:jack  仓库名:puppet

   

3.构建Docker镜像

   3.1 docker commit 

     构建镜像 (非主流)

            (一般说来,我们不是正在“创建”新镜像,而是基于一个已有的基础镜像,如ubuntu或fedora等,构建新镜像而已。真正从零构建一个全新的镜像 https://docs.docker.com/articles/baseimages/)             (案列:

      [root@iZ236vrgbw9Z containers]# docker login
      Username: jq82953
      Password:
      Email: qinjiayan@buaa.edu.cn
      WARNING: login credentials saved in /root/.docker/config.json
      Login Succeeded

      root@iZ236vrgbw9Z containers]# docker run -i -t --name container001 ubuntu /bin/bash

      root@1ff5efb563c8:/# apt-get -y install apache2

      [root@iZ236vrgbw9Z containers]# docker commit 1ff5efb563c8 coconut_repo/apache2
      bc343a5dcd089efd61f0ec8b63273e3a569426217e5cc6e7cbecc3d4cfcf5074
      [root@iZ236vrgbw9Z containers]#

      [root@iZ236vrgbw9Z containers]# docker images
      REPOSITORY                     TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
      coconut_repo/apache2      latest              bc343a5dcd08        3 minutes ago       206.5 MB
      job1                                latest              a3e7568f348d        3 weeks ago         1.113 MB
      ubuntu                            14.04               41cc538fb83a        3 weeks ago         187.9 MB

      [root@iZ236vrgbw9Z containers]# docker commit -m="A new custom image" --author="Coconut Man" \1ff5efb563c8 coconut_repo/apache2:webserver
      c575324738a5cb5a7535209116e409a3ed2c6ec535d72a03492493ad1d926a67
      [root@iZ236vrgbw9Z containers]#

      [root@iZ236vrgbw9Z containers]# docker images
      REPOSITORY                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
      coconut_repo/apache2             webserver           c575324738a5        31 seconds ago      206.5 MB
      coconut_repo/apache2             latest              bc343a5dcd08        8 minutes ago       206.5 MB

     )

   3.2 用Dockerfile 和 docker build来构建镜像

    3.2.1 docker build -t="coconut_repo/static_web" .
         -t 为新镜像设置了仓库和名称。 仓库名称为coconut_repo,镜像名称为:static_web. "." 表示从当前目录下加载Dockerfile

       docker build -t="coconut_repo/static_web:v1" .

       为新镜像指定标签

         docker build -t="coconut_repo/static_web" \git@github.com:jamtur01/docker-static_web

       指定一个Git仓库的原地址作为Dockerfile的位置。这里前提是Docker在这个Git仓库的根目录下存在Dockerfile文件

    3.2.2 Dockerfile案列

    (# Version: 0.0.1

    FROM ubuntu:14.04
    MAINTAINER Jiayan Qin "qinjiayan@buaa.edu.cn"
    RUN apt-get update
    RUN apt-get install -y nginx
    RUN echo 'Hi, I am in your container' \>/usr/share/nginx/html/index.html
    EXPOSE 80)

           FROM指令指定一个已经存在的镜像,后续指令都将基于该镜像进行,该镜像成为base image. 每个Dockerfile的第一条指令都应该是FROM.

    MAINTAINER镜像的创建者信息

    此后的三条RUN 指令会在当前镜像中运行指定的命令。

    EXPOSE告诉Docker 该容器内的应用程序将会使用容器的指定端口。但是出于安全考虑,要能访问容器的这个端口,还要使用docker run创建运行容器时指定打开这些端口。Docker并不会默认打开他们。

    3.2.3 docker build 输出案列

    [root@iZ236vrgbw9Z static_web]# docker build -t="coconut_repo/static-web:v1" .
    Message from syslogd@iZ236vrgbw9Z at May  4 20:41:49 ...
     kernel:unregister_netdevice: waiting for lo to become free. Usage count = 2
    Sending build context to Docker daemon 2.048 kB
    Sending build context to Docker daemon
    Step 0 : FROM ubuntu:14.04
     ---> 41cc538fb83a
    Step 1 : MAINTAINER Jiayan Qin "qinjiayan@buaa.edu.cn"
     ---> Using cache
     ---> 19400e6a67dc
    Step 2 : RUN apt-get install -y nginx
     ---> Running in 3fb40e0aa47f
    Reading package lists...
    Building dependency tree...
    Reading state information...
     The following extra packages will be installed:
     fontconfig-config fonts-dejavu-core geoip-database libfontconfig1
     Dockerfile中的每条指令都会被顺序执行,而且作为构建过程的最终结果,返回了新镜像的ID。构建的每一步机器对应的指令都会独立运行,并且在输出最终镜像ID之前,Docker会提交每步的构建结果。

    3.2.4 Dockerfile 运行中的某一步失败(如 apt-get install -y ngin)

    解决方法: Docker run -t -i 容器ID(已经成功的最后一个创建的容器) /bin/bash

          然后再在容器中运行一次正确的命令:apt-get install -y nginx

    3.2.5 docker build --no-cache -t="repository/image_name" 

    在build的过程中不缓存,也不用之前的缓存。 默认dockerfile中每一个RUN的结果都会被缓存。

    3.3.6 docker run -d -p 80 --name static_web coconut_repo/static-web:v1 \nginx -g "daemon off;"

    -d 告诉Docker以分离 detached 的方式在后台运行。这种方式适合运行类似Nginx守护进程这样的需要长时间运行的进程。

    \nginx -g "daemon off;" 将以前台运行的方式启动Nginx,来作为Web服务器。

    -p 用来控制Docker(容器)在运行时应该公开那些网络端口给外部(宿主机)。

    (Docker可以通过两种方法在宿主机上分配端口:1.(Docker自动)在宿主机选择一个49000~49900的一个大端口号来映射到容器的80端口。2.(我们)在Docker宿主机指定一个具体端口映射容器80端口)

    3.2.7 docker run -d -p 8080:80 --name static_web coconut_repo/static-web:v1 \nginx -g "daemon off;" 

     将容器的80端口绑定到宿主机的8080端口

    3.2.8 docker run -d -p 127.0.0.0:80:80 --name static_web coconut_repo/static-web:v1 \nginx -g "daemon off;"

     将容器的80端口绑定到宿主机的127.0.0.1这个IP的80端口

      3.2.9 docker run -d -p 127.0.0.0::80 --name static_web coconut_repo/static-web:v1 \nginx -g "daemon off;"

     将容器的80端口绑定到宿主机的127.0.0.1这个IP的一个随机端口  

    3.2.10 docker run -d -P --name static_web coconut_repo/static-web:v1 \nginx -g "daemon off;"     

    将容器的80端口对宿主机公开并绑定到一个随机端口。-P同时会将Dockerfile中EXPOSE的其他端口一并公开

    关于端口:https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/#network-port-mapping/refresher

  3.3 Dockerfile 指令

    (Dockerfile定义的要运行的命令放在一个数组结构,运行CMD+参数 的时候其实会被转换为 "/bin/sh -c"+参数)

    3.3.1 CMD ["/bin/true"]

      CMD 指令用于指定一个容器启动时要运行的命令。作用等价于 docker run -i -t container_name /bin/true

      CMD  与 RUN 类似, RUN是指定镜像被构建时要运行的命令

      docker run 命令会覆盖CMD指令。也就是前者优先级比较高。

    3.3.2 ENTRYPOINT ["/usr/sbin/nginx"]

      类似于CMD,也是让容器在启动时执行某个命令。但是不会被docker run覆盖,而且docker  run 的参数会被传给ENTRYPOIN。

      比如Dockerfile中定义了ENTRYPOINT ["/usr/sbin/nginx"], 创建容器时docker run -it some_container -g "daemon off"。容器启动时则会运行ENTRYPOINT ["/usr/sbin/nginx","-g","daemon off"]

      确实需要的话--entrypoint参数可以覆盖ENTRYPOINT

    3.3.3 WORKDIR /opt/webapp/db

        RUN bundle install 

        WORKDIR /opt/webapp/

        ENTRYPOINT [ "RACKUP" ]

        WORKDIR指令用来从镜像创建容器时,在容器内部设置一个工作目录,CMD或者ENTRYPOINT指定的程序会在这个目录下执行。

       以上的列子,ENTRYPOINT [ "RACKUP" ]最终会在/opt/webapp/中执行。WORKDIR可以重置指定的执行目录。

       通过-w能覆盖工作目录。 例:docker run -it -w /var/log ubuntu pwd /var/log 该命令将容器的工作目录社会为/var/log

    3.3.4 ENV RVM_PATH /home/rvm/

      ENV指令用来在镜像构建过程中设置环境变量。设置的变量可以在后续任何的RUN中使用 RUN gem install unicorn ==>转换成 RVM_PATH =/home/rvm/ gem install unicorn执行(不是很理解??)

      其他指令也可以声明使用。 如:1.ENV TARGET_DIR /opt/app/;  2.WORKDIR $ARGET_DIR

      -e可以在docker run的时候传递环境变量。如:docker -it -e "TARGET_DIR= /opt/app/" somecontainer 

     3.3.5 USER nginx;  USER user;  USER user:group;  USER uid;  USE user:gid;  USE user:group;

      USER指令用来指定镜像以什么样的用户去运行

      -u 可以在运行容器时 docker run -u 来覆盖

     3.3.6 VOLUME ["/opt/project"];  VOLUME ["/opt/project", "/data"]

        VOLUME指令用来向基于镜像创建的容器添加卷 一个卷是可以存在一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据

       进行持久化的功能:卷可以在容器间共享和重用; 一个容器可以不是必须和其他容器共享卷; 对卷的修改是即时生效的且痐对更新镜像产生影响; 卷会一直存在直到没有容器再使用它;

        卷功能让我们可以将数据(如源代码),数据库或者其他内容添加到镜像中而不是提交到容器中,并且允许我们在多个容器中共享这些内容。我们可以

        用此功能来测试容器和内部的应用城西代码,管理日志,或者处理容器内部的数据库

           关于卷:https://docs.docker.com/engine/userguide/dockervolumes/

    3.3.7 ADD software.lic  /opt/application/software.lic

        ADD指令用来将构建环境下的文件或者目标复制到镜像中。上面的命令会将构建目录下的software.lic文件复制到镜像中的/opt/application/software.lic.

        源目录或者文件必须在构建目录下或者Dockfile上下文中。

       ADD http://wordpress.org/latest.zip  /root/worldpress.zip

       源参数还可以是一个URL

       ADD latest.tar.gz  /war/www/wordpress

                  如果源是归档文件(压缩文件),ADD命令会将归档文件latest.tar.gz解压到目标地址/war/www/wordpress下。效果和 tar -x命令一样,如果目标地址下已经有同名文件,不会覆盖。

      ADD 指令会让构建缓存变得无效。

    3.3.8 COPY conf.d/ /etc/apache2

      COPY指令非常类似于ADD,但COPY只复制遇到归档文件不解压。文件源路径必须是一个与当前构建环境相对的文件或者目录,也就是源文件放到Dockerfile同一目录下,不能复制该文件之外的文件。

      因为构建环境会上传到Docker守护进程,而COPY动作是在守护进程中进行。所以构建环境之外的东西都不可见。COPY指令的的美乐迪位置必须是容器内部的一个绝对路径。

     3.3.9 ONBILD ADD . /app/src

          ONBUILD 指令能为镜像添加触发器trigger。当一个镜像被用作凄然镜像的基础镜像时,该镜像中的触发器将会被触发。且是在新镜像的FROM指令之后执行ONBILD指令的内容。隔代继承镜像,基础镜像ONBIULD会触发。

4. 关于卷  VOLUME(p98)

  3.3.6提到了卷,卷在Docker里非常重要,也很有用。 卷是在一个或者多个容器内被选定的目录,可以绕过Union File System, 为Docker提供持久数据或者共享数据。

    这意味着对卷的修改会直接生效,并绕过镜像。当提交或者创建镜像时,卷不被包含在镜像里。

     卷可以在容器间共享。即使容器停止,卷里的内容依旧存在。

    卷的应用场景:

      希望同时对代码做开发和测试;

      代码改动很频繁,不想在开发工程中重构镜像

      希望在多个容器中共享代码

    docker run -d -p 80 --name website \-v $PWD/website:/var/www/html/website \jiayan_prac01/nginx nginx

    -v 这个参数执行了卷的源目录(本地宿主机的目录)和容器里的目录,这两个目录通过:来分隔。如果目的目录不存在,Docker会自己创建一个。

    rw 和 ro 参数可以用来指定目的目录的读写状态,如:docker run -d -p 80 --name website \-v $PWD/website:/var/www/html/website:ro \jiayan_prac01/nginx nginx

    这将使目的目录:/var/www/html/website 变成只读状态。