博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Docker学习总结
阅读量:7098 次
发布时间:2019-06-28

本文共 25775 字,大约阅读时间需要 85 分钟。

1. 什么是Docker

  Docker是一种虚拟化技术,其在容器的基础上进一步封装了文件系统、网络互联、进程隔离等等,从而极大地简化了容器的创建和维护。Docker使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,实现进程隔离、文件系统隔离、网络隔离等。Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces,如pid namespace、mount namespace、network namespace等分别用来隔离进程、文件系统、网络)、控制组(Control groups)、Union 文件系统(Union file systems)和容器格式(Container format)。

Docker很重要的作用之一:隔离进程——Docker容器内的进程无法看到宿主机以及其他Docker容器内的进程,这样可以防止一个进程被入侵了来恶意访问或破坏其他非本Docker容器内的进程。

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

           

 

 

2. Docker三个核心概念

镜像Image:

对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

容器Container:容器是以镜像为基础,再加一层容器存储层,组成多层存储结构去运行的。

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

容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器

仓库Repository:就是一个集中存储、分发镜像的服务,类似于Maven的Repository。

(注意与注册服务器Registry的区别,后者指管理仓库Repository的具体服务器。每个服务器上可以有多个仓库,每个仓库下面有多个镜像。Docker Hub、DaoCloud等就是Registry,也可以在本地大家Registry供团队间分享镜像)

3. Docker架构

Docker 采用了 C/S 架构,包括客户端和服务端。客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。

  • Docker 守护进程 (Daemon)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器)。Docker 守护进程一般在宿主主机后台运行。
  • Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker 守护进程交互。

Docker 镜像(Images)

Docker 镜像是用于创建 Docker 容器的模板。

Docker 容器(Container)

容器是独立运行的一个或一组应用。

Docker 客户端(Client)

Docker 客户端通过命令行或者其他工具使用 Docker API () 与 Docker 的守护进程通信。

Docker 主机(Host)

一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。

Docker 仓库(Registry)

Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。

Docker Hub() 提供了庞大的镜像集合供使用。

Docker Machine

Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。

4. 安装Docker

Docker分为社区版CE和企业版EE,前者免费。不同OS及同中OS的不同版本上安装Docker的方法各异,详见 Docker安装。

这里以Ubuntu16.04为例。

1. apt源使用HTTPS以确保软件下载过程中不被篡改,故先安装HTTPS传输及CA证书相关:

sudo apt-get updatesudo apt-get install \  apt-transport-https \  ca-certificates \  curl \  software-properties-common

2. 为确认所下载软件包的合法性,需添加软件源的GPG密钥: curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add - 

3. 向/etc/apt/source.list添加Docker软件源:

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

4. 更新apt软件包缓存,安装Docker CE: sudo apt-get update && sudo apt-get install docker-ce 

5. 启动Docker CE:

设置docker服务开机启动: sudo systemctl enable docker 

启动docker服务: sudo systemctl start docker 

6. 测试是否正确安装: sudo docker run hello-world ,该命令首次运行会从Docker仓库下载hello-world镜像,输出如下信息表示运行成功:

Unable to find image 'hello-world:latest' locallylatest: Pulling from library/hello-worldca4f61b1923c: Pull completeDigest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905cStatus: Downloaded newer image for hello-world:latestHello 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 bashShare 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/
View Code

7. 由于国内网络原因,拉取镜像会很慢,可以设置为国内仓库服务器:在 /etc/docker/daemon.json 添加如下内容(若文件不存在则先建之):

{  "registry-mirrors": [    "https://registry.docker-cn.com"  ]}

然后重启服务:

sudo systemctl daemon-reloadsudo systemctl restart docker

执行 docker info ,若看到 Registry Mirrors: https://registry.docker-cn.com/ 则说明修改成功。

8、普通用户加入Docker用户组 

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

建立Docker用户组(一般安装完Docker后就默认创建了): $ sudo groupadd docker 

将普通用户加入Docker用户组: $ sudo usermod -aG docker $USER 

退出当前终端并重新登录即可。

 

5. 镜像与容器操作

各命令具体用法可以通过 docker 命令 --help 或 docker help you_command 查看。下面稍做总结。

5.0 查找与推送镜像

查找示例: docker search centos ,会从镜像仓库中查找指定镜像。

推送示例: docker push yourhubusername/ubuntu:16.04 ,会推送到镜像服务器。不过需要事先注册镜像服务器账号,且只能传到自己账号的下面,因此需将待传的镜像标签改为账号前缀。详见:

5.1 获取镜像

