python配置文件封装_Dockerfile封装实战学习——python项目

今天项目需要将python项目封装成docker提供服务,参考同事的Docker封装代码一边巩固学习一边实战。首先熟悉一下docker的一些概念。然后用一个实例介绍Dockerfile的指令,然后写一个dockerfile实例,最后使用把这个镜像制作出来,并运行。

关于在镜像封装中遇到的问题,或者觉得这个笔记没有记录清楚的内容,大家也可以留言告诉我,我会定期查看留言,并更新对应的问题。

一、基础概念

1. 镜像和容器

镜像和容器就像java语言的类和类的实例

2. 镜像的存储

docker的镜像是多层存储的,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。

3. 常用的指令

常用的指令包括拉取镜像、删除镜像、创建容器、进入容器进行修改、删除容器...

docker pull ubuntu

docker image ls -a

# 查看镜像、容器数据卷所占用的空间

docker system df

# 删除无用的镜像,比如none虚悬镜像

docker image prune

# 删除本地镜像

docker image rm [选项] <镜像1> [<镜像2> ...]

<镜像> 可以是 镜像短 ID 、 镜像长 ID 、 镜像名 或者 镜像摘要

# 运行容器

# -p 81:80表示将内部的80端口映射到外部的81端口,之后可以通过访问 http://localhost:81 看到结果

docker run --name webserver -d -p 81:80 nginx

# 进入容器并修改

docker exec -it webserver bash

root@3729b97e8226:/# echo '

Hello, Docker!

' > /usr/share

/nginx/html/index.html

root@3729b97e8226:/# exit

exit

# 查看容器的改动

docker diff webserver

# 查看容器

docker container ls -a

# 删除容器

docker container rm trusting_newton

#如果要删除一个运行中的容器,可以添加 -f 参数

# 清理所有处于终止状态的容器

docker container prune

4. Docker镜像制作

自己得到新镜像的方式有两种,一种是commit方式一种是使用dockerfile方式。其中前者不推荐,但是可以简单的介绍一下。

在本地修改了一个正在运行的容器之后(比如在上面的代码中的“进入容器并修改”),可以通过下面的代码保存这个容器为新的镜像

docker commit \

--author "Tao Wang " \

--message "修改了默认网页" \

webserver \

nginx:v2

通过docker image ls可以查到到新的镜像。

慎用commit使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。仔细观察之前的 docker diff webserver 的结果,你会发现除了真正想要修改的 /usr/share/nginx/html/index.html文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,如果不小心处理,将会导致镜像极为臃肿。

二、正确的定制镜像的方式

正如上面提到的,正确的方式应该是Dockerfile方式,这种方式可以简单的概括为两步:

写一个Dockerfile文件。对的,文件名就叫Dockerfile,无后缀名称。把这个文件放在项目的目录中,这个文件中包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

使用docker build 镜像名称 上下文路径的方式构建镜像。

下面进入第一步,先放上一个同事的镜像(代码来自fan),然后一步步的根据这个镜像学习里面的常用docker指令,以及一些小的初学者(就是我)要记住的知识点。

from ubuntu:18.04

MAINTAINER fan dingling@dinglingdingling.cn

ARG ANTISPAM_HOME

ARG PORT

ENV LC_ALL=zh_CN.UTF-8

COPY ${ANTISPAM_HOME:-.}/dashboard /usr/lib/antispam/dashboard

COPY ${ANTISPAM_HOME:-.}/bin /usr/lib/antispam/bin

COPY ${ANTISPAM_HOME:-.}/requirements /usr/lib/antispam/requirements

EXPOSE ${PORT}

RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list

RUN apt-get update

RUN apt-get install -y language-pack-zh-hans

RUN apt-get install -y python3-pip

RUN pip3 install -i http://pypi.douban.com/simple -r /usr/lib/antispam/requirements --trusted-host pypi.douban.com

ENTRYPOINT ["/usr/lib/antispam/bin/start-dashboard.sh"]

二一、常用的dockerfile指令以及编写注意事项

上下文环境的概念

1. FROM 需要修改的镜像名称

这个必须是第一条指令,指明了需要修改的镜像。如果本地不存在,则默认会去Docker Hub下载指定镜像。这里我们直接用ubuntu虚拟机。

2. MAINTAINER 标明作者

该信息将会写入生成镜像的Author属性域中。这个就像我们在写java或者python代码的时候,需要注明作者一样一样的。后面创建镜像的时候会提到这个指令的一个重要作用

3. ARG: 定义构建过程中需要的参数

