docker的提出
传统的kvm通过创建虚拟机的方式来部署多个业务程序,虽然它解决了物理机所不能解决的环境隔离、应用迁移问题,但也带来了大量的硬件资源消耗,因为不同的业务程序所需的资源不同,而每一个虚拟机都是一个完整的操作系统,粒度太重,要分配大量的系统资源,因此,为了高效利用硬件资源,粒度更轻的容器技术诞生了。
*发展历程:物理机——VM(虚拟机)——KVM(虚拟化技术)——容器化技术(Docker)
docker的诞生
Docker公司位于旧金山,原名dotcloud,底层利用了Linux内核支持的容器技术LXC,是为了更方便地创建和管理这些容器而开发的一套工具。
Docker由Golang编写,基于Linux内核的Cgroups、Namespace、以及Union FS等技术对进程进行环境隔离,是属于操作系统层面的虚拟化技术。
- 环境隔离:指进程空间、网络空间、文件系统空间独立
- 被隔离的进程独立于宿主机以及其它隔离的进程,称为容器
- 容器化的最大优势就是保持轻量级的同时实现了VM一样的环境隔离功能
- Docker是开源的容器引擎,可以方便地对容器进行管理,最初的Docker是基于LXC的,后转而使用该公司自行开发的LibContainer
容器的特点:
- 是镜像的实例化
- 随处运行:容器可以将代码与配置文件和相关依赖库进行打包,从而确保在任何环境下的运行都是一致的。
- 高资源利用率:容器提供进程级的隔离,因此可以更加精细地设置CPU和内存的使用率,进而更好地利用服务器的计算资源。
- 快速扩展:每个容器都可作为单独的进程予以运行,并且可以共享底层操作系统的系统资源,这样一来可以加快容器的启动和停止效率。
镜像:包含容器所要运行的程序本身及其依赖、配置项、脚本等一切所需环境和配置
使用docker后的开发—部署流程
开发人员:
- 编写业务项目程序的本体代码
- 编写dockerfile,包含shell部署逻辑、项目的运行环境配置、启动程序。
- docker根据dockerfile构建镜像
测试/运维人员:无需再了解项目的具体配置环境,直接使用开发交付的镜像创建容器实例
总体流程:发布源码-构建镜像-发布镜像-创建容器进程-创建容器实例(源码+镜像=实例)
Docker容器是与系统其他部分隔离开的一系列进程,运行这些进程所需的所有文件都由另一个镜像提供,从开发到测试再到生产的整个过程中,Linux 容器都具有可移植性和一致性。相对于依赖重复传统测试环境的开发渠道,容器的运行速度要快得多,并且支持在多种主流云平台(PaaS)和本地系统上部署。
容器彻底解决了开发、测试、生产等不同环境下环境不一致、环境迁移难、环境部署难的问题
镜像内容只有发行版及其依赖,与宿主机公用内核,相比于虚拟机非常轻量级
Dockerfile
Dockerfile是用来描述文件的构成的文本文档,其中包含了用户可以在使用行调用以组合Image的所有命令,用户还可以使用Docker build实现连续执行多个命令指今行的自动构建。
通过编写Dockerfile生磁镜像,可以为开发、测试团队提供基本一致的环境,从而提升开发、测试团队的效率,不用再为环境不统一而发愁,同时运维也能更加方便地管理我们的镜像。
Dockerfile的语法非常简单,常用的只有11个:

编写优雅的Dockerfile主要需要注意以下几点:
- Dockerfile文件不宜过长,层级越多最终制作出来的镜像也就越大。
- 构建出来的镜像不要包含不需要的内容,如日志、安装临时文件等。
- 尽量使用运行时的基础镜像,不需要将构建时的过程也放到运行时的Dockerfile里。
只要记住以上三点就能写出不错的Dockerfile。
为了方便大家了解,我们用两个Dockerfile实例进行简单的对比:
FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y apt-utils libjpeg-dev \ python-pip RUN pip install --upgrade pip RUN easy_install -U setuptools RUN apt-get clean
FROM ubuntu:16.04
RUN apt-get update && apt-get install -y apt-utils \
libjpeg-dev python-pip \
&& pip install --upgrade pip \
&& easy_install -U setuptools \
&& apt-get clean
我们看第一个Dockerfile,乍一看条理清晰,结构合理,似乎还不错。再看第二个Dockerfile,紧凑,不易阅读,为什么要这么写?
- 第一个Dockerfile的好处是:当正在执行的过程某一层出错,对其进行修正后再次Build,前面已经执行完成的层不会再次执行。这样能大大减少下次Build的时间,而它的问题就是会因层级变多了而使镜像占用的空间也变大。
- 第二个Dockerfile把所有的组件全部在一层解决,这样做能一定程度上减少镜像的占用空间,但在制作基础镜像的时候若其中某个组编译出错,修正后再次Build就相当于重头再来了,前面编译好的组件在一个层里,得全部都重新编译一遍,比较消耗时间。
- 注意,若Dockerfile非常长的话可以考虑减少层次,因为Dockerfile最高只能有127层。
*多阶构建(了解)
在多阶构建出来之前,传统方式是使用单个文件进行构建,单文件就是将所有的构建过程(包括项目的依赖、编译、测试、打包过程)全部包含在一个Dockerfile中之下:
FROM golang:1.11.4-alpine3.8 AS build-env
ENV GO111MODULE=off
ENV GO15VENDOREXPERIMENT=1
ENV BUILDPATH=github.com/lattecake/hello
RUN mkdir -p /go/src/${BUILDPATH}
COPY ./ /go/src/${BUILDPATH}
RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v
CMD [/go/bin/hello]
这种的做法会带来一些问题:
- Dockerfile文件会特别长,当需要的东西越来越多的时候可维护性指数级将会下降;
- 镜像层次过多,镜像的体积会逐步增大,部署也会变得越来越慢;
- 代码存在泄漏风险。
以Golang为例,它运行时不依赖任何环境,只需要有一个编译环境,那这个编译环境在实际运行时是没有任务作用的,编译完成后,那些源码和编译器已经没有任务用处了也就没必要留在镜像里。
多文件构建(多阶构建前的解决方案)
多文件构建,其实就是使用多个Dockerfile,然后通过脚本将它们进行组合。假设有三个文件分别是:Dockerfile.run、Dockerfile.build、build.sh。
- Dockerfile.run就是运行时程序所必须需要的一些组件的Dockerfile,它包含了最精简的库;
- Dockerfile.build只是用来构建,构建完就没用了;
- build.sh的功能就是将Dockerfile.run和Dockerfile.build进行组成,把Dockerfile.build构建好的东西拿出来,然后再执行Dockerfile.run,算是一个调度的角色。
Dockerfile.build
FROM golang:1.11.4-alpine3.8 AS build-env
ENV GO111MODULE=off
ENV GO15VENDOREXPERIMENT=1
ENV BUILDPATH=github.com/lattecake/hello
RUN mkdir -p /go/src/${BUILDPATH}
COPY ./ /go/src/${BUILDPATH}
RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v
Dockerfile.run
FROM alpine:latest RUN apk –no-cache add ca-certificates WORKDIR /root ADD hello . CMD ["./hello"]
Build.sh
#!/bin/sh docker build -t –rm hello:build . -f Dockerfile.build docker create –name extract hello:build docker cp extract:/go/bin/hello ./hello docker rm -f extract docker build –no-cache -t –rm hello:run . -f Dockerfile.run rm -rf ./hello
执行build.sh完成项目的构建。
多文件构建大大减小了镜像的占用空间,但它有三个文件需要管理,维护成本也更高一些。
多阶构建
完成多阶段构建我们只需要在Dockerfile中多次使用FORM声明,每次FROM指令可以使用不同的基础镜像,并且每次FROM指令都会开始新的构建,我们可以选择将一个阶段的构建结果复制到另一个阶段,在最终的镜像中只会留下最后一次构建的结果,这样就可以很容易地解决前面提到的问题,并且只需要编写一个Dockerfile文件。这里值得注意的是:需要确保Docker的版本在17.05及以上。下面我们来说说具体操作。
在Dockerfile里可以使用as来为某一阶段取一个别名”build-env”:
FROM golang:1.11.2-alpine3.8 AS build-env
然后从上一阶段的镜像中复制文件,也可以复制任意镜像中的文件:
COPY –from=build-env /go/bin/hello /usr/bin/hello
看一个简单的例子:
FROM golang:1.11.4-alpine3.8 AS build-env
ENV GO111MODULE=off
ENV GO15VENDOREXPERIMENT=1
ENV GITPATH=github.com/lattecake/hello
RUN mkdir -p /go/src/${GITPATH}
COPY ./ /go/src/${GITPATH}
RUN cd /go/src/${GITPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -v
FROM alpine:latest
ENV apk –no-cache add ca-certificates
COPY --from=build-env /go/bin/hello /root/hello
WORKDIR /root
CMD ["/root/hello"]
执行docker build -t –rm hello3 .后再执行docker images