Docker

  1. 云栖社区>
  2. 博客>
  3. 正文

Docker

东坡肉. 2018-07-23 14:13:57 浏览2548
展开阅读全文

一、 Docker简介

Docker是一个开源的应用容器引擎,基于Go语言开发并遵从Apache2.0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上。容器是完全使用沙箱机制,相互之间不会有任何接口(类似IPhone的App)。
Docker的应用场景:

  • Web应用的自动化打包和发布;
  • 自动化测试和持续集成、发布;
  • 在服务型环境中部署和调整数据库或其他的后台应用;
  • 从头编译或者扩展现有的OpenShift或CloudFoundry平台来搭建自己的PaaS环境;

有一个场景是在一个云平台上,一台16核32G内存的虚拟机,需要跑500+个用户的应用(每个应用的功能可以认为是一个网站+一系列Restful API),有两个事情很重要:

1.1 低消耗

虚拟化本身带来的损耗需要尽量的低;

image

两者区别概括:

  • 虚拟机运行在虚拟硬件上,应用运行在虚拟机内核上。而docker daemon是宿主机上的一个进程,应用只是docker daemon的一个子进程(直接运行在宿主机的内核上)。
  • 虚拟机需要特殊的虚拟化技术支持,因而只能运行在物理机上。docker没有硬件虚拟化,因而可以运行在物理机、虚拟机、甚至docker容器内(嵌套运行)。
  • 因为没有硬件虚拟化及多运行一个Linux内核的开销,应用运行在docker上比虚拟机更轻更快。

1.2 资源隔离

比如限制应用最大内存使用量,或者资源加载隔离等;我们不可能再一台机器上开500个虚拟机,虽然可以在资源方面做的很好,但这种虚拟化本身带来的资源消耗太严重。另一个方面我们可以考虑使用语言级别沙箱,虽然这种虚拟化本身的消耗可以低到忽略不计,但是资源隔离方面绝对是噩梦,比如你打算在一个JVM里隔离内存的使用。

而Docker很好的权衡了两者,即拥有了不错的资源隔离能力,又有很低的虚拟化开销。传统的虚拟机是虚拟出一套硬件后,在其上运行一个完整的操作系统(docker是没有这个从OS的,所以存储资源占用少),在该系统上再运行所需应用进程;而Docker容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此Docker容器要比传统虚拟机更为轻便

总结对比Docker容器和虚拟机:

image

二、 Docker核心概念

2.1 Docker镜像

镜像描述了docker容器运行的初始文件系统。包含运行应用所需的所有依赖,既可以是一个完整的操作系统,也可以是仅包含应用所需的最小bin/lib文件集合。
? 加入一个docker容器文件系统为10G,那么创建10个容器需要多少磁盘空间?,100G,错,因为文件系统是只读的那么是共享的,而docker镜像和容器采用分层文件系统结构,每个容器包含一层薄薄的可写层,只读部分是共享的,因而可以毫秒创建。
从整体的角度来讲,一个完整的Docker镜像可以支撑一个Docker容器的运行,在Docker容器运行过程中主要提供文件系统视角。例如一个Ubuntu 14.04镜像,提供了一个基本的Ubuntu 14.04的发行版,当然此镜像时不包含操作系统Linux内核的。(Linux内核与Ubuntu 14.04docker镜像的区别,一个特定的Ubuntu发行版是不包含的Linux内核,但是包含Linux之外的软件管理方式,软件驱动等,所以docker镜像对应个发行版操作系统,但不包含Linux内核)。
在docker架构中,docker镜像类似于Ubuntu操作系统的发行版,可以在任何满足Linux内核之上运行。镜像最终是提供运行环境,我们称之为容器
docker两方面的技术非常重要:Linux容器方面的技术;docker镜像的技术;
分层存储
Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
由于镜像包含OS完整的root文件系统,其体积往往是庞大的,因此Docker设计时,就充分利用了UnionFS的技术,将其设计为分层存储的架构。所以严格来说,镜像并非像一个ISO那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是一组文件系统组成或多层文件系统联合组成。镜像构建时会一层层构建,前一层是后一层的基础。每一层构建完就不再发生改变,后一层上的任何改变只发生在自己这一层。这种分层使得镜像在复用、定制变的更为容易,甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新镜像(即省事又省时)。
在docker镜像构建过程中,每个指令就会生成一层镜像,之前docker镜像上限层数为42层,现在未127层,这也就带来一个问题,在设计dockerfile时需要防止大规模的编写执行命令,需要合理利用&&来将多条指令进行合并,减少镜像层数,当然也不是一次性把所有指令都通过&&连接在一起,尽量是一个相对的完整的过程。

2.2 Docker容器

相对于镜像来说,容器是镜像的一个实例是其运行时的实体,容器可以被创建、启动、停止、删除、暂停等。容器的实质是进程。其以镜像作为基础层,在基础上加一层作为容器运行时的存储层。

Docker Registery

镜像构建完成后,可以很容易的在当前宿主上运行,但是如果需要在其他服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。常用的公开服务Docker HubGoogle Container Registry

标准化交付

docker将应用及所有依赖打包到镜像内,包括二进制文件(包括底层基础库)、静态配置文件、环境变量等。剥离了应用对操作系统和环境的依赖,松耦合。只需要拉取镜像启动容器即可完成应用部署,方便。毫秒级创建销毁容器,从而可以实现快速部署、快速迁移、快速扩容缩容,一键快速回滚(只依赖应用启动时间)。docker镜像制定了应用交付标准,开发人员对应用及其运行环境完全可控,并有效避免各种环境问题踩坑。不会出现【这段代码在我机器上没问题啊,深有感触尤其是maven依赖未清理干净】

微服务编排