命令格式: docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签] ,如 $ docker pull ubuntu:16.04 。不写标签的话默认是latest,即最新版。在 Docker 1.13+ 版本中推荐使用 docker image 来管理镜像。

此外,也可以通过 docker import 导入容器,会创建相应的镜像。

具体可参见:获取镜像

5.2 容器创建与容器操作

运行示例: $ docker run -it --rm ubuntu:16.04 bash ,表示在终端交互式操作、容器退出后删除掉,运行的是linux下的bash,若不指定运行的命令则默认运行/bin/bash;可以指定运行其他命令,如: docker run -it ubuntu cat /etc/os-release 

另一示例:  docker run --label version=1.5 --name zsmserver -d -p 80:80 nginx , #从镜像nginx启动名为zsmserver的容器,监听80短口,若镜像不存在则会下载 。-p参数指定本机端口映射到容器的哪些端口,其本质是在宿主机iptable的nat表添加相应的规则;--label用于给容器加一些metadata如license、vendor等。通过docker inspect 可以看到这些信息。

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

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

注:容器隔离指的是容器之间互相看不到对方容器内的进程。容器启动后,在宿主机 ps 是可以看到容器内的进程的。

5.2.1 关于后台运行

可以通过-d参数让Docker在后台运行(守护态运行)而不是把命令执行结果输出到当前宿主主机下(即这里的后台是相对于宿主主机而言的);但是,容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。

当以后台模式(-d)运行 run Dokcer容器时,容器内必须有一个前台进程(如top、tail等)否则容器就会结束退出(如service nginx start默认以后台模式运行),因为Docker觉得没事可做了。其本质原因是:docker 容器默认会把容器内部第一个进程,也就是pid=1的程序作为docker容器是否正在运行的依据,若该程序结束了则认为docker 容器挂了,从而docker容器便会直接退出,docker run的时候正是把command作为容器内部命令。

因此,要想避免容器运行就退出,可以在多个命令的最后加上top等命令,如  service nginx start && top  。详见: 

5.2.2 其他容器操作命令

docker help your_command  或 docker your_command --help  #查看命令详细用法 docker cp    xxx    xxx                  #files/folders between a container and the local filesystem docker run 镜像名 [运行的程序命令及参数]     #从镜像新建容器并启动,若镜像不存在则先下载docker ps -a                             #查看所有容器,包括正在运行的、已终止的等docker container ls #查看正在运行的容器docker [container] start/stop/restart containerid_or_name     #启动/停止/重启 已存在的容器docker logs containerid_or_name           #获取后台运行的容器的输出 docker exec -it containerid 命令          #重新进入后台运行的容器,也可以通过命令 docker attach containerid 进入,但这种方式下进入再退出时容器会跟着终止,所以推荐用exec方式。 docker inspect containerid_or_name     #查看容器信息。为了查看容器ip,可以:docker inspect --format '{
{ .NetworkSettings.IPAddress }}' containerid_or_name docker [container] rm [-f -v] containerid #删除指定的容器。-f参数可删除运行中的容器 Docker会发SIGKILL信号给容器、-v参数删除容器关联的数据卷 docker container prune #删除所有终止状态的容器,也可以 docker rm $(docker ps -aq),括号里的命令功能是获取所有container的id docker tag src_image_name new_image_name #为镜像重命名,如docker tag ubuntu:1.0 zsm/ubuntu:1.0 docker network create/connnect/disconnect/inspect/ls/prune/rm #网络管理

 

5.3 查看镜像

列出已下载镜像: docker images 或 docker image ls 。

  • 显示的镜像大小是解压后的,官网Docker Hub上显示的则是压缩了的;
  • 由于镜像是分层存储的,可以继承使用,因此总占用空间并不是各镜像占用大小的和;
  • 该命令列出顶层镜像,如果要列出包括中间层镜像的所有,可以加-a参数。可以看出,这里的ls跟Linux下的ls命令用法类似,可以部分匹配等、还支持强大的过滤器参数,具体可输入help命令查看。如列出redis镜像的镜像ID: docker image ls -q redis 

查看镜像、容器、数据卷所占用的空间: docker system df ,结果如下:

5.4 移除虚悬镜像

命令: docker image prune 

5.5 删除本地镜像

命令: docker image rm [选项] <镜像1> [<镜像2> ...] 或 docker rmi [选项] <镜像1> [<镜像2> ...] ,这里的镜像标识可以是ID、镜像名、镜像摘要等。

  • 删除镜像时实际上是在要求删除某个标签的镜像,由于一个镜像可对应多个标签,因此若删除指定标签的镜像后仍有其他标签指向该镜像则实际上不会删除该镜像,而只是标记指定标签被删除。
  • 同样地,对于要删除的镜像,若有从该镜像启动的容器存在,则即使容器没在运行,该镜像也不会被删除。

