IT学习笔记--Docker

Docker基本概念:

Docker是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。

为什么要用Docker:

  • Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 "这段代码在我机器上没问题啊" 这类问题;------一致的运行环境
  • 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。------更快速的启动时间
  • 避免公用的服务器,资源会容易受到其他用户的影响。------隔离性
  • 善于处理集中爆发的服务器使用压力;------弹性伸缩,快速扩展
  • 可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。------迁移方便
  • 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。------持续交付和部署

镜像(Image)

操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而Docker 镜像(Image),就相当于是一个 root 文件系统。

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

Docker 设计时,就充分利用 Union FS的技术,将其设计为 分层存储的架构 。 镜像实际是由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

容器(Container)

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

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储,容器也是如此。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据 ,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器可以随意删除、重新 run ,数据却不会丢失。

仓库(Repository)

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

一个 Docker Registry中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:镜像仓库是Docker用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。

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

Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。

最常使用的 Registry 公开服务是官方的 Docker Hub ,这也是默认的 Registry,并拥有大量的高质量的官方镜像,网址为:https://hub.docker.com/ 。在国内访问Docker Hub 可能会比较慢国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 时速云镜像库、网易云镜像服务、DaoCloud 镜像市场、阿里云镜像库等。

除了使用公开服务外,用户还可以在 本地搭建私有 Docker Registry 。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。

Docker命令管理:

镜像管理:

  • 查看本地镜像列表: docker images
  • 镜像搜索: docker search xxx
  • 镜像下载: docker pull xxx
  • 删除镜像: docker rmi -f [IMAGE_ID|REPOSITORY]

容器管理:

  • 新建容器并启动: docker run [可选参数] images

#参数说明

--name="Name" 容器名称,用于区分容器

-d 后台方式启动

-it 使用交互方式运行

-p 指定容器端口

-p 主机端口:容器端口(常用)

-p 容器命令

-P 随机指定端口

--rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。

  • 列举正在运行的容器: docker ps
  • 列举所有的容器: docker ps -a
  • 关闭容器: docker stop [容器名|容器ID]
  • 启动容器: docker start [容器名|容器ID]
  • 重启容器: docker restart [容器名|容器ID]
  • 删除容器: docker rm [-f] [容器名|容器ID] #-f强制删除正在运行的容器
  • 重命名容器: docker rename old_name new_name

其他常用命令:

  • 查看容器日志: docker logs -tf [容器名|容器ID]

-t:显示时间戳

-f:实时响应日志输出

--tail number:显示末尾N行

  • 查看容器中的进程信息: docker top [容器名|容器ID]
  • 进入正在运行的容器: docker exec -it [容器ID] [shell]

-it 表示以交互方式运行

shell参数用于指明以容器中的哪个shell为基础运行

  • 附着到容器:

Docker容器重新启动的时候,会沿用docker run命令时的参数来运行,因此我们容器重新启动后会运行一个交互式会话Shell。此外,我们可以使用docker attach命令重新附着到该容器的会话上:

$ docker attach [容器名|容器ID]

docker attach:进入容器正在执行的终端,不会启动新的进程。

  • 从容器中拷贝文件到宿主机中:docker cp [容器id]:[容器内路径] [目的主机路径]
  • 从宿主机中拷贝文件到容器中:docker cp [目的主机路径] [容器id]:[容器内路径]
  • 查看容器元数据: docker inspect [容器名|容器ID]

容器的网络端口映射:

有些Docker容器创建后会向外暴露端口,例如Nginx会暴露80,但是容器是隔离的环境,那么在创建时我们可以使用-P(大写)随机将容器80端口与本机端口进行绑定:

$ docker run -d -P --name nginx nginx:latest

我们也可以使用 -p(小写) 标识来指定容器端口绑定到主机端口,格式为:主机(宿主)端口:容器端口。

docker run -d -p 8080:80 --name nginx nginx:latest

上述命令的作用是将容器的80端口绑定到本机的8080端口,此时我们在本机中即可通过localhost:8080来访问容器中运行的Nginx。

注意:上面的例子中,默认都是绑定 tcp 端口,如果要绑定 UDP 端口,可以在端口后面加上 /udp。

$ docker run -d -p 8080:80/udp --name nginx nginx:latest

另外,我们可以指定容器绑定的网络地址,比如绑定 127.0.0.1。

$ docker run -d -p 127.0.0.1:8080:80 --name nginx nginx:latest

