Docker 基础

1. Docker 简介

1.1 什么是 Docker?

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

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

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

1.2 为啥要用容器?

应用容器是个啥样子呢,一个作好的应用容器长得就像一个装好了一组特定应用的虚拟机同样,好比我如今想用 Redis,那我就找个装好了 Redis 的容器就能够了,而后运行起来,我就能直接使用了。node

那为何不能直接安装一个 Redis 呢?确定是可行的,可是有的时候根据每一个人电脑的不一样,在安装的时候可能会报出各类各样的错误,万一你的机器中毒了,你的电脑挂了,你全部的服务都须要从新安装。可是有了 Docker 或者说有了容器就不同了,你就至关于有了一个能够运行起来的虚拟机,只要你能运行容器,Redis 的配置就省了。并且若是你想换个电脑,没问题,很简单,直接把容器”端过来”就可使用容器里面的服务了。python

1.3 Docker Engine

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

  • 常驻后台进程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套接字或网络接口进行通讯。 ​​linux

  • 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/根据提示安装便可。nginx

安装依赖软件包:git

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

 

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

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

 

而后直接安装便可:web

$ 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课程当中会用到。

相关文章
相关标签/搜索