当前位置:   article > 正文

Docker_设置及用法基本与旧版相同,不

设置及用法基本与旧版相同,不

前言:

这篇文章是我的同事写的,让他给我发了一份,感觉很高大上,就分享出来吧。
在此感谢 zhenglinhai 同志写这么棒的文章。

什么是 Docker

Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新,并于 2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。Docker 项目后来还加入了 Linux 基金会,并成立推动开放容器联盟(OCI)。

Docker 自开源后受到广泛的关注和讨论,至今其 GitHub 项目已经超过 4 万 6 千个星标和一万多个 fork。甚至由于 Docker 项目的火爆,在 2013 年底,dotCloud 公司决定改名为Docker。Docker 最初是在 Ubuntu 12.04 上开发实现的;Red Hat 则从 RHEL 6.5 开始对Docker 进行支持;Google 也在其 PaaS 产品中广泛应用 Docker。

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC,转而使用自行开发的libcontainer,从 1.11开始,则进一步演进为使用 runC 和 containerd。

Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker技术比虚拟机技术更为轻便、快捷。

传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。


为什么要使用 Docker?

作为一种新兴的虚拟化方式,Docker跟传统的虚拟化方式相比具有众多的优势。

更高效的利用系统资源

由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。

更快速的启动时间

传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。

一致的运行环境

开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 「这段代码在我机器上没问题啊」 这类问题。

持续交付和部署

对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(ContinuousDelivery/Deployment) 系统进行自动部署。

而且使用 Dockerfile使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。

更轻松的迁移

由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docke可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

更轻松的维护和扩展

Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。

对比传统虚拟机总结

特性容器虚拟机
启动秒级分钟级
硬盘使用一般为 MB一般为GB
性能接近原生弱于
系统支持量单机支持上千个容器一般几十个

基本概念

Docker 包括三个基本概念

  • 镜像( Image )
  • 容器( Container )
  • 仓库( Repository )

理解了这三个概念,就理解了 Docker 的整个生命周期
建议大家去Google了解一下Docker的这三个概念,这里简单介绍一下

镜像

操作系统分为内核和用户空间,对于 Linux 而言,内核启动后,会挂载 root文件系统为其提供用户空间支持。而Docker镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu 16.04 最小系统的 root 文件系统。

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

容器

镜像( Image )和容器( Container)的关系,就像是面向对象程序设计中的 类 和 实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。

容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人学Docker时常常会混淆容器和虚拟机。

Docker Registry

镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。

一个 Docker Registry 中可以包含多个仓库( Repository );每个仓库可以包含多个标签( Tag );每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。


安装Docker

Docker 划分为 CE 和 EE。CE 即社区版(免费,支持周期三个月),EE 即企业版,强调安全,付费使用。

Docker CE 每月发布一个 Edge 版本 (17.03, 17.04, 17.05…),每三个月发布一个 Stable 版本(17.03, 17.06, 17.09…),Docker EE 和 Stable 版本号保持一致,但每个版本提供一年维护。

这里主要介绍 Docker CE 在 Linux 、Windows 10(PC) 和 MacOS 上的安装。

Ubuntu 安装 Docker CE

系统要求

Docker CE 支持以下版本的 Ubuntu 操作系统:

  • Artful 17.10 (Docker CE 17.11 Edge +)
  • Xenial 16.04 (LTS)
  • Trusty 14.04 (LTS)

Docker CE 可以安装在 64 位的 x86 平台或 ARM 平台上。Ubuntu 发行版中,LTS(LongTerm-Support)长期支持版本,会获得5年的升级维护支持,这样的版本会更稳定,因此在生产环境中推荐使用 LTS版本,当前最新的 LTS 版本为 Ubuntu 16.04。

卸载旧版本

旧版本的 Docker 称为 docker 或者 docker-engine ,使用以下命令卸载旧版本:

$ sudo apt-get remove docker docker-engine docker.io
  • 1

Ubuntu 14.04 可选内核模块

从 Ubuntu 14.04 开始,一部分内核模块移到了可选内核模块包 ( linux-image-extra-* ) ,以减少内核软件包的体积。正常安装的系统应该会包含可选内核模块包,而一些裁剪后的系统可能会将其精简掉。 AUFS内核驱动属于可选内核模块的一部分,作为推荐的 Docker 存储层驱动,一般建议安装可选内核模块包以使用 AUFS 。

如果系统没有安装可选内核模块的话,可以执行下面的命令来安装可选内核模块包:

$ sudo apt-get update

$ sudo apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual
  • 1
  • 2
  • 3

Ubuntu 16.04 +

Ubuntu 16.04 + 上的 Docker CE 默认使用 overlay2 存储层驱动,无需手动配置。

使用 APT 安装

由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用HTTPS 传输的软件包以及 CA 证书。

$ sudo apt-get update

$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
  • 1
  • 2
  • 3

鉴于国内网络问题,强烈建议使用国内源
为了确认所下载软件包的合法性,需要添加软件源的 GPG 密钥。

$ curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
  • 1

