如何利用Dockerfile,从零开始制作一个docker镜像?
发布于 作者:苏南大叔 来源:程序如此灵动~
如何从零开始,制作一个docker镜像呢?如果可以自定义一个docker镜像,就可以封装自己想要的功能和版本。这个是一件令人振奋的事情。那么,本文中,苏南大叔介绍的就是:如何定义并使用Dockerfile文件,并最终封装一个docker镜像。
本文中,所使用的具体代码例子,来源自网络。镜像的具体的功用是:安装一个基于python的框架flask,对外提供www服务。这个例子,在网络上还是很流行的。苏南大叔就根据自己的理解,对这个例子进行一下解读。

整个套路是这样的:新建一个Dockerfile文件。在文件里,定义编写特定的功能脚本。最后在当前目录下面,执行命令docker build -t <imgname> .,就可以得到一个自定义的docker镜像了。
本文的测试环境是:centos7.5,docker 18.06.1-ce, build e68fc7a。
Dockerfile内容
首先,苏南大叔在测试机,新建一个文件夹test,作为本次镜像制作的工作目录。test目录里面,放置一个名为Dockerfile的文件,注意:不能改名,注意大小写。
该Dockerfile内容如下:
# Use an official Python runtime as a parent image
FROM python:2.7-slim
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
ADD . /app
# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# Make port 80 available to the world outside this container
EXPOSE 80
# Define environment variable
ENV NAME World
# Run app.py when the container launches
CMD ["python", "app.py"]参数说明From
在上述范例中,需要特别注意的就是:From。From,指的是:基础镜像。一般来说设置为centos:latest。
我们要设置的基础镜像,我们可以使用docker search找到基本镜像名称。然后,通过选中的镜像名称,查找相关的tags。查找到合适的tag,就可以继续继续了。
具体可以参见这篇文章:
- 《如何查找特定功能的docker镜像?docker镜像hub地址大全》
https://newsn.net/say/docker-search.html
参数说明WORKDIR
WORKDIR,苏南大叔这边理解着就是:类似于大家常见的cd命令。在执行后续的构建命令的时候,需要先切换到某个目录中,那么这个WORKDIR,就起到了cd的作用。那么因此,路径中的斜线之类的都会是有特殊意义的,多次WORKDIR,路径间也是有相互作用的。anyway,大家把它理解为cd,就很好说明一切问题了。
同时,这个参数,还可以影响容器命令行的默认目录。正常情况下来说,通过docker exec <name> /bin/bash进入容器内部的时候,默认所处的目录,就是这个WORKDIR目录。
WORKDIR目录,存在于镜像(容器)内部,与宿主机无关。
参数说明ADD
ADD命令两个参数,ADD <source> <dest>。苏南大叔把这个ADD命令,就理解为cp命令。就是复制文件的意思,把制作镜像时的宿主机上的某些文件或目录,复制到镜像内部的某个位置上去。这些文件,一般都是未来可能会在镜像里面,用到的特殊文件。
在本例中,在执行docker build命令之前,宿主机的test目录下面,所有的文件,都会被无差别的复制到镜像的/app目录下面。所以,那些文件,需要被复制到镜像里面呢?大家可以先想好,再执行build。比如Dockerfile文件,其实就并不需要复制到镜像里面。
参数说明RUN
RUN命令,就是常理上的执行某条命令。本例子中,就是利用pip安装了一些依赖包,特殊的地方,就是依赖包列表是放在了requirements.txt文件里面了。这里不做详细解释。更多详细内容,大家可以参见苏南大叔的pip相关经验链接。
在本例中,requirement.txt对于Dockerfile并不是必须的,只是本次实验的一个普通文件。内容如下:
Flask
RedisRUN就是镜像构建时的CMD命令,但是RUN和CMD还是有比较重大的区别的,这里做个伏笔。作为小白入门教程,这里的RUN就简单理解为执行某条命令即可。
参数说明CMD
和RUN基本上一致,只是CMD是构建好镜像之后,运行镜像的时候,才执行的。在写法上,CMD也可以不使用这种数组的形式,具体请参见未来的经验文章。在功用上,CMD和ENTRYPOINT是很类似的。这里,苏南大叔就简要的说一句,CMD在docker run -it的时候,可以被覆盖的。而ENTRYPOINT则不会被覆盖。就是说,ENTRYPOINT,恒定会执行的。
参数说明EXPOSE
EXPOSE英文单词,就是暴露的意思,就是说,对外提供一个什么样的端口号。就相当于docker run时,-p <port1>:<port2>中的port2,这个比较好理解。需要暴露多个端口号的时候,就写很多行的EXPOSE语句即可。
EXPOSE 80
EXPOSE 81
EXPOSE 82
# ...入口文件app.py
这个app.py,就是基于python的www脚本。这里涉及的python脚本的用法,苏南大叔就不具体解释了。因为,和本文的主要议题docker的镜像制作,关系不大。
至是需要说明的是:这个入口文件,可以用于接收环境变量。也就是docker run时的-e参数。这个环境变量,还可以在Dockerfile里面使用ENV命令,设置默认值。
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)构建docker镜像
苏南大叔使用docker build命令,来构建最终的docker镜像。构建的依据就是:Dockerfile文件。使用-t来定义镜像名称,.表示要操作的目录。
docker build -t test .
那么,上述命令,就把上述Dockerfile文件构建成了,一个带有www功能的镜像,名字为test。
运行镜像为容器
得到了test镜像之后,就可以docker run了。这里,苏南大叔也不做详细解释了。下面是个docker run的命令范例。
docker run --name test2 -p 4000:80 -e NAME=sunan -d test大家,可以仔细想想-p和-e参数,是怎么来的。对比一下Dockerfile里面的相关参数,来加深一下印象。

总结
本文中,苏南大叔所叙述的Dockerfile,仅仅是冰山一角。里面还有很多不同的参数命令。更多Dockerfile的应用经验文字,请点击下面的链接查看。