5.6 定制docker镜像

可以通过commit命令(具体参见利用-commit-理解镜像构成),但是不推荐,因为会产生不必要的修改使得镜像臃肿;

通常通过Dockerfile定制镜像。文件Dockerfile的内容示例如下:

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

RUN指令用来执行命令行命令:

  • 可以是shell 格式: RUN <命令> ,像直接在命令行输入命令一样,如 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html ;
  • 也可以是exec 格式: RUN ["可执行文件", "参数1", "参数2"] ,像是函数调用中的格式。

Dockerfile 中每一个指令都会建立一层新存储层, RUN指令也不例外(即每一个RUN指令都对应一个新容器。将包括如下指令的Dockerfile构建运行后会提示找不到txt文件就是因为这个原因,若在中间加上WORKDIR则没问题),并且Docker中有层数的限制。比较好的做法是用一个RUN指令和多个&&将多个命令连接起来。

RUN cd /app # WORKDIR /appRUN echo "hello" > world.txt

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

5.7 构建镜像

(具体可参见 镜像构建上下文 )

写了上节所述的Dockerfile后,执行命令生成Docker镜像: docker build [选项] <上下文路径/URL/-> 。假设Dockerfile在./dockertest/下,则命令为: Docker build -t zsmserver ./dockertest  

  • docker build的工作原理:Docker在运行时分为 Docker 引擎(即服务端守护进程)和客户端工具。Docker 引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,docker build 命令构建镜像其实并非在本地构建,虽表面上像是在本机执行各种 docker 功能,但实际上一切都是使用的远程调用形式在服务端(Docker 引擎)完成。
  • 关于上下文路径:进行镜像构建时并非所有定制都会通过 RUN 指令完成,经常需将一些本地文件复制进镜像,如通过 COPY 指令、ADD 指令等,这些指令指定的路径参数以给定的上下文路径作为根目录。
  • Docker构建实际上是把当前上下文下的文件打包上传到服务端,在服务端进行构建,服务端以上传上来的该文件夹为上下文(即根目录)
  • 一般将Dockerfile置于空目录或项目根目录,若不希望目录下的一些东西构建时传给Docker引擎,可以用与Git的.gitignore一样的语法写个.dockerignore文件加以剔除。
  • 构建命令中并没有指定Dockerfile的名字,服务端构建时默认会查找名字为“Dockerfile”的文件进行构建,也可以通过-f参数指定Dockerfile文件。如: docker build -t zsmnginx -f MyDockerFile2 . 

其他构建方式(详见其他Docker Build方法):

  • 直接用 Git repo 进行构建,要求repo内有Dockerfile
  • 用给定的 tar 压缩包构建,要求压缩包内有Dockerfile
  • 从标准输入中读取 Dockerfile 进行构建
  • 从标准输入中读取上下文压缩包进行构建等

5.8容器和镜像的保存与恢复

(更多可参考 )

docker save / load 用于保存与恢复镜像

save时参数可以是镜像或容器,参数为容器时实际保存的仍是容器对应的镜像而不是容器

可以指定多个镜像名以将多个镜像保存到一个压缩文件中

适用场景:如要部署的客户服务器不能连外网,此时可以先将若干个镜像打包,再复制到客户服务器上并通过 docker load 导入。

示例:

docker save -o images.tar postgres:9.6 mongo:3.4  # 将两个镜像打包为images.tar文件。亦可 docker save postgres:9.6 mongo:3.4 > images.tardocker load -i images.tar   # 从文件中恢复镜像

docker export / import 用于将容器(的文件系统)打包为文件、从文件恢复为一个镜像

import时可以为镜像指定新名称

适用场景:制作基础镜像。如从一个ubuntu镜像启动一个容器,然后安装一些软件和进行一些设置后,使用docker export保存为一个基础镜像,就可把这个镜像分发给其他人使用,如作为基础的开发环境。

示例:

docker export -o zsmredis.tar redis_sensestudy  # 将容器打包成tar文件。亦可 docker export redis_sensestudy > zsmredis.tardocker import zsmredis.tar  myredis:latest # 从文件导入为镜像 docker import saved_container_filename_or_url_image_name

 :docker load 和 docker import 的区别在于后者将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

6. Dockerfile指令

(详见Dockerfile指令详解)

