Docker

Docker笔记

导语

Docker容器本质上是宿主机上的进程。Docker通过nampespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制(copy-on-write)实现了高效的文件操作。

一、资源隔离


1、namespace资源隔离


namespace6项隔离表

namespace 系统调用参数 隔离内容
UTS CLONE_NEWUTS 主机名与域名
IPC CLONE_NEWIPC 信号量、消息队列和共享内存
PID CLONE_NEWPID 进程编号
Network CLONE_NEWNET 网络设备、网络栈、端口等
Mount CLONE_NEWNS 挂载点(文件系统)
User CLONE_NEWUSER 用户和用户组

2、cgroups资源限制


cgroups是Linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分割)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架;
通俗地来讲,cgroups可以限制、记录任务组所使用的物理资源(包括CPU、memory、IO等),为容器实现虚拟化提供了基本保证,是构建docker等一系列虚拟化管理工具的基石;
实现cgroups的主要目的是为不同用户层面的资源管理,提供一个统一化的接口,从单个任务的资源控制到操作系统层面的虚拟化,cgroups提供了一下四大功能。

cgroups的作用

  • 资源限制:cgroups可以对任务使用的资源总额进行限制。如设定应用运行时使用内存的上限;
  • 优先级分配:通过分配的CPU时间片数量及磁盘IO带宽大小,实际上就相当于控制了任务运行的优先级;
  • 资源统计:cgroups可以统计系统的资源使用量,如CPU使用时长、内存使用量等,这个功能非常适用于计费;
  • 任务控制:cgroups可以对任务执行挂起、恢复等操作;

3、libcontaner


在2013年Docker刚发布的时候,它是一款基于LXC的开源容器管理引擎。把LXC复杂的容器创建与使用方式简化为Docker自己的一套命令体系。随着Docker的不断发展,它开始有了更为远大的目标,那就是反向定义容器的实现标准,将底层实现都抽象化到Libcontainer的接口。这就意味着,底层容器的实现方式变成了一种可变的方案,无论是使用namespace、cgroups技术抑或是使用systemd等其他方案,只要实现了Libcontainer定义的一组接口,Docker都可以运行。这也为Docker实现全面的跨平台带来了可能。

licontainer特性

目前版本的Libcontainer,功能实现上涵盖了包括namespaces使用、cgroups管理、Rootfs的配置启动、默认的Linux capability权限集、以及进程运行的环境变量配置。内核版本最低要求为2.6,最好是3.8,这与内核对namespace的支持有关。

二、Docker架构


Docker通过driver模块来实现对Docker容器执行环境的定制。当需要创建Docker容器时,可以从Docker registry中下载镜像,并通过镜像管理驱动graphdriver将下载的镜像以graph的形式存储在本地;当需要为Docker容器创建网络环境时,则通过网络管理驱动networkdriver创建并配置Docker容器的网络环境;当需要限制Docker容器运行资源或执行用户指令等操作时,则通过execdriver来完成。libcontainer是一个独立的容器管理包,networkdriver和execdriver都通过libcontainer来实现对容器的具体操作,包括利用UTS、IPC、PID、Network、Mount、User等namespace实现容器之间的资源隔离和利用cgroup实现对容器的资源限制。当运行容器的命令执行完毕后,一个实际的容器就处于运行状态,该容器拥有独立的文件系统、安全且相互隔离的运行环境。

Docker架构总览

1、Docker daemon

Docker doemon是Docker最核心的后台进程,它负责响应来自Docker Client的请求,然后将这些请求翻译成系统调用完成容器管理操作。该进程会在后台启动一个API Server,负责接收有Docker Client发送的请求;接收到的请求将通过Docker daemon内部的一个路由分发调度,再由具体的函数来执行请求;

2、Docker Client

Docker Client是一个泛称,用来向指定的Docker daemon发起请求,执行相应的容器管理操作。它既可以是docker命令行工具,也可以是任何遵循了Docker API的客户端,包括C#、Java、Go、Ruby、Javascript等;

3、graph

graph组件负责维护已下载的镜像信息及它们之间的关系,所以大部分Docker镜像相关的操作都会由graph组件来完成。graph通过镜像“层”和每层的元数据来记录这些镜像的信息,用户发起的镜像管理操作最终都转换成了graph对这些层和元数据的操作。但正是由于这个原因,而且很多时候Docker操作都需要加载当前Docker daemon维护着的所有镜像信息,graph组件常常会成为性能瓶颈。

