微服务体系架构
代码提交过程
FROM ubuntu:15.04 ## FROM 从ubuntu:15.04 创建一个新的layer -基础环境
COPY . /app ## COPY 从docker client执行的当前目录拷贝文件到镜像中 -代码准备
RUN make /app ## RUN 使用make命令编译应用 -编译
CMD python /app/app.py ## CMD(配置启动) 指定容器启动的命令 -运行
docker build -t image:tag . ## 通过Dockerfile 构建docker image
## -t: tag
## image:tag: imageName: tagId
## .: context的路径 当前路径之下有此DockerFile,以及当前路径就是DockerFile构建的根的context
## docker image类似与git,git上有很多branch,branch会在某些形态下打一个tag(milestone),这个milestone发布时支持回滚。例如:branchA:0.01
## git存储上是差量存储,.gitlog和之前的代码进行比对出差异,每次都是增量往上append。docker也是同样的不断在base容器上差量增加。
docker run -d image:tag
## 通过docker image 创建docker container
很多新手选择Centos7作为安装,可安装后运行image都失败了。Docker跑起来的方式不用于Virtual Box虚拟化的方式,Docker跑起来的container实际是你主机上的一个进程,如果进程退出来,那么container就退出了。例如我们跑Centos系统,但我们没有跑Centos中任何内容,所以对于Docker Container来讲只是把这个系统启动,但没有给它任何命令,所以退出了。一个好的Container遵守单一进程原则,给docker image唯一的进程作为Container的主进程,让其前台运行(例如Linux通过top命令查看一些监控信息,此时std::out一直有输出;而后台执行命令包括cat文件,此时又出现一个新光标==执行即退出,所以算作是后台。因此我们建议开发者建立一个前台运行的命令,让这个命令一直运行。),这样你的Container才会运行下去,而不会直接退出。
dockerfile的目标是将应用进行抽象打包,通过构建产出docker image实现标准化交付。
标准化交付理论基础
dockerfile的设计思路和方法最初是由Cloud Foundry(PAAS)提出的,对于Cloud Foundry,对于一个Java Spring来说,你只需要提交代码到仓库,然后再把仓库配置到Cloud Foundry的系统中;Cloud Foundry会拉取代码并尝试使用探测的机制或代码识别的机制来生成运行时环境(build pack机制),探测出Java应用、 Spring框架和ubuntu依赖去安装并运行。
Cloud Foundry是怎么识别的?
Cloud Foundry把任何应用切成3层:
Cloud Foundry | Docker | |
Application Layer | Java Code | 定义了软件的运行方式:CMD python /app/app.py |
Runtime Layer | Java Runtime + Dependency Package | 定义软件运行时的依赖:COPY . /app RUN make /app |
OS Image | 定义基础的系统依赖:FROM ubuntu:15.04 |
Dockerfile的语法
Description | Example | |
FROM | 拉取基础镜像 | ubuntu:16.04 |
LABEL | MAINTAINER => LABEL,可以在运行起来的container中看得到。此LABEL非常实用,很多监控方案是通过LABEL做上层概念的聚合。例如在k8s,dupliment、 slice而这些参数和container是没有物理的关系,所以很多监控系统会把这些LABEL打到container上去表示这个container是一个dupliment 或 slice,通过这种方式把一些源数据信息进行下发和标识,而采集系统会把这个LABEL采集去做上层的聚合。 | LABEL maintainer="klopp@usc.edu" |
RUN | 定义dockerfile中的命令,在dockerfile构建成docker image时生效。 | RUN apt-get update -y |
EXPOSE | 暴露端口,并没有实际价值,只具备文档用途,增加可读性(应用的特征信息)。描述应用的特征信息。 | 80/tcp |
ENV | 和Linux中expose类似,把k-v环境变量暴露到container中在运行时生效。 我们会把业务逻辑相关的代码放到container中,但我们会有线上环境/运发环境、测试环境等,每个环境会有差异性。我们不希望开发者去打3个镜像,而是希望把这些变化的信息抽取出来,以配置文件件或环境变量的方式完成注入。Docker中推荐把这些变化的东西放入环境变量中 | app demo |
ADD | ADD是COPY的超集,还支持ADD压缩包并自解压和ADD远程的地址并下载到Image中 | src dest |
COPY | src dest | |
ENTRYPOINT | 和CMD区分:如果Dockerfile需要继承自父类Dockerfile推荐使用,没有就用CMD(ENTRYPOINT是/bin/sh -c来执行的,不是PID1的进程运行,无法收到SIGTERM.这样会导致外层去杀死这个进程时,只有PID=1的进程可以收到信号量。所以如果用ENTRYPOINT运行是无法收到该信号量,此时会导致容器被杀死而不能优雅的停止;如果对于没有捕获SIGTERM的场景,会出现超时过后被强制删除。例如:用ENTRYPOINT运行dockerfile,点击删除很久都没有删除掉,实际是PID=1没有收到信号量) | ["executable", "param1"] |
CMD | CMD会自动添加到ENTRYPOINT后面,CMD不会被Dockerfile继承 | ["executable", "param1"] |
SHELL | 可以替换默认的tty环境 | ["executable", "param1"] |
VOLUME | 容器添加一个挂载点,更多是一个文档作用,告诉后续的开发者怎么使用volume,更多是文档作用。 | /myvol |
USER | 安全性:传统在Linux上使用www,nginx上使用wwwData.因此你可以设置一个容器的user,从而保证容器是在一个降权的用户下执行。 | root |
WORKDIR | 默认目录 | /myvol |
从Dockerfile到Docker Image
Dockerfile中的每一条指令都是一个Layer,而Docker Image就是一个由多个Layer组成的文件。相对于普通的ISO系统镜像来说,分层存储会带来两个优势:
- 分层存储的Image比较容易扩展,例如在下面nginx环境中的docker
FROM ubuntu16.04
- 通过Layer优化镜像存储空间:相同的Layer在docker的Image Repo中或在本地环境中只存储一份。例如你有两个Image都依赖于Ubuntu的Standard Image(Same tag),你会发现在主机上这两个镜像:第一个会下载ubuntu而第二个不会下载(速度很快)。通过下面可以观察出镜像下两层是相同的,不仅节约了存储空间,更减少了网络拉取时间。
Docker Image底层实现
Docker Image是分层实现的,把Image跑成Container时,Container会增加容器读写层;如果从客户视角来看,我们实际使用联合文件系统(Union FileSystem)用写时复制机制(Copy On Write)来进行拷贝。