在一个Dockerfile中,CMD、ENTRYPOINT、HEALTHCHECK等只可出现一次,若出现多次则只有最后一次生效。

6.1 COPY

格式: COPY <源路径>... <目标路径> 或 COPY ["<源路径1>",... "<目标路径>"] 。如: COPY package.json zh* /usr/src/app/ 

作用:从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置,复制时源文件的各种元数据都会保留。

注意:若被复制的文件是个目录,则不会复制该目录自身,只会复制其下的文件或目录,如  COPY ./* ./ ,若 ./ 下有doc目录,则只会复制doc下的文件或目录而不会复制doc。解决:改为 copy . ./ 

6.2 ADD

与COPY类似,只不过源文件可以为URL或压缩包,会自动下载或解压。推荐优先使用COPY

6.3 CMD

作用:指定容器启动时的默认执行程序。上面示例 docker run -it ubuntu cat /etc/os-release 指定启动时显示系统信息,该默认操作可通过Dockerfile里的CMD指令指定(注:若在启动容器时指定默认执行程序,则会覆盖Dockerfile里CMD指定的

格式: shell 格式:CMD <命令> 或 exec 格式:CMD ["可执行文件", "参数1", "参数2"...] 。如: CMD cat /etc/os-release ,第一种格式实际上会被包装为 sh -c 的参数 的形式执行,即: CMD [ "sh", "-c", "cat /etc/os-release" ] 。

Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样, 用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。如 CMD service nginx start 并不会使得容器一直在运行,其实际上会被理解为 CMD [ "sh", "-c", "service nginx start"] ,此时主进程实际上是 sh。则当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。正确做法是直接以前台形式执行nginx: CMD ["nginx", "-g", "daemon off;"]  

6.4 ENTRYPOINT

目的和 CMD 一样,都是在指定容器启动时执行的默认程序及参数,命令格式也一样。不同的是ENTRYPOINT功能更强大,使用ENTRYPOINT指定的默认程序,可以接收在启动容器时给定的参数,从而使容器像个可执行程序一样。

应用场景1:让镜像如一个命令般可执行。”An ENTRYPOINT allows you to configure a container that will run as an executable.“

示例如下,功能为显示当前的公网IP:

FROM ubuntu:16.04RUN apt-get update \    && apt-get install -y curl \    && rm -rf /var/lib/apt/lists/*#CMD [ "curl", "-s", "http://ip.cn" ]ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

经构建 docker build -t myip . 后,若Dockerfile中使用CMD则只能 docker run myip 运行,无法为curl指定其他参数;若用ENTRYPOINT则可以 docker run myip -i 为curl进一步指定参数(-i表示curl结果输出响应头)。

从上面可见CMD和ENTRYPOINT的区别:后者在运行容器时指定的参数(即-i)会拼接到ENTRYPOINT指定的命令上(变成curl -s http://ip.cn -i);前者则不会拼接而是替换,即把 -i 当成默认命令替换掉 curl -s http//ip.cn 进行执行,从而报错。

另一示例: ENTRYPOINT ["/bin/echo"] ,假设build成镜像echoimage,则可以执行 docker run -it imageecho “this is a test” 就会输出字符串,使得镜像像个echo可执行程序。

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

借助ENTRYPOINT指定一个脚本文件作为默认程序,完成系统配置、初始化等预处理工作。由于启动docker时指定的参数会拼接到ENTRYPOINT指定的默认程序后,因此参数被当成脚本的参数,脚本里可以根据这些参数作一些处理工作。用CMD则无法达到此功能。

6.5 ENV

设置环境变量,格式为: ENV <key> <value>  或 ENV <key1>=<value1> <key2>=<value2>... ,Docker里后面的指令如RUN以及运行时的应用都可以用此定义的变量,引用方式示例:$filename。 

6.6 ARG

与ENV类似,定义环境变量及默认值。不同的是定义的环境变量在容器运行时不存在;此外,该默认值可在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。

6.7 VOLUMN

定义匿名卷及其挂载位置,格式为: VOLUME ["<路径1>", "<路径2>"...]  或 VOLUME <路径> ,如 VOLUMN /data ,容器运行时会自动将匿名卷挂载到指定目录。可在容器运行时指定数据卷的位置,覆盖匿名卷设置,如 docker run -d -v mydata:/data echo 将本机的命名卷mydata挂载到容器内的目录 /data 下。

注:

容器运行时应尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。定义匿名卷后,Docker容器启动时会将指定的路径挂载为匿名卷,这样向该路径写入的数据不会容器存储层,从而保证容器存储层无状态化。

对于 匿名卷 和 非绝对路径的 命名卷,默认都存于本机的 /var/lib/docker/volumes/ 下。对于匿名卷,会在宿主机上的该目录下随机产生一个对应的子目录。

可以通过命令 docker inspect --type container -f '{

{range $i, $v := .Mounts }}{
{printf "%v\n" $v}}{
{end}}' $containerName 查看容器的卷信息。

 

更多相关见下文的 数据卷操作 一节。

6.8 EXPOSE

格式: EXPOSE <端口1> [<端口2>...] 。作用:声明容器运行时可以使用的端口。这只是声明容器打算使用的端口,在运行时并不会因为这个声明应用就会开启指定的端口,容器启动时通过 -p <宿主端口>:<容器端口> 可启用对应端口。

6.9 RUN

见5.6节

6.10 WORKDIR

格式: WORKDIR <工作目录路径> 。作用:指定工作目录(或称为当前目录),以后各层的当前目录就被改为指定的目录,若该目录不存在,WORKDIR 会建立该目录。

6.11 USER

格式: USER <用户名> 。作用:与WORKDIR类似,切换为指定的用户,改变之后层执行 RUNCMD 以及 ENTRYPOINT 这类命令的身份。用户需要事先存在。 

6.12 HEALTHCHECK

作用:周期性运行给定的命令以判断容器主程序是否正常运行。详见 healthcheck-健康检查。格式如下:

 HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令

 HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

6.13 ONBUILD

格式: ONBUILD <其它Dockerfile指令> 。作用:用来设定触发器,在当前镜像build时不会执行ONBUILD指定的指令,只有在当前镜像的直接子镜像(即不包括孙镜像)build时才会执行ONBUILD指定的指令。

ONBUILD指令相当于创建一个模板镜像,后续可根据该模板镜像创建不同的特定子镜像,需要在子镜像构建过程中执行的一些通用操作就可以在模板镜像对应的Dockerfile文件中用ONBUILD指令指定,从而减少dockerfile文件的重复内容编写。

示例:

Dockerfile1: 

FROM ubuntuONBUILD RUN mkdir mydir

构建为onbuild_image1: docker build -t onbuild_image -f Dockerfile1 . ,此时不会执行ONBUILD指定的指令。

Dockerfile2:

FROM onbuild_image1CMD echo

构建为onbuild_image2: docker build -t onbuild_image2 -f Dockerfile2 . ,此时会执行ONBUILD指定的指令。如下:

 

分别查看是否有mydir目录: docker run --rm onbuild_image2 ls |grep mydir ,可以得到onbuild_image2有mydir目录而onbuild_image1没有。

6.14 多阶段构建

作用:从Docker 17.05.0-ce版本起,官方提供了简便的多阶段构建(multi-stage build) 方案,允许在同一Dockerfile中书写多阶段构建指令。

示例:(关键在于AS关键字和 --from 选项

FROM muninn/glide:alpine AS build-env # AS关键字给此阶段起名ADD . /go/src/appWORKDIR /go/src/appRUN glide installRUN go build -v -o /go/src/app/app-serverFROM alpineRUN apk add -U tzdataRUN ln -sf /usr/share/zoneinfo/Asia/Shanghai  /etc/localtimeCOPY --from=build-env /go/src/app/app-server /usr/local/bin/app-server # --from 指定从指定阶段的镜像复制文件EXPOSE 80CMD ["app-server"]
View Code

在多阶段构建的支持下,可以在build时加上  --target 阶段名 选项来只构建某阶段的镜像,如: docker build --target build-env -t go/helloworld:v1 . 

此外,借助多阶段构建,可以将多个项目的二进制文件集成到一个镜像里,伪码示例如下:

from debian as build-essentialarg APT_MIRRORrun apt-get updaterun apt-get install -y make gccworkdir /srcfrom build-essential as foocopy src1 .run makefrom build-essential as barcopy src2 .run makefrom alpinecopy --from=foo bin1 .copy --from=bar bin2 .cmd ...
View Code

7. 私有仓库

Docker Hub、DaoCloud等是公共的Registry,人们可以在上面创建Repository。有时候公共仓库不方便或者网络环境差,可以搭建私有仓库供私人或团队使用。构建私有镜像仓库可以借助官方工具docker-registry。详见 私有仓库 。

8. 数据卷与挂载

数据卷的作用在于把宿主机上的指定目录挂载到容器内的某个位置(目录)上,即把宿主机的指定目录共享给容器,因此容器内对该目录的操作会反应到宿主机,从而操作永久有效,即使容器被删除了。

在Docker中管理数据的方式有两种:挂载数据卷(Volumes)和挂载主机目录(Bind mounts)。前者本质上就是后者,因为创建数据卷就是在本机上创建一个目录(默认在本机的 /var/lib/docker/volumes/ 下创建目录)并此后将该目录关联到容器内的指定目录。这两种方式都使得可以在主机和容器间共享文件夹或文件。

数据卷:

数据卷是一个可供一个或多个容器使用的特殊目录,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷

数据卷绕过 UFS,可以提供很多有用的特性:

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

数据卷操作:

docker volumn create   vol_name  #创建数据卷,名字中的字符只能是[a-zA-Z0-9_.-]docker volumn   ls  #查看所有数据卷docker volumn inspect   vol_name  #查看指定数据卷docker volumn rm   vol_name  #删除指定数据卷docker volumn   prune  #删除无主数据卷

数据卷 是被设计用来持久化数据的,其生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。若需要在删除容器的同时移除数据卷,可在删除容器时加 -v 参数。

挂载操作:将本机上的目录或创建的数据卷挂载到容器内的指定路径上,两种:一种是用 --mount 参数、另一种是用-v(或--volumn)参数,大同小异。数据卷可以不用事先创建,指定挂载时若数据卷不存在则会创建之。

法1: -v vol_name_or_absolute_path:target_dir ,对于挂载数据卷或挂载本机目录命令一样,如 -v my-vol:/webapp 或 -v /home/zsm/data:/webapp ,卷或源目录不存在时会创建。

法2: --mount [type=bind] source=vol_name_orabsolute_path,target=target_dir [,readonly] 。卷不存在时会创建、源目录不存在时会报错。当挂载本机目录时需要 type=bind 选项,当启用readonly时在容器内的挂载目录内只有读权限。示例:

docker run -d -p 80:80 \    --name web \    # -v my-vol:/wepapp \    --mount source=my-vol,target=/webapp \     #--mount type=bind source=/home/zsm/data,target=/webapp,readonly\    training/webapp  python app.py

通过命令 docker inspect web 查看容器信息,数据卷信息在“Mounts”key下,可见my-vol被创建在了 /var/lib/docker/volumns/ 下:

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

也可以将主机的一个文件挂载到容器中,示例如下(下例使得主机可以记录在容器中输入过的命令):

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 

注意:在docker run 等命令中指定挂载路径时,若路径含有空格则会因被当成多个参数而出错,故最好将路径用引号括起来。如 -v "$logDirInHost":"$logDirInContainer" 

查看数据卷信息(包括匿名卷): docker inspect --type container -f '{

{range $i, $v := .Mounts }}{
{printf "%v\n" $v}}{
{end}}' $containerName ,结果示例:

{bind  /etc/hosts /etc/hosts  rw true rprivate}{volume 62a13e0bc4a63a88de20a2c6c33dc347f542ebdfcbac5278fa082dbf790c3bff /var/lib/docker/volumes/62a13e0bc4a63a88de20a2c6c33dc347f542ebdfcbac5278fa082dbf790c3bff/_data /var/lib/mysql local  true }

 

 

 

9. 进阶_Docker网络

(其他可参阅 使用网络、高级网络设置、Docker网络实现)

9.1 原理

Docker利用Linux的namespace来实现资源隔离,如pid namespace、mount namespace、network namespace分别用来隔离进程、文件系统、网络。Docker 的网络实现就是利用了 Linux 上的network namespace和虚拟网络设备(特别是 veth pair)。

首先,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)来收发数据包;此外,如果不同子网之间要进行通信,需要路由机制。

Docker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。 Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像一个正常的以太网卡,只是它不需要真正同外部网络设备通信,速度要快很多。

Docker 容器网络就利用了这项技术,它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)。

Docker创建一个容器时,会进行如下与网络相关的操作

  1. 创建一对虚拟接口,分别放到本地主机和新容器中;
  2. 本地主机一端桥接到默认的 docker0 或指定网桥上,并具有一个唯一的名字,如 veth65f9;
  3. 容器一端放到新容器中,并修改名字作为 eth0,这个接口只在容器的命名空间可见;
  4. 从网桥可用地址段中获取一个空闲地址分配给容器的 eth0,并配置默认路由到桥接网卡 veth65f9。

完成这些之后,容器就可以使用 eth0 虚拟网卡来连接其他容器和其他网络。

9.2 网络设置

可在 docker run 时通过 --net 参数来指定容器的网络配置,有4个可选值:

  1. bridge模式:--net=brige,默认值,连接到默认的网桥(即docker0)
  2. host模式:--net=host
  3. container模式:--net=container:NAME_or_ID
  4. none模式:--net=none

9.2.1 bridge模式

当 Docker server启动时,会自动在主机上创建一个 docker0 虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口。比如典型的 172.17.0.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)

通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。

9.2.2 host模式

一个Docker容器一般会分配一个独立的network namespace。但若启动容器的时候使用host模式,则此容器将不会获得一个独立的network namespace,而是和宿主机共用一个network namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。此时在容器中执行任何类似ifconfig命令查看网络环境时,看到的都是宿主机上的信息;外界访问容器应用时直接使用宿主机地址,不会进行NAT转换。

此外,容器进程跟主机其它 root 进程一样可以打开低范围的端口,可以访问本地网络服务比如 D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。

然而,容器除了网络外的其他方面如文件系统、进程等还是和宿主机隔离的。

9.2.3 container模式

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

9.2.4 none模式

此模式下,创建的容器拥有自己的network namespace,但并不会进行任何网络配置。即这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

 

10. Docker-Compose

10.1 是什么

“Compose is a tool for defining and running multi-container Docker applications”。

Docker-Compose是一个用来组织多个容器运行的工具(容器编排?orchestration)。与shell脚本类似,只需要编写一个yml文件就能组织并运行多个容器并对它们进行管理。

10.2 使用

使用:编写docker-compose.yml文件即可,可以定义Docker环境变量文件(不指定文件的话默认查找当前目录的.env文件并解析成环境变量),这些变量会传给Docker容器、也可以在yml文件中使用。

示例:

1 fversion: "3" 2 services: 3   ss_manager:  # global unique 4     image: marchonzsm/sensestudy_manage_server 5     env_file: 6       - .env  # still default to this even not given 7     container_name: manage_server_sensestudy_${serverPortOfJava}_${serverPortOfWebsocket} 8     environment: 9       POSTGRES_PASSWORD: 1q2w3e4R10       SERVICE_8081_NAME: ss_backend_service11       SERVICE_8091_NAME: ss_queue_service12       SERVICE_8081_TAGS: sensestudy,backend,java13       SERVICE_8091_TAGS: sensestudy,backend,websocket14       SERVICE_8081_CHECK_TCP: "true"15       SERVICE_8091_CHECK_TCP: "true"16     # network_mode: "host"17     ports:18       - ${serverPortOfJava}:808119       - ${serverPortOfWebsocket}:809120     volumes:21       - $configFileDirPath/$configFileNameOfService:/home/$configFileNameOfService22       - $configFileDirPath/$configFileNameOfDB:/home/$configFileNameOfDB23       - "$logDirPath/port_$serverPortOfJava:$logDirPath/port_$serverPortOfJava"24     command: --server.port=8081  --sensestudy.websocket.port=809125     restart: unless-stopped
docker-compose.yml
1 serverPortOfJava=80852 serverPortOfWebsocket=80953 4 configFileDirPath=./config_file5 configFileNameOfService=application.yml6 configFileNameOfDB=application-prod.yml7 logDirPath=/sensestudy_logs/javaserver_logs
.env

docker-compose scale可以启多个容器,但若容器需要占用端口则启多个时会端口冲突。可以通过在yml中为不同容器指定多个端口来解决。然而yml的一个不足是key不能含有变量,解决:借助模板,如python的jinja、java的jsp等,Linux下显然前者更方便。这里以jinja为例,将上述yml改造成jinja模板如下:

1 version: "3" 2 services: 3   {% set numberOfContainerInstance = 3 %} 4   {% set firstServerPort = 12001 %} 5   {% for i in range(numberOfContainerInstance) %} 6   {% set serverPortOfJava = firstServerPort + 2*i %} 7   {% set serverPortOfWebsocket = firstServerPort + 2*i+1 %} 8   {% set configFileDirPath = "./config_file" %} 9   {% set configFileNameOfService = "application.yml" %}10   {% set configFileNameOfDB = "application-prod.yml" %}11   {% set logDirPath  = "/sensestudy_logs/javaserver_logs/port_" ~  serverPortOfJava  %}12   ss_manager_server_{
{ serverPortOfJava }}_{
{ serverPortOfWebsocket }}:13 image: marchonzsm/sensestudy_manage_server14 container_name: manage_server_sensestudy_{
{ serverPortOfJava }}_{
{ serverPortOfWebsocket }}15 environment:16 SERVICE_{
{ serverPortOfJava }}_NAME: ss_backend_service17 SERVICE_{
{ serverPortOfWebsocket }}_NAME: ss_queue_service18 SERVICE_{
{ serverPortOfJava }}_TAGS: sensestudy,backend,java19 SERVICE_{
{ serverPortOfWebsocket }}_TAGS: sensestudy,backend,websocket20 SERVICE_{
{ serverPortOfJava }}_CHECK_TCP: "true"21 SERVICE_{
{ serverPortOfWebsocket }}_CHECK_TCP: "true"22 network_mode: "host"23 ports:24 - {
{ serverPortOfJava }}:{
{ serverPortOfJava }}25 - {
{ serverPortOfWebsocket }}:{
{ serverPortOfWebsocket }}26 volumes:27 - {
{ configFileDirPath }}/{
{ configFileNameOfService }}:/home/{
{ configFileNameOfService }}28 - {
{ configFileDirPath }}/{
{ configFileNameOfDB }}:/home/{
{ configFileNameOfDB }}29 - {
{ logDirPath }}:{
{ logDirPath }}30 command: --server.port={
{ serverPortOfJava }} --sensestudy.websocket.port={
{ serverPortOfWebsocket }}31 restart: unless-stopped32 {% endfor %}
docker-compose.yml.jinja
1 from jinja2 import Environment, FileSystemLoader2 env = Environment(loader=FileSystemLoader('.'))3 template = env.get_template('docker-compose.yml.jinja')4 output_from_parsed_template = template.render()5 print output_from_parsed_template6  7 # to save the results8 with open("docker-compose.yml", "wb") as fh:9     fh.write(output_from_parsed_template)
jinja_to_yml.py 

10.3 原理

 

Docker-Compose中有两个概念:

  1. 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  2. 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Docker-Compose 的默认管理对象是项目。

Docker-Compose使用Python编写,实际上调用了Docker服务的API来管理容器。

 

更多使用方法,详见:

https://docker_practice.gitee.io/compose/install.html

 

遇到的坑:docker-compose.yml中若不指定network-mode则默认为bridge模式,且与docker run不同的是每个compose服务都会创建一个虚拟网桥而不是使用默认的docker0网桥,这样多个网桥分别占用一个地址段,很容易与本机的地址冲突出问题。

解决:显示指定network-bridge: "bridge",这样就不会创建多个bridge而是都使用默认的bridge(docker0)。

 

11. 进阶_底层实现

(见 底层实现)

Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces)、控制组(Control groups)、联合文件系统(Union file systems)和容器格式(Container format)。

命名空间:命名空间是 Linux 内核一个强大的特性。每个容器都有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统中运行一样。命名空间保证了容器之间彼此互不影响。包括pid命名空间、net命名空间、ipc命名空间、mnt命名空间、uts命名空间、user命名空间等。

控制组:是 Linux 内核的一个特性,主要用来对容器的内存、CPU、磁盘IO等共享资源进行隔离、限制、审计等。只有能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争。

联合文件系统:是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像;另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。Docker 目前支持的联合文件系统(存储驱动)包括 OverlayFSAUFSBtrfsVFSZFS 和 Device Mapper,overlay2 是目前 Docker 默认的存储驱动,以前则是 aufs。

容器格式:最初,Docker 采用了 LXC 中的容器格式。从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。

12. 其他

12.1 重启

docker run通过 --restart 参数来指定重启策略,有 no、on-failure[:max-retries]、unless-stopped、always几种策略,几种策略见名知意。后两个都用于在容器退出时重启容器,不同之处在于Docker Daemon还会在启动时重启restart模式为always的容器。这点很特殊,可以借助之实现应用容器开机自启动

13. 参考资料

《Docker-从入门到实践》

 进一步阅读(To do):

 

转载地址:http://gxhql.baihongyu.com/

你可能感兴趣的文章
android studio获取SHA1
查看>>
怎么才能在windows使用git命令
查看>>
Sigar应用
查看>>
从单体架构到微服务的演变之路
查看>>
Valgrind内存泄露检测工具使用初步
查看>>
PDF 补丁丁 0.5.0.2657 发布
查看>>
vue之axios使用
查看>>
VBA批量删除excel表高级版
查看>>
docker & nodejs & mongodb
查看>>
css 清除浮动
查看>>
Python_Selenium学习笔记(2)-浏览器操作方法
查看>>
excel自定义函数添加和使用方法
查看>>
C# 压缩组件介绍与入门
查看>>
结对学习心得感想及创意照
查看>>
sug
查看>>
windows 环境变量
查看>>
Linux下模拟Http发送的Get和Post请求
查看>>
input checked取值
查看>>
内核参数
查看>>
android中dip、dp、px、sp和屏幕密度
查看>>