4、GraphDB

Docker daemon通过GraphDB记录它所维护的所有容器(节点)以及它们之间的link关系(边),这也就是为什么这里采用了一个图结构来保存这些数据。具体来说,GraphDB就是一个基于SQLite的最简单版本的图形数据库,能够为调用者提供节点增、删、遍历、连接、所有的父子节点的查询等操作。这些节点对应的就是一个容器,而节点间的边就是一个Docker link关系。每创建一个容器,Docker daemon都会在GraphDB里添加一个节点,而当我们为某个容器设置了link操作后,在GraphDB中就会为它创建一个父子关系,即一条边。显然,虽然名字容易混淆,但是GraphDB与前面提到的负责镜像操作的graph组件没有多大关系。

5、driver

前面提到,Docker daemon负责将用户请求转译成系统调用,进而创建和管理容器的核心进程。而在具体实现过程中,为了将这些系统调用抽象成为统一的操作接口,方便调用者使用,Docker把这些操作分类成容器管理驱动、网络管理驱动、文件存储驱动3种,分别对应execdriver、networkdriver和graphdriver。

execdriver是对Linux操作系统的namespace、cgroups、apparmor、SELinux等容器运行所需的系统操作进行的一层二次封装,其本质作用类似LXC,但是功能更全面。这也是为什么LXC会作为execdriver的一种实现而存在。当然,execdriver最主要的实现也是现在的默认实现是Docker官方编写的libcontainer库。

networkdriver是对容器网络环境操作所进行的封装。对于容器来说,网络设备的配置相对比较独立,并且应该允许用户进行更多的配置,所以在Docker中,这一部分是单独作为一个driver来设计和实现的。这些操作具体包括创建容器通信所需的网络,容器的network namespace,这个网络所需的虚拟网卡,分配通信所需的IP,服务访问的端口和容器与宿主机之间的端口映射,设置hosts、resolv.conf、iptables等。

graphdriver是所有与容器镜像相关操作的最终执行者。graphdriver会在Docker工作目录下维护一组与镜像层对应的目录,并记下容器和镜像之间关系等元数据。这样,用户对镜像的操作最终会被映射成对这些目录文件以及元数据的增删改查,从而屏蔽掉不同文件存储实现对于上层调用者的影响。目前Docker已经支持的文件存储实现包括aufs、btrfs、devicemapper、overlay和vfs。

6、client和daemon

yum -y remove docker
yum -y install docker-io
service docker start //会启动docker守护进程,或者docker daemon &

docker [OPTIONS] COMMAND [arg…]

OPTIONS为-d时,Docker就会创建一个运行在宿主机的daemon进程,否则,按照用户声明的COMMAND向指定的Docker daemon发送对应的请求。

三、Docker镜像


使用libcontainer可以快速构建起应用的运行环境,但是当需要进行容器迁移、对容器的运行环境进行全盘打包时,libcontainer就束手无策了。Docker镜像技术用来解决此问题,作为Docker管理文件系统以及运行环境的强有力补充。

概述

Docker的镜像是由一系列的只读层组合而来的,当启动一个容器时,Docker加载镜像的所有只读层,并在最上层加入一个读写层。这个设计使得Docker可以提高镜像构建、存储和分发的效率,节省了时间和存储空间。

Docker镜像含有启动Docker容器所需的文件系统结构及其内容,因此是启动一个Docker容器的基础。Docker镜像采用分层的结构构建,最底层是bootfs,之上的部分为rootfs。

bootfs是Docker镜像最底层的引导文件系统,包括bootloader和操作系统内核,类似于传统的Linux/Unix引导文件系统。然而,Docker用户很少有机会直接与bootfs打交道,并且,在容器启动完毕之后,为了节省内存空间,bootfs将会被卸载。

rootfs位于bootfs之上,是Docker容器在启动时内部进程可见的文件系统,即Docker容器的根目录。rootfs通常包含一个操作系统运行所需的文件系统,例如可能包含典型的类Unix操作系统中的目录系统,如/dev、/proc、/bin、/etc、/lib、/usr、/tmp及运行Docker容器所需的配置文件、工具等。