官方源

    $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

      然后,我们需要向 source.list 中添加 Docker 软件源

      $ sudo add-apt-repository "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
      
      • 1

      官方源

        $ sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable”

          以上命令会添加稳定版本的 Docker CE APT 镜像源,如果需要最新或者测试版本的Docker CE 请将 stable 改为 edge 或者 test。从 Docker 17.06 开始,edge test 版本的APT 镜像源也会包含稳定版本的 Docker。

          安装 Docker CE

          更新 apt 软件包缓存,并安装 docker-ce :

          $ sudo apt-get update
          
          $ sudo apt-get install docker-ce
          
          • 1
          • 2
          • 3

          使用脚本自动安装

          在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Ubuntu系统上可以使用这套脚本安装:

          $ curl -fsSL get.docker.com -o get-docker.sh
          
          $ sudo sh get-docker.sh --mirror Aliyun
          
          • 1
          • 2
          • 3

          执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker CE 的 Edge 版本安装在系统中。

          启动 Docker CE

          $ sudo systemctl enable docke
          
          $ sudo systemctl start docker
          
          • 1
          • 2
          • 3

          Ubuntu 14.04 请使用以下命令启动:
          $ sudo service docker start

          建立 docker 用户组

          默认情况下, docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker用户组。

          建立 docker 组:

          $ sudo groupadd docker
          
          • 1

          将当前用户加入 docker 组:

          $ sudo usermod -aG docker $USER
          
          • 1

          退出当前终端并重新登录,进行如下测试。

          测试 Docker 是否安装正确

          $ docker run hello-world
          
          Unable to find image 'hello-world:latest' locally
          latest: Pulling from library/hello-world
          ca4f61b1923c: Pull complete
          Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
          Status: Downloaded newer image for hello-world:latest
          Hello from Docker!
          This message shows that your installation appears to be working correctly.
          To generate this message, Docker took the following steps:
          1. The Docker client contacted the Docker daemon.
          2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
          (amd64)
          3. The Docker daemon created a new container from that image which runs the
          executable that produces the output you are currently reading.
          4. The Docker daemon streamed that output to the Docker client, which sent it
          to your terminal.
          To try something more ambitious, you can run an Ubuntu container with:
          $ docker run -it ubuntu bash
          Share images, automate workflows, and more with a free Docker ID:
          https://cloud.docker.com/
          For more examples and ideas, visit:
          https://docs.docker.com/engine/userguide/
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23

          若能正常输出以上信息,则说明安装成功。

          镜像加速
          鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,强烈建议安装 Docker 之后配置镜像加速器

          参考文档
          Docker 官方 Ubuntu 安装文档


          Debian 安装 Docker CE

          系统要求

          Docker CE 支持以下版本的 Debian 操作系统:

          • Buster 10 (Docker CE 17.11 Edge +)
          • Stretch 9
          • Jessie 8 (LTS)
          • Wheezy 7.7 (LTS)

          卸载旧版本

          $ sudo apt-get remove docker docker-engine docker.io
          
          • 1

          Debian 7 Wheezy

          Debian 7 的内核默认为 3.2,为了满足 Docker CE 的需求,应该安装 backports 的内核。

          使用 APT 安装

          由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此,我们首先需要添加使用HTTPS 传输的软件包以及 CA 证书。

          Debian 8 Jessie 或者 Debian 9 Stretch 使用以下命令:

          $ sudo apt-get updat
          
          $ sudo apt-get install apt-transport-https ca-certificates curl gnupg2 lsb-release software-properties-common
          
          • 1
          • 2
          • 3

          Debian 7 Wheezy 使用以下命令:

          $ sudo apt-get update
          
          $ sudo apt-get install apt-transport-https ca-certificates curl lsb-release python-software-properties
          
          • 1
          • 2
          • 3

          鉴于国内网络问题,强烈建议使用国内源
          为了确认所下载软件包的合法性,需要添加软件源的 GPG 密钥。

          $ curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/debian/gpg | sudo apt-key add -
          
          • 1

          官方源

            $ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

              然后,我们需要向 source.list 中添加 Docker CE 软件源:

              $ sudo add-apt-repository "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/debian \
              $(lsb_release -cs) stable"
              
              • 1
              • 2

              官方源

                $ sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable”

                  以上命令会添加稳定版本的 Docker CE APT 源,如果需要最新或者测试版本的 Docker CE 请将 stable 改为 edge 或者 test。从 Docker 17.06 开始,edge test 版本的 APT 源也会包含稳定版本的 Docker CE。

                  Debian 7 需要进行额外的操作:
                  编辑 /etc/apt/sources.list 将 deb-src 一行删除或者使用 # 注释。

                  $ deb-src [arch=amd64] https://download.docker.com/linux/debian wheezy stable
                  
                  • 1

                  安装 Docker CE

                  更新 apt 软件包缓存,并安装 docker-ce 。

                  $ sudo apt-get update
                  
                  $ sudo apt-get install docker-ce
                  
                  • 1
                  • 2
                  • 3

                  使用脚本自动安装

                  在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,Debian系统上可以使用这套脚本安装:

                  $ curl -fsSL get.docker.com -o get-docker.sh
                  
                  $ sudo sh get-docker.sh --mirror Aliyun
                  
                  • 1
                  • 2
                  • 3

                  执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker CE 的 Edge 版本安装在系统中。

                  启动 Docker CE

                  $ sudo systemctl enable docker
                  
                  $ sudo systemctl start docker
                  
                  • 1
                  • 2
                  • 3

                  Debian 7 Wheezy 请使用以下命令启动

                  $ sudo service docker start
                  
                  • 1

                  建立 docker 用户组

                  默认情况下, docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker用户组。

                  建立 docker 组:

                  $ sudo groupadd docker
                  
                  • 1

                  将当前用户加入 docker 组:

                  $ sudo usermod -aG docker $USER
                  
                  • 1

                  退出当前终端并重新登录,进行如下测试。

                  测试 Docker 是否安装正确

                  $ docker run hello-world
                  Unable to find image 'hello-world:latest' locally
                  latest: Pulling from library/hello-world
                  ca4f61b1923c: Pull complete
                  Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
                  Status: Downloaded newer image for hello-world:latest
                  Hello from Docker!
                  This message shows that your installation appears to be working correctly.
                  To generate this message, Docker took the following steps:
                  1. The Docker client contacted the Docker daemon.
                  2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
                  (amd64)
                  3. The Docker daemon created a new container from that image which runs the
                  executable that produces the output you are currently reading.
                  4. The Docker daemon streamed that output to the Docker client, which sent it
                  to your terminal.
                  To try something more ambitious, you can run an Ubuntu container with:
                  $ docker run -it ubuntu bash
                  Share images, automate workflows, and more with a free Docker ID:
                  https://cloud.docker.com/
                  For more examples and ideas, visit:
                  https://docs.docker.com/engine/userguide/
                  
                  • 1
                  • 2
                  • 3
                  • 4
                  • 5
                  • 6
                  • 7
                  • 8
                  • 9
                  • 10
                  • 11
                  • 12
                  • 13
                  • 14
                  • 15
                  • 16
                  • 17
                  • 18
                  • 19
                  • 20
                  • 21
                  • 22

                  若能正常输出以上信息,则说明安装成功。

                  镜像加速
                  鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,强烈建议安装 Docker 之后配置镜像加速器

                  参考文档
                  Docker 官方 Debian 安装文档


                  CentOS 安装 Docker CE

                  系统要求

                  Docker CE 支持 64 位版本 CentOS 7,并且要求内核版本不低于 3.10。 CentOS 7 满足最低内核的要求,但由于内核版本比较低,部分功能(如 overlay2 存储层驱动)无法使用,并且部分功能可能不太稳定。

                  卸载旧版本

                  旧版本的 Docker 称为 docker 或者 docker-engine ,使用以下命令卸载旧版本:

                  $ sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine
                  
                  • 1

                  使用 yum 安装

                  执行以下命令安装依赖包:

                  $ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
                  
                  • 1

                  鉴于国内网络问题,强烈建议使用国内源
                  执行下面的命令添加 yum 软件源:

                  $ sudo yum-config-manager --add-repo https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo
                  
                  • 1

                  官方源

                    $ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

                      如果需要最新版本的 Docker CE 请使用以下命令:

                      $ sudo yum-config-manager --enable docker-ce-edge
                      
                      • 1

                      如果需要测试版本的 Docker CE 请使用以下命令:

                      $ sudo yum-config-manager --enable docker-ce-test
                      
                      • 1

                      安装 Docker CE

                      更新 yum 软件源缓存,并安装 docker-ce

                      $ sudo yum makecache fast
                      
                      $ sudo yum install docker-ce
                      
                      • 1
                      • 2
                      • 3

                      使用脚本自动安装

                      在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,CentOS系统上可以使用这套脚本安装:

                      $ curl -fsSL get.docker.com -o get-docker.sh
                      
                      $ sudo sh get-docker.sh --mirror Aliyun
                      
                      • 1
                      • 2
                      • 3

                      执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker CE 的 Edge 版本安装在系统中。

                      启动 Docker CE

                      $ sudo systemctl enable docker
                      
                      $ sudo systemctl start docker
                      
                      • 1
                      • 2
                      • 3

                      建立 docker 用户组

                      默认情况下, docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker用户组。

                      建立 docker 组:

                      $ sudo groupadd docker
                      
                      • 1

                      将当前用户加入 docker 组:

                      $ $ sudo usermod -aG docker $USER
                      
                      • 1

                      退出当前终端并重新登录,进行如下测试。

                      测试 Docker 是否安装正确

                      $ docker run hello-world
                      Unable to find image 'hello-world:latest' locally
                      latest: Pulling from library/hello-world
                      ca4f61b1923c: Pull complete
                      Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
                      Status: Downloaded newer image for hello-world:latest
                      Hello from Docker!
                      This message shows that your installation appears to be working correctly.
                      To generate this message, Docker took the following steps:
                      1. The Docker client contacted the Docker daemon.
                      2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
                      (amd64)
                      3. The Docker daemon created a new container from that image which runs the
                      executable that produces the output you are currently reading.
                      4. The Docker daemon streamed that output to the Docker client, which sent it
                      to your terminal.
                      To try something more ambitious, you can run an Ubuntu container with:
                      $ docker run -it ubuntu bash
                      Share images, automate workflows, and more with a free Docker ID:
                      https://cloud.docker.com/
                      For more examples and ideas, visit:
                      https://docs.docker.com/engine/userguide/
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9
                      • 10
                      • 11
                      • 12
                      • 13
                      • 14
                      • 15
                      • 16
                      • 17
                      • 18
                      • 19
                      • 20
                      • 21
                      • 22

                      若能正常输出以上信息,则说明安装成功。

                      镜像加速
                      鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,强烈建议安装 Docker 之后配置镜像加速器

                      添加内核参数

                      默认配置下,如果在 CentOS 使用 Docker CE 看到下面的这些警告信息:

                      WARNING: bridge-nf-call-iptables is disabled
                      WARNING: bridge-nf-call-ip6tables is disabled
                      
                      • 1
                      • 2

                      请添加内核配置参数以启用这些功能。

                      $ sudo tee -a /etc/sysctl.conf <<-EOF
                      net.bridge.bridge-nf-call-ip6tables = 1
                      net.bridge.bridge-nf-call-iptables = 1
                      EOF
                      
                      • 1
                      • 2
                      • 3
                      • 4

                      然后重新加载 sysctl.conf 即可

                       $ sudo sysctl -p
                      
                      • 1

                      参考文档
                      Docker 官方 CentOS 安装文档


                      macOS 安装 Docker

                      系统要求

                      Docker for Mac 要求系统最低为 macOS 10.10.3 Yosemite。如果系统不满足需求,可以安装Docker Toolbox。

                      使用 Homebrew 安装

                      Homebrew 的 Cask 已经支持 Docker for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装:

                      $ brew cask install docker
                      
                      • 1

                      手动下载安装

                      如果需要手动下载,请点击以下链接下载 StableEdge 版本的 Docker for Mac。

                      如同 macOS 其它软件一样,安装也非常简单,双击下载的 .dmg 文件,然后将那只叫 Moby的鲸鱼图标拖拽到 Application 文件夹即可(其间需要输入用户密码)。

                      1. 从应用中找到 Docker 图标并点击运行。

                      2. 运行之后,会在右上角菜单栏看到一个鲸鱼图标,这个图标表明了 Docker 的运行状态。

                      3. 第一次点击图标,可能会看到安装成功的界面,点击 “Got it!” 可以关闭这个窗口。

                      4. 启动终端后,通过命令可以检查安装后的 Docker 版本。

                      $ docker --version
                      Docker version 17.10.0-ce, build f4ffd25
                      
                      $ docker-compose --version
                      docker-compose version 1.17.0-rc1, build a0f95af
                      
                      $ docker-machine --version
                      docker-machine version 0.13.0, build 9ba6da9
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8

                      如果 docker version 、 docker info 都正常的话,可以尝试运行一个 Nginx 服务器:

                      $ docker run -d -p 80:80 --name webserver nginx
                      
                      • 1

                      服务运行后,可以访问 http://localhost,如果看到了 “Welcome to nginx!”,就说明 Docker for Mac 安装成功了。

                      要停止 Nginx 服务器并删除执行下面的命令:

                      $ docker stop webserver
                      $ docker rm webserver
                      
                      • 1
                      • 2

                      镜像加速
                      鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,强烈建议安装 Docker 之后配置镜像加速器


                      Windows 10 PC 安装 Docker CE

                      系统要求

                      Docker for Windows 支持 64 位版本的 Windows 10 Pro,且必须开启 Hyper-V。

                      安装

                      点击以下链接下载 StableEdge 版本的 Docker for Windows。

                      下载好之后双击 Docker for Windows Installer.exe 开始安装。

                      运行

                      在 Windows 搜索栏输入 Docker 点击 Docker for Windows 开始运行。
                      Docker CE 启动之后会在 Windows 任务栏出现鲸鱼图标。

                      等待片刻,点击 Got it 开始使用 Docker CE。

                      镜像加速
                      鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,强烈建议安装 Docker 之后配置镜像加速器


                      镜像加速器

                      国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务,例如:

                      我们以 Docker 官方加速器为例进行介绍。

                      Ubuntu 14.04、Debian 7 Wheezy

                      对于使用 upstart 的系统而言,编辑 /etc/default/docker 文件,在其中的 DOCKER_OPTS 中配置加速器地址:

                      DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"
                      
                      • 1

                      重新启动服务。

                      $ sudo service docker restart
                      
                      • 1

                      Ubuntu 16.04+、Debian 8+、CentOS 7

                      对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件)

                      {
                          "registry-mirrors": [
                              "https://registry.docker-cn.com"
                          ]
                      }
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5

                      注意,一定要保证该文件符合 json 规范,否则 Docker 将不能启动。

                      之后重新启动服务。

                      $ sudo systemctl daemon-reload
                      $ sudo systemctl restart docker
                      
                      • 1
                      • 2

                      Windows 10

                      对于使用 Windows 10 的系统,在系统右下角托盘 Docker 图标内右键菜单选择 Settings ,打开配置窗口后左侧导航菜单选择 Daemon 。在 Registry mirrors 一栏中填写加速器地址 https://registry.docker-cn.com ,之后点击 Apply 保存后 Docker 就会重启并应用配置的镜像地址了。

                      MacOS

                      对于使用 MacOS 的用户,在任务栏点击 Docker for mac 应用图标 -> Perferences… -> Daemon -> Registry mirrors。在列表中填写加速器地址 https://registry.docker-cn.com 。 修改完成之后,点击 Apply & Restart 按钮,Docker 就会重启并应用配置的镜像地址了。

                      检查加速器是否生效

                      配置加速器之后,如果拉取镜像仍然十分缓慢,请手动检查加速器配置是否生效,在命令行执行 docker info ,如果从结果中看到了如下内容,说明配置成功。

                      Registry Mirrors:
                      https://registry.docker-cn.com/
                      
                      • 1
                      • 2

                      使用 Docker 镜像

                      在之前的介绍中,我们知道镜像是 Docker 的三大组件之一。

                      Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜像。

                      现在介绍一下关于更多的镜像内容:

                      • 从仓库获取镜像
                      • 管理本地主机上的镜像
                      • 介绍镜像实现的基本原理

                      获取镜像

                      Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些
                      镜像。

                      从 Docker 镜像仓库获取镜像的命令是 docker pull 。其命令格式为:

                      docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
                      
                      • 1

                      具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。

                      • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号] 。默认地址是 Docker Hub。
                      • 仓库名:这里的仓库名是两段式名称,即 <用户名>/<软件名> 。对于 Docker Hub,如果不给出用户名,则默认为 library ,也就是官方镜像。

                      比如:

                      $ docker pull ubuntu:16.04
                      16.04: Pulling from library/ubuntu
                      bf5d46315322: Pull complete
                      9f13e0ac480c: Pull complete
                      e8988b5b3097: Pull complete
                      40af181810e7: Pull complete
                      e6f7c7e5c03e: Pull complete
                      Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe
                      Status: Downloaded newer image for ubuntu:16.04
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9

                      上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是 ubuntu:16.04 ,因此将会获取官方镜像 library/ubuntu 仓库中标签为 16.04 的镜像。


                      运行

                      有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的 ubuntu:16.04 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令。

                      $ docker run -it --rm ubuntu:16.04 bash
                      
                      • 1

                      docker run 就是运行容器的命令,具体格式我们会在下面详细讲解容器,我们这里简要的说明一下上面用到的参数。

                      • -it :这是两个参数,一个是 -i :交互式操作,一个是 -t 终端。我们这里打算进入bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
                      • –rm :这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm 。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
                      • ubuntu:16.04 :这是指用 ubuntu:16.04 镜像为基础来启动容器。
                      • bash :放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash 。

                      进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。
                      这里,我们执行 cat /etc/os-release ,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 16.04.4 LTS 系统。

                      最后我们通过 exit 退出了这个容器。

                      这里主要介绍docker run,请记住这个命令和参数,作用为运行容器,指定镜像


                      列出镜像

                      要想列出已经下载下来的镜像,可以使用 docker image ls 命令。

                      $ docker image ls
                      REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
                      nginx               latest              7f70b30f2cc6        5 days ago          109MB
                      ubuntu              16.04               f975c5035748        2 weeks ago         112MB
                      centos              7                   2d194b392dd1        3 weeks ago         195MB
                      hello-world         latest              f2a91732366c        4 months ago        1.85kB
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6

                      列表包含了 仓库名 、 标签 、 镜像 ID 、 创建时间 以及 所占用的空间
                      镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个标签


                      镜像体积

                      这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同。
                      比如, ubuntu:16.04 镜像大小,在这里是 112 MB ,但是在 Docker Hub 显示的却是 50MB 。
                      这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。
                      而docker image ls 显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空
                      间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。

                      docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。
                      由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。

                      你可以通过以下命令来便捷的查看镜像、容器、数据卷所占用的空间。

                      $ docker system df
                      TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
                      Images              4                   2                   416.6MB             307.9MB (73%)
                      Containers          2                   0                   95B                 95B (100%)
                      Local Volumes       0                   0                   0B                  0B
                      Build Cache                                                 0B                  0B
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6

                      虚悬镜像

                      上面的镜像列表中,有时候可能会看到一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 none

                      这样的镜像原本是有镜像名和标签的,比如原来为 mongo:3.2 ,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2 时, mongo:3.2 这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 none 。
                      除了 docker pull 可能导致这种情况, docker build也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 none 的镜像。
                      这类无标签镜像也被称为 虚悬镜像(dangling image) ,可以用下面的命令专门显示这类镜像:

                      $ docker image ls -f dangling=true
                      
                      • 1

                      一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。

                      $ docker image prune
                      
                      • 1

                      中间层镜像

                      为了加速镜像构建、重复利用资源,Docker 会利用 中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。
                      默认的 docker image ls 列表中只会显示顶层镜像,如果望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。

                      $ docker image ls -a
                      
                      • 1

                      注意:这里可能会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。
                      实际上,这些镜像也没必要删除,因为相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。


                      列出部分镜像

                      不加任何参数的情况下, docker image ls 会列出所有顶级镜像,但是有时候我们只希望列出
                      部分镜像。 docker image ls 有好几个参数可以帮助做到这个事情。

                      根据仓库名列出镜像

                      $ docker image ls ubuntu
                      
                      • 1

                      列出特定的某个镜像,也就是说指定仓库名和标签

                      $ docker image ls ubuntu:16.04
                      
                      • 1

                      除此以外, docker image ls 还支持强大的过滤器参数 --filter ,或者简写 -f 。之前我们已经看到了使用过滤器来列出虚悬镜像的用法,它还有更多的用法。比如,我们希望看到在mongo:3.2 之后建立的镜像,可以用下面的命令:

                      $ docker image ls -f since=mongo:3.2
                      
                      • 1

                      想查看某个位置之前的镜像也可以,只需要把 since 换成 before 即可。

                      此外,如果镜像构建时,定义了 LABEL ,还可以通过 LABEL 来过滤。

                      $ docker image ls -f label=com.example.version=0.1
                      
                      • 1

                      以特定格式显示

                      默认情况下, docker image ls 会输出一个完整的表格,但是我们并非所有时候都会需要这些内容。比如,刚才删除虚悬镜像的时候,我们需要利用 docker image ls 把所有的虚悬镜像的 ID 列出来,然后才可以交给 docker image rm 命令作为参数来删除指定的这些镜像,这个时候就用到了 -q 参数。

                      $ docker image ls -q
                      
                      • 1

                      –filter 配合 -q 产生出指定范围的 ID 列表,然后送给另一个 docker 命令作为参数,从而针对这组实体成批的进行某种操作的做法在 Docker 命令行使用过程中非常常见,不仅仅是镜像,将来我们会在各个命令中看到这类搭配以完成很强大的功能。每次看到过滤器后,可以多注意一下它们的用法。

                      另外一些时候,我们可能只是对表格的结构不满意,希望自己组织列;或者不希望有标题,这样方便其它程序解析结果等,这就用到了 Go 的模板语法

                      比如,下面的命令会直接列出镜像结果,并且只包含镜像ID和仓库名:

                      $ docker image ls --format "{{.ID}}: {{.Repository}}"
                      7f70b30f2cc6: nginx
                      f975c5035748: ubuntu
                      2d194b392dd1: centos
                      f2a91732366c: hello-world
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5

                      或者打算以表格等距显示,并且有标题行,和默认一样,不过自己定义列:

                      $ docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
                      IMAGE ID            REPOSITORY          TAG
                      7f70b30f2cc6        nginx               latest
                      f975c5035748        ubuntu              16.04
                      2d194b392dd1        centos              7
                      f2a91732366c        hello-world         latest
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6

                      删除本地镜像

                      如果要删除本地的镜像,可以使用 docker image rm 命令,其格式为:

                      $ docker image rm [选项] <镜像1> [<镜像2> ...]
                      
                      • 1

                      用 ID、镜像名、摘要删除镜像

                      其中, <镜像> 可以是 镜像短 ID 、 镜像长 ID 、 镜像名 或者 镜像摘要 。

                      比如我们有这么一些镜像:

                      $ docker image ls
                      REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
                      nginx               latest              7f70b30f2cc6        5 days ago          109MB
                      ubuntu              16.04               f975c5035748        2 weeks ago         112MB
                      centos              7                   2d194b392dd1        3 weeks ago         195MB
                      hello-world         latest              f2a91732366c        4 months ago        1.85kB
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6

                      我们可以用镜像的完整 ID,也称为 长 ID ,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。

                      docker image ls 默认列出的就已经是短 ID 了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。

                      比如这里,如果我们要删除 nginx 镜像,可以执行:

                      $ docker image rm 7f7
                      
                      • 1

                      我们也可以用 镜像名 ,也就是 <仓库名>:<标签> ,来删除镜像。

                      $ docker image rm centos
                      
                      • 1

                      当然,更精确的是使用 镜像摘要 删除镜像。

                      # 使用ls命令获取镜像列表, 显示列表添加digests列
                      $ docker image ls --digests
                      REPOSITORY          TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
                      nginx               latest              sha256:ab0cf552dec24d2c1ca959a9972118d7dfc05c2debfb9637a1289b656ee4ec5b   7f70b30f2cc6        5 days ago          109MB
                      ubuntu              16.04               sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6   f975c5035748        2 weeks ago         112MB
                      centos              7                   sha256:dcbc4e5e7052ea2306eed59563da1fec09196f2ecacbe042acbdcd2b44b05270   2d194b392dd1        3 weeks ago         195MB
                      hello-world         latest              sha256:97ce6fa4b6cdc0790cda65fe7290b74cfebd9fa0c9b8c38e979330d547d22ce1   f2a91732366c        4 months ago        1.85kB
                      
                      # 根据digests删除镜像
                      $ docker image rm node@sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9
                      • 10

                      Untagged 和 Deleted

                      删除行为分为两类,一类是Untagged ,另一类是 Deleted 。
                      我们之前介绍过,镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。

                      上面的命令删除镜像的时候实际上是在要求删除某个标签的镜像,上面我们删除某个镜像回车后最下面也会出现标识,Untagged:node@sha256:…

                      当我们删除一个镜像的时候docker会将满足我们要求的所有镜像标签都取消,并不一定是删除,当标签被取消的时候就是我们看到的Untagged

                      为什么说不一定是删除呢?因为一个镜像可以有多个标签,当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。只是取消了某个标签而已。

                      当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。

                      镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。
                      镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。
                      这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的源。

                      除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。
                      之前讲过,容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。


                      用 docker image ls 命令来配合

                      像其它可以承接多个实体的命令一样,可以使用 docker image ls -q 来配合使用 docker image rm ,这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。

                      比如,我们需要删除所有仓库名为 redis 的镜像:

                      $ docker image rm $(docker image ls -q redis)
                      
                      • 1

                      或者删除所有在 mongo:3.2 之前的镜像:

                      $ docker image rm $(docker image ls -q -f before=mongo:3.2)
                      
                      • 1

                      充分利用你的想象力和 Linux 命令行的强大,你可以完成很多非常赞的功能。


                      CentOS/RHEL 的用户需要注意的事项

                      在 Ubuntu/Debian 上有 UnionFS 可以使用,如 aufs 或者 overlay2 ,而 CentOS 和 RHEL的内核中没有相关驱动。因此对于这类系统,一般使用 devicemapper 驱动利用 LVM 的一些机制来模拟分层存储。这样的做法除了性能比较差外,稳定性一般也不好,而且配置相对复杂。

                      所以不建议将docker安装在CentOS/RHEL中,反而更建议安装在Windows中


                      利用 commit 理解镜像构成

                      注意: docker commit 命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用 docker commit 定制镜像,定制镜像应该使用 Dockerfile 来完成。

                      定制一个 Web 服务器的例子,看下镜像是如何构建的。

                      $ docker run --name webserver -d -p 80:80 nginx
                      
                      • 1

                      这条命令会用 nginx 镜像启动一个容器,命名为 webserver ,并且映射了 80 端口,这样我
                      们可以用浏览器去访问这个 nginx 服务器。

                      在浏览器中运行一下:http://localhost试试吧,不出意外可以看到Nginx欢迎页面

                      现在,假设我们非常不喜欢这个欢迎页面,我们希望改成欢迎 Docker 的文字,我们可以使用
                      docker exec 命令进入容器,修改其内容。

                      # docker exec命令以交互式终端方式进入webserver容器
                      $ docker exec -it webserver bash
                      
                      # 覆盖Nginx的index.html文件内容
                      root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
                      
                      # 退出Linux系统
                      root@3729b97e8226:/# exit
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8

                      这时候刷新浏览器看看效果吧

                      我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 docker diff 命令看到具体的改动。

                      $ docker diff webserver
                      
                      • 1

                      现在我们定制好了变化,我们希望能将其保存下来形成镜像。

                      要知道,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。

                      docker commit 的语法格式为:

                      docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
                      
                      • 1

                      我们可以用下面的命令将容器保存为镜像:

                      $ docker commit --author "Tao Wang <twang2218@gmail.com>" --message "修改了默认网页"  webserver nginx:v2
                      
                      • 1
                      • –author:是指定修改的作者
                      • –message:则是记录本次修改的内容

                      我们可以在 docker image ls 中看到这个新定制的镜像:

                      $ docker image ls nginx
                      
                      • 1

                      我们还可以用 docker history 具体查看镜像内的历史记录,如果比较 nginx:latest 的历史记录,我们会发现新增了我们刚刚提交的这一层。

                      $ docker history nginx:v2
                      
                      • 1

                      新的镜像定制好后,我们可以来运行这个镜像。

                      $ docker run --name web2 -d -p 81:80 nginx:v2
                      
                      • 1

                      现在可以运行 http://localhost:81看看

                      至此,我们第一次完成了定制镜像,使用的是 docker commit 命令,手动操作给旧的镜像添加了新的一层,形成新的镜像,对镜像多层存储应该有了更直观的感觉。

                      慎用 docker commit

                      使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。
                      你会发现除了真正想要修改的**/usr/share/nginx/html/index.html** 文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,如果不小心清理,将会导致镜像极为臃肿。

                      此外,使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体在操作的。虽然 docker diff 或许可以告诉得到一些线索。


                      使用 Dockerfile 定制镜像

                      镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile

                      Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

                      还以之前定制 nginx 镜像为例,这次我们使用 Dockerfile 来定制。
                      Windows系统可以找个目录新建个文件,Linux用户在一个空白目录中,建立一个文本文件,并命名为 Dockerfile :

                      $ mkdir mynginx
                      $ cd mynginx
                      $ touch Dockerfile
                      
                      • 1
                      • 2
                      • 3

                      其内容为:

                      FROM nginx
                      RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
                      
                      • 1
                      • 2

                      这个 Dockerfile 很简单,一共就两行。涉及到了两条指令, FROM 和 RUN 。


                      FROM 指定基础镜像

                      所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。

                      在 Docker Store 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如nginxredismongomysqlhttpdphptomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 nodeopenjdkpythonrubygolang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

                      如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如ubuntudebiancentosfedoraalpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。

                      除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

                      FROM scratch
                      
                      • 1

                      如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。


                      RUN 执行命令

                      RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

                      • shell 格式:RUN <命令> ,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
                      • exec 格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。

                      既然 RUN 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个
                      命令对应一个 RUN 呢?比如这样:

                      FROM debian:jessie
                      RUN apt-get update
                      RUN apt-get install -y gcc libc6-dev make
                      RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
                      RUN mkdir -p /usr/src/redis
                      RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
                      RUN make -C /usr/src/redis
                      RUN make -C /usr/src/redis install
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8

                      之前说过,Dockerfile 中每一个指令都会建立一层, RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。

                      而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 所以千万不要使用上面那种写法。
                      而且Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过127 层。

                      应该这样写:

                      FROM debian:jessie
                      RUN buildDeps='gcc libc6-dev make' \
                      && apt-get update \
                      && apt-get install -y $buildDeps \
                      && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
                      && mkdir -p /usr/src/redis \
                      && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
                      && make -C /usr/src/redis \
                      && make -C /usr/src/redis install \
                      && rm -rf /var/lib/apt/lists/* \
                      && rm redis.tar.gz \
                      && rm -r /usr/src/redis \
                      && apt-get purge -y --auto-remove $buildDeps
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9
                      • 10
                      • 11
                      • 12
                      • 13

                      此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

                      –auto-remove $buildDeps 这个一定要加上


                      构建镜像

                      现在我们再次定制nginx 镜像的 Dockerfile,开始构建这个镜像

                      在 Dockerfile 文件所在目录执行:

                      $ docker build -t nginx:v3 .
                      Sending build context to Docker daemon 2.048 kB
                      Step 1 : FROM nginx
                      ---> e43d811ce2f4
                      Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
                      ---> Running in 9cdc27646c7b
                      ---> 44aa4490ce2c
                      Removing intermediate container 9cdc27646c7b
                      Successfully built 44aa4490ce2c
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9

                      这里我们使用了 docker build 命令进行镜像构建。其格式为:

                      docker build [选项] <上下文路径/URL/->
                      
                      • 1

                      在这里我们指定了最终镜像的名称 -t nginx:v3

                      注意命令中 docker build -t nginx:v3 后面的那个点,非常重要,下面就说说这个点的意思


                      镜像构建上下文(Context)

                      如果注意,会看到 docker build 命令最后有一个 .
                      . 表示当前目录,而 Dockerfile就在当前目录,因此可能会让人误解为这个路径是在指定 Dockerfile 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定上下文路径。

                      如果在 Dockerfile 中这么写:

                      COPY ./package.json /app
                      
                      • 1

                      这并不是要复制执行 docker build 命令所在的目录下的 package.json ,也不是复制Dockerfile 所在目录下的 package.json ,而是复制 上下文(context) 目录下的package.json 。

                      这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile ,而且并不要求必须位于上下文目录中,比如可以用 -f …/Dockerfile.c 参数指定某个文件作为Dockerfile 。

                      当然,一般大家习惯性的会使用默认的文件名 Dockerfile ,以及会将其置于镜像构建上下文目录中。


                      其它 docker build 的用法

                      直接用 Git repo 进行构建

                      docker build 还支持从 URL 构建,比如可以直接从 Git repo 中构建:

                      $ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14
                      docker build https://github.com/twang2218/gitlab-ce-zh.git\#:8.14
                      Sending build context to Docker daemon 2.048 kB
                      Step 1 : FROM gitlab/gitlab-ce:8.14.0-ce.0
                      8.14.0-ce.0: Pulling from gitlab/gitlab-ce
                      aed15891ba52: Already exists
                      773ae8583d14: Already exists
                      ...
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8

                      这行命令指定了构建所需的 Git repo,并且指定默认的 master 分支,构建目录为 /8.14/ ,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。

                      用给定的 tar 压缩包构建

                      $ docker build http://server/context.tar.gz
                      
                      • 1

                      如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。

                      从标准输入中读取 Dockerfile 进行构建

                      docker build - < Dockerfile
                      
                      • 1

                      cat Dockerfile | docker build -
                      
                      • 1

                      如果标准输入传入的是文本文件,则将其视为 Dockerfile ,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。

                      从标准输入中读取上下文压缩包进行构建

                      $ docker build - < context.tar.gz
                      
                      • 1

                      如果发现标准输入的文件格式是 gzip 、 bzip2 以及 xz 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。


                      Dockerfile 指令详解

                      我们已经介绍了 FROMRUN ,还提及了 COPY , ADD ,其实 Dockerfile 功能很强大,它提供了十多个指令。


                      COPY 复制文件

                      格式:

                      • COPY <源路径>… <目标路径>
                      • COPY ["<源路径1>",… “<目标路径>”]

                      和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。

                      COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如:

                      COPY package.json /usr/src/app/
                      
                      • 1

                      <源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,如:

                      COPY hom* /mydir/
                      COPY hom?.txt /mydir/
                      
                      • 1
                      • 2

                      <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

                      使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git进行管理的时候。


                      ADD 更高级的复制文件

                      ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。

                      <源路径> 可以是一个 URL ,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600 ,

                      如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip , bzip2 以及 xz 的情况下, ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。

                      在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu 中:

                      FROM scratch
                      ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
                      
                      • 1
                      • 2

                      但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令了。

                      在 Docker 官方要求,尽可能的使用 COPY ,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
                      另外需要注意的是, ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

                      因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY 指令,仅在需要自动解压缩的场合使用 ADD 。


                      CMD 容器启动命令

                      CMD 指令的格式和 RUN 相似,也是两种格式:

                      • shell 格式: CMD <命令>
                      • exec 格式: CMD [“可执行文件”, “参数1”, “参数2”…]
                      • 参数列表格式: CMD [“参数1”, “参数2”…] 。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。

                      Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。 CMD 指令就是用于指定默认的容器主进程的启动命令的。

                      在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如, ubuntu 镜像默认的CMD 是 /bin/bash ,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash 。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release 。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。

                      在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 " ,而不要使用单引号。

                      如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:

                      CMD echo $HOME
                      
                      • 1

                      在实际执行中,会将其变更为:

                      CMD [ "sh", "-c", "echo $HOME" ]
                      
                      • 1

                      这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。

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

                      比如下面一种错误写法:

                      CMD service nginx start
                      
                      • 1

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

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

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

                      ENTRYPOINT 入口点

                      ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。

                      ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。 ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。

                      那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 “” 有什么好处么?让我们来看几个场景。

                      场景一:让镜像变成像命令一样使用

                      假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD 来实现:

                      FROM ubuntu:16.04
                      RUN apt-get update \
                      && apt-get install -y curl \
                      && rm -rf /var/lib/apt/lists/*
                      CMD [ "curl", "-s", "http://ip.cn" ]
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5

                      假如我们使用 docker build -t myip . 来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行:

                      $ docker run myip
                      当前 IP:124.205.203.174 来自:北京市 鹏博士
                      
                      • 1
                      • 2

                      这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 CMD 中可以看到实质的命令是 curl ,那么如果我们希望显示 HTTP头信息,就需要加上 -i 参数。那么我们可以直接加 -i 参数给 docker run myip 么?

                      $ docker run myip -i
                      docker: Error response from daemon: invalid header field value "oci runtime error: con
                      tainer_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable
                      file not found in $PATH\"\n".
                      
                      • 1
                      • 2
                      • 3
                      • 4

                      我们可以看到可执行文件找不到的报错, executable file not found 。

                      那么如果我们希望加入 -i 这参数,我们就必须重新完整的输入这个命令:

                      $ docker run myip curl -s http://ip.cn -i
                      
                      • 1

                      这显然不是很好的解决方案,而使用 ENTRYPOINT 就可以解决这个问题。现在我们重新用ENTRYPOINT 来实现这个镜像:

                      FROM ubuntu:16.04
                      RUN apt-get update \
                      && apt-get install -y curl \
                      && rm -rf /var/lib/apt/lists/*
                      ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5

                      这次我们再来尝试直接使用 docker run myip -i :

                      $ docker run myip -i
                      HTTP/1.1 200 OK
                      Server: nginx/1.8.0
                      Date: Tue, 22 Nov 2016 05:12:40 GMT
                      Content-Type: text/html; charset=UTF-8
                      Vary: Accept-Encoding
                      X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
                      X-Cache: MISS from cache-2
                      X-Cache-Lookup: MISS from cache-2:80
                      X-Cache: MISS from proxy-2_6
                      Transfer-Encoding: chunked
                      Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
                      Connection: keep-alive
                      当前 IP:124.205.203.174 来自:北京市 鹏博士
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9
                      • 10
                      • 11
                      • 12
                      • 13
                      • 14

                      场景二:应用运行前的准备工作

                      启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。

                      比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的mysql 服务器运行之前解决。

                      此外,可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 root 身份执行,方便调试等。

                      这些准备工作是和容器 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" ]
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7

                      可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINT 为docker-entrypoint.sh脚本。

                      docker-entrypoint.sh脚本:

                      #!/bin/sh
                      ...
                      # allow the container to be started with `--user`
                      if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
                      chown -R redis .
                      exec su-exec redis "$0" "$@"
                      fi
                      exec "$@"
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8

                      该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis用户身份启动服务器,否则依旧使用 root 身份执行。比如:

                      $ docker run -it redis id
                      uid=0(root) gid=0(root) groups=0(root)
                      
                      • 1
                      • 2

                      ENV 设置环境变量

                      格式有两种:

                      • ENV
                      • ENV = =…

                      这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN ,还是运行时的应用,都可以直接使用这里定义的环境变量。

                      ENV VERSION=1.0 DEBUG=on \
                      NAME="Happy Feet"
                      
                      • 1
                      • 2

                      这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。

                      定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 node 镜像Dockerfile 中,就有类似这样的代码:

                      ENV NODE_VERSION 7.2.0
                      RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.ta
                      r.xz" \
                      && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
                      && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
                      && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
                      && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=
                      1 \
                      && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
                      && ln -s /usr/local/bin/node /usr/local/bin/nodejs
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9
                      • 10

                      在这里先定义了环境变量 NODE_VERSION ,其后的 RUN 这层里,多次使用 $NODE_VERSION 来进行操作定制。可以看到,将来升级镜像构建版本的时候,只需要更新 7.2.0 即可, Dockerfile 构建维护变得更轻松了。

                      下列指令可以支持环境变量展开:
                      ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、 STOPSIGNAL 、 ONBUILD 。

                      可以从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份 Dockerfile 制作更多的镜像,只需使用不同的环境变量即可。


                      ARG 构建参数

                      格式: ARG <参数名>[=<默认值>]

                      构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是, ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。

                      Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build 中用 --build-arg <参数名>=<值> 来覆盖。


                      VOLUME 定义匿名卷

                      格式为:

                      • VOLUME ["<路径1>", “<路径2>”…]
                      • VOLUME <路径>

                      之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面我们会进一步介绍Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

                      VOLUME /data
                      
                      • 1

                      这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:

                      docker run -d -v mydata:/data xxxx
                      
                      • 1

                      在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了Dockerfile 中定义的匿名卷的挂载配置。


                      EXPOSE 声明端口

                      格式为 EXPOSE <端口1> [<端口2>…] 。

                      EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

                      要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。 -p ,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。


                      WORKDIR 指定工作目录

                      格式为 WORKDIR <工作目录路径> 。

                      使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在, WORKDIR 会帮你建立目录。

                      有些人可能会犯一些错误,错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误:

                      RUN cd /app
                      RUN echo "hello" > world.txt
                      
                      • 1
                      • 2

                      如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello 。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。

                      每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。

                      因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。


                      USER 指定当前用户

                      格式: USER <用户名>

                      USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。 WORKDIR 是改变工作目录, USER 则是改变之后层的执行 RUN , CMD 以及 ENTRYPOINT 这类命令的身份。

                      当然,和 WORKDIR 一样, USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

                      RUN groupadd -r redis && useradd -r -g redis redis
                      USER redis
                      RUN [ "redis-server" ]
                      
                      • 1
                      • 2
                      • 3

                      如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo ,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu 。

                      # 建立 redis 用户,并使用 gosu 换另一个用户执行命令
                      RUN groupadd -r redis && useradd -r -g redis redis
                      # 下载 gosu
                      RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/
                      gosu-amd64" \
                      && chmod +x /usr/local/bin/gosu \
                      && gosu nobody true
                      # 设置 CMD,并以另外的用户执行
                      CMD [ "exec", "gosu", "redis", "redis-server" ]
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9

                      HEALTHCHECK 健康检查

                      格式:

                      • HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令
                      • HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

                      HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12引入的新指令。

                      当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting ,在HEALTHCHECK 指令检查成功后变为 healthy ,如果连续一定次数失败,则会变为unhealthy

                      HEALTHCHECK 支持下列选项:

                      • –interval=<间隔> :两次健康检查的间隔,默认为 30 秒;
                      • –timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
                      • –retries=<次数> :当连续失败指定次数后,则将容器状态视为 unhealthy ,默认 3次。

                      和 CMD , ENTRYPOINT 一样, HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。

                      在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和exec 格式。命令的返回值决定了该次健康检查的成功与否: 0 :成功; 1 :失败; 2 :保留,不要使用这个值。

                      假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:

                      FROM nginx
                      RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
                      HEALTHCHECK --interval=5s --timeout=3s \
                      CMD curl -fs http://localhost/ || exit 1
                      
                      • 1
                      • 2
                      • 3
                      • 4

                      这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。

                      使用 docker build 来构建这个镜像:

                      $ docker build -t myweb:v1 .
                      
                      • 1

                      构建好了后,我们启动一个容器:

                      $ docker run -d --name web -p 80:80 myweb:v1
                      
                      • 1

                      当运行该镜像后,可以通过 docker container ls 看到最初的状态为 (health: starting) :

                      $ docker container ls
                      CONTAINER ID IMAGE COMMAND CREATED S
                      TATUS PORTS NAMES
                      03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago U
                      p 2 seconds (health: starting) 80/tcp, 443/tcp web
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5

                      在等待几秒钟后,再次 docker container ls ,就会看到健康状态变化为了 (healthy) :

                      $ docker container ls
                      CONTAINER ID IMAGE COMMAND CREATED S
                      TATUS PORTS NAMES
                      03e28eb00bd0 myweb:v1 “nginx -g 'daemon off” 18 seconds ago U
                      p 16 seconds (healthy) 80/tcp, 443/tcp web

                      如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy) 。

                      为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr )都会被存储于健康状态
                      里,可以用 docker inspect 来查看。

                      $ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
                      {
                      "FailingStreak": 0,
                      "Log": [
                      {
                      "End": "2016-11-25T14:35:37.940957051Z",
                      "ExitCode": 0,
                      "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</titl
                      e>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-f
                      amily: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welc
                      ome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully inst
                      alled and\nworking. Further configuration is required.</p>\n\n<p>For online documentat
                      ion and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCo
                      mmercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n
                      <p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
                      "Start": "2016-11-25T14:35:37.780192565Z"
                      }
                      ],
                      "Status": "healthy"
                      }
                      
                      • 1
                      • 2
                      • 3
                      • 4
                      • 5
                      • 6
                      • 7
                      • 8
                      • 9
                      • 10
                      • 11
                      • 12
                      • 13
                      • 14
                      • 15
                      • 16
                      • 17
                      • 18
                      • 19
                      • 20
                      声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/64863?site
                      推荐阅读
                      相关标签
                        

                      闽ICP备14008679号