程序员笔记|如何编写优雅的Dockerfile
导读
Kubernetes要从容器化开始,而容器又需要从Dockerfile开始,本文将介绍如何写出一个优雅的Dockerfile文件。
文章主要内容包括:
Docker容器
Dockerfile
- 使用多阶构建
感谢公司提供大量机器资源及时间让我们可以实践,感谢在此专题上不断实践的部分项目及人员的支持。
一、Docker容器
1.1 容器的特点
我们都知道容器就是一个标准的软件单元,它有以下特点:
随处运行:容器可以将代码与配置文件和相关依赖库进行打包,从而确保在任何环境下的运行都是一致的。
高资源利用率:容器提供进程级的隔离,因此可以更加精细地设置CPU和内存的使用率,进而更好地利用服务器的计算资源。
- 快速扩展:每个容器都可作为单独的进程予以运行,并且可以共享底层操作系统的系统资源,这样一来可以加快容器的启动和停止效率。
1.2 Docker容器
目前市面上的主流容器引擎有Docker、Rocket/rkt、OpenVZ/Odin等等,而独霸一方的容器引擎就是使用最多的Docker容器引擎。
Docker容器是与系统其他部分隔离开的一系列进程,运行这些进程所需的所有文件都由另一个镜像提供,从开发到测试再到生产的整个过程中,Linux 容器都具有可移植性和一致性。相对于依赖重复传统测试环境的开发渠道,容器的运行速度要快得多,并且支持在多种主流云平台(PaaS)和本地系统上部署。Docker容器很好地解决了“开发环境能正常跑,一上线就各种崩”的尴尬。
Docker容器的特点:
轻量:容器是进程级的资源隔离,而虚拟机是操作系统级的资源隔离,所以Docker容器相对于虚拟机来说可以节省更多的资源开销,因为Docker容器不再需要GuestOS这一层操作系统了。
快速:容器的启动和创建无需启动GuestOS,可以实现秒级甚至毫秒级的启动。
可移植性:Docker容器技术是将应用及所依赖的库和运行时的环境技术改造包成容器镜像,可以在不同的平台运行。
- 自动化:容器生态中的容器编排工作(如:Kubernetes)可帮助我们实现容器的自动化管理。
二、Dockerfile
Dockerfile是用来描述文件的构成的文本文档,其中包含了用户可以在使用行调用以组合Image的所有命令,用户还可以使用Docker build实现连续执行多个命令指今行的自动构建。
通过编写Dockerfile生磁镜像,可以为开发、测试团队提供基本一致的环境,从而提升开发、测试团队的效率,不用再为环境不统一而发愁,同时运维也能更加方便地管理我们的镜像。
Dockerfile的语法非常简单,常用的只有11个:
2.1 编写优雅地Dockerfile
编写优雅的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所编译出来的镜像大小:
$ docker images | grep ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 16.04 9361ce633ff1 1 days ago 422MB
ubuntu 16.04-1 3f5b979df1a9 1 days ago 412MB
呃…. 好像并没有特别的效果,但若Dockerfile非常长的话可以考虑减少层次,因为Dockerfile最高只能有127层。
三、使用多阶构建
Docker在升级到Docker 17.05之后就能支持多阶构建了,为了使镜像更加小巧,我们采用多阶构建的方式来打包镜像。在多阶构建出现之前我们通常使用一个Dockerfile或多个Dockerfile来构建镜像。
3.1单文件构建
在多阶构建出来之前使用单个文件进行构建,单文件就是将所有的构建过程(包括项目的依赖、编译、测试、打包过程)全部包含在一个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 –vCMD [/go/bin/hello]
这种的做法会带来一些问题:
Dockerfile文件会特别长,当需要的东西越来越多的时候可维护性指数级将会下降;
镜像层次过多,镜像的体积会逐步增大,部署也会变得越来越慢;
- 代码存在泄漏风险。
以Golang为例,它运行时不依赖任何环境,只需要有一个编译环境,那这个编译环境在实际运行时是没有任务作用的,编译完成后,那些源码和编译器已经没有任务用处了也就没必要留在镜像里。
上表可以看到,单文件构建最终占用了312MB的空间。
3.2 多文件构建
在多阶构建出来之前有没有好的解决方案呢?有,比如采用多文件构建或在构建服务器上安装编译器,不过在构建服务器上安装编译器这种方法我们就不推荐了,因为在构建服务器上安装编译器会导致构建服务器变得非常臃肿,需要适配各个语言多个版本、依赖,容易出错,维护成本高。所以我们只介绍多文件构建的方式。
多文件构建,其实就是使用多个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完成项目的构建。
从上表可以看到,多文件构建大大减小了镜像的占用空间,但它有三个文件需要管理,维护成本也更高一些。
3.3 多阶构建
最后我们来看看万众期待的多阶构建。
完成多阶段构建我们只需要在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-envENV 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 -vFROM 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 ,然后我们来看镜像的大小:
多阶构建给我们带来很多便利,最大的优势是在保证运行镜像足够小的情况下还减轻了Dockerfile的维护负担,因此我们极力推荐使用多阶构建来将你的代码打包成Docker 镜像。
作者:王聪
内容来源:宜信技术学院
转载于:https://blog.51cto.com/14159827/2386102
相关文章:

Oracle面试问题汇总
1:SqL 优化 1:尽量避免使用 select * 查询方式 因为oracle 在解析过程中 会将*依次转化成所以的列名。 2:减小访问数据库的次数 因为每执行一条sql语句的时候,oracle内部会做许多的事情 如:解析sql ,估算索引的利用效率…

相关性分析p值_一行代码掌握皮尔逊相关分析,洞察变量关系
变量类型与推荐的假设检验方法可以看到,当我们探索两个连续变量之间的关系时,相关分析是一个很好的选择。那么,相关分析的原理是什么?如何在Python中实现相关分析呢?一、Pearson相关系数针对两个独立的服从正态分布的连…

润前报表简单问题
Q:设计器如何打开A:如果是安装的,那么直接到菜单下,打开润乾报表设计器就行了,如果是压缩包或者从别人那儿拷过来的,就运行\reportHome\bin下面的startup.bat就可以打开了。 Q:做一个报表最基本…

一个简单的slider滑块组件
2019独角兽企业重金招聘Python工程师标准>>> 我们先来看一张图片: 要实现这样的效果我们有很多种方法,比如直接使用<input type"range" />修改样式即可,也可用下面的这种方式修改 样式 HTML代码: <…

压测接口线程数设置_ZAT掌门性能压测巡检系统实战和落地
项目背景随着业务拓展,对于接口性能的要求也在上升,各部门也开始针对部分慢接口进行优化,从测试角度针对这些优化需求进行测试时不仅要保证对应接口的功能正常使用同时也要验证接口优化成果。在日常的开发工作中一些后台服务配置的改动也会对…

01python语言程序设计基础——初识python
1.python的字符串中format函数用法 format 函数可以接受不限个参数,位置可以不按顺序。In [2]:"{} {}".format("hello", "world") # 不设置指定位置,按默认顺序Out[2]:hello world In [3]:"{0} {1}".format(&q…

没有什么不可能(1)
近在读一本书《没有什么不可能》,书中宗旨就是:这个世界没有什么不可能,每个人的脚下都有一条通往成功的道路,信念是一切力量的源泉。这本书看了三分之一,跟大家分享一下前三个观点。 1、只有想不到,没有做…

浅浅认识之VBS脚本访问接口与COMODO拦截COM接口
这2天测试了一个使用了WMI提供ASEC后门,里面使用了JS脚本往外请求http获取执行命令。但我的分析系统却没抓到这个行为,可在真机中确实抓到有HTTP请求。相当奇怪。 最后无奈windbg出手,内核断点afd 发送函数。最后发现是scrcons.exe进程&#…

redis最大储存512m_redis系列篇01
今天写的这篇是redis系列的文章,我的安排是由浅入深写redis系列。本篇是简单的介绍入门,后续的文章会详细讲解redis深层次的知识。欢迎大家关注我的微信公众号:码农Bug首先说几个简单的命令:keys *:查询所有的键值del key…

批处理命令——goto 和 :
谈起goto,相信大家应该想到的是面向过程编程。其实,这就相当于当有人向你谈起class,意味着你就懂得面向对象编程。如果你不懂,那么你们的沟通将会很困难。不懂我说的啥意思吗?请参见曾经分享王路的一篇文章《永远不要对…

浮动布局会受父框滚动条影响
此时的效果是:如果此时把父框的滚动条去掉或隐藏掉:而此时的效果是:总结:1 有时我们的布局发生了改变可能就是受到出现滚动条的影响了 而我们很容易忽略掉这一点转载于:https://blog.51cto.com/11871779/2387118

Delphi7的主窗口
Delphi7的主窗口转载于:https://www.cnblogs.com/LoveFishC/archive/2012/08/10/3845692.html