核心概念

  • registry:注册中心,repository的集合,Docker hub;
  • repository:仓库,镜像的集合;
  • graph:在graph的本地目录/var/lib/docker/graph中,保存了每一个下载到本地的Docker镜像的元数据,包括json与layersize。其中json文件中记录了相应Docker镜像的ID、依赖关系、创建时间和配置信息等,layersize为Docker镜像的大小。
  • Dockerfile

四、Docker存储驱动


存储驱动根据操作系统底层的支持提供了针对某种文件系统的初始化操作以及对镜像层的增、删、改、查和差异比较等操作。目前存储系统的接口已经有aufs、btrfs、device mapper、vfs、overlay这5种具体实现,其中vfs不支持写时复制,是为使用volume提供的存储驱动,仅仅做了简单的文件挂载操作;

1、aufs驱动

aufs是一种支持联合挂载的文件系统,简单来说就是支持将不同目录挂载到同一个目录下,这些挂载操作对用户来说是透明的,用户在操作该目录时不会觉得与其他目录有什么不同。这些目录的挂载是分层次的,通常来说最上层是可读写层,下层是只读层。所以,aufs的每一层都是一个普通文件系统。

2、Device Mapper

Linux2.6内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机制下,用户可以很方便地根据自己的需要制定实现存储资源的管理策略。

Device Mapper包含3个概念:映射设备、映射表、目标设备;映射设备是内核向外提供的逻辑设备。一个映射设备通过一个映射表与多个目标设备映射起来,映射表包含了多个多元组,每个多元组记录了这个映射设备的起始地址、范围与一个目标设备的地址偏移量的映射关系。目标设备可以是一个屋里设备,也可以是一个映射设备,这个映射设备可以继续向下迭代。一个映射设备最终通过一棵映射树映射到屋里设备上。Device Mapper的本质功能是根据映射关系描述IO处理规则,当映射设备接收到IO请求时,这个IO请求会根据映射表逐级转发,直到这个请求最终传到最底层的物理设备上。

3、备注

写入时复制(Copy-on-write)

是一个被使用在程式设计领域的最佳化策略。其基础的观念是,如果有多个呼叫者(callers)同时要求相同资源,他们会共同取得相同的指标指向相同的资源,直到某个呼叫者(caller)尝试修改资源时,系统才会真正复制一个副本(private copy)给该呼叫者,以避免被修改的资源被直接察觉到,这过程对其他的呼叫只都是通透的(transparently)。此作法主要的优点是如果呼叫者并没有修改该资源,就不会有副本(private copy)被建立。

第一代Unix系统实现了一种傻瓜式的进程创建:当发出fork()系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程。这种行为是非常耗时的,因为它需要:
    - 为子进程的页表分配页帧
    - 为子进程的页分配页帧
    - 初始化子进程的页表
    - 把父进程的页复制到子进程相应的页中

这种创建地址空间的方法涉及许多内存访问,消耗许多CPU周期,并且完全破坏了高速缓存中的内容。在大多数情况下,这样做常常是毫无意义的,因为许多子进程通过装入一个新的程序开始它们的执行,这样就完全丢弃了所继承的地址空间。

现在的Linux内核采用一种更为有效的方法,称之为写时复制(Copy On Write,COW)。这种思想相当简单:父进程和子进程共享页帧而不是复制页帧。然而,只要页帧被共享,它们就不能被修改,即页帧被保护。无论父进程还是子进程何时试图写一个共享的页帧,就产生一个异常,这时内核就把这个页复制到一个新的页帧中并标记为可写。原来的页帧仍然是写保护的:当其他进程试图写入时,内核检查写进程是否是这个页帧的唯一属主,如果是,就把这个页帧标记为对这个进程是可写的。

五、Docker数据卷


相对数据存储而言,Docker镜像存在的问题:

  • 容器中的文件在宿主机上存在形式复杂,不能在宿主机上很方便地对容器中的文件进行访问;
  • 多个容器之间的数据无法共享;
  • 当删除容器时,容器产生的数据将丢失

为了解决上述问题,Docker引入了数据卷机制;volume的生存周期独立于容器的生存周期,即使删除容器,volume仍然会存在,没有任何使用的volume也不会被删除;本质上市容器中一个特殊的目录(挂载点);

六、Docker网络管理


1、docker0网桥

Docker_第1张图片

veth类似于交换机上的端口,可以将多个容器或虚拟机链接在其上,这些veth位于第二层,所以不需要配置IP信息;(网桥属于二层设备,不应该有IP的)但docker0是普通的linux网桥,它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口。在Docker的桥接网络模式中,docker0的IP地址作为连于之上的容器的默认网关地址存在;

