zoukankan      html  css  js  c++  java
  • Docker 基础

    1. Docker 简介

    1.1 什么是 Docker?

    Docker的英文翻译是“搬运工”的意思,他搬运的东西就是我们常说的集装箱Container,Container 里面装的是任意类型的 App,我们的开发人员可以通过 Docker 将App 变成一种标准化的、可移植的、自管理的组件,我们可以在任何主流的操作系统中开发、调试和运行。

    从概念上来看 Docker 和我们传统的虚拟机比较类似,只是更加轻量级,更加方便使,Docker 和虚拟机最主要的区别有以下几点:

    • 虚拟化技术依赖的是物理CPU和内存,是硬件级别的;而我们的 Docker 是构建在操作系统层面的,利用操作系统的容器化技术,所以 Docker 同样的可以运行在虚拟机上面。
    • 我们知道虚拟机中的系统就是我们常说的操作系统镜像,比较复杂;而 Docker 比较轻量级,我们可以用 Docker 部署一个独立的 Redis,就类似于在虚拟机当中安装一个 Redis 应用,但是我们用 Docker 部署的应用是完全隔离的。
    • 我们都知道传统的虚拟化技术是通过快照来保存状态的;而 Docker 引入了类似于源码管理的机制,将容器的快照历史版本一一记录下来,切换成本非常之低。
    • 传统虚拟化技术在构建系统的时候非常复杂;而 Docker 可以通过一个简单的 Dockerfile 文件来构建整个容器,更重要的是 Dockerfile 可以手动编写,这样应用程序开发人员可以通过发布 Dockerfile 来定义应用的环境和依赖,这样对于持续交付非常有利。 ​​​​

    1.2 为啥要用容器?

    应用容器是个啥样子呢,一个做好的应用容器长得就像一个装好了一组特定应用的虚拟机一样,比如我现在想用 Redis,那我就找个装好了 Redis 的容器就可以了,然后运行起来,我就能直接使用了。

    那为什么不能直接安装一个 Redis 呢?肯定是可行的,但是有的时候根据每个人电脑的不同,在安装的时候可能会报出各种各样的错误,万一你的机器中毒了,你的电脑挂了,你所有的服务都需要重新安装。但是有了 Docker 或者说有了容器就不一样了,你就相当于有了一个可以运行起来的虚拟机,只要你能运行容器,Redis 的配置就省了。而且如果你想换个电脑,没问题,很简单,直接把容器”端过来”就可以使用容器里面的服务了。

    1.3 Docker Engine

    Docker Engine是一个C/S架构的应用程序,主要包含下面几个组件:

    • 常驻后台进程Dockerd
    • 一个用来和 Dockerd 交互的 REST API Server
    • 命令行CLI接口,通过和 REST API 进行交互(我们经常使用的 docker 命令)

    1.4 Docker 架构

    Docker 使用 C/S (客户端/服务器)体系的架构,Docker 客户端与 Docker 守护进程通信,Docker 守护进程负责构建,运行和分发 Docker 容器。Docker 客户端和守护进程可以在同一个系统上运行,也可以将 Docker 客户端连接到远程 Docker 守护进程。Docker 客户端和守护进程使用 REST API 通过UNIX套接字或网络接口进行通信。 ​​

    • Docker Damon:dockerd,用来监听 Docker API 的请求和管理 Docker 对象,比如镜像、容器、网络和 Volume。
    • Docker Client:docker,docker client 是我们和 Docker 进行交互的最主要的方式方法,比如我们可以通过 docker run 命令来运行一个容器,然后我们的这个 client 会把命令发送给上面的 Dockerd,让他来做真正事情。
    • Docker Registry:用来存储 Docker 镜像的仓库,Docker Hub 是 Docker 官方提供的一个公共仓库,而且 Docker 默认也是从 Docker Hub 上查找镜像的,当然你也可以很方便的运行一个私有仓库,当我们使用 docker pull 或者 docker run 命令时,就会从我们配置的 Docker 镜像仓库中去拉取镜像,使用 docker push 命令时,会将我们构建的镜像推送到对应的镜像仓库中。
    • Images:镜像,镜像是一个只读模板,带有创建 Docker 容器的说明,一般来说的,镜像会基于另外的一些基础镜像并加上一些额外的自定义功能。比如,你可以构建一个基于 Centos 的镜像,然后在这个基础镜像上面安装一个 Nginx 服务器,这样就可以构成一个属于我们自己的镜像了。
    • Containers:容器,容器是一个镜像的可运行的实例,可以使用 Docker REST API 或者 CLI 来操作容器,容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。
    • 底层技术支持:Namespaces(做隔离)、CGroups(做资源限制)、UnionFS(镜像和容器的分层) the-underlying-technology Docker 底层架构分析

    1.5 安装

    直接前往官方文档选择合适的平台安装即可,比如我们这里想要在centos系统上安装 Docker,这前往地址https://docs.docker.com/install/linux/docker-ce/centos/根据提示安装即可。

    安装依赖软件包:

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

    添加软件仓库,我们这里使用稳定版 Docker,执行下面命令添加 yum 仓库地址:

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

    然后直接安装即可:

    $ sudo yum install docker-ce

    如果要安装指定的版本,可以使用 yum list 列出可用的版本:

    $ yum list docker-ce --showduplicates | sort -r
    docker-ce.x86_64            18.03.0.ce-1.el7.centos             docker-ce-stable

    比如这里可以安装18.03.0.ce版本:

    $ sudo yum install docker-ce-18.03.0.ce

    要启动 Docker 也非常简单:

    $ sudo systemctl enable docker
    $ sudo systemctl start docker

    另外一种安装方式是可以直接下载指定的软件包直接安装即可,前往地址:https://download.docker.com/linux/centos/7/x86_64/stable/Packages/ 找到合适的.rpm包下载,然后安装即可:

    $ sudo yum install /path/to/package.rpm

    2. 镜像和容器的基本操作

    这节课给大家讲解Docker镜像和容器的一些基本操作方法。

    2.1 获取镜像

    之前我们提到过 Docker 官方提供了一个公共的镜像仓库:Docker Hub,我们就可以从这上面获取镜像,获取镜像的命令:docker pull,格式为:

    $ docker pull [选项] [Docker Registry 地址[:端口]/]仓库名[:标签]
    
    • 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
      上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是 ubuntu:16.04,因此将会获取官方镜像 library/ubuntu 仓库中标签为 16.04 的镜像。 从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的sha256的摘要,以确保下载一致性。

    2.2 运行

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

    $ docker run -it --rm 
        ubuntu:16.04 
        /bin/bash
    
    root@e7009c6ce357:/# cat /etc/os-release
    NAME="Ubuntu"
    VERSION="16.04.4 LTS, Trusty Tahr"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 16.04.4 LTS"
    VERSION_ID="16.04"
    HOME_URL="http://www.ubuntu.com/"
    SUPPORT_URL="http://help.ubuntu.com/"
    BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"

    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 退出了这个容器。

    2.3 列出镜像

    $ docker image ls

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

    2.4 镜像大小

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

    另外一个需要注意的问题是,docker image ls列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。你可以通过以下命令来便捷的查看镜像、容器、数据卷所占用的空间。

    $ docker system df

    2.5 新建并启动

    所需要的命令主要为docker run。 例如,下面的命令输出一个 “Hello World”,之后终止容器。

    $ docker run ubuntu:16.04 /bin/echo 'Hello world'
    Hello world

    这跟在本地直接执行/bin/echo 'hello world'几乎感觉不出任何区别。下面的命令则启动一个 bash 终端,允许用户进行交互。

    $ docker run -t -i ubuntu:16.04 /bin/bash
    root@af8bae53bdd3:/#

    其中,-t选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上,-i则让容器的标准输入保持打开。 在交互模式下,用户可以通过所创建的终端来输入命令,例如:

    root@af8bae53bdd3:/# pwd
    /
    root@af8bae53bdd3:/# ls
    bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

    当利用docker run来创建容器时,Docker 在后台运行的标准操作包括:

    • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
    • 利用镜像创建并启动一个容器
    • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
    • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
    • 从地址池配置一个 ip 地址给容器
    • 执行用户指定的应用程序
    • 执行完毕后容器被终止

    2.6 启动已终止容器

    可以利用docker container start命令,直接将一个已经终止的容器启动运行。

    容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 ps 或 top 来查看进程信息。

    root@ba267838cc1b:/# ps
      PID TTY          TIME CMD
        1 ?        00:00:00 bash
       11 ?        00:00:00 ps

    可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。

    2.7 后台运行

    更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加-d参数来实现。下面举两个例子来说明一下。

    如果不使用-d参数运行容器。

    $ docker run ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
    hello world
    hello world
    hello world
    hello world

    容器会把输出的结果 (STDOUT) 打印到宿主机上面。如果使用了-d参数运行容器。

    $ docker run -d ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
    77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a

    此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 docker logs 查看)。

    注: 容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。

    使用-d参数启动后会返回一个唯一的 id,也可以通过docker container ls命令来查看容器信息。

    $ docker container ls
    CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES
    77b2dc01fe0f  ubuntu:16.04  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        agitated_wright
    
    要获取容器的输出信息,可以通过 docker container logs 命令。
    
    $ docker container logs [container ID or NAMES]
    hello world
    hello world
    hello world
    . . .

    2.8 终止容器

    可以使用docker container stop来终止一个运行中的容器。此外,当 Docker 容器中指定的应用终结时,容器也自动终止。

    例如对于上一章节中只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。终止状态的容器可以用docker container ls -a 命令看到。例如

    $ docker container ls -a
    CONTAINER ID        IMAGE                    COMMAND                CREATED             STATUS                          PORTS               NAMES
    ba267838cc1b        ubuntu:16.04             "/bin/bash"            30 minutes ago      Exited (0) About a minute ago                       trusting_newton

    处于终止状态的容器,可以通过docker container start命令来重新启动。

    此外,docker container restart命令会将一个运行态的容器终止,然后再重新启动它。

    2.9 进入容器

    在使用-d参数时,容器启动后会进入后台。某些时候需要进入容器进行操作:exec 命令 -i -t 参数。

    只用-i参数时,由于没有分配伪终端,界面没有我们熟悉的Linux命令提示符,但命令执行结果仍然可以返回。 当-i -t参数一起使用时,则可以看到我们熟悉的 Linux命令提示符。

    $ docker run -dit ubuntu:16.04
    69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6
    
    $ docker container ls
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    69d137adef7a        ubuntu:16.04       "/bin/bash"         18 seconds ago      Up 17 seconds                           zealous_swirles
    
    $ docker exec -i 69d1 bash
    ls
    bin
    boot
    dev
    ...
    
    $ docker exec -it 69d1 bash
    root@69d137adef7a:/#

    如果从这个 stdin 中 exit,不会导致容器的停止。这就是为什么推荐大家使用docker exec的原因。

    更多参数说明请使用docker exec --help查看。

    2.10 删除容器

    可以使用docker container rm来删除一个处于终止状态的容器。例如:

    $ docker container rm  trusting_newton
    trusting_newton

    也可用使用docker rm容器名来删除,如果要删除一个运行中的容器,可以添加-f参数。Docker 会发送 SIGKILL信号给容器。

    docker container ls -a (或者docker ps -a)命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用下面的命令可以清理掉所有处于终止状态的容器。

    $ docker container prune

    或者

    $ docker ps -aq

    2.11 删除本地镜像

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

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

    或者

    $ docker rmi 镜像名

    或者用 ID、镜像名、摘要删除镜像 其中,<镜像> 可以是 镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要。 比如我们有这么一些镜像:

    $ docker image ls
    REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
    centos                      latest              0584b3d2cf6d        3 weeks ago         196.5 MB
    redis                       alpine              501ad78535f0        3 weeks ago         21.03 MB
    docker                      latest              cf693ec9b5c7        3 weeks ago         105.1 MB
    nginx                       latest              e43d811ce2f4        5 weeks ago         181.5 MB

    我们可以用镜像的完整 ID,也称为 长 ID,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。docker image ls默认列出的就已经是短 ID 了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。

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

    $ docker image rm 501
    Untagged: redis:alpine
    Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
    Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
    Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
    Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
    Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa
    Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3
    Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7

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

    $ docker image rm centos
    Untagged: centos:latest
    Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
    Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
    Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38

    2.12 docker commit定制镜像

    镜像是容器的基础,每次执行docker run的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中,我们所使用的都是来自于 Docker Hub 的镜像。直接使用这些镜像是可以满足一定的需求,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。接下来的几节就将讲解如何定制镜像。

    回顾一下之前我们学到的知识,镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。

    现在让我们以定制一个 Web 服务器为例子,来讲解镜像是如何构建的。

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

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

    如果是在 Linux 本机运行的 Docker,或者如果使用的是 Docker for Mac、Docker for Windows,那么可以直接访问:http://localhost;如果使用的是 Docker Toolbox,或者是在虚拟机、云服务器上安装的 Docker,则需要将 localhost 换为虚拟机地址或者实际云服务器地址。

    直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。

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

    $ docker exec -it webserver bash
    root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
    root@3729b97e8226:/# exit

    我们以交互式终端方式进入 webserver 容器,并执行了 bash 命令,也就是获得一个可操作的 Shell。 然后,我们用<h1>Hello, Docker!</h1>覆盖了 /usr/share/nginx/html/index.html的内容。 现在我们再刷新浏览器的话,会发现内容被改变了。

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

    $ docker diff webserver
    C /root
    A /root/.bash_history
    C /run
    C /usr
    C /usr/share
    C /usr/share/nginx
    C /usr/share/nginx/html
    C /usr/share/nginx/html/index.html
    C /var
    C /var/cache
    C /var/cache/nginx
    A /var/cache/nginx/client_temp
    A /var/cache/nginx/fastcgi_temp
    A /var/cache/nginx/proxy_temp
    A /var/cache/nginx/scgi_temp
    A /var/cache/nginx/uwsgi_temp

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

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

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

    $ docker commit 
        --author "海马学院" 
        --message "修改了默认首页" 
        webserver 
        nginx:v2
    sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214

    其中--author是指定修改的作者,而--message则是记录本次修改的内容。这点和 git 版本控制相似,不过这里这些信息可以省略留空。

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

    $ docker image ls nginx
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    nginx               v2                  07e334659748        9 seconds ago       181.5 MB
    nginx               1.11                05a60462f8ba        12 days ago         181.5 MB
    nginx               latest              e43d811ce2f4        4 weeks ago         181.5 MB

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

    $ docker history nginx:v2
    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    07e334659748        54 seconds ago      nginx -g daemon off;                            95 B                修改了默认网页
    e43d811ce2f4        4 weeks ago         /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon    0 B
    <missing>           4 weeks ago         /bin/sh -c #(nop)  EXPOSE 443/tcp 80/tcp        0 B
    <missing>           4 weeks ago         /bin/sh -c ln -sf /dev/stdout /var/log/nginx/   22 B
    <missing>           4 weeks ago         /bin/sh -c apt-key adv --keyserver hkp://pgp.   58.46 MB
    <missing>           4 weeks ago         /bin/sh -c #(nop)  ENV NGINX_VERSION=1.11.5-1   0 B
    <missing>           4 weeks ago         /bin/sh -c #(nop)  MAINTAINER NGINX Docker Ma   0 B
    <missing>           4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0 B
    <missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:23aa4f893e3288698c   123 MB

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

    $ docker run --name webserv2 -d -p 81:80 nginx:v2

    这里我们命名为新的服务为 webserv2,并且映射到 81 端口。如果是 Docker for Mac/Windows 或 Linux 桌面的话,我们就可以直接访问 http://localhost:81 看到结果,其内容应该和之前修改后的 webserver 一样。

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

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

    3.Dockerfile 定制镜像

    从前面一节的docker commit的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件等信息,但是命令毕竟只是命令,每次定制都得去重复执行这个命令,而且还不够直观,如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么这些问题不就都可以解决了吗?对的,这个脚本就是我们说的Dockerfile

    3.1 介绍

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

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

    $ mkdir mynginx
    $ cd mynginx
    $ touch Dockerfile

    其内容为:

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

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

    3.2 FROM 指定基础镜像

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

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

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

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

    FROM scratch
    ...
    

    如果你以scratch为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。有的同学可能感觉很奇怪,没有任何基础镜像,我怎么去执行我的程序呢,其实对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接FROM scratch会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。

    3.3 RUN 执行命令

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

    • shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
      RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
      
    • 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
      

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

    而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。

    Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。

    上面的 Dockerfile 正确的写法应该是这样:

    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

    首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对一一对应不同的命令,而是仅仅使用一个 RUN 指令,并使用&&将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。

    并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加的命令换行方式,以及行首#进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。

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

    3.4 构建镜像

    好了,让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 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

    从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 Step 2 中,如同我们之前所说的那样,RUN 指令启动了一个容器 9cdc27646c7b,执行了所要求的命令,并最后提交了这一层 44aa4490ce2c,随后删除了所用到的这个容器 9cdc27646c7b。这里我们使用了 docker build命令进行镜像构建。其格式为:

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

    在这里我们指定了最终镜像的名称 -t nginx:v3,构建成功后,我们可以像之前运行 nginx:v2 那样来运行这个镜像,其结果会和 nginx:v2 一样。

    3.5 镜像构建上下文(Context)

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

    首先我们要理解 docker build 的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

    当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?

    这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。如果在 Dockerfile 中这么写:

    COPY ./package.json /app/

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

    因此,COPY这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。

    现在就可以理解刚才的命令docker build -t nginx:v3 .中的这个.,实际上是在指定上下文的目录,docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。

    如果观察 docker build 输出,我们其实已经看到了这个发送上下文的过程:

    $ docker build -t nginx:v3 .
    Sending build context to Docker daemon 2.048 kB
    ...

    理解构建上下文对于镜像构建是很重要的,可以避免犯一些不应该的错误。比如有些初学者在发现 COPY /opt/xxxx /app 不工作后,于是干脆将 Dockerfile 放到了硬盘根目录去构建,结果发现 docker build 执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 docker build 打包整个硬盘,这显然是使用错误。

    一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个.dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

    那么为什么会有人误以为 . 是指定 Dockerfile 所在目录呢?这是因为在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。

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

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

    3.6 迁移镜像

    Docker 还提供了docker loaddocker save命令,用以将镜像保存为一个 tar 文件,然后传输到另一个位置上,再加载进来。这是在没有 Docker Registry 时的做法,现在已经不推荐,镜像迁移应该直接使用 Docker Registry,无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。

    使用docker save命令可以将镜像保存为归档文件。比如我们希望保存这个 alpine 镜像。

    $ docker image ls alpine
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    alpine              latest              baa5d63471ea        5 weeks ago         4.803 MB

    保存镜像的命令为:

    $ docker save alpine | gzip > alpine-latest.tar.gz

    然后我们将 alpine-latest.tar.gz 文件复制到了到了另一个机器上,可以用下面这个命令加载镜像:

    $ docker load -i alpine-latest.tar.gz
    Loaded image: alpine:latest

    如果我们结合这两个命令以及 ssh 甚至 pv 的话,利用 Linux 强大的管道,我们可以写一个命令完成从一个机器将镜像迁移到另一个机器,并且带进度条的功能:

    docker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> 'cat | docker load'



    4. 私有镜像仓库

    这节课给大家讲讲私有镜像仓库的使用。

    4.1 Docker Hub

    目前 Docker 官方维护了一个公共仓库Docker Hub,大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。如果你觉得拉取 Docker Hub 的镜像比较慢的话,我们可以配置一个镜像加速器:http://docker-cn.com/,当然国内大部分云厂商都提供了相应的加速器,简单配置即可。

    4.1.1 注册

    你可以在 https://cloud.docker.com 免费注册一个 Docker 账号。

    4.1.2 登录

    通过执行docker login命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。

    4.1.3 注销

    你可以通过docker logout退出登录。 拉取镜像

    4.1.4 拉取镜像

    你可以通过docker search命令来查找官方仓库中的镜像,并利用docker pull命令来将它下载到本地。

    例如以 centos 为关键词进行搜索:

    $ docker search centos
    NAME                                            DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
    centos                                          The official build of CentOS.                   465       [OK]
    tianon/centos                                   CentOS 5 and 6, created using rinse instea...   28
    blalor/centos                                   Bare-bones base CentOS 6.5 image                6                    [OK]
    saltstack/centos-6-minimal                                                                      6                    [OK]
    tutum/centos-6.4                                DEPRECATED. Use tutum/centos:6.4 instead. ...   5                    [OK]

    可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、收藏数(表示该镜像的受关注程度)、是否官方创建、是否自动创建。

    官方的镜像说明是官方项目组创建和维护的,automated资源允许用户验证镜像的来源和内容。

    根据是否是官方提供,可将镜像资源分为两类。

    • 一种是类似 centos 这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。
    • 还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀username/来指定使用某个用户提供的镜像,比如 tianon 用户。

    另外,在查找的时候通过--filter=stars=N参数可以指定仅显示收藏数量为 N 以上的镜像。下载官方 centos 镜像到本地。

    $ docker pull centos
    Pulling repository centos
    0b443ba03958: Download complete
    539c0211cd76: Download complete
    511136ea3c5a: Download complete
    7064731afe90: Download complete

    4.1.5 推送镜像

    用户也可以在登录后通过docker push命令来将自己的镜像推送到 Docker Hub。以下命令中的 username 请替换为你的 Docker 账号用户名。

    $ docker tag ubuntu:17.10 username/ubuntu:17.10
    $ docker image ls
    
    REPOSITORY                                               TAG                    IMAGE ID            CREATED             SIZE
    ubuntu                                                   17.10                  275d79972a86        6 days ago          94.6MB
    username/ubuntu                                          17.10                  275d79972a86        6 days ago          94.6MB
    $ docker push username/ubuntu:17.10
    $ docker search username
    
    NAME                      DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
    username/ubuntu

    4.2 私有仓库

    有时候使用 Docker Hub 这样的公共仓库可能不方便,用户可以创建一个本地仓库供私人使用。

    docker-registry是官方提供的工具,可以用于构建私有的镜像仓库。本文内容基于 docker-registry v2.x 版本。你可以通过获取官方 registry 镜像来运行。

    $ docker run -d -p 5000:5000 --restart=always --name registry registry

    这将使用官方的registry镜像来启动私有仓库。默认情况下,仓库会被创建在容器的/var/lib/registry目录下。你可以通过 -v 参数来将镜像文件存放在本地的指定路径。例如下面的例子将上传的镜像放到本地的 /opt/data/registry 目录。

    $ docker run -d 
        -p 5000:5000 
        -v /opt/data/registry:/var/lib/registry 
        registry

    4.2.1 在私有仓库上传、搜索、下载镜像

    创建好私有仓库之后,就可以使用docker tag来标记一个镜像,然后推送它到仓库。例如私有仓库地址为 127.0.0.1:5000。先在本机查看已有的镜像。

    $ docker image ls
    REPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    ubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB

    使用docker tag将 ubuntu:latest 这个镜像标记为 127.0.0.1:5000/ubuntu:latest。 格式为 docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]

    $ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
    $ docker image ls
    REPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    ubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB
    127.0.0.1:5000/ubuntu:latest      latest              ba5877dc9bec        6 weeks ago         192.7 MB

    使用docker push上传标记的镜像。

    $ docker push 127.0.0.1:5000/ubuntu:latest
    The push refers to repository [127.0.0.1:5000/ubuntu]
    373a30c24545: Pushed
    a9148f5200b0: Pushed
    cdd3de0940ab: Pushedfc56279bbb33: Pushed
    b38367233d37: Pushed
    2aebd096e0e2: Pushed
    latest: digest: sha256:fe4277621f10b5026266932ddf760f5a756d2facd505a94d2da12f4f52f71f5a size: 1568

    curl查看仓库中的镜像。

    $ curl 127.0.0.1:5000/v2/_catalog
    {"repositories":["ubuntu"]}

    这里可以看到 {"repositories":["ubuntu"]},表明镜像已经被成功上传了。

    先删除已有镜像,再尝试从私有仓库中下载这个镜像。

    $ docker image rm 127.0.0.1:5000/ubuntu:latest
    
    $ docker pull 127.0.0.1:5000/ubuntu:latest
    Pulling repository 127.0.0.1:5000/ubuntu:latest
    ba5877dc9bec: Download complete
    511136ea3c5a: Download complete
    9bad880da3d2: Download complete
    25f11f5fb0cb: Download complete
    ebc34468f71d: Download complete
    2318d26665ef: Download complete
    
    $ docker image ls
    REPOSITORY                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    127.0.0.1:5000/ubuntu:latest       latest              ba5877dc9bec        6 weeks ago         192.7 MB

    4.3 注意事项

    如果你不想使用 127.0.0.1:5000 作为仓库地址,比如想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如 192.168.199.100:5000 这样的内网地址作为私有仓库地址,这时你会发现无法成功推送镜像。

    这是因为 Docker 默认不允许非 HTTPS 方式推送镜像。我们可以通过 Docker 的配置选项来取消这个限制。

    4.3.1 Ubuntu 14.04, Debian 7 Wheezy

    对于使用 upstart 的系统而言,编辑/etc/default/docker文件,在其中的DOCKER_OPTS中增加如下内容:

    DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com --insecure-registries=192.168.199.100:5000"

    重新启动服务:

    $ sudo service docker restart

    4.3.2 Ubuntu 16.04+, Debian 8+, centos 7

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

    {
      "registry-mirror": [
        "https://registry.docker-cn.com"
      ],
      "insecure-registries": [
        "192.168.199.100:5000"
      ]
    }

    注意:该文件必须符合json规范,否则 Docker 将不能启动。

    4.3.3 其他

    对于 Docker for Windows、Docker for Mac 在设置中编辑daemon.json增加和上边一样的字符串即可。

    5. 数据共享与持久化

    这一节介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:

    • 数据卷(Data Volumes)
    • 挂载主机目录 (Bind mounts)

    5.1 数据卷

    数据卷是一个可供一个或多个容器使用的特殊目录,它绕过UFS,可以提供很多有用的特性:

    • 数据卷 可以在容器之间共享和重用
    • 对 数据卷 的修改会立马生效
    • 对 数据卷 的更新,不会影响镜像
    • 数据卷 默认会一直存在,即使容器被删除

    注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷。

    选择 -v 还是 -–mount 参数: Docker 新用户应该选择--mount参数,经验丰富的 Docker 使用者对-v或者 --volume已经很熟悉了,但是推荐使用--mount参数。

    创建一个数据卷:

    $ docker volume create my-vol

    查看所有的 数据卷:

    $ docker volume ls
    local               my-vol

    在主机里使用以下命令可以查看指定 数据卷 的信息

    $ docker volume inspect my-vol
    [
        {
            "Driver": "local",
            "Labels": {},
            "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
            "Name": "my-vol",
            "Options": {},
            "Scope": "local"
        }
    ]

    启动一个挂载数据卷的容器:在用docker run命令的时候,使用--mount标记来将 数据卷 挂载到容器里。在一次docker run中可以挂载多个 数据卷。下面创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录。

    $ docker run -d -P 
        --name web 
        # -v my-vol:/wepapp 
        --mount source=my-vol,target=/webapp 
        training/webapp 
        python app.py

    查看数据卷的具体信息:在主机里使用以下命令可以查看 web 容器的信息

    $ docker inspect web
    ...
    "Mounts": [
        {
            "Type": "volume",
            "Name": "my-vol",
            "Source": "/var/lib/docker/volumes/my-vol/_data",
            "Destination": "/app",
            "Driver": "local",
            "Mode": "",
            "RW": true,
            "Propagation": ""
        }
    ],
    ...

    删除数据卷:

    $ docker volume rm my-vol

    数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用docker rm -v这个命令。 无主的数据卷可能会占据很多空间,要清理请使用以下命令

    $ docker volume prune

    5.2 挂载主机目录

    选择 -v 还是 -–mount 参数: Docker 新用户应该选择 --mount 参数,经验丰富的 Docker 使用者对 -v 或者 --volume 已经很熟悉了,但是推荐使用 --mount 参数。

    挂载一个主机目录作为数据卷:使用 --mount 标记可以指定挂载一个本地主机的目录到容器中去。

    $ docker run -d -P 
        --name web 
        # -v /src/webapp:/opt/webapp 
        --mount type=bind,source=/src/webapp,target=/opt/webapp 
        training/webapp 
        python app.py

    上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,以前使用 -v 参数时如果本地目录不存在 Docker 会自动为你创建一个文件夹,现在使用 --mount 参数时如果本地目录不存在,Docker 会报错。

    Docker 挂载主机目录的默认权限是 读写,用户也可以通过增加readonly指定为 只读。

    $ docker run -d -P 
        --name web 
        # -v /src/webapp:/opt/webapp:ro 
        --mount type=bind,source=/src/webapp,target=/opt/webapp,readonly 
        training/webapp 
        python app.py

    加了readonly之后,就挂载为 只读 了。如果你在容器内 /opt/webapp 目录新建文件,会显示如下错误:

    /opt/webapp # touch new.txt
    touch: new.txt: Read-only file system

    查看数据卷的具体信息:在主机里使用以下命令可以查看 web 容器的信息

    $ docker inspect web
    ...
    "Mounts": [
        {
            "Type": "bind",
            "Source": "/src/webapp",
            "Destination": "/opt/webapp",
            "Mode": "",
            "RW": true,
            "Propagation": "rprivate"
        }
    ],

    挂载一个本地主机文件作为数据卷:--mount标记也可以从主机挂载单个文件到容器中

    $ docker run --rm -it 
       # -v $HOME/.bash_history:/root/.bash_history 
       --mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history 
       ubuntu:17.10 
       bash
    
    root@2affd44b4667:/# history
    1  ls
    2  diskutil list

    这样就可以记录在容器输入过的命令了。

    6. Docker 的网络模式

    6.1 Bridge模式

    Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。从docker0子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备,Docker 将 veth pair 设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到 docker0 网桥中。可以通过brctl show命令查看。

    bridge模式是 docker 的默认网络模式,不写–net参数,就是bridge模式。使用docker run -p时,docker 实际是在iptables做了DNAT规则,实现端口转发功能。可以使用iptables -t nat -vnL查看。bridge模式如下图所示:​​

    演示:

    $ docker run -tid --net=bridge --name docker_bri1 
                 ubuntu-base:v3
                 docker run -tid --net=bridge --name docker_bri2 
                 ubuntu-base:v3 
    
    $ brctl show
    $ docker exec -ti docker_bri1 /bin/bash
    $ ifconfig –a
    $ route –n

    如果你之前有 Docker 使用经验,你可能已经习惯了使用--link参数来使容器互联。

    随着 Docker 网络的完善,强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 --link 参数。

    下面先创建一个新的 Docker 网络。

    $ docker network create -d bridge my-net

    -d参数指定 Docker 网络类型,有 bridge overlay。其中 overlay 网络类型用于 Swarm mode,在本小节中你可以忽略它。

    运行一个容器并连接到新建的 my-net 网络

    $ docker run -it --rm --name busybox1 --network my-net busybox sh

    打开新的终端,再运行一个容器并加入到 my-net 网络

    $ docker run -it --rm --name busybox2 --network my-net busybox sh

    再打开一个新的终端查看容器信息

    $ docker container ls
    
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    b47060aca56b        busybox             "sh"                11 minutes ago      Up 11 minutes                           busybox2
    8720575823ec        busybox             "sh"                16 minutes ago      Up 16 minutes                           busybox1

    下面通过 ping 来证明 busybox1 容器和 busybox2 容器建立了互联关系。 在 busybox1 容器输入以下命令

    / # ping busybox2
    PING busybox2 (172.19.0.3): 56 data bytes
    64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms
    64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.118 ms

    用 ping 来测试连接 busybox2 容器,它会解析成 172.19.0.3。 同理在 busybox2 容器执行 ping busybox1,也会成功连接到。

    / # ping busybox1
    PING busybox1 (172.19.0.2): 56 data bytes
    64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.064 ms
    64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.143 ms

    这样,busybox1 容器和 busybox2 容器建立了互联关系。

    如果你有多个容器之间需要互相连接,推荐使用Docker Compose

    6.2 Host 模式

    如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。 Host模式如下图所示:

    ​​

    演示:

    $ docker run -tid --net=host --name docker_host1 ubuntu-base:v3
    $ docker run -tid --net=host --name docker_host2 ubuntu-base:v3
    
    $ docker exec -ti docker_host1 /bin/bash
    $ docker exec -ti docker_host1 /bin/bash
    
    $ ifconfig –a
    $ route –n

    6.3 Container 模式

    这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。 Container模式示意图:​​

    演示:

    $ docker run -tid --net=container:docker_bri1 
                  --name docker_con1 ubuntu-base:v3
    
    $ docker exec -ti docker_con1 /bin/bash
    $ docker exec -ti docker_bri1 /bin/bash
    
    $ ifconfig –a
    $ route -n

    6.4 None模式

    使用none模式,Docker 容器拥有自己的 Network Namespace,但是,并不为Docker 容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等。 None模式示意图: ​​演示:

    $ docker run -tid --net=none --name 
                    docker_non1 ubuntu-base:v3
    
    $ docker exec -ti docker_non1 /bin/bash
    
    $ ifconfig –a
    $ route -n

    Docker 的跨主机通信我们这里就先暂时不讲解,我们在后面的Kubernetes课程当中会用到。






  • 相关阅读:
    search for a range
    search in rotated sorted array
    MSDN--Windows Server
    Windows Server 2016- Windows Server 2016 上 HYPER-V 中的新增功能
    Azure 媒体服务产品简介
    EMC Avamar Virtual Edition 7.1.1 for Microsoft Azure and Hyper-V Released
    漫游用户配置文件 -文件夹重定向
    How to Install office 365 Plus without ODT
    Azure IoT
    AD FS deployment in Azure
  • 原文地址:https://www.cnblogs.com/sandshell/p/11662067.html
Copyright © 2011-2022 走看看