Docker入门到实战(三)

自制Docker镜像

一般而言,当我们的程序开发完成后,会连同程序文件与运行环境一起制作成一个新的镜像。要制作镜像,需要编写Dockerfile。DockeFile由多个命令组成,常用的命令有:

  • FROM:基于某个镜像来制作新的镜像。格式为:FROM 镜像名称:镜像版本。
  • COPY:从宿主机复制文件,支持?、*等通配符。格式为:COPY 源文件路径 目标文件路径。
  • ADD:从宿主机添加文件,格式与COPY相同,区别在于当文件为压缩文件时,会解压缩到目标路径。
  • RUN:在创建新镜像的过程中执行的shell命令。格式为:RUN shell命令行。注意,此shell命令将在容器内执行。
  • CMD:在容器实例中运行的命令,格式与RUN相同。注意,如果在docker run时指定了命令,将不会执行CMD的内容。
  • ENTRYPOINT:在容器实例中运行的命令,格式与CMD相同。注意,如果在docker run时指定了命令,该命令会以命令行参数的形式传递到ENTRYPOINT中。
  • ENV:在容器中创建环境变量,格式为:ENV 变量名值。

注意,Docker镜像中有一个层的概念,每执行一个RUN命令,就会创建一个层,层过多会导致镜像文件体积增大。尽量在RUN命令中使用&&连接多条shell命令,减少RUN命令的个数,可以有效减小镜像文件的体积。

5.1 自制显示文本文件内容镜像

编写cat.py,接收一个文件名,由python读取文件并显示文件的内容:

import os
import sys

input = sys.argv[1]

with open(input, "r") as fp:
    print(fp.read())

这个例子比较简单,缩写Dockerfile如下:

FROM python:3.8
WORKDIR /files
COPY cat.py /cat.py
ENTRYPOINT ["python", "/cat.py"]

这个Dockerfile的含义是:

  • 以python:3.8为基础镜像
  • 容器启动命令的工作目录为/files,在运行镜像时,需要我们把宿主机的某目录挂载到容器的/files目录
  • 复制cat.py到容器的根目录下
  • 启动时运行python /cat.py命令

需要说明的是,ENTRYPOINT有两种写法:

ENTRYPOINT python /cat.py
ENTRYPOINT ["python", "/cat.py"]

这里采用第二种写法,是因为我们要在外部给容器传递参数。执行命令编译Docker镜像:

docker build -t cat:1.0 .

这个命令中,-t的含义是目标,即生成的为cat,版本号为1.0,别忘了最后那个.,这叫上下文路径,是指 docker 在构建镜像,有时候想要使用到本机的文件(比如复制),docker build 命令得知这个路径后,会将路径下的所有内容打包。

这样,我们的第一个镜像就制作完成了,使用下面的命令执行它:

docker run -it -v ~/docker_test/cat/files:/files cat:1.0 test.txt

即可看到~/docker_test/cat/files/test.txt的内容。

5.2 自制web服务器镜像

我们使用tornado开发一个网站,而python的官方镜像是没有tornado库的,这就需要在制作镜像时进行安装。测试的ws.py如下:

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello world")

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

编写Dockerfile文件如下:

FROM python:3.8
WORKDIR /ws
COPY ws.py /ws/ws.py
RUN pip install tornado
CMD python hello.py

在此我们验证一下CMD与ENTRYPOINT的区别。在Dockerfile所在有目录下执行如下命令:

docker build -t ws:1.0 .

执行完成后,再使用docker images使用就可以看到生成的镜像了,然后使用下面的命令运行:

docker run -it -p 8000:8000 ws:1.0

在浏览器中输入宿主机的ip和8000端口,就可以看到页面了。在这个例子中,我使用的运行命令是CMD,如果在docker run中指定的其他的命令,此命令就不会被执行,如:

$ docker run -it -p 8000:8000 ws:1.0 python
Python 3.8.7 (default, Dec 22 2020, 18:46:25) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

此时,容器中被执行的是python命令,而不是我们的服务。在更多情况下,我们希望在docker run命令中为我们的服务传参,而不是覆盖执行命令,那么,我们应该使用ENTRYPOINT而不是CMD:

FROM python:3.8
WORKDIR /ws
COPY ws.py /ws/ws.py
RUN pip install tornado
ENTRYPOINT python ws.py

上面这种写法,是不支持传递参数的,ENTRYPOINT和CMD还支持另一种写法:

FROM python:3.8
WORKDIR /ws
COPY ws.py /ws/ws.py
RUN pip install tornado
ENTRYPOINT ["python", "ws.py"]

使用这种写法,docker run命令中的参数才可以传递给hello.py:

docker run -it -p 8000:9000 ws:1.0 --port=9000

这个命令中,--port=9000被作为参数传递到hello.py中,因此容器内的端口就成了9000。在生产环境中运行时,不会使用-it选项,而是使用-d选项,让容器在后台运行:

$ docker run -d -p 8000:9000 ws:1.0 --port=9000
4a2df9b252e2aff6a8853b3a8bf46c0577545764831bb7557b836ddcd85cba70
$ docker ps                                       
CONTAINER ID   IMAGE        COMMAND                  CREATED           STATUS            PORTS                    NAMES
4a2df9b252e2   hello:1.0    "python ws.py --p…"   9 seconds ago     Up 8 seconds      0.0.0.0:8000->9000/tcp   elegant_sammet

这种方式下,即使当前的控制台被关闭,该容器也不会停止。

5.3 自制apscheduler服务镜像

接下来,制作一个使用apscheduler编写的服务镜像,代码如下:

import sys
import shutil
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger

def scan_files():
    shutil.copytree(sys[1], sys[2])

scheduler = BlockingScheduler()
scheduler.add_job(
    scan_files,
    trigger=CronTrigger(minute="*"),
    misfire_grace_time=30
)

Dockerfile也是信手拈来:

FROM python:3.8
WORKDIR /
COPY sch.py /sch.py
RUN pip install apscheduler
ENTRYPOINT ["python", "sch.py"]

生成镜像:

docker build -t sch:1.0 .

应该可以运行了,文件复制需要两个目录,在运行时,可以使用两次-v来挂载不同的目录:

docker run -d -v ~/docker_test/sch/src:/src -v ~/docker_test/sch/dest:/dest sch:1.0 /src /dest

猜你喜欢

转载自blog.csdn.net/qq_28165595/article/details/131878132