近期需要将一个Flask+MongoDB+Nginx 小系统打包部署,之前只用过docker对简单的代码进行打包,这次使用docker-compose踩了很多坑,特此记录下。主要参考这篇详细的指南How To Set Up Flask with MongoDB and Docker {建议细看}。
系统环境
开始直接在原生机器上进行系统开发,采用前后端分离的设计方便协作,主要环境如下:
- web后端:Python Flask
- 数据库: MongoDB,约10G数据
- 前端静态资源: Nginx
其中除了Flask之外,还有一些其他Python库,比如pandas, numpy等。最终要将系统利用docker部署到内部无网络机器上。
最终实现目录结果如下:
1 | ├── app |
其中app目录里面存关于Flask的文件; mongodb目录为数据文件,直接将本机的mongodb数据库拷贝过去; nginx目录存储了静态资源,相关配置; docker-compose.yml为核心启动文件。下面先介绍下最终的配置,再一一记录遇到的坑。
方法
最终的docker-cmpose.yml
如下, 基本是参考上面的教程来的:
1 | version: '3' |
一共包含三个主要的container:
- flask: 主要是web后端flask程序。
- mongodb: 数据库服务
- webserver: 基于nginx的静态资源访问
下面记录下每一部分。
Flask
build
的context
表明Dockerfile在app目录下。另外就是挂载 将./app
挂载到容器中的/var/www
中。 其中app目录中包含了flask程序(即application目录)。这里用挂载的好处就是方便外部修改程序,并可以直接同步到镜像内。
这部分的Dockerfile
核心如下:
1 | FROM nickgryg/alpine-pandas:3.8.5 |
这里面大部分命令都比较容易理解,暴露5000端口作为接口,比一般的多了一些用户配置。
坑:docker的alpine系镜像利用pip安装pandas安装失败,或者编译耗时非常长
原因:因为alpine系python或者linux是基于MUSL,而不是原生的glibc,因此对whl支持不太好。
解决方法:尝试了多种方法,最后如下的两种方法可以;
- 使用Debian的官方Python镜像,比如
python:3.8-slim-buster
- 使用第三方将pandas编译好的Alpine镜像,如
nickgryg/alpine-pandas:3.8.5
后续python应用docker化时候,建议镜像直接使用Debian的镜像(python:3.8-slim-buster),而非Alpine Linux的镜像(python:3.8-alpine), 不过有时候需要一些linux bash命令,还是得用alpine版的Python镜像。
参考:
- https://stackoverflow.com/questions/49037742/why-does-it-take-ages-to-install-pandas-on-alpine-linux
- https://pythonspeed.com/articles/alpine-docker-python/ {mark}
- https://pythonspeed.com/articles/base-image-python-docker-images/ {mark}
MongoDB
从docker-compose.yml里面的mongodb部分看,没有特别需要注意的地方,将本地数据目录挂载即可, mongodb的默认数据存储在 /data/db
, 因此只需要将本地的mongodb的数据目录挂载映射过去即可,如果需要指定额外的数据目录,可以在command
中指定-dpath
即可,比如:
1 | mongodb: |
坑: Mongo版本问题导致:mongodb exited with code 14
这个bug查了很多地方,最终发现是版本问题,mongodb貌似不能向下兼容,我们主机用的3.6.19,latest的已经到了4.x了,数据格式有不兼容的地方,将镜像切换为3.6.19之后就正常了。
Nginx
因为我们主要采用前后端分离进行开发,因此前端资源可以直接使用Nginx作为静态资源存储服务器,这个体现在docker-compose.yml
的两处挂载映射,一个是挂载nginx配置文件,另外就是静态资源(Html, JS, CSS等)。
首先Nginx的Dockerfile 如下:
1 | FROM alpine:latest |
还是用alpine 镜像,这里没有特别需要注意的。 下面就是如何配置nginx了,主要有两点需要考虑:
- 静态资源
- flask的后端api访问
首先静态资源,这个比较简单:
1 | server { |
坑:不同域下前端ajax请求后端flask API
之前在本机开发时,ajax访问的domain就是一个固定的ip, 现在在docker中,肯定不能直接使用ip来访问,因为不同部署的ip是不同的, 在docker-compose
中已经将nginx的80映射到外部的85端口,因此从外部直接访问flask后端api肯定是不行的,因此需要用nginx再加一个location配置,将api访问转到flask:
1 | location /api/ { |
因为docker中的nginx容器是可以直接访问flask容器的,因此可以直接使用flask:5000
。 这样就可以达到,比如在外部访问 ip:85/api/login
会转到http://flask:5000/api/login
就可以实现ajax访问flask 的API。
Nginx完整的app.conf:
1 | server { |
最终的镜像打包使用docker save
总结
之前没有用docker完整的进行打包和部署,这次完整的走一遍,又将docker恶补了一遍,上面的记录更多是根据本次项目的需求做的时候遇到的问题,写的比较杂乱,单纯作为记录吧。