用户在使用docker build构建镜像的时候,需要以--build-arg ANTISPAM_HOME=XXX --build-arg PORT=XXX的形式这些参数传入,否则会报错哦。当然也可以设置一个默认值。

ARG a_nother_name=a_default_value # 指定默认值。

如果在Dockerfile中,ARG指令定义参数之前,就有其他指令引用了参数,则参数值为空字符串。

不建议在构建的过程中,以参数的形式传递保密信息,如key, password等。

4. ENV: 在镜像的构建过程中设置环境变量

后续RUN可以使用这些环境变量,生成了镜像,运行成容器之后,这个环境变量也会被保存在env中。

未来以这个镜像为基础镜像再构建一个新的镜像v2,那么v2也会拥有该环境变量.

通过ENV定义的环境变量不能被后面的CMD指令使用,也不能被docker run 的命令参数引用。

如果不想再dockerfile中写这个环境变量,同时自己的记性足够的好,那么可以在创建容器的时候使用docker run -it -e "env_1=hahaha" 镜像名的方式传入环境变量。

5. EXPOSE :暴露端口,非必须

指明这个容器要暴露出来的内部端口,然后系统可以通过docker run -p 内部端口:外部端口的方式访问这个暴露的端口,不是必须的,没有这个EXPOSE指令,照样可以通过 -p命令暴露端口。

6. WORKDIR: 设置工作目录

设置Dockerfile的任何RUN/CMD/ENTRPOINT,COPY,ADD指令的工作目录。

这个指令虽然这里没有用到,但是也挺常用的。当使用相对目录的情况下,采用上一个WORKDIR指定的目录作为基准。举个例子,我想要封装的项目是projectA,那么我希望在docker的虚拟环境中,也有这样一个目录叫projectA,于是我可以使用这个指令创建这个目录,然后将我project中的代码一股脑的扔到这个路径中(前面提到了这会成为COPY和ADD指令的工作目录):

WORKDIR /projectA

ADD . /projectA

# 上面一句和下面一句是同一个意思,想一想是为啥

# ADD . .

相当与cd 命令,但不同的是指定了WORKDIR后,容器启动时执行的命令会在该目录下执行.

7. ADD&&COPY:同样是将本地的文件/目录拷贝到docker中

区别在于copy不会对压缩文件进行解压,也不能通过url链接进行拷贝。一般的语法为ADD [本地路径/文件] [虚拟环境路径]

需要注意本地的路径一定要在docker的上下文环境中。 对于 COPY 和 ADD 命令来说,如果要把本地的文件拷贝到镜像中,那么本地的文件必须是在上下文目录中的文件。其实这一点很好解释,因为在执行 build 命令时,docker 客户端会把上下文中的所有文件发送给 docker daemon。考虑 docker 客户端和 docker daemon 不在同一台机器上的情况,build 命令只能从上下文中获取文件。如果我们在 Dockerfile 的 COPY 和 ADD 命令中引用了上下文中没有的文件,就会收到“no such file or directory”报错。

8. RUN: 运行指令,比如apt-get install一些软件啥的

RUN语句一般有两种使用方式

RUN 在shell中使用/bin/sh作为执行器执行命令

RUN ["executable", "param1", "param2"] 调用申明exec执行命令,比如“/bin/bash”(注意要使用双引号)RUN ["/bin/bash","-c","echo hello"]

每一个RUN语句都是在当前的基础上执行指定命令,并提交为新的镜像。当命令较长时,可以使用\来换行。如果想要在一个RUN中跑很多个任务,那可以使用 &&连接。

来看看同事的这段代码中,使用RUN做了什么

RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 更换ubuntu的源为国内源,加快下载的速度。 sed是文件处理工作,主要以行为单位进行处理,可以将数据行进行替换、删除、新增、选取等特定工作。sed -i 's/要替换的字符串/更新的字符串/g' filename命令的作用是替换文件。

RUN apt-get update 下载器的更新,没啥好说的

RUN apt-get install -y language-pack-zh-hans 下载并安装简体中文包

RUN apt-get install -y python3-pip 安装pip3

RUN pip3 install -i http://pypi.douban.com/simple -r /usr/lib/antispam/requirements --trusted-host pypi.douban.com使用pip3的下载requirements文件中的python依赖包,同时指定使用国内的pip源(这样速度会快很多,超级赞)

所以走到这一步的时候,同事已经完成了python环境的配置啦。

如果对requirements文件是咋么生成的有疑惑的童鞋可以看下面的附录,考虑到不是docker的主要内容,正文里面就不详细介绍啦。

9. ENTRYPOINT&&CMD:都是在docker镜像启动时,执行命令。