单机部署多应用时,应用之间完全解耦,可以任意部署编排,完美支持微服务编排的需求。多个C应用混布时,docker化实现C依赖隔离,避免依赖冲突。多应用部署:降低成本而进行服务器整合;将一个整体式的应用拆分成松耦合的单个服务(微服务架构)

提升资源利用率

docker是轻量级解决方案,不做虚拟化,不运行多余的kernel和init进程,能有效提升资源利用率。

调试能力

提供很多工具,这些工具不一定只是针对容器,但是却适用于容器。它们提供了很多的功能,包括可以为容器设置检查点、设置版本和查看两个容器之间的差别,这些特性可以帮助调试bug。

多租户环境

docker可以为每一个租户的应用层的多个实例创建隔离的环境,这不仅简单而且成本低廉。当然一切得益于docker环境的启动速度和其高效的diff命令。

持续交付与部署

对于开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。使用Docker可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过Dockerfile来进行镜像构建,并结合持续集成系统(Aone 系统自动构建、部署、测试从而确保让代码能够快速、安全的部署到产品生产环境中。)进行集成测试,而运维人员可以直接在生产环境中快速部署该镜像,甚至结合持续部署系统(Freedom)进行自动部署。

Docker使用

镜像获取

docker pull [选项] [Docker Registry地址]<仓库名>:<标签>

// 拉取镜像
docker pull ubuntu:14.04
// 运行镜像
docker run -it --rm ubuntu:14.04 bash

dockerfile

  • docker创建镜像主要有两种方式,把做了一系列操作的容器关闭,然后利用Docker的commit指令。docker commit 容器ID镜像名:tag。然后* docker push到镜像仓库。别人pull下来的再次启动的时候,就是你当前的操作的形态。
  • 另一种是通过Dockerfile 构建的方式,把操作的步骤通过脚本的形式写下来,然后构建的时候,docker会按照你写的步骤,一步一步的构建。这是目前主流的构建方式。

FROM

定制镜像一般是以一个镜像为基础适用FROM命令来实现,在其上进行定制,这个基础镜像是必须指定的且必须是第一条指令。在Docker Hub上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类镜像,如Nginx、redis、mongo、mysql、httpd、tomcat、PHP等,也有一些方便开发构建、运行各种语言引用的镜像如node、openjdk、Python、ruby、golang等,此外官网还提供了一些更为基础的操作系统镜像如Ubuntu、Debian、Centos、Fedora、Alpine等

RUN

用来执行命令行命令的,由于命令行的强大能力,RUN指令在定制镜像时是最常用的指令之一,其格式为

  • shell格式: RUN <命令>
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec格式:RUN ["可执行文件", "参数1", "参数2"]
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

【注意】

FROM debian:jessie

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

Dockerfile中每一个指令都会简历一层,RUN也不例外。每一个RUN的行为,就和刚才我们手工简历镜像的过程一样,新建立一层,在其上执行这些命令,执行结束后,commit这一层的修改,构成新的镜像。而上面这种写法创建了7层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等。结果就产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。这是很多初学Docker的人常犯的一个错误。而且UnionFS是有最大层数限制的,比如AUFS,曾经是最大不超过42层,现在是127层。

FROM debian:jessie

RUN buildDeps='gcc libc6-dev make' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
    && mkdir -p /usr/src/redis \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -rf /var/lib/apt/lists/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

之前所有的命令只有一个目的,就是编译、安装redis可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个RUN对一一对应不同的命令,而是仅仅使用一个RUN指令,并使用&&将各个所需命令串联起来。将之前的7层,简化为了1层。在撰写Dockerfile的时候,要经常提醒自己,这并不是在写shell脚本,而是在定义每一层如何构建,另外有一个值得学习的地方,就是我们中间会为了安装一些软件下载很多文件,由于镜像是分层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。每一层构建的最后一定要清理掉无关文件

CMD

执行上与RUN类似分为shell和exec格式,容器就是进程,那么在容器启动时,需要指定所运行程序及参数,CMD指令就是用于指定默认的容器主进程的启动命令。

ENTRYPOINT

RUN、CMD与ENTRYPOINT三者均可用于执行命令,但无论在目的、运行时机、用法等方面都存在差异;RUN在Dockerfile构建镜像的过程中运行,最终被commit到镜像。
ENTRYPOINT和CMD在容器运行(run、start)时运行。

ENV

设置环境变量

  • ENV
  • ENV ==...
ENV JAVA_HOME /opt/taobao/install/ajdk-8.2.4-b52 \
    PATH $PATH:$JAVA_HOME/bin \
    CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar \
    ODPS_HOME /home/admin/odps/odps_clt_release_64 \
    PATH $PATH:$ODPS_HOME/bin

VOLUME

定义匿名卷,对于容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态的数据的应用,其数据文件应该保存于卷(Volume)中。在dockerfile中可以事先指定某些目录挂载为匿名卷,这样运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

COPY

将从构建上下文目录中<源路径>的文件或目录复制到新的一层的镜像内的<目标路径>位置。目标路径可以是容器内的绝对路径,也可以是相对工作目录的相对路径。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建确实目录。

COPY package.json /usr/src/app/

ADD

更为高级的复制,格式与COPY基本一致,但在COPY基础上增加了一些功能。比如<源路径>可以是一个URL,这种情况下,Docker引擎会试图去下载这个链接的文件放到<目标路径>去。下载后的文件权限自动设置为600,如果这并不是想要的权限,还需增加一层RUN进行权限调整。压缩包类似需要RUN来解压。而ADD会自动做解压。
一般两者在选择时遵循,所有文件的复制均可使用COPY,仅在需要自动解压缩的场合使用ADD

ARG

网友评论

登录后评论
0/500
评论
东坡肉.
+ 关注