部分导航:

p-guide.png

概念

Docker使用Google公司推出的Go语言进行开发实现,基于Linux内核的cgroup、namespace,以及AUFS类的UnionFS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其他的隔离的进程,因此也称为容器。容器就是将软件打包成标准化单元,以用于开发、交付和部署。

Docker 可以解决虚拟机能够解决的问题,也可以解决虚拟机由于资源要求过高而无法解决的问题。

  • 隔离应用依赖
  • 创建应用镜像并复制
  • 创建易于分发的即开即用的应用或者应用镜像
  • 允许实例简单快速扩展
  • 易于测试和销毁

Docker 的初衷是创建软件程序可移植的轻量容器,让软件可以在任何安装了 Docker 的主机上运行,而不用关心底层操作系统。

1 初识Docker

Docker是基于Go语言实现的云开源项目,诞生于2013年初。

1.1 为什么要使用Docker

Docker的主要目标是“Build, Ship and Run Any App, Anywhere.”,即通过对应用组件的封装(Packaging)、分发(Distribution)、部署(Deployment)、运行(Runtime)等生命周期的管理,达到应用组件级别的“一次封装,到处运行”。这里的应用组件,既可以是一个Web应用,也可以是一套数据库服务,甚至是一个操作系统或编译器。

Linux容器技术

Docker引擎的基础是Linux容器(Linux Containers, LXC)技术。

IBM Developer Works给出了关于容器技术的描述:容器有效地将由单个操作系统管理的资源划分到鼓励的组中,以便更好地在孤立的组之间平衡有冲突的资源使用需求。与虚拟化相比,这样既不需要指令级模拟,也不需要即时编译。容器可以在核心CPU本地运行指令,而不需要任何专门的解释机制。此外,也避免了准虚拟化(paravirtualization)和系统调用替换中的复杂性。

从Linux容器到Docker

Docker进一步优化了LXC容器的使用体验,它提供了各种容器管理工具(如分发、版本、移植等)让用户无需关注底层的操作,可以简单明了地管理和使用容器。用户操作Docker就像操作一个轻量级的虚拟机一样简单。

可以简单地将Docker容器理解为一种沙盒(Sandbox)。每个容器内运行一个应用,不同的容器相互隔离,容器之间也可以建立通信机制。容器的创建和停止都十分快速,容器自身对资源的需求也十分有限,远远低于虚拟机。

1.2 为什么要使用Docker

Docker容器虚拟化的好处

现在开发者需要能方便地创建运行在云平台上的应用,也就是说应用必须能够脱离底层机器,而且同时必须是“任何时间任何地点”可获取的,因此开发者人需要一种创建分布式应用程序的方式->Docker。

Docker在开发和运维中的优势

  • 更快速的交付和部署。
  • 更高效的资源利用。它是内核级的虚拟化,可以实现更高的性能,同时对资源的额外需求很低
  • 更轻松的迁移和拓展。
  • 更简单的更新管理。使用Dockerfile。

Docker与虚拟机比较

  • Docker容器更快
  • Docker容器对系统资源需求很少
  • Docker通过类似Git的操作来方便用户获取、分发和更新应用镜像,指令简明,学习成本较低。
  • Docker通过Dockerfile配置文件来支持灵活的自动化创建和部署机制,提高工作效率
特性 容器 虚拟机
启动速度 秒级 分钟级
硬盘使用 一般为MB 一般为GB
性能 接近原生 弱于
系统支持量 单机支持上千个容器 一般几十个
隔离性 安全隔离 完全隔离

Docker 不是虚拟机,传统的虚拟机是先虚拟硬件资源,然后在虚拟的硬件资源之上运行操作系统。而 Docker 容器作为一个进程,直接运行于宿主主机内核,因此 Docker 更加快捷

enter image description here

正是由于这些不同,使得 Docker 具有很多优势:

  • 高效利用系统资源(没有虚拟硬件的额外开销)。
  • 更快的启动时间(通常可以在1秒内启动)。
  • 便于部署(镜像包含了应用和相关依赖,可以运行在任何配置了 Docker 的主机上)。
  • 轻松迁移。
  • 分层存储,提高存储效率。

1.3 虚拟化与Docker

虚拟化技术是一个通用的概念,在不同领域有不同的理解。在计算领域,一般指的是计算虚拟化(Computing Virtualization),或通常说的服务器虚拟化。