线程组多次调用_详细分析 Java 中启动线程的正确和错误方式
start 方法和 run 方法的比较代码演示:/** * * start() 和 run() 的比较 * * * author 踏雪彡寻梅 * version 1.0 * date 2020/9/20 - 16:15 * since JDK1.8 */public class StartAndRunMethod {public static void main(String[] args) {// run 方法演示// 输出: name: main//…

Concurrency Runtime in Visual C++ 2010
PDC 2010 Hejlsberg的演讲中我们看到了VB.NET、C#新的简化异步编程的方式(可以下载新的Async CTP体验)。之前的TPL(Task Parallel Library)简化了并行编程。工业语言的飞速发展大大改进、简化了开发人员的编程方式。不仅是微软平台…

关于安卓你不知道的6件事
安卓第一次亮相是出如今2008年公布的HTC Dream手机上,到如今为止它已经6岁了。或许没有人想过在2010年底它就成为了智能手机平台率先的操作系统。这当然要感谢谷歌的努力和强大的財力支持。尽管眼下安卓系统是世界上最流行的移动操作系统,可是关于它的非…

vmware的三种网络模式讲解
vmware有三种网络设置模式,分别是Bridged(桥接),NAT(网络地址转换),Host-only(私有网络共享主机) 1.Bridged(桥接) 桥接模式默认使用的是:VMnet0 什么是桥接模式?桥接模式就是把主机网卡和虚拟机虚拟的网卡利用虚拟网桥进行通信。…

当前路径_[JSP] 07 JSP 路径问题
首先先明确一下下列URL的假设一个URL是这样的losthost:8080/myservlet/path服务器根路径:losthost:8080/项目根路径:losthost:8080/myservlet/明确了服务器根路径和项目根路径之后,我们就可以开始学习后面的知识了servlet路径问题请求转发和重定向的相对路径写法总结:Servlet重…

CPU时间戳获取
inline long long timt(){long long p; int&a*(((int*)&p)1);__asm__ __volatile__("rdtsc":"a"(p),"d"(a));return p; } 因为在64位CPU上rdtsc出来的结果仍然是在%eax和%edx,而%rax却不是由%eax和%edx拼起来的(反正我试了它没用...也许…

NYOJ 366 D的小L
地址:http://acm.nyist.net/JudgeOnline/problem.php?pid366 方法;用next_permutation(pɝmjʊteʃə)来求解,递归调用代码 1 #include<stdio.h>2 #include<algorithm>3 using namespace std;4 int main(…

MySQL 语句整理 2019-5-3
MySQL 语句整理 在整理完Oracle的一些常见用语句后,由于MySQL的语法跟Oracle略有不同,随跟PN的MySQL视频进行了间接整理. 查询薪水大于1800, 并且部门编号为20或30的员工sql select deptno,ename,sal from emp where sal > 1800 and (deptno 20 or deptno 30); and 优先级…

联想e580没有声音_现在你可以购买通过 Linux 认证的联想 ThinkPad 和 ThinkStation
曾经有一段时间,ThinkPad 是 Linux 用户的首选系统。但那是在 ThinkPad 还是 IBM 的产品的时候。来源:https://linux.cn/article-12283-1.html作者:Abhishek Prakash译者:Xingyu.Wang曾经有一段时间, ThinkPad 是 Linu…

linux平台下防火墙iptables原理(转)
原文地址:http://www.cnblogs.com/ggjucheng/archive/2012/08/19/2646466.html iptables简介 netfilter/iptables(简称为iptables)组成Linux平台下的包过滤防火墙,与大多数的Linux软件一样,这个包过滤防火墙是免费的&a…

SQL语法大全
SQL语句大全 --语 句 功 能--数据操作SELECT --从数据库表中检索数据行和列INSERT --向数据库表添加新数据行DELETE --从数据库表中删除数据行UPDATE --更新数据库表中的数据--数据定义CREATE TABLE --创建一个数据库表DROP TABLE --从数据库中删除表ALTER TABLE --修改数据库表…

Node.js的url模块简介
什么是URL URL是Uniform Location Resource的缩写,翻译为“统一资源定位符”,也就是描述资源位置的固定表示方法。被URL描述的资源可以位于互联网上,也可以位于本地。 URL的组成结构 基本URL包含模式(或者成为协议)&am…

python游戏结束显示分数代码_当游戏循环在Python中运行时,多线程来显示游戏分数?...
我想根据比赛时间的推移得分。为此,我想让两个循环同时运行。游戏循环和得分循环,每1.5秒加1。当我运行程序时,分数不会出现。我是否正确使用多线程?这是最好的办法吗?为了简单起见,我只发布了相关代码。谢…

NOIP2015解题报告 By ljt12138
Day1t1 幻方 练过的一道题,简单模拟,用二维数组存储,ij两个游标记录横纵坐标,利用题目条件改变坐标直到填入n个数即可。复杂度O(n^2) AC Day2t2 图的最小环 首先抽象出图论模型。每个人对应点,传输对应边。因为自己…

设计模式笔记——Adapter
适配器模式Adapter 将一个类的接口转换成用户希望的另外一个接口。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用的环境要求不一样的情况。 package adapter.pattern; /*** * author Real H LI**/ public class Existed {public void OldRequest(){Sy…

iOS12-crash错误-reason: 'UITableView failed to obtain a cell from its dataSource'
错误原因: cellForRowAtIndexPath函数返回了nil override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {let cell tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: in…

with as python_python - with as的用法
摘自:http://www.cnblogs.com/DswCnblog/p/6126588.htmlWith 是什么?有一些任务可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取…

wcf系列学习5天速成——第四天 wcf之分布式架构(转载)
今天是wcf系列的第四天,也该出手压轴戏了。嗯,现在的大型架构,都是神马的, nginx鸡群,iis鸡群,wcf鸡群,DB鸡群,由一个人作战变成了群殴....... 今天我就分享下wcf鸡群,高…