2、docker容器的4种网络模式

  • bridge模式
    Docker默认的网络模式,将创建出来的docker容器链接到Docker网桥上(docker0或其他自定义网桥):

    1. 创建一对虚拟网卡(veth pair);
    2. 赋予其中一块网卡一个类似“veth65f9”的名字,将其留在宿主机root network namespace中,并绑定到Docker0网桥上;
    3. 将另一块网卡放入新创建的network namespace中(Docker容器中),命名为eth0;
    4. 从Docker网桥的子网中选取一个未使用的IP分配给eth0,并为Docker容器设置默认路由,默认网关为Docker网桥
  • host模式
    这种模式Docker Server将不为Docker容器创建网络协议栈,即不会创建独立的network namespace,Docker容器中的进程处于宿主机的网络环境中,相当于Docker容器和宿主机共用同一个network namespace,使用宿主机的网卡、IP和端口等信息;

  • container模式
    新创建的容器和已经存在的某个容器共享同一个network namespace;

  • none模式
    Docker容器拥有自己的network namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器除了network namespace自带的loopback网卡外没有其他任何网卡、IP、路由等信息,需要用户为Docker容器添加网卡、配置IP等;

3、Link原理

  1. 设置接收容器的环境变量;
  2. 更新接收容器的/etc/hosts文件;接收容器即发起link的容器;
  3. 添加iptables规则使容器链接的两个容器可以通信;

七、Dockerfile


Dockerfile十一个文本格式的配置文件,用户可以使用Dockerfile快速创建自定义的镜像。

1、Hello World

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

# 注释
# this dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ...

# 第一行必须制定基于的基础镜像
FROM ubuntu

# 维护者信息
MAINTAINER userName [email protected]

# 镜像的操作命令
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf

# 容器启动时执行的命令
CMD /usr/sbin/nginx

RUN指令是将对镜像执行跟随的命令,每运行一条RUN指令,镜像添加新的一层,并提交;最后CMD指令,用来制定运行容器时的操作命令。

2、指令

  • FROM
    第一条指令必须是FROM;如果在同一个Dockerfile中创建多个镜像时,可以使用多个FROM指令(每个镜像一个)。
  • MAINTAINER
  • RUN
    每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用“\”来换行。
  • CMD
    每个Dockerfile只能有一条CMD命令。
  • EXPOSE
    格式:EXPOSE […]
    告诉Docker服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过-P,Docker主机会自动分配一个端口转发到指定的端口;使用-p,则可以具体指定哪个本地端口映射过来。
  • ENV
    格式:ENV
    指定环境变量,会被后续RUN指令使用,并在容器运行时保持。
  • ADD
    格式:ADD
    该命令讲复制指定的到容器中的。其中可以是Dockerfile所在目录的一个相对路径(文件或目录);也可以是一个URL;还可以是一个tar文件(自动解压为目录)。
  • COPY
    格式:COPY
    复制本地主机的(为Dockerfile所在目录的相对路径,文件或目录)为容器中的。目标路径不存在时,会自动创建。当使用本地目录为源目录时,推荐使用COPY。
  • ENTRYPOINT
    格式:ENTRYPOINT [“executable”, “param1”, “param2”]
       ENTRYPOINT [“executable”, “param1”, “param2”]
    配置容器启动后执行的命令,并且不可被docker run提供的参数覆盖。每个Dockerfile中只能有一个ENTRYPOINT。
  • VOLUME
    格式:VOLUME [“/data”]
    创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
  • USER
    格式:USER daemon
    指定运行容器时的用户名或UID,后续的RUN也会使用指定用户。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres。要临时获取管理员权限可以使用gosu,而不推荐sudo。
  • WORKDIR
    格式:WORKDIR /path/to/workdir。
    为后续的RUN、CMD、ENTRYPOINT指令配置工作目录。可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
  • ONBUILD
    格式:ONBUILD [INSTRUCTION]
    配置当所创建的镜像作为其他新创建镜像的基础镜像时,所执行的操作指令。

3、创建镜像

编写完成Dockerfile之后,可以通过docker build命令来创建镜像。

格式:docker build [选项] 路径

该命令将读取指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给Docker服务端,由服务端来创建镜像。因此一般建议放置Dockerfile的目录为空目录。