在计算机技术中,虚拟化(Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如服务器、网络、内存及存储等,予以抽象、转换后呈现出来,打破实体结构间的不可切割的障碍,使用户可以用比原生的组态更好的方式来应用这些资源。 ——wiki

虚拟化的核心是对资源进行抽象。从大类上分,虚拟化技术可分为基于硬件的虚拟化和基于软件的虚拟化。

基于软件的虚拟化从对象所在的层次,又可以分为应用虚拟化和平台虚拟化。

平台虚拟化可以细分为如下几个子类:

  • 完全虚拟化。虚拟机模拟完整的底层硬件环境和特权指令的执行过程,客户操作系统无需进行修改。如VMware Workstation,VirtualBox、QEMU等。
  • 硬件辅助虚拟化。利用硬件(主要是CPU)辅助支持处理敏感指令来实现完全虚拟化的功能,客户操作系统无需修改,如VMware Workstation、Xen、KVM。
  • 部分虚拟化。只针对部分硬件资源进行虚拟化,客户操作系统需要进行修改。
  • 超虚拟化(Paravirtualization)。部分硬件接口以软件的形式提供给客户机操作系统,客户操作系统需要进行修改,如早期的Xen。
  • 操作系统级虚拟化。内核通过创建多个虚拟的操作系统实例(内核和库)来隔离不同的进程。Docker及其他容器相关技术在这个范畴。

1-1

1.4 Namespaces

命名空间(namespaces)是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。在日常使用 Linux 或者 macOS 时,我们并没有运行多个完全分离的服务器的需要,但是如果我们在服务器上启动了多个服务,这些服务其实会相互影响的,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,这是很多时候我们都不愿意看到的,我们更希望运行在同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样。

在这种情况下,一旦服务器上的某一个服务被入侵,那么入侵者就能够访问当前机器上的所有服务和文件,这也是我们不想看到的,而 Docker 其实就通过 Linux 的 Namespaces 对不同的容器实现了隔离。

Linux 的命名空间机制提供了以下七种不同的命名空间,包括 CLONE_NEWCGROUP、CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER 和 CLONE_NEWUTS,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。

1.5 cgroup

Docker 容器使用 linux namespace 来隔离其运行环境,使得容器中的进程看起来就像一个独立环境中运行一样。但是,光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU以及内存 等。关于其目的,一方面,是为了防止它占用了太多的资源而影响到其它进程;另一方面,在系统资源耗尽的时候,linux 内核会触发 OOM,这会让一些被杀掉的进程成了无辜的替死鬼。因此,为了让容器中的进程更加可控,Docker 使用 Linux cgroups 来限制容器中的进程允许使用的系统资源。

Linux Cgroup 可为系统中所运行任务(进程)的用户定义组群分配资源 — 比如 CPU 时间、系统内存、网络带宽或者这些资源的组合。可以监控管理员配置的 cgroup,拒绝 cgroup 访问某些资源,甚至在运行的系统中动态配置 cgroup。所以,可以将 controll groups 理解为 controller (system resource) (for) (process)groups,也就是是说它以一组进程为目标进行系统资源分配和控制。它主要提供了如下功能:

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。

  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。

  • Accounting: 一些审计或一些统计,主要目的是为了计费。

  • Controll: 挂起进程,恢复执行进程。

使用 cgroup,系统管理员可更具体地控制对系统资源的分配、优先顺序、拒绝、管理和监控。可更好地根据任务和用户分配硬件资源,提高总体效率。

在实践中,系统管理员一般会利用CGroup做下面这些事:

  • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。

  • 为这组进程分配其足够使用的内存

  • 为这组进程分配相应的网络带宽和磁盘存储限制

  • 限制访问某些设备(通过设置设备的白名单)

>>Docker 核心技术与实现原理

2 Docker的核心概念和安装

2.1 核心概念

Docker的三大核心概念:

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

Docker镜像(Image)

Docker镜像类似于虚拟机镜像。可以将它理解为一个面向Docker引擎的只读模板,包含了文件系统。

Docker 镜像是一个特殊的文件系统,类似于 Linux 的 root 文件系统,镜像提供了容器运行时所需的程序、库、资源、配置等文件,还包含了一些为运行时准备的一些配置参数。镜像是一个静态的概念,镜像不包含任何动态数据,其内容在构建之后也不会被改变。

由于镜像包含完整的 Linux root 文件系统,所以它可能会很庞大。因此,Docker 的设计者充分利用 Unions FS 技术,把 Docker 设计为分层存储的结构,什么意思呢?

意思是说,镜像是分层构建的,每一层是上面一层的基础,每一层在构建完成之后都不会再发生变化,这提醒我们,构建镜像的时候我们要保证每一层都只包含我们的应用需要的东西,不要有包含不需要的文件,因为每一层在构建之后不再发生变化,所以即使你在之上的层删除了那些不需要的文件,这些文件也只是被标记为删除,实际上并没有真正删除;如果每一层都包含一些可有可无的文件,就会使得我们的镜像越来越臃肿。通过之前的叙述,我们可以看出,一个镜像实际上并不是一个文件,而是一组分层文件。分层存储还使得不同的镜像可以共享某些层,便于镜像的复用。

Docker容器(Container)

Docker容器类似于一个轻量级的沙箱,Docker利用容器来运行和隔离应用。
容器是从镜像创建的应用运行实例,可以将其启动、开始、停止、删除,而这些容器都是相互隔离、互不可见的。

可以把容器看做一个简易版的Linux系统环境(包括root用户权限、进程空间、用户空间和网络空间等),以及运行在其中的应用程序打包而成的应用盒子。

镜像自身是子都的。容器从镜像启动的时候,Docker会在镜像的最上层创建一个可写层,镜像本身将保持不变。

你可以从镜像创建容器,就像从快照创建虚拟机一样。容器是一个动态的概念,你的程序都是在容器里运行的。

容器就是一个进程,但是它有自己独立的命名空间,拥有自己独立的文件系统,独立的网络等资源。因此,容器就好像一个完全独立于宿主主机的操作系统一样,完全可以被当做一个独立的操作系统使用。

容器也是分层存储的,当你从一个镜像创建并运行一个容器的时候,是以镜像作为基础层,在此之上创建一个容器的存储层,你对容器的读写都是在这一层完成的。但是当容器消亡的时候,容器的存储层也会随之消失

Docker仓库(Registry)

Docker仓库类似于代码仓库,是Docker集中存放镜像文件的场所。
注册服务器是存放仓库的地方,其上往往存放着多个仓库,每个仓库集中存放某一类镜像,往往包括多个镜像文件,通过不同的标签来进行区分。根据所存储的镜像公开分享与否,Docker仓库可以分为公开仓库(Public)和私有仓库(Private)两种形式。

目前,最大的公开仓库是Docker Hub,存放了数量庞大的镜像提供用户下载。国内的公开仓库包括Docker Pool等,可以提供稳定的国内访问。当然如果用户徐希望公开分享自己的镜像文件,Docker也支持用户再本地网络内创建一个只能自己访问的私有仓库。

Docker 仓库是一个集中存储和分发镜像的服务,你可以建立自己的私有仓库,也可以使用共有仓库,比如 Docker Store。你可以从仓库里搜索你需要的镜像,也可以把你自己构建的镜像分享到仓库供别人下载使用。

Docker 究竟做了什么?

为了理解 Docker 帮助我们做了什么,我们先来看看 Linux 内核做了什么。简单来说,Linux 内核做了下面几件事:

  • 对来自硬件的消息作出响应;
  • 启动和规划程序的运行;
  • 控制和组织存储;
  • 在程序之间传递消息;
  • 分配资源——内存,CPU,网络等;
Docker 的 C/S 模型

Docker 采用了 C/S 架构,包括客户端(Client)和服务端(Server),服务端通过 socket 接受来自客户端的请求,这些请求可以是创建镜像,运行容器,终止容器等等。

p-518-2.png

服务端既可以运行在本地主机,也可以运行在远程服务器或者云端,只要你可以访问 Docker 的服务端,你甚至可以在容器里运行容器。现在,我们来看看在 Docker 容器里运行 Docker 容器的例子:

p-518-3.png

网络

在深入讲解 Docker 网络原理之前,不得不简单提一下网络有关的知识:

  • Ethernet(以太网):通过有线或者无线传递“帧”(frame)
  • IP Layer:在局域网内传递数据包
  • Routing(路由):在不同网络之间传递数据包
  • Ports(端口):寻址一台主机的特定程序,这里指的是某些程序监听某些端口

其实在之前学习 Docker 网络操作部分的时候,我们已经介绍过 Docker 网络的一些原理了。Docker 并不是像变魔术一样直接在容器之间传递包,而是运行的时候会自动在宿主主机上创建一个名为 docker0 的虚拟网桥,它就像软件交换机一样,在挂载到它的网口之间进行消息转发。运行一个 Docker 容器时,会创建 veth 对(Virtual Ethernet Pair)接口,这对接口一端在容器内,另一端挂载到 Docker 的网桥(默认 docker0,或者使用– net 参数指定网络)。veth 总是成对出现,并且从一端进入的数据会从另一端流出,这样就可以实现挂载到同一网桥的容器间通信。

p-518-4.png

之前在学习 Docker 端口映射的时候,使用 -p 参数将宿主主机的端口映射到容器内部,这个过程用到了 Linux 的防火墙命令 iptable,iptable 会创建映射规则。

进程和控制组

先来简单描述一下 Linux 进程有关知识。

Linux 的进程都是来自一个父进程,所以进程之间是父子关系。当一个子进程结束的时候,会返回一个退出代码给父进程。在众多进程中,有一个进程是特殊的,它就是初始化进程(init),进程号为0,这个进程负责启动所有其它进程。

使用 Docker 运行容器时,容器启动的时候也有一个初始化进程,当这个进程终止的时候,对应的容器也就终止了。

并且,Docker 使用 Linux 控制组(cgroup, control group) 来对容器进程进行隔离。cgroup 是 Linux 内核的特性之一,它保证所有在一个控制组内的进程组成一个私密的、隔离的空间。控制组内的进程有自己的进程号,并且无法访问所在控制组之外的进程。所以控制组可以把你的系统中的进程划分为若干相互隔离的区域,并且控制组内的父进程衍生的子进程依旧在这个控制组内。Docker 正是利用这个特性实现容器间进程隔离。同时,控制组还提供了资源限制,资源审计等功能,这些在 Docker 里都有所体现。

分层存储

在学习编写 Dockerfile 的部分,我们已经详细介绍过了 Docker 的分层存储架构,所以如果忘记了,请返回去复习相关内容。

2.2 安装Docker

Docker在Linux平台上是原生支持,体验最好。

具体安装在此就不介绍了。

2.3 Docker总体架构

2-1.jpg

其中:

  • distribution 负责与docker registry交互,上传洗澡镜像以及v2 registry 有关的源数据
  • registry负责docker registry有关的身份认证、镜像查找、镜像验证以及管理registry mirror等交互操作
  • image 负责与镜像源数据有关的存储、查找,镜像层的索引、查找以及镜像tar包有关的导入、导出操作
  • reference负责存储本地所有镜像的repository和tag名,并维护与镜像id之间的映射关系
  • layer模块负责与镜像层和容器层源数据有关的增删改查,并负责将镜像层的增删改查映射到实际存储镜像层文件的graphdriver模块
  • graghdriver是所有与容器镜像相关操作的执行者

3 镜像

Docker运行容器前需要本地存在对应的镜像,如果镜像不存在本地,Docker会尝试先从默认镜像仓库下载(默认使用Docker Hub公共注册服务器中的仓库),用户也可以通过配置,使用自定义的镜像仓库。

3.1 获取镜像

可以使用docker pull命令从网络上下周镜像。格式

1
docker pull NAME[:TAG]

如果不显式地指定TAG,则默认会选择latest标签(最新版本)。


1
sudo docker pull ubuntu

其中下载过程中可以看出,镜像文件一般由若干层组成,下载过程中会获取并输出镜像的各层信息。层(Layer)其实是AUFS(Advanced Union File System, 一种联合文件系统)中的重要概念,是实现增量保存与更新的基础。

下载镜像到本地后,即可随时使用该镜像了,例如利用该镜像创建一个容器,在其中运行bash应用。

1
sudo docker run -t -i ubuntu /bin/bash

3.2 查看镜像信息

使用docker images命令可以列出本地主机上已有的镜像。

1
sudo docker images

在列出信息中,可以看到几个字段信息:

  • 来自于那个仓库,比如Ubuntu仓库
  • 镜像的标签信息,比如14.04
  • 镜像的ID号
  • 创建时间
  • 镜像大小

TAG信息用于标记来自同一个仓库的不同镜像。为了方便子啊后续工作中使用这个镜像,还可以使用docker tag命令为本地镜像添加新的标签,如

1
sudo docker tag dl.dockerpool.com:5000/ubuntu:latest ubuntu:latest

使用docker inspect命令可以获取该镜像的详细信息,它返回的是一个JSON格式的消息,如果我们只要其中一项内容时,可以使用-f参数来指定。

3.3 搜寻镜像

使用docker search命令可以搜索远端仓库中共享的镜像,默认搜索Docker Hub官方仓库中的镜像。使用方法为

1
docker search TERM

支持的参数包括:

  • --automated=false:仅显示自动创建的镜像
  • --no-trunc=false:输出信息不截断显示
  • -s--stars=0:指定仅显示评价为指定星级以上的镜像。

3.4 删除镜像

使用docker rmi命令可以删除镜像,格式为

1
docker rmi IMAGE[IMAGE...]

其中IMAGE可以为标签或ID。

例如要删除dl.dockerpool.com:5000/ubuntu:latest镜像,可以使用如下命令:

1
sudo docker rmi dl.dockerpool.com:5000/ubuntu

当同一个镜像拥有多个标签的时候,docker rmi命令只是删除了该镜像多个标签中的指定标签而已,并不影响镜像文件。但当镜像只剩下一个标签的时候就要小心了,此时再使用docker rmi命令会彻底删除该镜像。

使用镜像ID删除镜像

当使用docker rmi命令后面跟上镜像的ID(或ID部分前缀)时,会先尝试删除所有指向该镜像的标签,然后删除该镜像文件本身。

注意,当有该镜像创建的容器存在时,镜像文件默认是无法被删除的。如果要强行删除镜像,可以使用-f参数,但并不推荐。推荐先删除依赖该镜像的所有容器,再来删除镜像

3.5 创建镜像

创建镜像的方法有三种:基于已有镜像的容器创建、基于本地模板导入、基于Dockerfile创建。

基于已有镜像的容器创建

该方法主要是使用docker commit命令,命令格式

1
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

主要选项为:

  • -a--author="":作者信息
  • -m--message="":提交信息
  • -p--pause=true:提交时暂停容器运行

如下创建一个新镜像:
首先启动一个镜像,并在其中进行修改操作

1
2
3
sudo docker run -ti ubuntu:14.04 /bin/bash
touch test # 创建一个test文件
exit # 退出

提交为一个新的镜像,并用ID或名称来指定容器:

1
sudo docker commit -m "added a new file" -a "Docker newbee" a925cb3923gd test

顺利的话会返回新创建的镜像的ID信息

基于本地模板导入

比如使用OpenVZ提供的模板来创建(下载地址:http://openva.org/Download/templates/precreated)。

比如现已下载了一个Ubuntu14.04的模板压缩包后,可以使用以下命令导入:

1
sudo cat ubuntu-14.040x86_64-minimal.tar.gz |docker import -ubuntu:14.04

3.6 存出和载入镜像

可以使用docker save和docker load命令来存出和载入镜像。

存出镜像

如果要存出镜像到本地文件,可以使用docker save命令


1
sudo docker save -o ubuntu_14.04.tar ubuntu:14.04

载入镜像

可以使用docker load从存出的本地文件中再导入到本地镜像库,如

1
sudo docker load --input ubuntu_14.04.tar


1
sudo docker load < ubuntu_14.04.tar

3.7 上传镜像

可以使用docker push命令上传镜像到仓库,默认上传到DockerHub官方仓库,格式

1
docker push NAME[:TAG]

用户在DockerHub网站注册后,即可上传自制的镜像。如

1
2
sudo docker tag test:latest user/test:latest
sudo docker push user/test:latest

4 容器

容器是镜像的一个运行实例,所不同的是,它带有额外的可写文件层。

4.1 创建容器

新建容器

可以使用docker create命令新建一个容器,如

1
sudo docker create -it ubuntu:latest

查看

1
sudo docker ps -a

使用docker create命令新建的容器处于停止状态,可以使用docker start命令来启动它。

新建并启动容器

启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。所需要的命令主要为docker run,等价于先执行docker create命令,再执行docker start命令。

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

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

下面的命令则启动一个bash终端,允许用户进行交互:

1
sudo docker run -t -i ubuntu:14.04 /bin/bash

其中,-t选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上,-i则让容器的标准输入保持打开。

在交互模式下,用户可以通过所创建的终端来输入命令,例如:

1
2
pwd
ls

用户可以按Ctrl+d或输入exit命令来退出容器

对于所创建的bash容器,当使用exit命令退出之后,该容器就自动处于终止状态了。这是因为对于Docker容器来说,当运行的应用退出后,容器也就没有继续运行的必要了。

守护态运行

更多的时候,需要让Docker容器在后台以守护态(Daemonized)形式运行。用户可以通过添加-d参数来实现。

如:

1
sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello; sleep 1; done"

容器启动后会返回一个唯一的ID,也可以通过docker ps命令来查看容器信息,要获取容器的输出信息,可以通过docker logs命令

4.2 终止容器

可以使用docker stop来终止一个运行中的容器,命令的格式为:

1
docker stop [-t|--time[=10]]

它会首先向容器发送SIGTERM信号,等待一段时间后(默认为10s),再发送SIGKILL信号终止容器。

此外,当Docker容器中指定的应用终结时,容器也自动终止。例如对于上一节中只启动了一个终端的容器,用户通过exit命令或Ctrl+d来退出终端时,所创建的容器立刻终止。

此外,可以使用docker stop来终止一个运行中的容器:

1
sudo docker stop ce5

docker kill命令会直接发送SIGKILL信号来强行终止容器。

可以使用docker ps -a -q命令看到处于终止状态的容器的ID信息。处于终止状态的容器,可以通过docker start命令来重新启动。

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

4.3 进入容器

attach命令

4-1.jpg

使用attach命令有时候并不方便,当多个窗口同时attach到同一个容器的时候,所有窗口都会同步显示。当某个窗口因命令阻塞时,其他窗口也无法执行操作了。

exec命令(v1.3起)

如进入到刚创建的容器中并启动一个bash:

1
sudo docker exec -ti 243c32535da7 /bin/bash

nsenter工具

4.4 删除容器

可以使用docker rm命令删除处于终止状态的容器,命令格式为:

1
docker rm [OPTIONS] CONTAINER [CONTAINER...]

支持的选项:

  • -f/--force=false:强行终止并删除一个运行中的容器。
  • -l/--link=false:删除容器的连接,但保留容器。
  • -v/--volumes=false:删除容器挂载的数据券。

4.5 导入和导出容器

导出容器

导出容器是指导出一个已经创建的容器到一个文件,不管此时这个容器是否处于运行状态。可以使用docker export命令,命令格式为:

1
docker export CONTAINER

如:

1
sudo docker export ce5 >test_for_run.tar

导入容器

导出的文件又可以使用docker import 命令导入,成为镜像。如

1
2
cat test_for_run.tar | sudo docker import - test/ubuntu:v1.0
sudo docker images

import和load命令很像,都可以用来导入一个镜像文件到本地。
只不过load命令来导入镜像存储文件到本地的镜像库,import命令来导入一个容器快照到本地镜像库。区别在与容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态)。而镜像存储文件将保存完整记录,体积也要打。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

5 仓库

仓库(Repository)是集中存放镜像的地方。仓库又分为公共仓库和私有仓库。

5.1 Docker Hub

Docker官方维护的一个公共仓库https://hub.docker.com

登录

可以通过执行docker login命令来输入用户名、密码和邮箱来完成注册和登录。注册成功后本地用户目录的.dockercfg中将保存用户的认证信息。

基本操作

  • docker search:查找官方仓库中的镜像(无需登录)。
  • docker pull:下载镜像到本地。
  • docker push:将本地镜像推送到Docker Hub。

5.2 Docker Pool

技术社区

5.3 创建和使用私有仓库

使用registry镜像创建私有仓库

安装Docker后,可以通过官方提供的registry镜像来简单搭建一套本地私有仓库环境:

1
sudo docker run -d -p 5000:5000 registry

这将自动下载并启动一个registry容器,创建本地的私有仓库服务。

默认情况下,会将仓库创建在容器的/tmp/registry目录下,可以通过-v参数来将镜像文件存放在本地的指定路径上。

如下面的例子将上传的镜像放到/opt/data/registry目录:

1
sudo docker run -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry

此时,在本地将启动一个私有仓库服务,监听端口为5000。

管理私有仓库镜像

5-1.jpg

6 数据管理

用户在使用Docker的过程中,往往需要能查看容器内应用产生的数据,或者需要把容器内的数据进行备份,甚至多个容器之间进行数据的共享,这必然涉及容器的数据管理操作。

容器中管理数据主要有两种方式:

  • 数据卷(Data Volumes)
  • 数据卷容器(Data Volume Dontainers)

6.1 数据卷

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

  • 数据卷可以在容器之间共享和重用。
  • 对数据卷的修改会立马生效。
  • 对数据卷的更新,不会影响镜像。
  • 卷会一直存在,直到没有容器使用。

数据卷的使用,类似于Linux下对目录或文件进行mount操作。

在容器内创建一个数据卷

在使用docker run命令的时候,使用-v标记可以在容器内创建一个数据卷。多次使用-v标记可以创建多个数据卷。

如,使用training/webapp镜像创建一个Web容器,并创建一个数据卷挂载到容器的/webapp目录:

1
sudo docker run -d -P --name web -v /webapp training/webapp python app.py

-P是允许外部访问容器需要暴露的端口。

挂载一个主机目录作为数据卷

使用-v标记也可以指定挂载一个本地的已有目录到容器中去作为数据卷:

1
sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py

上面的命令加载主机的/src/webapp目录到容器的/opt/webapp目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序或数据到本地目录中,然后在容器内运行和使用。另外,本地目录的路径必须是绝对路径,如果目录不存在,Docker会自动创建。

Docker挂载数据卷的默认权限是读写(rw),用户也可以通过,ro指定为只读:

1
2
sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro
training/webapp python app.py

加了:ro之后,容器内挂载的数据卷的数据就无法修改了。

挂载一个本地主机文件作为数据卷

-v标记也可以从主机挂载耽搁文件到容器中作为数据卷:

1
sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

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

6.2 数据卷容器

如果用户需要再容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器其实是一个普通的容器,专门用它提供数据卷供其他容器挂载使用。方法如下。

首先创建一个数据卷容器dbdata,并在其中创建一个数据卷挂载到/dbdata。

1
sudo docker run -it -v /dbdata --name dbdata ubuntu

然后可以子啊其他容器中使用--volumes-from来挂载dbdata容器中的数据卷,例如创建db1和db2两个容器,并从dbdata容器挂载数据卷:

1
2
sudo docker run -it --volumes-form dbdata --name db1 ubuntu
sudo docker run -it --volumes-form dbdata --name db2 ubuntu

此时,容器db1和db2都挂载同一个数据卷到相同的/dbdata目录。三个容器任何一方在该目录下的写入,其他容器都可以看到。

例如,在dbdata容器中创建一个test文件:

1
2
3
cd /dbdata
touch test
ls

可以多次使用--volumes-form参数来从多个容器挂载多个数据卷。还可以从其他已经挂载了容器卷的容器来挂载数据卷:

1
sudo docker run -d --name db3 --volumes-form db1 training/postgres

使用--volumes-form参数所挂载数据卷的容器本身并不需要保持在运行状态。

如果删除了了挂载的容器(包括dbdata、db1和db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显式使用docker rm -v命令来指定同时删除关联的容器。

使用数据卷容器可以让用户在容器之间自由地升级和移动数据卷。

6.3 利用数据卷容器迁移数据

可以利用数据卷容器对其中的数据卷进行备份、恢复,以实现数据的迁移。

备份

如:

1
sudo docker run --volumes-form dbdata -v $(pwd):/backup --name worker ubuntu tar cvf /backup/backup.tar /dbdata

这个命令首先利用Ubuntu镜像创建了一个容器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。

恢复

首先创建一个带有数据卷的容器dbdata2:

1
sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/sh

然后创建另一个新的容器,挂载dbdata2的容器,并使用untar解压备份文件到所挂载的容器卷中即可:

1
sudo docker run --volumes-form dbdata2 -v $(pwd):/backup busybos tar xvf /backup/backup.tar

7 网络基础配置

Docker目前提供了映射容器端口到宿主主机和容器互联机制来为容器提供网络服务。

7.1 端口映射实现访问容器

从外部访问容器应用

在启动容器的时候,如果不指定对应参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的。

当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-P-p参数来指定端口映射。当使用-P标记时,Docker会随机映射一个49000~49900的端口至容器内部开放的网络端口

如:

1
sudo docker run -d -P training/webapp python app.py

此时,可以使用docker ps看到,本地主机的49155被映射到了容器的5000端口。访问宿主主机的49155端口即可访问容器内Web应用提供的界面。

同样,可以通过docker logs命令来查看应用的信息:

1
sudo docker logs -f nostalgic_morse

-p则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。

映射所有接口地址

使用hostPort:containerPort格式将本地的5000端口映射到容器的5000端口,可以执行如下命令:

1
sudo docker run -d -p 5000:5000 training/webapp python app.python

此时默认会绑定本地所有接口上的所有地址。多次使用-p标记可以绑定多个端口。如

1
sudo docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.python

映射到指定地址的指定端口

可以使用ip:hostPort:containerPort格式指定映射使用搞一个特定地址,比如localhost地址127.0.0.1:

1
sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py

映射到指定地址的任意端口

使用ip::containerPort绑定localhost的任意端口到容器的5000端口,本地主机会自动分配一个端口:

1
sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.python

还可以使用udp标记来指定udp端口:

1
sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.python

查看映射端口配置

使用docker port来查看当前映射的端口配置,也可以查看到绑定的地址:

1
sudo docker port nosealgic_morse 5000

容器有自己的内部网络和IP地址(使用docker inspect+容器ID可以获取所有的变量值)

7.2 容器互联实现容器间通信

容器的连接(linking)系统是除了端口映射外另一种可以与容器中应用进行交互的方式。它会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。

自定义容器命令

使用--name标记可以为容器自定义命名:

1
sudo docker run -d -P --name web training/webapp python app.py

容器的名称是唯一的。如果已经命名了一个叫web的容器,当你要再次使用web这个名称的时候,需要先用docker rm来删除之前创建的同名容器。

在执行docker run的时候如果添加--rm标记,则容器在终止后会立刻删除。(--rm-d不能同时使用)

容器互联

使用--link参数可以让容器之间安全的进行交互。

先创建一个新的数据库容器:

1
sudo docker run -d --name db training/postgres

删除之前创建的web容器

1
sudo docker rm -f web

然后创建一个新的web容器,并将它连接到db容器:

1
sudo docker run -d -P --name web --link db:db training/webapp python app.py

此时,db容器和web容器简历互联关系。

--link参数的格式为--link name:alias,其中name是要链接的容器的名称,alias是这个链接的别名。

Docker在两个互联的容器之间创建了一个安全隧道,并且不用映射它们的端口到宿主主机上。在启动db容器的时候并没有使用-p-P标记,从而避免了保留数据库端口到外部网络上。

Docker通过两种方式为容器公开连接信息:

  • 环境变量
  • 更新/etc/hosts/文件

8 使用Dockerfile创建镜像

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

8.1 基本结构

Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# dockerfile

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

# 维护者信息
MAINTAINER docker_user docker_user@email.com

# 镜像的操作指令
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

8.2 指令

指令的一般格式为INSTRUCTION arguments,指令包括FROM、MAINTAINER、RUN等。

FROM

格式为FROM <image>FROM<image>:<tag>

第一条指令必须为FROM指令。并且,如果在同一个Dockerfiel中创建多个镜像时,可以使用多个FROM指令(每个镜像一次)。

MAINTAINER

格式为MAINTAINER <name>,指定维护者信息

RUN

格式为RUN <command>RUN ["executable", "param1", "param2"]
前者将在shell终端中运行命令,即/bin/sh -c;后者则使用exec执行。指定使用其他终端可以通过第二种方式实现,例如RUM ["/bin/bash", "-c", "echo hello"]

每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用\来换行。

CMD

支持三种格式:

  • CMD ["executable", "param1", "param2"]使用exec执行,推荐方式。
  • CMD command param1 param2在/bin/sh中执行,提供给需要交互的应用。
  • CMD ["param1", "param2"]提供给ENTRYPOINT的默认参数。

指定启动容器时执行的命令,每个Dockerfile只能有一条CMD命令。如果指定了多条命令,只有最后一条会被执行。

如果用户启动容器时指定了运行的命令,则会覆盖掉CMD指定的命令。

EXPOSE

格式为EXPOSE <port> [<port>...]

告诉DOcker服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过-P,Docker主机会自动分配一个端口转发到指定的端口;使用-p,则可以具体指定那个本地端口映射过来。

ENV

格式为ENV <key> <value>。指定一个环境变量,会被后续RUN指令使用,并在容器运行时保持。

ADD

格式为ADD <src> <dest>。该命令将复制指定的到容器中的。其中可以是DOckerfile所在目录的一个相对路径(文件或目录);也可以是一个URL;还可以是一个tar文件(自动解压为目录)。

COPY

格式为COPY <src> <dest>。复制本地主机的(为Dockerfile所在目录的相对路径,文件或目录)为容器中的。目标路径不存在时,会自动构建。

ENTRYPOINT

有两种格式:

  • ENTRYPOINT ["executable", "param1", "param2"]
  • ENTRYPOINT command param1 param2

配置容器启动后执行的命令,并且不可被docker run提供的参数覆盖。
每个Dockerfile中只能有一个ENTRYPOINT,当指定多个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指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。如

1
2
3
4
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

则最终路径为/a/b/c。

ONBUILD

格式为ONBUILD [INSTRUCTION]。配置当所创建的镜像作为其他新创建镜像的基础镜像时,所执行的操作指令。

例如,Dockerfile使用如下的内容创建了镜像image-A。

1
2
3
4
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

如果基于image-A创建新的镜像时,新的Dockerfile中使用FROM image-A指定基础镜像时,会自动执行ONBUILD指令内容,等价于在后面添加了两条指令。

8.3 创建镜像

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

格式为:

1
docker build [选项] 路径

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

另外,可以通过.dockerugnore文件(每一行添加一条匹配模式)来让Docker忽略路径下的目录和文件。

要指定镜像的标签信息,可以通过-t选项。


1
sudo docker build -t build_repo/first_image /tmp/docker_builder

指定Dockerfile所在路径为/tmp/docker_builder/,并且希望生成镜像标签为build_repo/first_image。


k8s

已经有了Docker,为什么还要k8s?Docker作为容器提供应用级的系统抽象;而k8s提供应用级的集群抽象。

1.概念

Kubernetes(k8s)是自动化容器操作的开源平台,这些操作包括部署,调度和节点集群间扩展。如果你曾经用过Docker容器技术部署容器,那么可以将Docker看成Kubernetes内部使用的低级别组件。Kubernetes不仅仅支持Docker,还支持Rocket,这是另一种容器技术。使用Kubernetes可以:

  • 自动化容器的部署和复制
  • 随时扩展或收缩容器规模
  • 将容器组织成组,并且提供容器间的负载均衡
  • 很容易地升级应用程序容器的新版本
  • 提供容器弹性,如果容器失效就替换它,等等…

实际上,使用Kubernetes只需一个部署文件,使用一条命令就可以部署多层容器(前端,后台等)的完整集群:

1
kubectl create -f single-config-file.yaml

kubectl是和Kubernetes API交互的命令行程序。现在介绍一些核心概念。

2.集群

集群是一组节点,这些节点可以是物理服务器或者虚拟机,之上安装了Kubernetes平台。下图展示这样的集群。注意该图为了强调核心概念有所简化。

p-cluster.png

上图可以看到如下组件,使用特别的图标表示Service和Label:

  • Pod
  • Container(容器)
  • Label(label)(标签)
  • Replication Controller(复制控制器)
  • Service(enter image description here)(服务)
  • Node(节点)
  • Kubernetes Master(Kubernetes主节点)

2.1 Pod

Pod(上图绿色方框)安排在节点上,包含一组容器和卷。同一个Pod里的容器共享同一个网络命名空间,可以使用localhost互相通信。Pod是短暂的,不是持续性实体。你可能会有这些问题:

  • 如果Pod是短暂的,那么我怎么才能持久化容器数据使其能够跨重启而存在呢? 是的,Kubernetes支持卷的概念,因此可以使用持久化的卷类型。
  • 是否手动创建Pod,如果想要创建同一个容器的多份拷贝,需要一个个分别创建出来么?可以手动创建单个Pod,但是也可以使用Replication Controller使用Pod模板创建出多份拷贝,下文会详细介绍。
  • 如果Pod是短暂的,那么重启时IP地址可能会改变,那么怎么才能从前端容器正确可靠地指向后台容器呢?这时可以使用Service,下文会详细介绍。

2.2 Lable

正如图所示,一些Pod有Label(enter image description here)。一个Label是attach到Pod的一对键/值对,用来传递用户定义的属性。比如,你可能创建了一个”tier”和“app”标签,通过Label(tier=frontend, app=myapp)来标记前端Pod容器,使用Label(tier=backend, app=myapp)标记后台Pod。然后可以使用Selectors选择带有特定Label的Pod,并且将Service或者Replication Controller应用到上面。

2.3 Replication Controller

是否手动创建Pod,如果想要创建同一个容器的多份拷贝,需要一个个分别创建出来么,能否将Pods划到逻辑组里?

Replication Controller确保任意时间都有指定数量的Pod“副本”在运行。如果为某个Pod创建了Replication Controller并且指定3个副本,它会创建3个Pod,并且持续监控它们。如果某个Pod不响应,那么Replication Controller会替换它,保持总数为3.如下面的动画所示:

p-rec.png

如果之前不响应的Pod恢复了,现在就有4个Pod了,那么Replication Controller会将其中一个终止保持总数为3。如果在运行中将副本总数改为5,Replication Controller会立刻启动2个新Pod,保证总数为5。还可以按照这样的方式缩小Pod,这个特性在执行滚动升级时很有用。

当创建Replication Controller时,需要指定两个东西:

  • Pod模板:用来创建Pod副本的模板
  • Label:Replication Controller需要监控的Pod的标签。

现在已经创建了Pod的一些副本,那么在这些副本上如何均衡负载呢?我们需要的是Service。

2.4 Service

如果Pods是短暂的,那么重启时IP地址可能会改变,怎么才能从前端容器正确可靠地指向后台容器呢?

Service是定义一系列Pod以及访问这些Pod的策略的一层抽象。Service通过Label找到Pod组。因为Service是抽象的,所以在图表里通常看不到它们的存在,这也就让这一概念更难以理解。

现在,假定有2个后台Pod,并且定义后台Service的名称为‘backend-service’,lable选择器为(tier=backend, app=myapp)。backend-service 的Service会完成如下两件重要的事情:

  • 会为Service创建一个本地集群的DNS入口,因此前端Pod只需要DNS查找主机名为 ‘backend-service’,就能够解析出前端应用程序可用的IP地址。
  • 现在前端已经得到了后台服务的IP地址,但是它应该访问2个后台Pod的哪一个呢?Service在这2个后台Pod之间提供透明的负载均衡,会将请求分发给其中的任意一个(如下面的动画所示)。通过每个Node上运行的代理(kube-proxy)完成。这里有更多技术细节。

有一个特别类型的Kubernetes Service,称为’LoadBalancer’,作为外部负载均衡器使用,在一定数量的Pod之间均衡流量。比如,对于负载均衡Web流量很有用。

2.5 Node

节点(上图橘色方框)是物理或者虚拟机器,作为Kubernetes worker,通常称为Minion。每个节点都运行如下Kubernetes关键组件:

  • Kubelet:是主节点代理。
  • Kube-proxy:Service使用其将链接路由到Pod,如上文所述。
  • Docker或Rocket:Kubernetes使用的容器技术来创建容器。

2.6 Kubernetes Master

集群拥有一个Kubernetes Master(紫色方框)。Kubernetes Master提供集群的独特视角,并且拥有一系列组件,比如Kubernetes API Server。API Server提供可以用来和集群交互的REST端点。master节点包括用来创建和复制Pod的Replication Controller。