Docker基础介绍
Docker基础
基本概念
- 镜像(Image):相当于一个root文件系统,是创建Docker容器的模板;
- 容器(Container):镜像是静态文件,容器是运行镜像的实体;
- 仓库(Repository):用于保存镜像;
- 客户端(Client):用于执行docker命令;
- 服务端(Docker Deamon):实际上是一个守护进程。负责监听客户端请求,并且管理镜像、容器、网络、磁盘。
细化概念
- Docker Client 用户通过Docker Client与Docker Deamon建立通信,以命令行可执行命令行(docker)形式,发送请求给后者。
- Docker Deamon 架构的主体。主要包括Server和Engine。
- Docker Server C/S架构的服务端。作用:1. 接收client的请求;2. 路由、分发调度,找到对应的Handler执行请求。 在 Docker 的启动过程中,通过包 gorilla/mux 创建了一个 mux.Router 来提供请求的路由功能。在 Golang 中 gorilla/mux 是一个强大的 URL 路由器以及调度分发器。该 mux.Router 中添加了众多的路由项,每一个路由项由 HTTP 请求方法(PUT、POST、GET 或DELETE)、URL、Handler 三部分组成。
- Docker Engine架构中的运行引擎,是Docker运行的核心。扮演着Container 存储仓库的角色,并且通过执行 Job 的方式来操纵管理这些容器。每一项工作都以Job形式存在。
- JobDocker最基本的工作执行单元。在容器内部运行一个进程,创建一个容器,都是Job。
- Docker Registry镜像注册中心,远端存储镜像的仓库。Docker Daemon通过“search”、”pull” 与 “push”这三类Job实现搜索镜像、下载镜像、上传镜像功能。Docker Registry 可分为公有仓库( Docker Hub)和私有仓库。
- GraphDocker内部数据库。分为Repository和GraphDB。
- Repository本地镜像仓库(下载的和自己构建的)。
- GraphDB构建在 SQLite 之上的小型数据库,实现了已下载镜像与容器直接关系的记录。
- Driver Docker 架构中的驱动模块。实现了对Docker容器环境的定制。即 Graph 负责镜像的存储,Driver 负责容器的执行。
- Graphdriver 镜像相关的驱动,主要完成镜像的存储(docker pull 下载的镜像由 Graphdriver 存储到本地Graph的指定目录)与获取(docker run(create)用镜像来创建容器的时候由 Graphdriver 到本地 Graph中获取镜像。)。
- Networkdriver用于完成 Docker 容器网络环境的配置。
- Docker 启动时为 Docker 环境创建网桥。
- Docker 容器创建时为其创建专属虚拟网卡设备。
- Docker 容器分配IP、端口并与宿主机做端口映射,设置容器防火墙策略等。
- Execdriver执行驱动,负责创建容器运行命名空间、容器资源使用的统计与限制、容器内部进程的真正运行等。
- Libcontainer使用 Go 语言设计实现的函数库,设计初衷是希望该库可以不依靠任何依赖,直接访问内核中与容器相关的 API。Docker 可以直接调用 Libcontainer 来操纵容器的 Namespace、Cgroups、Apparmor、网络设备以及防火墙规则等。Libcontainer 提供了一整套标准的接口来满足上层对容器管理的需求。或者说 Libcontainer 屏蔽了 Docker 上层对容器的直接管理。
- Docker container服务交付的最终体现形式。
- 用户通过指定容器镜像,使得 Docker 容器可以自定义 rootfs 等文件系统。
- 用户通过指定计算资源的配额,使得 Docker 容器使用指定的计算资源。
- 用户通过配置网络及其安全策略,使得 Docker 容器拥有独立且安全的网络环境。
- 用户通过指定运行的命令,使得 Docker 容器执行指定的工作。
优势
- 1、更高效的利用系统资源由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。2、更快速的启动时间传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接 运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启 动时间。大大的节约了开发、测试、部署的时间。3、一致的运行环境开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环 境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内 核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码 在我机器上没问题啊” 这类问题。4、持续交付和部署对开发和运维人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员 可+以通过 Dockerfile 来进行镜像构建,并结合持续集成(Continuous Integration) 系 统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环 境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。5、更轻松的迁移由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在 很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一 个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
Docker调优
镜像体积优化
- 选用最小的基础镜像团队/公司采用公共的基础镜像等 。
- 减少层,去除不必要的文件在一个Dockerfile中每个命令都会在原来的基础上生成一层镜像,这毫无疑问将会对镜像增加体积,尽量将RUN命令进行组合,这将有效的减少镜像层数达到减少体积的效果。在实际制作镜像的过程中,一味的合并层不可取,需要学会充分的利用Docker的缓存机制,提取公共层,加速构建。
- 删除文件不要换行Docker几乎每一行命令都会生成一个层,删除文件的时候:因为底下各层都是只读的,当需要删除这些层中的文件时,AUFS 使用whiteout机制,它的实现是通过在上层的可写的目录下建立对应的whiteout隐藏文件来实现的,所以在当前层去删除上一层的文件,只是会把这个文件隐藏掉罢了。
- 依赖文件和实际的代码文件单独分层在实际的开发过程中,我们的依赖包往往是变动不大的,但是我们正在开发的源码的变动是较为频繁,如果我们实际的代码只有10M,但是依赖项有1G,如果在COPY的时候直接
COPY . .
,会导致每次修改代码都会使这一层的缓存失效,导致浪费复制以及推送到镜像仓库的时间,将COPY语句分开,每次push就可以只变更我们频繁修改的代码层,而不是连着依赖一起。 - 使用.dockerignore在使用Git时,我们可以通过.gitignore忽略文件,在docker build的时候也可以使用.dockerignore在Docker上下文中忽略文件,这样不仅可以减少一些非必要文件的导入,也可以提高安全性,避免将一些配置文件打包到镜像中。
- 使用多阶段构建往往我们在构建阶段和实际运行阶段需要的依赖环境是不同的,例如Java应用运行的时候只需要一个Jar包,甚至一个class文件;Golang编写的程序实际运行的时候仅仅需要一个二进制文件即可;对于Node来说,可能最后运行的只是一些打包之后的js文件而不需要包含node_modules里成千上万的依赖。通过多阶段构建,我们可以在Dockerfile中使用多个基础镜像,将编译的成品、配置文件等从一个阶段复制到另一个阶段,这样就可以减少运行容器里不需要的工具,达到减轻镜像体积的目的。
- 注意,在每个阶段构建的镜像不会被自动删除,Docker会将它缓存下来,如果其他镜像构建过程中执行了相同的阶段,就会直接使用缓存里的镜像。
- 镜像服务化当有多个服务时,分多个镜像去运行这些服务,各司其职。例如在同一个镜像中打包了Java环境/python环境/缓存中间件/MQ中间件等,将造成镜像耦合性差,体积臃肿等问题。
- 清除缓存和临时文件镜像构建过程中,可能会采用apt-get或yum等方式进行包的安装,完成后会残留不必要的文件/源代码。清理这些文件也可以减小镜像体积。
构建速度优化
- 构建缓存Docker的缓存触发原则是,单条命令的内容没有变化,构建时则默认使用缓存。所以我们可以通过将一些不变的操作、文件独立成单独的命令,这样在构建的时候只构建需要重新执行的指令。
- 删除构建目录额外的文件在构建镜像时将会上传构建文件夹中的所有文件,但有时不是全部的文件都是构建所需的,可以通过.dockerignore文件进行过滤,这将提高构建的速度及镜像的大小。
- 优化网络请求 在构建镜像过程中,可能有指令需要使用网络去实现下载、安装等操作,由于网络的不确定性,可能会造成构建过程较久,而且构建的时间不一等问题,建议采用本地化引用网络资源来解决问题,例如Centos使用yum进行软件包的管理时,可以通过Nexus进行本地代理,从而减少网络请求的问题。
使用CMD VOLUMN命令
原生网络
Docker原生网络是基于 Linux的网络命名空间(net namespace) 和 虚拟网络设备 (特别是veth pair)实现。
在Docker中,网络接口默认都是虚拟的接口。Linux在内核中通过数据复制实现接口之间的数据传输,可以充分发挥数据在不同Docker容器或容器与宿主机转发效率。即发送接口发送缓存中的数据包,将被直接复制到接收接口的缓存中,无需通过物理网络设备进行交换。
Docker容器创建网络时,会在本地主机和容器内分别创建一个虚拟接口,并且让彼此连通,形成一对虚拟网络接口。这样的一对接口叫做veth pair。
Docker进程启动后,它会配置一个叫docker0的虚拟网桥在宿主机上,这个网桥接口允许Docker去分配虚拟的子网给即将启动的容器。这个网桥在容器内的网络和宿主机网络之间将作为网络接口的主节点呈现。
当Docker容器启动后,创建一对虚拟接口,分别放到本地主机和新容器的命名空间中。本地宿主机一端的虚拟接口连接到默认的docker0网桥或指定网桥上,并具有一个以veth开头的唯一名字。容器一端的虚拟接口将放到新创建的容器中,并修改名字作为eth0,这个接口只在容器的命名空间可见。
从网桥可用地址段中,分配一个网桥子网内的IP地址给容器的eth0。这个IP地址嵌在容器内网络中,用于提供容器网络到宿主机docker0网桥上的一个通道。并配置默认路由网关为docker0网卡的内部接口docker0的IP地址。Docker会自动配置iptables规则和配置NAT,便于连通宿主机上的docker0网桥。完成这些操作之后,容器就可以使用它所能看到的eth0虚拟网卡,来连接其他容器和访问外部网络。
单主机容器间网络
基于命名空间,Docker可以为容器创建隔离的网络环境,每个容器具有完全独立的网络栈,与宿主机隔离;也可以与宿主机或其他容器共享命名空间。Docker容器的网络模式有5种:
- brigde:默认的网络模式,为容器创建独立的网络命名空间,容器具有独立的网卡等所有的网络栈。使用该模式的所有容器都是连接到docker0这个网桥,作为虚拟交换机使容器可以相互通信。但是由于宿主机的IP地址与容器veth pair的 IP地址均不在同一个网段,所以为了宿主机以外的网络通信,docker采用了端口绑定的方式,也就是通过iptables的NAT,将宿主机上的端口端口流量转发到容器内的端口上。
- host:使用宿主机的命名空间,容器的IP地址即为宿主机的IP地址。可以直接使用宿主机的IP地址与外界进行通信,若宿主机具有公有IP,那么容器也拥有这个公有IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行NAT转换,而且由于容器通信时,不再需要通过linux bridge等方式转发或者数据包的拆封,性能上有很大优势。劣势:网络栈不隔离;与宿主机竞争网络栈/端口;容器的崩溃可能导致宿主机崩溃。
- none:为容器创建独立的网络命名空间,但不为它做任何网络配置,容器中只有lo这个loopback(回环网络)网卡用于进程通信。none模式为容器做了最少的网络设置。在没有网络配置的情况下,通过第三方工具或者手工的方式,开发这任意定制容器的网络,提供了最高的灵活性。
- container:与host模式类似,只是容器将与指定的容器共享网络命名空间。这两个容器之间不存在网络隔离,而她们又与宿主机以及除此之外其他的容器存在网络隔离。该模式下的容器可以通过localhost来同一网络命名空间下的其他容器,传输效率较高,且节约了一定的网络资源。在一些特殊的场景中非常有用,例如,kubernetes的pod。
- 用户自定义:docker 1.9版本以后新增的特性,允许容器使用第三方的网络实现或者创建单独的bridge网络,提供网络隔离能力。在用户定义网络模式下,开发者可以使用任何docker支持的第三方网络driver来定制容器的网络。并且,docker 1.9以上的版本默认自带了bridge和overlay两种类型的自定义网络driver。可以用于集成calico、weave、openvswitch等第三方厂商的网络实现。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Docker基础介绍
谢谢你的文章。