此前在公司实习时,平常的开发、工做按规定都必须使用Windows操做系统,可是项目实际的测试、上线环境都是基于Linux的,因此每次只能在本地编写某一功能的代码后经过“跳板机”将项目代码传送到服务器上进行测试。出现问题也只能在服务器上修改,运行经过后再将修改后的代码传送到本地而后继续下一阶段的工做。因为我本身不太喜欢直接在服务器上使用vim编辑代码,也受不了每次都要经过"跳板机"将代码传来传去的繁复操做,本着生命在于折腾的真理,开启了此次的折腾之旅。html
为了既能够愉快地在Windows下编码、交流、玩耍,又能够一键在Linux下快速部署、测试代码,本文经过Docker for Windows在Windows系统中布置与服务器上一致的Linux测试环境,不用再将咱们的代码拷贝到远程服务器上,只占用较小的资源就能够一键在本地以相同的运行环境测试咱们的代码。python
下载地址:Download Docker Desktoplinux
安装前提:64bit Windows 10 Pro and Microsoft Hyper-Vgit
开启Hyper-V功能:github
安装Docker;web
设置国内镜像仓库地址:docker
启动Docker后右击Docker的运行图标选择"setting"进入设置界面;shell
选择设置面板左侧的"Daemon"选项,在"Registry mirrors"中填入国内的镜像仓库地址(推荐使用阿里云的,在我这边测试的速度较快,可是可能须要注册帐号);bootstrap
点击"Apply";vim
设置共享目录:
进入Docker的设置面板,选择"Shared Drives"选项;
勾选想要共享的盘符,点击"Apply",可能须要输入Windows帐户的密码;
在Docker的安装和设置过程当中,最可能遇到问题的地方多是“设置共享目录”的时候,可能会出现勾选了盘符并点击"Apply"后选项又被取消的问题。因为挂载Windows的盘符失败会在加入"-v"选项启动Docker容器时会出现卡死的状况。查看Docker的日志文件(C:\ProgramData\DockerDesktop\service.txt)会发现以下错误:
[Error] Unable to mount C drive: C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: OCI runtime create failed: container_linux.go:344: starting container process caused "exec: \"/usr/bin/nsenter1\": stat /usr/bin/nsenter1: no such file or directory": unknown.
复制代码
网络上关于该问题的解决方案主要分为如下几种:
我尝试了第一、3个方法,第1个方法对个人Docker不起做用,而后使用第3中方法从新安装后就能够了,比较奇怪。
首先须要设置Windows与docker的Linux测试环境的共享目录为:D:\pc_share\
,而该目录下的目录树以下所示:
.
|-- apps # 放置咱们的项目
| |-- test_demo1 # 示例项目1
| | |-- app.log
| | `-- app.py
| `-- test_demo2 # 示例项目2
| |-- app.log
| `-- app.py
`-- etc # 放置咱们的配置文件
|-- DockerFile # 镜像的配置文件,用于构建docker镜像
|-- requirements.txt # 用于记录、安装python项目的依赖库
`-- supervisor # 放置示例项目的运行配置文件
|-- test_demo1.conf # 记录了示例项目1的启动信息,经过软件supervisor启动、监控
`-- test_demo2.conf # 记录了示例项目2的启动信息,经过软件supervisor启动、监控
复制代码
以上就是咱们此次实验的全部文件的目录树示意图,将相关项目都放在"D:\pc_share\apps"下,为了方便演示,在这里建立了两个基于tornado的Web项目test_demo1
,test_demo1
,每一个项目中只包含了一个启动文件app.py
:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, from test_demo_1")
def make_app():
return tornado.web.Application([
(r"/", MainHandler), # 路由
])
if __name__ == "__main__":
app = make_app()
app.listen(8888) # 监听8888端口,示例项目2的监听改成另外一个端口,不然有一个将因为端口占用没法成功启动
tornado.ioloop.IOLoop.current().start() # 开启Web服务
复制代码
如上所示是一段十分简单的Web程序,该程序监听'8888'端口,若是访问"http:\\localhost:8888"就能够获得回复:"Hello, from test_demo_1"。
由章节2.1可知,运行上述项目至少须要三个环境:操做系统
、python
、tornado框架
。为了与项目上线的环境保持一致,这里假设三个环境条件分别为:Centos7
、python2.7
、tornado5.1.1
。因此咱们须要使用Dockerfile配置相应的运行环境,不熟悉Dockerfile的同窗能够浏览下教程:Dockerfile教程。
为了更方便地修改运行环境,Dockerfile主要分为两部分:配置下层运行环境
、配置上层运行环境
。
如上图所示为底层运行环境的配置过程示意图,对应的Dockerfile的部分为:
FROM centos:7 as centos_python2 # 基于镜像:"centos:7"
MAINTAINER mileskong <xiangqian_kong@126.com> # 做者信息
# 环境变量,反斜杠表示换行
ENV PYPI_REPO=https://mirrors.aliyun.com/pypi/simple/ \
PYTHONIOENCODING=UTF-8 \
SHARE_PATH=/mnt/share # docker容器的挂载点,对应Windows中的"D:\pc_share\"路径(启动容器时能够设置共享路径与容器中的挂载点)
# 为Centos7安装必要的软件,这里只安装了三个经常使用的软件,能够按需修改
RUN set -ex \ && yum -y install epel-release wget net-tools \ && yum clean all # 清空缓存,精简镜像
# 安装pip软件(这里省略了python2.7的安装,由于Centos7中自带了python2.7.5,如需安装其余版本的同窗在此以前加入python的安装过程)
ENV PIP_VERSION 19.1.1
RUN set -ex; \ \ wget -O get-pip.py 'https://bootstrap.pypa.io/get-pip.py'; \ \ python get-pip.py \ --disable-pip-version-check \ --no-cache-dir \ "pip==$PIP_VERSION" \ ; \ pip --version; \ \ find /usr/local -depth \ \( \ \( -type d -a \( -name test -o -name tests \) \) \ -o \ \( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \ \) -exec rm -rf '{}' +; \ rm -f get-pip.py 复制代码
经过以上Dockerfile中的内容,咱们已经完成了一个镜像的配置,该镜像中主要包含了:Centos7
、python2.7
、pip
。
将以上内容保存为:D:\pc_share\etc\DockerFile
,而后在Windows的命令行软件cmd
中键入以下命令经过Dockerfile构建镜像centos_python2:1.0(centos_python2为镜像名,1.0为tag,也即版本号):
cd D:\pc_share\etc # 进入配置文件的目录
# docker的镜像构建命令,
# -t [镜像名:tag]:设置镜像名与tag;
# -f [文件名]:指定Dockerfile;
# --target=[阶段名]:用于多阶段构建;
docker build -t centos_python2:1.0 -f DockerFile --target=centos_python2 .
复制代码
经过上述命令能够构建出包含Centos7
、python2.7
、pip
的镜像:centos_python2:1.0
。在cmd
中键入docker images
命令能够获得以下信息:
如上图中红框中即为对应的镜像,其大小仅为237Mb,若是使用alpine
代替Centos7
甚至能够将镜像控制在30Mb之内。
下层运行环境提供了一个较为通用的基于Centos的python开发平台,是全部python项目运行的通用基础,在后续的开发、测试过程当中基本不须要修改。可是,不一样的python项目可能会面临着不一样的问题,主要能够分为如下几项:
因为docker镜像是只读的而且构建过程较为耗时,在开发过程当中咱们会常常修改项目文件、配置文件、依赖库等信息。咱们不可能每修改一次就从新构建一个新的镜像。为了解决这一问题,咱们将常常变更的过程放置到另外一个镜像中,也即上层运行环境
。
为了解决上述第一个问题,咱们在开发python项目时经常使用requestments.txt
文件记录并控制项目所依赖python第三方库,经过如下命令安装requestments.txt
中记录的第三方库:
pip install -r /&{path}/requirements.txt
复制代码
本文示例项目的第三方库配置文件放置在:D:\pc_share\etc\requestments.txt
:
tornado==5.1.1 # 依赖的Web框架,版本号为5.1.1
supervisor==4.0.2 # 使用supervisor监管python项目的运行
复制代码
当项目依赖的第三方库发生变化时直接修改requestments.txt
文件并基于下层运行环境
从新构建上层运行环境
的镜像就能够了,减小了没必要要的操做。
为了解决第2、三个问题,这里使用了supervisor
管理python项目,关于其详细的教程能够参见“淡水网志”的博客:supervisor使用教程。按照supervisor
的格式为python项目编写启动配置文件并将其放置在supervisor
监控的文件夹中,supervisor
就能够按照咱们的配置文件很方便地对项目进行启动、监控。例如示例项目1的启动配置文件放在:D:\pc_share\etc\supervisor\test_demo1.conf
:
[program:test_demo1] # 项目名称,惟一
directory=/mnt/share/apps/test_demo1 # 项目所在路径,由于是在docker的Linux中执行,因此须要填项目在Linux中的路径,这里假设将Windows中的共享路径挂载到"/mnt/share/"下
command=python app.py # 项目的启动命令
stdout_logfile=/mnt/share/apps/test_demo1/app.log # 项目的日志文件
priority=1 # 优先级
numprocs=1 # 进程数
autostart=true # 自动启动
autorestart=true # 自动重启
startretries=5 # 启动尝试次数
复制代码
以上就是示例项目1的启动配置文件,修改supervisor
的监控路径为/mnt/share/etc/supervisor/
,由于docker能够将Windows的共享目录D:\pc_share\
挂载到/mnt/share/
下,因此当supervisor
启动时就会在/mnt/share/etc/supervisor/
(也即Windows中的D:\pc_share\etc\supervisor
)下寻找项目的配置文件,并按照项目的配置文件启动对应的项目。
FROM centos_python2:1.0 as centos_python2_supervisor # 上层运行环境的镜像基于下层运行环境的镜像:"centos_python2:1.0"
ENV SUPERVISOR_PATH=$SHARE_PATH/etc/supervisor # 放置项目的启动配置文件的路径,下面会配置supervisor的设置文件将其读取配置文件的目录改成"$SUPERVISOR_PATH"所指向的目录
# 经过pip按照requirements.txt记录的依赖库列表安装python的第三方库
COPY requirements.txt /tmp/ # 将 D:\pc_share\etc\ 目录下的requirements.txt复制到镜像的/tmp/中 RUN set -ex \ && pip install -r /tmp/requirements.txt -i $PYPI_REPO \ && rm -rf ~/.cache/pip/*
# 配置supervisor的设置文件"/etc/supervisord.conf"
RUN set -ex \ && echo_supervisord_conf > /etc/supervisord.conf \ && mkdir /etc/supervisord.d/ \ && echo "[include]" >> /etc/supervisord.conf \ && echo "files = $SUPERVISOR_PATH/*.conf" >> /etc/supervisord.conf \ # 监控"$SUPERVISOR_PATH"目录中的项目启动配置文件 && sed -i '/nodaemon/s/false/true/' /etc/supervisord.conf # 【十分重要】将supervisord.conf中的nodaemon变量改由false改成true,也即将supervisor改成前台运行,后面做详细解释
EXPOSE 8888 8889 # 声明暴露的端口
CMD ["supervisord", "-c", "/etc/supervisord.conf"] # 容器(不是镜像)启动时执行的命令 复制代码
如上所示为上层运行环境
的镜像配置文件。首先,该镜像基于刚刚构建的下层运行环境
镜像:centos_python2:1.0
,在此基础上,使用pip工具依照requirements.txt
中记录的依赖项为项目安装运行时必要的python第三方库。而后,生成、配置supervisor
的设置文件/etc/supervisord.conf
,将supervisor
读取项目的启动配置文件的目录改成$SUPERVISOR_PATH/
,其中$SUPERVISOR_PATH
为前文设置的环境变量,记录了Windows中项目的启动配置文件在docker镜像中的挂载点。则经过$SUPERVISOR_PATH
读取的配置文件实际就是Windows中D:\pc_share\etc\supervisor\
下的配置文件。
最后,CMD
后的内容表示容器启动时会被执行的语句,一般能够用来启动项目。因为咱们使用了supervisor
来监控项目,因此文件中直接开启了supervisor
的服务。
supervisor
的设置过程当中有一行命令为sed -i '/nodaemon/s/false/true/' /etc/supervisord.conf
,这是我在不了解docker运行机制的状况下踩过的坑。Docker镜像使用run命令运行起来后就会产生一个容器,能够简单地把容器看成一个进程,进程在执行完它的指令后就会退出、中止。因此当咱们的指令为启动supervisor
时,而supervisor
默认是后台运行的,因此咱们的容器启动时执行命令启动supervisor
:supervisord -c /etc/supervisord.conf
,这是supervisor
进入了后台运行,该命令执行结束,docker容器也认为它完成了本身的任务,顺利退出、中止运行。形成的结果就是每次启动docker容器都会马上中止运行,没法使容器按咱们的想法持续的运行。为了解决这个问题,咱们能够将supervisor
改成在前台运行,也即修改设置文件/etc/supervisord.conf
中的"nodaemon"为"true"。这时再启动容器,supervisor
会一直占据着前台,使容器不能"完成"本身的指令,咱们的容器就能够保持着持续的运行状态。
经过2.2节咱们完成了docker镜像的配置文件Dockerfile的编写,在这一份Dockerfile中,为了方便修改、加速镜像的构建过程,咱们将其分为两个部分,分别对应着下层运行环境镜像与上层运行环境镜像。为了区分,根据docker镜像的命名规范,咱们将前者命名为:centos_python2:1.0,后者命名为:centos_python2_supervisor:1.0。
综上所述,整个Dockerfile的内容为(D:\pc_share\etc\DockerFile):
FROM centos:7 as centos_python2
MAINTAINER mileskong <xiangqian_kong@126.com>
ENV PYPI_REPO=https://mirrors.aliyun.com/pypi/simple/ \
PYTHONIOENCODING=UTF-8 \
SHARE_PATH=/mnt/share
# install software
RUN set -ex \ && yum -y install epel-release wget net-tools \ && yum clean all
# install pip
ENV PIP_VERSION 19.1.1
RUN set -ex; \ \ wget -O get-pip.py 'https://bootstrap.pypa.io/get-pip.py'; \ \ python get-pip.py \ --disable-pip-version-check \ --no-cache-dir \ "pip==$PIP_VERSION" \ ; \ pip --version; \ \ find /usr/local -depth \ \( \ \( -type d -a \( -name test -o -name tests \) \) \ -o \ \( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \ \) -exec rm -rf '{}' +; \ rm -f get-pip.py
FROM centos_python2:1.0 as centos_python2_supervisor
ENV SUPERVISOR_PATH=$SHARE_PATH/etc/supervisor
# install python libs by pip
COPY requirements.txt /tmp/ RUN set -ex \ && pip install -r /tmp/requirements.txt -i $PYPI_REPO \ && rm -rf ~/.cache/pip/*
# build supervisord.conf
RUN set -ex \ && echo_supervisord_conf > /etc/supervisord.conf \ && mkdir /etc/supervisord.d/ \ && echo "[include]" >> /etc/supervisord.conf \ && echo "files = $SUPERVISOR_PATH/*.conf" >> /etc/supervisord.conf \ && sed -i '/nodaemon/s/false/true/' /etc/supervisord.conf
EXPOSE 8888 8889
CMD ["supervisord", "-c", "/etc/supervisord.conf"] 复制代码
下面则是根据Dockerfile构建镜像并运行的步骤:
cd D:\pc_share\etc # 进入配置文件的目录
docker build -t centos_python2:1.0 -f DockerFile --target=centos_python2 . # 构建下层运行环境镜像:"centos_python2:1.0"
docker build -t centos_python2_supervisor:1.0 -f DockerFile --target=centos_python2_supervisor . # 基于"centos_python2:1.0",构建上层运行环境镜像:"centos_python2_supervisor:1.0"
docker run -itd -v d:/pc_share/:/mnt/share -p 8888:8888 -p 8889:8889 ${image_id} # 运行镜像"centos_python2:1.0"
复制代码
上述命令首先构建了下层运行环境的镜像centos_python2:1.0
,而后在此基础上构建了上层运行环境的镜像centos_python2_supervisor:1.0
,最后经过docker的run命令启动镜像。其中比较重要的是run命令中的一些参数,下面做详细介绍:
docker run [options] [image_id]
# options:
-v [宿主机的共享目录]:[docker容器中的挂载点]:将宿主机中的共享目录挂载到docker容器的某个挂载点下,若是挂载点不存在则启动容器时自动建立;
-p [宿主机端口]:[docker容器端口]:将宿主机的端口映射到docker容器的端口上;
复制代码
根据run的参数能够看出,咱们在运行镜像时将Windows系统下的目录:d:/pc_share/
挂载到docker容器中的/mnt/share
下;将宿主机的8888端口映射到docker容器的8888端口,将8889映射到docker容器的8889端口。
经过上述步骤启动镜像centos_python2_supervisor:1.0
后,经过命令docker ps -a
能够查看经过该镜像开启的容器:
当咱们在本地浏览器中分别输入"localhost:8888"和"localhost:8889"后就能够访问运行在docker中的示例项目了。
在第2章节中详细介绍了怎么使用docker自定义项目的运行环境,整个过程基本能够划分为如下几部分:
在开发过程当中,前两个步骤是配置环境的过程,而第三个步骤是咱们常常重复进行的,每当咱们在Windows中修改了项目代码或者配置文件后,都须要重启docker容器验证代码的运行状况,而run命令的参数也较为复杂,因此频繁的重启容器也多是比较烦人的。
因为我日常比较喜欢使用vscode
做为开发的IDE工具,偶尔中发现了vscode
也支持docker插件,十分方便,下面简要为你们介绍下如何经过vscode
的docker插件实现“一键启动”。
vscode
的扩展插件商店中搜索"docker"并安装该插件,重启后会发现vscode
的侧边栏多出了一个docker的图标:如上图中所示,这个插件能够直接显示出本机中存在的镜像、容器和仓库信息。由图能够看到咱们以前建立的全部镜像。
centos_python2_supervisor:1.0
,右击并在选项列表中选择Run
,会发现咱们的容器列表中新增了一个运行的容器:Restart Container
,就能够重启咱们的容器,从新启动咱们的项目:因此,当咱们经过编写Dockerfile、构建镜像后,基本的运行环境就搭建成功了。以后每次须要测试修改后的代码时只须要在docker的插件界面中选中对应容器选择重启就能够了。若是测试想要了解项目运行更详细的信息,右击选择Attach Shell
就能够进入容器中经过熟悉的终端查看相关日志: