Docker标准部署最佳实践
最佳实践系列文档编写目的是为规范公司统一标准化运维项目,以便未来形成相关知识库以及流水线的标准化操作。
Docker是一个开源的应用容器引擎,本篇文档将介绍Docker标准部署最佳实践。容器在普遍意义上指的是可以装下其它物品的工具,比如人类使用的衣柜、行李箱、背包等可以称为容器。在IT领域,容器是镜像的运行时实例,用来容纳软件应用。正如从虚拟机模板上启动VM一样,用户也同样可以从单个镜像上启动一个或多个容器。
容器技术是虚拟化、云计算、大数据之后的一门新兴的热门技术,其提高了硬件资源利用率、方便了企业的业务快速横向扩容、实现了业务宕机自愈功能,这是一个对于IT行业来说非常有价值和影响力的技术。
Docker是一个开放源代码软件项目,目标是实现轻量级的操作系统虚拟化解决方案。
Docker利用Linux核心中的资源分离机制(例如cgroups),以及Linux核心名字空间(namespaces),来创建独立的容器(containers),使其可以在单一Linux实体下运作,避免启动一个虚拟机造成的额外负担。
Linux核心对名字空间的支持完全隔离了工作环境中应用程序的可视范围,包括进程树、网络、用户ID、挂载文件系统等,而核心的资源分离机制隔离了包括CPU、存储器、block I/O、网络等资源。
Dockers有能力打包应用程序及其虚拟容器,可以在任何Linux服务器上运行,这有助于实现灵活性和便携性,使应用程序在任何地方都可以运行,无论是公有云、私有云、单机等。
正在上传…重新上传取消
下面的图片比较了Docker和传统虚拟化方式的不同之处,可见容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统方式则是在硬件层面实现。
Docker的组成分别是:
- Docker主机(Host):一个物理机或虚拟机,用于运行Docker服务进程和容器。
- Docker服务端(Server):Docker守护进程,运行docker容器。
- Docker客户端(Clien):客户端使用docker命令或其他工具调用docker API。
- Docker仓库(Registry):保存镜像的仓库,类似于git或svn这样的版本控制系
- Docker镜像(Images):镜像可以理解为创建实例使用的模板。
- Docker容器(Container ): 容器是从镜像生成对外提供服务的一个或一组服务。
-
正在上传…重新上传取消 Docker的Imgae镜像
-
镜像介绍
在Docker的术语里,一个只读层被称为镜像,一个镜像是永久不会变的。
由于Docker使用一个统一文件系统,Docker 进程认为整个文件系统是以读写方式挂载的。但是所有的变更都发生在顶层的可写层,而下层的原始只读镜像文件并未变化。由于镜像不可写,所以镜像是无状态的。
容器和镜像的关系
容器其实是在镜像的最上面加了一层读写层,在运行容器里文件改动时,会先从镜像里面把要写的文件复制到容器自己的文件系统中(即读写层)。
如果容器删除了,最上面的读写层也就删除了,改动也就丢失了。所以无论多少个容器共享一个镜像,所做的写操作都是从镜像的文件系统中复制过来操作的,并不会修改镜像的源文件,这种方式提高磁盘利用率。
若想持久化这些改动,可以通过docker commit将容器保存成一个新的镜像,但是不推荐这么操作,而是直接重新制作一个镜像,作为模板,这样的镜像会更小。
Docker采用了C/S架构,包括客户端和服务端。Docker daemon作为服务端接受来自客户的请求,并处理这些请求(创建、运行、分发容器)。客户端和服务端既可以运行在一个机器上,也可通过socket或者RESTful API来进行通信。
Docker daemon一般在宿主主机后台运行,等待接收来自客户端的消息。Docker客户端则为用户提供一系列可执行命令,用户用这些命令实现跟Docker daemon交互。
名字空间是Linux内核一个强大的特性。每个容器都有自己单独的名字空间,运行在其中的应用都像是在独立的操作系统中运行一样。名字空间保证了容器之间彼此互不影响。
pid名字空间
不同用户的进程就是通过pid名字空间隔离开的,且不同名字空间中可以有相同pid。所有的LXC进程在Docker中的父进程为Docker进程,每个LXC进程具有不同的名字空间。同时由于允许嵌套,因此可以很方便的实现嵌套的Docker容器。
net名字空间
有了pid名字空间,每个名字空间中的pid能够相互隔离,但是网络端口还是共享host的端口。网络隔离是通过net名字空间实现的,每个net名字空间有独立的网络设备,IP地址,路由表,proc/net目录。这样每个容器的网络就能隔离开来。Docker默认采用veth的方式,将容器中的虚拟网卡同host上的一个Docker 网桥docker0连接在一起。
ipc名字空间
容器中进程交互还是采用了Linux常见的进程间交互方法(interprocess communication – IPC),包括信号量、消息队列和共享内存等。然而同VM不同的是,容器的进程间交互实际上还是host上具有相同pid名字空间中的进程间交互,因此需要在IPC资源申请时加入名字空间信息,每个IPC资源有一个唯一的32位id。
mnt名字空间
类似chroot,将一个进程放到一个特定的目录执行。mnt名字空间允许不同名字空间的进程看到的文件结构不同,这样每个名字空间 中的进程所看到的文件目录就被隔离开了。同chroot不同,每个名字空间中的容器在/proc/mounts的信息只包含所在名字空间的mount point。
uts名字空间
UTS(“UNIX Time-sharing System”)名字空间允许每个容器拥有独立的hostname和domain name,使其在网络上可以被视作一个独立的节点而非主机上的一个进程。
user名字空间
每个容器可以有不同的用户和组id,也就是说可以在容器内用容器内部的用户执行程序而非主机上的用户。
控制组(cgroups)是Linux内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争。
控制组技术最早是由Google的程序员2006年起提出,Linux内核自2.6.24版本开始支持。
控制组可以提供对容器的内存、CPU、磁盘IO等资源的限制和审计管理。
联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。
联合文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
另外,不同Docker容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。
Docker中使用的AUFS(AnotherUnionFS)就是一种联合文件系统。AUFS支持为每一个成员目录(类似Git的分支)设定只读(readonly)、读写(readwrite)和写出(whiteout-able)权限, 同时 AUFS 里有一个类似分层的概念,对只读权限的分支可以逻辑上进行增量地修改(不影响只读部分的)。
Docker支持的联合文件系统种类包括AUFS,btrfs,vfs和DeviceMapper。
最初,Docker采用了LXC中的容器格式。自1.20版本开始,Docker也开始支持新的libcontainer格式,并作为默认选项。
Docker的网络实现其实就是利用了Linux上的网络名字空间和虚拟网络设备(特别是veth pair)。
基本原理
首先,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)来收发数据包;此外,如果不同子网之间要进行通信,需要路由机制。
Docker中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像是一个正常的以太网卡,只是它不需要真正同外部网络设备通信,速度要快很多。
Docker容器网络就利用了这项技术。它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)。
创建网络参数
Docker创建一个容器的时候,会执行如下操作:
创建一对虚拟接口,分别放到本地主机和新容器中;
本地主机一端桥接到默认的 docker0 或指定网桥上,并具有一个唯一的名字,如 veth65f9;
容器一端放到新容器中,并修改名字作为 eth0,这个接口只在容器的名字空间可见;
从网桥可用地址段中获取一个空闲地址分配给容器的eth0,并配置默认路由到桥接网卡veth65f9。
完成这些之后,容器就可以使用eth0虚拟网卡来连接其他容器和其他网络。
可以在docker run的时候通过--net参数来指定容器的网络配置,有4个可选值:
- --net=bridge这个是默认值,连接到默认的网桥。
- --net=host告诉Docker不要将容器网络放到隔离的名字空间中,即不要容器化容器内的网络。此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权限。容器进程可以跟主机其它 root 进程一样可以打开低范围的端口,可以访问本地网络服务比如 D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。如果进一步的使用 --privileged=true,容器会被允许直接配置主机的网络堆栈。
- --net=container:NAME_or_ID让Docker将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享 IP 地址和端口等网络资源,两者进程可以直接通过lo环回接口通信。
-
正在上传…重新上传取消 --net=none让Docker将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自己进行配置。
- run
docker run :创建一个新的容器并运行一个命令
语法:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
OPTIONS说明:
- -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
- -d: 后台运行容器,并返回容器ID;
- -i: 以交互模式运行容器,通常与 -t 同时使用;
- -P: 随机端口映射,容器内部端口随机映射到主机的端口
- -p: 指定端口映射,格式为:主机(宿主)端口:容器端口
- -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
- --name="nginx-lb": 为容器指定一个名称;
- --dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
- --dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
- -h "mars": 指定容器的hostname;
- -e username="ritchie": 设置环境变量;
- --env-file=[]: 从指定文件读入环境变量;
- --cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行;
- -m :设置容器使用内存最大值;
- --net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
- --link=[]: 添加链接到另一个容器;
- --expose=[]: 开放一个端口或一组端口;
- --volume , -v: 绑定一个卷
- start/stop/restart
- docker start :启动一个或多个已经被停止的容器
- docker stop :停止一个运行中的容器
- docker restart :重启容器
语法:
docker start [OPTIONS] CONTAINER [CONTAINER...]
docker stop [OPTIONS] CONTAINER [CONTAINER...]
docker restart [OPTIONS] CONTAINER [CONTAINER...]
- kill
docker kill :杀掉一个运行中的容器。
语法:
docker kill [OPTIONS] CONTAINER [CONTAINER...]
OPTIONS说明:
-s :向容器发送一个信号
- rm
docker rm :删除一个或多个容器。
语法:
docker rm [OPTIONS] CONTAINER [CONTAINER...]
OPTIONS说明:
- -f :通过 SIGKILL 信号强制删除一个运行中的容器。
- -l :移除容器间的网络连接,而非容器本身。
- -v :删除与容器关联的卷。
- pause/unpause
- docker pause :暂停容器中所有的进程。
- docker unpause :恢复容器中所有的进程。
语法:
docker pause CONTAINER [CONTAINER...]
docker unpause CONTAINER [CONTAINER...]
- create
docker create :创建一个新的容器但不启动它用法同 docker run
- exec
docker exec :在运行的容器中执行命令
语法:
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
OPTIONS说明:
- -d :分离模式: 在后台运行
- -i :即使没有附加也保持STDIN 打开
- -t :分配一个伪终端
- Ps
docker ps : 列出容器
语法:
docker ps [OPTIONS]
OPTIONS说明:
-a :显示所有的容器,包括未运行的。
-f :根据条件过滤显示的内容。
--format :指定返回值的模板文件。
-l :显示最近创建的容器。
-n :列出最近创建的n个容器。
--no-trunc :<