这个语句感觉上已经和docker 镜像制作没啥关系了,这个指定docker run起来的时候,开机执行的语句。

类似于RUN命令,这里也有两种使用方式:

CMD [command]使用exec模式

CMD "command" 使用shell模式

这里要大写的注意!

使用 shell 模式时,docker 会以 /bin/sh -c "command" 的方式执行任务命令。 这意味着容器的1 号进程不是任务进程而是 bash 进程,这样我们执行的命令或者脚本可以取到环境变量。

使用exec模式,会将command的语句指定为1号进程。这意味着,如果你开启了这个镜像的容器,然后使用docker exec 容器名 ps aux去查看,会发现该命令的进程为1. exec 模式是建议的使用模式,因为当运行任务的进程作为容器中的 1 号进程时,我们可以通过 docker 的 stop 命令优雅的结束容器.

exec 模式的特点是不会通过 shell 执行相关的命令,所以像 $HOME 这样的环境变量是取不到的:

FROM ubuntu

CMD [ "echo", "$HOME" ]

# 如果想要取到,需要使用这样的表示,指定执行shell来获得

FROM ubuntu

CMD [ "sh", "-c", "echo $HOME" ]

二二、写自己的镜像文件Dockerfile

我需要封装的是一个python项目,可以使用CPU,也可以使用GPU,处于简单的角度考虑,我先写一个CPU版本的。总结如下:

镜像源是ubuntu:18.04

指定workdir

将本地的项目文件拷贝到镜像的项目目录中

更改ubuntu的源路径

更新apy-get update

下载一些依赖

下载简体中文包

安装python3-pip

安装TensorFlow1.10.0的二进制版本(pip默认安装的tensorflow版本没有对AVX指令集进行支持)

将项目的其他依赖包放在requirements文件中,使用pip3下载安装

FROM ubuntu:18.04

MAINTAINER wenting

LABEL version="1.0"

WORKDIR /chineseocr_api

ADD . /chineseocr_api

RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list

RUN rm -rf /var/lib/apt/lists/* # 如果修改加速镜像之后,发现docker还是从源路径拉取安装包,那么这句话就很重要了

RUN apt-get update

RUN apt-get install libsm6 libxrender1 libxext-dev gcc -y

RUN apt-get install -y language-pack-zh-hans

RUN apt-get install -y python3-pip

RUN pip3 install tensorflow-1.10.0-cp36-cp36m-linux_x86_64.whl

RUN pip3 install -i http://pypi.douban.com/simple -r /chineseocr_api/requirements --trusted-host pypi.douban.com

二三、Docker build构建镜像

这个指令挺简单的 给出想要命名的镜像名称和创建的上下文环境

sudo docker build -t chineseocr_api/v1 .

如果在build的过程中出错了,不用担心错处语句前面的工作都白做了。因为docker build语句使用了缓存机制。

Dockerfile的每条指令都会将结果提交为新的镜像。下一条指令基于上一条指令的镜像进行构建。也就是你修改Dockerfile后,build任务会快速略过你之前成功的步骤,从你修改的那一步之后的操作,都会重新运行。因此,为了有效的利用缓存,尽量保持Dockerfile一致,并且尽量在末尾修改。

更改 MAINTAINER 指令会使Docker强制执行 run 指令来更新apt,而不是使用缓存。

二四、 运行一下我们创建的镜像

此时,镜像是在本地的。可以通过docker images ls来查看镜像。

然后就可以使用下面的命令运行这个镜像。

sudo docker run --name chineseocr_api -p 7070:7070 chineseocr_api/v1 python3 server.py

这里使用--name为容器命令,使用-p 7070:7070指定容器内端口和外部端口的对应关系。 python server.py表示开启容器之后,运行/bin/sh -c python3 server.py 这样我的服务就以docker的形式完成的封装以及提供了服务。大家可以通过访问 http://hostname:7070来提交查询。

参考资料

附录:

A. requirements是如何生成的

在写python项目的时候,推荐的使用conda构建自己的python依赖环境,这样可以通过conda list -e>requirements的方式,将依赖导出,导出的形式是这样的:

readline==7.0

requests==2.22.0

scipy==1.3.0

setuptools==41.0.1

setuptools==39.1.0

······

但是这里需要注意的是,这种导出方式导出的只是使用conda install下载的依赖。

如果是使用pip下载的依赖,那么可以使用pip freeze>requirements的方式导出依赖。

这样导出了之后,可以通过pip install -r requirement的方式批量下载依赖。

最后

如果在docker容器封装中,遇到问题,可以留言,我会定期查看留言,如果有我能解决的也会和你讨论一下。共同学习共同进步~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值