容器数据卷:

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

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

注意:数据卷 的使用,类似于 Linux 下对目录或文件进行挂载,镜像中的被指定为挂载点的目录中的文件会复制到数据卷中(仅数据卷为空时会复制)。

创建一个数据卷:

$ docker volume create my-vol

查看所有的数据卷: docker volume ls

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

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

创建一个数据卷默认的主机路径是/var/lib/docker/volumes/*

删除数据卷:

$ docker volume rm my-vol

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

无主的数据卷可能会占据很多空间,要清理请使用以下命令: docker volume prune

挂载数据卷:

挂载一个已经存在的卷:

在用 docker run 命令的时候,使用 --mount 标记来将数据卷挂载到容器里。在一次 docker run 中可以挂载多个数据卷。

下面创建一个名为web的容器,并加载一个数据卷到容器的 /usr/share/nginx/html 目录。

bash 复制代码
$ docker run -d -P \
    --name web \
    # --mount source=my-vol,target=/usr/share/nginx/html \
    -v my-vol:/usr/share/nginx/html \
    nginx:alpine

创建一个卷并挂载到容器中:

bash 复制代码
$ docker run -d -P \
    --name web \
    #--mount type=bind,source=/src/webapp,target=/usr/share/nginx/html \
    -v /src/webapp:/usr/share/nginx/html \
    nginx:alpine

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

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

-v /src/webapp:/usr/share/nginx/html:ro

ro代表readonly只读权限,在容器内部是无法更改这个目录下的文件的,只能通过修改容器挂载的目录进行修改,docker默认权限时rw,可读可写加了 readonly 之后,就挂载为只读了。

具名挂载和匿名挂载:

  • 匿名名挂载:-v [容器内路径]
  • 具名挂载:-v [卷名]:[容器内路径]
  • 指定路径挂载:-v [主机路径]:[容器内路径]

匿名挂载时会自动生成一个名称。

Dockerfile:

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

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

Dockerfile指令:

一般的,Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。

|------------|--------------|---------------------------------------------------------------------------------------------------|
| 关键字 | 作用 | 备注 |
| FROM | 指定父镜像 | 指定dockerfile基于哪个image构建 |
| MAINTAINER | 作者信息 | 用来标明这个dockerfile谁写的 |
| LABEL | 标签 | 用来标明dockerfile的标签。可以使用Label代替Maintainer最终都是在docker image基本信息中可以查看 |
| RUN | 执行命令 | 执行一段命令,默认是/bin/sh,格式: RUN command或者RUN ["command","param1L ","param2"] |
| CMD | 容器启动命令 | 提供启动容器时候的默认命令,和ENTRYPOINT配合使用.格式CMD command param1 param2或者CMD ["command" , "param1", "param2"] |
| COPY | 复制文件 | build的时候复制本地文件到image中 |
| ADD | 添加文件 | build的时候添加文件到image中,可自动解压缩,不仅仅局限于当前build上下文,可以来源于远程服务 |
| EXPOSE | 暴露端口 | 定义容器运行的时候监听的端口,启动容器的使用-p来绑定暴露端口,格式:EXPOSE 8080或者EXPOSE 8080/udp |
| WORKDIR | 工作目录 | 指定容器内部的工作目录,如果没有创建则自动创建,如果指定"/",使用的是绝对地址,从根目录开始,如果不是"/"开头,那么是在上一条workdir的路径的相对路径 |
| ENV | 环境变量 | 指定build时候的环境变量,可以在启动容器的时候通过-e覆盖,格式ENVname=value |
| ARG | 构建参数 | 只在构建的时候使用的参数,如果有ENV,那么ENV的相同名字的值始终覆盖arg的参数 |
| VOLUME | 定义外部可以挂载的数据卷 | 指定build的image哪些目录可以启动的时候挂载到文件系统中,启动容器的时候使用-v绑定,格式VOLUME ["目录"] |
| USER | 指定执行用户 | 指定build或者启动的时候,在RUN CMD ENTRYPONT执行的时候的用户 |

FROM 指定基础镜像:

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。基础镜像是必须指定的。而FROM 就是指定 基础镜像。

  • FROM 是必备的指令,并且必须是第一条指令。
  • FROM指定构建镜像的基础源镜像,如果本地没有指定的镜像,则会自动从 Docker 的公共库 pull 镜像下来。
  • FROM可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像。
  • 如果FROM语句没有指定镜像标签,则默认使用latest标签。

RUN 执行命令:

RUN 指令是用来执行命令行命令的。每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像,后续的RUN都在之前RUN提交后的镜像为基础。由于命令行的强大能力,RUN指令在定制镜像时是最常用的指令之一。其格式有两种:

shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。

exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。

Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。

COPY 复制文件:

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

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

COPY [--chown=<user>:<group>] <源路径>... <目标路径>

COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

文件复制准则:

  • <源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则.
  • <源路径>必须是build的上下文的路径不能是其父目录;<目标路径>是容器中的绝对路径,或者是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。
  • 如果<源路径>是目录,则其内部文件或子目录会被递归复制,但<源路径>目录自身不会复制
  • 如果指定了多个<源路径>,或在<源路径>中使用了通配符,则<目标路径>必须是一个目录,且必须以/结尾。
  • 如果<目标路径>事先不存在,它将会被自动创建,这包括其父目录。
  • 使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。

在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。

ADD 更高级的复制文件:

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

比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。

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

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

在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。

另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

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

在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。

CMD 容器启动命令:

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

  • shell 格式:CMD <命令>
  • exec 格式:CMD ["可执行文件", "参数1", "参数2"...]

参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用于为ENTRYPOINT提供参数。

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

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

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

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

ENTRYPOINT 入口点:

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

<ENTRYPOINT> "<CMD>"

ENTRYPOINT 在运行时也可以替代,需要通过 docker run 的参数 --entrypoint 来指定。

ENV设置环境变量:

格式有两种:

  • ENV <key> <value>
  • ENV <key1>=<value1> <key2>=<value2>...

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

ENV VERSION=1.0 DEBUG=on \

NAME="Happy Feet"

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

定义了环境变量,那么在后续的指令中,就可以使用这个环境变量:

bash 复制代码
FROM centos
LABEL author="Jindong.Tian" email="bigcoder84@gmail.com"
ENV TMP_DIR="tmp/"
COPY ${TMP_DIR} /opt/
EXPOSE 80/tcp

ARG 构建参数:

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

构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。

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

灵活的使用 ARG 指令,能够在不修改 Dockerfile 的情况下,构建出不同的镜像。

ARG 指令有生效范围,如果在 FROM 指令之前指定,那么只能用于 FROM 指令中。

bash 复制代码
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo ${DOCKER_USERNAME}

使用上述 Dockerfile 会发现无法输出 ${DOCKER_USERNAME} 变量的值,要想正常输出,你必须在 FROM 之后再次指定 ARG:

bash 复制代码
# 只在 FROM 中生效
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# 要想在 FROM 之后使用,必须再次指定
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}

VOLUME 定义匿名卷:

格式为:

  • VOLUME ["<路径1>", "<路径2>"...]
  • VOLUME <路径>

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

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

docker run -d -v mydata:/data xxxx

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

EXPOSE 暴露端口:

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

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

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

WORKDIR 指定工作目录:

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

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

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

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

bash 复制代码
WORKDIR /app
RUN echo "hello" > world.txt

如果你的 WORKDIR 指令使用的相对路径,那么所切换的路径与之前的 WORKDIR 有关:

bash 复制代码
WORKDIR /a
WORKDIR b
WORKDIR c

RUN pwd

pwd 输出的结果为 /a/b/c。

USER 指定当前用户:

格式:USER <用户名>[:<用户组>]

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

注意,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

bash 复制代码
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

LABEL 为镜像添加元数据:

LABEL 指令用来给镜像以键值对的形式添加一些元数据(metadata)。

LABEL <key>=<value> <key>=<value> <key>=<value> ...

我们还可以用一些标签来申明镜像的作者、文档地址等:

bash 复制代码
LABEL org.opencontainers.image.authors="yeasy"

LABEL org.opencontainers.image.documentation="https://yeasy.gitbooks.io"

ONBUILD 为他人作嫁衣裳:

格式:ONBUILD <其它指令>。

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。

MAINTAINER:

用于让Dockerfile制作者提供本人的联系信息。

语法:MAINTAINER 个人信息

bash 复制代码
MAINTAINER "bigcoder84@gmail.com"

该指令在新版本已被废弃,官方建议使用LABEL指令代替MAINTAINER。

构建镜像:

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

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

-t:构建镜像的标签名称(一般是仓库名称/镜像名称:版本的格式书写)

-f:指定Dockerfile的文件名称,在Dockerfile不是默认名称时使用。

在 Dockerfile 文件所在目录执行:

$ docker build -t nginx:v3 . #注意最后的点,表示在当前目录寻找Dockerfile

Docker网络原理:

Docker作为目前最火的轻量级容器技术,牛逼的功能,如Docker的镜像管理,不足的地方网络方面。

Docker自身的4种网络工作方式,和一些自定义网络模式。安装Docker时,它会自动创建三个网络,bridge(创建容器默认连接到此网络)、 none 、host。

  • host:容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
  • bridge:此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。
  • joined:创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围。
  • none:该模式关闭了容器的网络功能。

以上都是不用动手的,真正需要配置的是自定义网络。

默认网络:

当你安装Docker时,它会自动创建三个网络。你可以使用以下docker network ls命令列出这些网络:

bash 复制代码
$ docker network ls
NETWORK ID          NAME                DRIVER
7fca4eb8c647        bridge              bridge
9f904ee27bf5        none                null
cf03ee007fb4        host                host

Docker内置这三个网络,运行容器时,你可以使用该--network标志来指定容器应连接到哪些网络。

该bridge网络代表docker0所有Docker安装中存在的网络。除非你使用该docker run --network=选项指定,否则Docker守护程序默认将容器连接到此网络。

我们在使用docker run创建Docker容器时,可以用 --net 选项指定容器的网络模式,Docker可以有以下4种网络模式:

  • host模式:使用 --net=host 指定。
  • none模式:使用 --net=none 指定。
  • bridge模式:使用 --net=bridge 指定,默认设置。
  • container模式:使用 --net=container:[容器名称|容器ID] 指定。

四种网络模式:

Docker默认有四种网络模式:none模式、bridge模式、container模式、host模式。

这四种模式的开放度依次增高,在不影响基本使用的情况下应尽量选择开放度低的网络模式以保证数据安全。

none模式(closed):

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

bridged:

当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配IP了,Docker会从RFC1918所定义的私有IP网段中,选择一个和宿主机不同的IP地址和子网分配给docker0,连接到docker0的容器就从这个子网中选择一个未占用的IP使用。如一般Docker会使用172.17.0.0/16这个网段,并将172.17.0.1/16分配给docker0网桥(在主机上使用ifconfig命令是可以看到docker0的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)。单机环境下的网络拓扑如下,主机地址为10.10.0.186/24。

在bridge模式下,连在同一网桥上的容器可以相互通信(若出于安全考虑,也可以禁止它们之间通信,方法是在DOCKER_OPTS变量中设置--icc=false,这样只有使用--link才能使两个容器通信)。

Docker可以开启容器间通信(意味着默认配置--icc=true),也就是说,宿主机上的所有容器可以不受任何限制地相互通信,这可能导致拒绝服务攻击。进一步地,Docker可以通过--ip_forward和--iptables两个选项控制容器间、容器和外部世界的通信。

container模式(joined):

容器之间可以共享网络协议栈,即可以通过套接字来进行通信。

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

host模式(open):

Host模式使用是在容器启动时候指明--network host,此时容器共享宿主机的Network Namespace,容器内启动的端口直接是宿主机的端口,容器不会创建网卡和IP,直接使用宿主机的网卡和IP,但是容器内的其他资源是隔离的,如文件系统、用户和用户组。直接使用宿主机网络。同样启动一个nginx,此时共享主机网络,根据情况来使用,这样子也不用做端口转发,网络传输效率会比较高(思考一下为什么)。

例如,我们在10.10.0.186/24的机器上用host模式启动一个含有nginx应用的Docker容器,监听tcp80端口。

bash 复制代码
# 运行容器;
$ docker run --name=nginx_host --net=host -p 80:80 -d nginx
74c911272942841875f4faf2aca02e3814035c900840d11e3f141fbaa884ae5c
# 查看容器;
$ docker ps  
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
74c911272942        nginx        "nginx -g 'daemon ..."         25 seconds ago      Up 25 seconds                           nginx_host

当我们在容器中执行任何类似ifconfig命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用10.10.0.186:80即可,不用任何NAT转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

bash 复制代码
$ netstat -nplt | grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      27340/nginx: master

使用Link连接容器:

docker run --link可以用来链接2个容器,使得源容器(被链接的容器)和接收容器(主动去链接的容器)之间可以互相通信,并且接收容器可以获取源容器的一些数据,如源容器的环境变量。

--link的格式:

--link [容器名称|容器ID]:[别名]

例如:

源容器:创建并启动名为selenium_hub的容器。

bash 复制代码
docker run -d --name selenium_hub selenium/hub

接收容器:创建并启动名为node的容器,并把该容器和名为selenium_hub的容器链接起来。

bash 复制代码
docker run -d --name node --link selenium_hub:hub selenium/node-chrome-debug

其中:--link selenium_hub:hub

selenium_hub是上面启动的源容器的名字,hub是该容器在link下的别名(alias),通俗易懂的讲,站在node容器的角度,selenium_hub和hub都是源容器的名字,并且作为容器的hostname,node用这2个名字中的哪一个都可以访问到源容器并与之通信(docker通过DNS自动解析)。

--link的原理:

我们使用--link连接容器,实际上容器做了下面三件事:

  • 容器里添加了解析
  • 环境变量里设置了相应的地址和ip
  • 如果跨容器通讯被禁止了,Docker会添加特定的防火墙规则来允许被连接容器间的通讯。

下面启动两个容器:nginx 和 centos

两个容器的依赖关系如下:

链接的本质是静态的,具有方向性和无传递性的依赖。

无传递性:

现在我么再启动一个容器依赖于centos,发现新启动的容器是无法通过容器名连接web容器的:

具有方向性的

我们保持上图的依赖关系,进入到centos容器内部,发现是无法连接centos2的。

静态的

链接通过检测源容器(被链接的容器)的网络信息(IP地址和开放端口),然后将这些信息注入到新容器中(信息存入/etc/hosts环境变量中)

自定义网络:

可以根据需要创建任意数量的网络,并且可以在任何给定时间将容器连接到这些网络中的零个或多个网络。此外,您可以连接并断开网络中的运行容器,而无需重新启动容器。当容器连接到多个网络时,其外部连接通过第一个非内部网络以词法顺序提供。

创建自定义网络:

格式:docker network create [option] 名称

设置驱动类型:

我们可以通过 --driver指定网络的驱动类型,默认情况驱动类型为bridge。bridge网络是Docker中最常用的网络类型。桥接网络类似于默认bridge网络,但添加一些新功能并删除一些旧的能力。以下示例创建一些桥接网络,并对这些网络上的容器执行一些实验。

bash 复制代码
$ docker network create --driver bridge new_bridge

自定义网段和网关:

bash 复制代码
docker network create -d bridge --subnet 192.168.1.0/24 --gateway 192.168.1.1 my_bridge

自定义IP:

我们可以在容器启动时自定IP:

bash 复制代码
docker run -it --name centos2 --network my_bridge --ip 192.168.1.88 centos bash

注意:桥接在不同网桥上的容器无法直接通讯

将容器连接至网络中:

我们可以将正在运行的容器连接至指定网络:docker network connect [网络名] [容器名|容器ID]

bash 复制代码
$ docker network connect bridge centos1

可以看到一个容器是可以连接到多个网络上的。

内嵌DNS:

docker在1.10以后,都会内嵌一个DNSserver,同子网下的容器之间除了通过IP进行通信以外,还可以使用容器名称进行通讯。

由于自定义网络自带DNS解析,连接在同一个网络下的多个容器之间可以通过服务名直接连接:

其他网络常用命令:

  • 查看当前主机上的网络:docker network ls
  • 删除一个网络:docker rm [网络名称|网络ID]
  • 查看网络详情:docker network inspect [网络名称|网络ID]
  • 清除未使用的docker网络:docker network prune -f
相关推荐
爱吃西瓜的小菜鸡几秒前
【C语言】判断回文
c语言·学习·算法
dessler22 分钟前
Docker-run命令详细讲解
linux·运维·后端·docker
小A15923 分钟前
STM32完全学习——SPI接口的FLASH(DMA模式)
stm32·嵌入式硬件·学习
亦枫Leonlew30 分钟前
微积分复习笔记 Calculus Volume 2 - 5.1 Sequences
笔记·数学·微积分
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
qq_589568101 小时前
数据可视化echarts学习笔记
学习·信息可视化·echarts
爱码小白1 小时前
网络编程(王铭东老师)笔记
服务器·网络·笔记
陌北v11 小时前
Docker Compose 配置指南
运维·docker·容器·docker-compose
LuH11242 小时前
【论文阅读笔记】Learning to sample
论文阅读·笔记·图形渲染·点云