要指定镜像的标签信息,可以通过-t选项。
docker build -t build_repo/first_image /tmp/docker_builder/
指定Dockerfile所在路径为/tmp/docker_builder/,并且希望生成镜像标签为build_repo/first_image

八、Docker基本命令


官方文档

子命令分类 子命令
Docker环境信息 info、version
容器生命周期管理 create、exec、kill、pause、restart、rm、run、start、stop、unpause
镜像仓库命令 login、logout、pull、push、search
镜像管理 build、images、import、load、rmi、save、tag、commit
容器运维操作 attach、export、inspect、port、ps、rename、stats、top、wait、cp、diff
系统日志信息 events、history、logs

1、镜像

  • 获取镜像
    docker pull name:tag
    docker pull centos:7.2
    docker pull centos //默认选择latest版本

  • 查看镜像信息
    docker iamges //列出本地镜像
    docker inspect imageId //查看镜像详细信息
    docker tag centos:latest centos:7.2 //给镜像打新的标签centos:latest

  • 搜索镜像
    docker search mysql

  • 删除镜像
    docker rmi imageName/imageId
    当同一个镜像拥有多个标签时,只删除了指定的标签,不影响镜像文件;
    本地有容器关联时,不能删除镜像;

  • 创建镜像

    1. 基于已有镜像的容器创建
      docker commit -a 作者信息 -m 提交信息 -p 提交时暂停容器运行 containerId repositoryName //返回新的镜像ID

    2. 基于本地模板导入

  • 存出镜像
    docker save -o centos7.tar centos:latest

  • 载入镜像
    docker load -i centos7.tar

  • 上传镜像
    docker push NAME[:TAG] //默认上传到DockerHub

2、容器

  • 创建容器
    docker create -it centos:latest
    docker start containerId //启动容器

  • 创建并启动
    docker run -it centos:latest /bin/bash

  • 守护状态运行
    docker run -d centos:latest /bin/bash

  • 查看本机的所有容器
    docker ps -a/-l

  • 获取容器的输出
    docker logs containerId

  • 终止/启动/重启容器
    docker stop/start/restart containerId

  • 进入容器
    docker exec -it containerId /bin/bash

  • 删除容器
    docker rm containerId

  • 导出容器
    docker export -o centos.tar containerId

  • 导入容器,生成镜像
    docker import centos.tar centos:latest

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

3、仓库

DockerHub

4、数据卷

  • 挂载一个主机目录作为数据卷
    docker run -it -d –name web -v /src/webapp:/opt/webapp volumeName centos:latest /bin/bash

  • 读写权限:rw/ro
    docker run -it -d –name web -v /src/webapp:/opt/webapp:rw volumeName centos:latest /bin/bash

数据卷容器

  • 创建数据卷容器
    docker run -it -v /dbdata –name dbdata centos:latest

  • –volumes-from挂载dbdata容器
    docker run -it –volumes-from dbdata –name db1 centos:latest
    docker run -it –volumes-from dbdata –name db2 centos:latest

如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显式使用docker rm -v命令来指定同时删除关联的容器;

备份
docker run –volumes-from dbdata -v $(pwd):/backup –name worker centos
tar cvf /backup/backup.tar /dbdata

首先用centos镜像创建了一个容器worker,使用–volumes-from dbdata参数来让worker容器挂载dbdata容器的数据卷(即dbdata数据卷);

使用-v $(pwd):/backup参数来挂载本地的当前目录到worker容器的/backup目录;

worker容器启动后,使用了tar cvf /backup/backup.tar /dbdata命令来将/dbdata下内容备份为容器内的/backup/backup.tar,即宿主主机当前目录下的backup.tar;

恢复
docker run -v /dbdata –name dbdata2 centos /bin/bash

首先创建一个带有数据卷的容器dbdata2,然后创建另一个新的容器,挂载dbdata2的容器,并使用untar解压备份文件到所挂载的容器卷中即可;

docker run –volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar

5、网络基础配置

  • 端口映射
    docker run -d -p 5000:5000 -p 3000:80 training/webapp /bin/bash

  • 查看端口映射配置
    docker port containerName 5000

  • 容器互联
    docker run -d –name db centos:latest
    docker run -d -P –name web –link db:linkName centos:latest /bin/bash

  • Ambassador容器

6、Dockerfile

你可能感兴趣的:(docker,docker)