一、简介
将应用程序整合到容器中进行运行的过程,称为应用的“容器化”,也可以叫做“Docker化”。一个应用程序在代码编写,测试完成之后,还要面临在生产环境的构建,部署,运维等一系列繁杂的问题,开发人员与运维人员也许要经过多次的协调沟通才能将一个应用成功投入生产。设想如果将应用程序转变成一个容器,那些与应用运行所需环境相关的问题就几乎都不存在了,开发只需要专注于应用代码的开发,稍微加上一点“如何将应用变成容器”的说明工作,而运维人员只需要统一考虑容器的运行管理就可以了,本文就来介绍这个“如何将应用变成容器”的工作---应用的容器化。
二、应用容器化的整体流程
容器化可以简化应用的构建、部署和运行过程,我们先大体掌握以下主要流程步骤:
- 编写应用代码
- 在应用主目录下,创建一个Dockerfile,其中包括当前应用的描述、环境依赖、以及如何运行这个应用
- 在应用主目录下,对Dockerfile文件执行docker image build命令
- 等待Docker将应用程序构建到Docker镜像中
- 一旦应用被打包成了一个Docker镜像,就能以镜像的形式交付、转移、团队分享等,并以Docker容器的方式运行了
注意,Dockerfile文件首字母必须大写,这是Docker image build默认寻找的Dockerfile文件名,当然其实也可以通过-f 来指定Dockerfile的文件名,只不过一般情况下没有必要
三、Dockerfile详解
Dockerfile的两个主要作用:
- 对当前应用的描述
- 指导Docker完成应用的容器化,从构建至运行
打开一个Dockerfile文件,看一下具体长什么样子:
$ cat Dockerfile
# Dockerfile of Example
# Version 1.0
# Base Images
FROM centos
LABEL maintainer="you@example.com"
RUN yum install -y --update nodejs nodejs-npm
COPY . /src
WORKDIR /src
RUN npm install
EXPOSE 8080
ENTRYPOINT ["node", "./app.js"]
- Dockerfile中的注释以#开头,除注释之外的每一行都是一条指令(Instruction)
- 指令的格式:INSTRUCTION args,指令不区分大小写,但建议指令大写,增强Dockerfile的可读性
- docker image build命令会按行一行一行来解析Dockerfile中的指令,并顺序执行
对上面Dockerfile示例文件的解读:
- FROM centos //以centos镜像作为当前镜像的基础,也就是上一篇中讲的基础镜像层
- LABEL maintainer="you@example.com" //指定维护者(maintainer)为“you@example.com”,元数据,不会添加新的镜像层,建议添加上维护者信息,这样可以给镜像潜在使用者提供沟通途径。
- RUN yum install -y --update nodejs nodejs-npm //在centos上用yum安装Nodejs和NPM到当前镜像中,新建镜像层
- COPY . /src //将当前目录中的所有文件(也就是应用代码)复制到镜像中,并新建镜像层
- WORKDIR /src //设置当前的工作目录,后续指令在此目录下执行,会作为元数据记录到镜像配置中,不会创建镜像层
- RUN npm instal //执行npm install安装依赖文件到镜像中,并创建镜像层
- EXPOSE 8080 //指定应用的网络端口,通过8080端口对外提供Web服务,属于容器内的端口,元数据,不会新建镜像层
- ENTRYPOINT ["node", "./app.js"] //最后将app.js设置为镜像默认运行的应用,元数据,不会新建镜像层
指令 | 含义 |
---|---|
FROM 镜像 | 指定基础镜像,第一条指令必须为FROM,每创建一个镜像就需要一条FROM |
MAINTAINER 名字 | 说明镜像的维护者信息 |
RUN 命令 | 在所基于的镜像上执行命令,并提交到新的镜像中 |
CMD["要运行的程序","参数一","参数二"] | 启动容器时要运行的命令或脚本,只能有一条CMD命令,如果有多个,执行最后一个 |
EXPOSE 端口号 | 指定新镜像加载到Docker时需要开启的端口,也就是容器监听连接者的端口 |
ENV 环境变量 变量值 | 设置一个环境变量比如path,会被RUN,CMD等指令用到,还可以定义版本信息便于维护 |
ADD 源文件/目录 目标文件/目录 | 复制文件,源文件要和Dockerfile同级目录,与COPY不同是源文件可以是url |
COPY 源文件/目录 目标文件/目录 | 复制文件,源文件要和Dockerfile同级目录 |
VOLUME ["目录"] | 在容器中创建一个挂载点,里面存放的数据在容器销毁后仍然存在 |
USER 用户名/UID | 指定运行容器时的用户 |
WORKDIR 路径 | 为在该条命令之后的RUN,CMD,ENTRYPOINT指定工作目录,建议绝对路径 |
ENTRYPOINT ["运行的程序","若干参数"] | 指定镜像以容器方式启动后默认运行的程序 |
HEALTHCHECK | 健康检查 |
两个比较:
- COPY和ADD ADD是有更复杂语义的复制文件,COPY就是单纯的复制文件,ADD支持源文件是url远程下载,如果源文件是压缩文件,ADD指令会自动解压,复杂语义会带来一定的不确定性,因此建议复制文件就用明确的COPY
- CMD和ENTRYPOINT 这两个命令是在镜像运行的时候执行的,RUN是在镜像构建的时候执行的,ENTRYPOINT一般指示镜像的主程序入口,CMD中的在和ENTRYPOINT同时存在时作为ENTRYPOINT的参数,Docker run 后附加的参数会被附加在ENTRYPOINT的后面。推荐的做法是ENTRYPOINT作为镜像的主命令,CMD来设定参数。
四、从Dockerfile构建镜像
进入应用主目录,执行如下命令:
docker image build -t mywebapp:latest -f Dockerfile.dev .
-t 指定镜像标签,-f 指定Dockerfile的路径和名称,如果不写,代表是当前目录下的Dockerfile,最后那个点.不能丢,代表从当前目录构建。
镜像构建完成后,可以从镜像启动容器:
docker run -d --name c1 -p 80:8080 mywebapp:latest
-d 让应用程序以守护进程的形式后台运行,-p 80:8080表示将主机的80端口映射到容器内的8080端口,--name 为容器起个名字,代替容器ID方便后续命令使用。
.Dockerignore文件
类似Git的.gitignore文件,Docker也有自己的构建忽略,就是.Dockerignore文件,像一些temp临时文件,测试文件,.git目录,*.md文件等,不必要放入Docker镜像中,在Docker镜像的构建过程中,加入.Dockerignore文件,可以提高构建效率。
五、Dockerfile的一些原则及注意事项
- 避免安装非必要软件包,如文本编辑器,辅助工具等
- 一个容器只运行一个进程,便于对每个容器进行监控和调优,也便于集群扩容或缩容。
- 镜像层尽量保持最少,COPY,RUN,ADD指令会创建镜像层,不要随意使用这些命令
- 多行参数分割,如果安装的软件包很多,建议按照字母顺序排序,并用反斜杠\换行分割,增强Dockerfile可读性
- FROM指令建议使用官方提供的版本,不建议使用上百M的ubuntu,centos,建议使用轻量级安全的Alpine linux
- Dockerfile文件要看作程序的组成部分,加入Git等版本控制
六、总结
本文主要介绍了通过Dockerfile将自己的应用构建成Docker容器的过程,主要以单体应用的构建为例,至于多容器的应用用到的Docker Compose,以及Docker Swarm集群,后续了解的更为深入时再来介绍。