docker部署Flask+MongoDB+Nginx

文章目录
  1. 系统环境
  2. 方法
    1. Flask
    2. MongoDB
    3. Nginx
  3. 总结

近期需要将一个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
2
3
4
5
6
7
8
9
10
11
12
13
├── app
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── run.py
│   ├── application/
├── docker-compose.yml
├── images.tar
├── mongodb/
├── nginx/
│   ├── Dockerfile
│   ├── conf.d/
│   ├── resource/
│   ├── log/

其中app目录里面存关于Flask的文件; mongodb目录为数据文件,直接将本机的mongodb数据库拷贝过去; nginx目录存储了静态资源,相关配置; docker-compose.yml为核心启动文件。下面先介绍下最终的配置,再一一记录遇到的坑。

方法

最终的docker-cmpose.yml 如下, 基本是参考上面的教程来的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
version: '3'
services:
flask:
build:
context: app
dockerfile: Dockerfile
container_name: flask
image: digitalocean.com/flask-python:3.6
restart: unless-stopped
environment:
APP_PORT: 5000
MONGODB_DATABASE: mydata
MONGODB_HOSTNAME: mongodb
volumes:
- ./app:/var/www
depends_on:
- mongodb
networks:
- frontend
- backend

mongodb:
image: mongo:3.6.19
container_name: mongodb
restart: unless-stopped
environment:
MONGODB_DATA_DIR: /data/db
MONDODB_LOG_DIR: /dev/null
volumes:
- ./mongodb:/data/db
networks:
- backend

webserver:
build:
context: nginx
dockerfile: Dockerfile
image: digitalocean.com/webserver:latest
container_name: webserver
restart: unless-stopped
ports:
- "85:80"
volumes:
- ./nginx/log:/var/log/nginx
- ./nginx/resource:/var/www/
depends_on:
- flask
networks:
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge

一共包含三个主要的container:

  • flask: 主要是web后端flask程序。
  • mongodb: 数据库服务
  • webserver: 基于nginx的静态资源访问

下面记录下每一部分。

Flask

buildcontext表明Dockerfile在app目录下。另外就是挂载 将./app 挂载到容器中的/var/www中。 其中app目录中包含了flask程序(即application目录)。这里用挂载的好处就是方便外部修改程序,并可以直接同步到镜像内。

这部分的Dockerfile 核心如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM nickgryg/alpine-pandas:3.8.5

ENV GROUP_ID=1000 \
USER_ID=1000
WORKDIR /var/www/

ADD . /var/www/
RUN pip install -r requirements.txt
RUN pip install gunicorn

RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh

USER www
EXPOSE 5000
CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "run:app"]

这里面大部分命令都比较容易理解,暴露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
2
3
4
5
6
7
8
9
10
11
12
13
14
mongodb:
image: mongo:3.6.19
container_name: mongodb
restart: unless-stopped
command: mongod --auth -dpath=/data/other_path
environment:
MONGO_INITDB_ROOT_USERNAME: mongodbuser
MONGO_INITDB_ROOT_PASSWORD: password
MONGODB_DATA_DIR: /data/db
MONDODB_LOG_DIR: /dev/null
volumes:
- ./mongodb:/data/other_path
networks:
- backend

坑: 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
2
3
4
5
6
7
8
9
10
11
12
FROM alpine:latest
RUN apk --update add nginx && \
ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log && \
mkdir /etc/nginx/sites-enabled/ && \
mkdir -p /run/nginx && \
rm -rf /etc/nginx/conf.d/default.conf && \
rm -rf /var/cache/apk/*

COPY conf.d/app.conf /etc/nginx/conf.d/app.conf
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

还是用alpine 镜像,这里没有特别需要注意的。 下面就是如何配置nginx了,主要有两点需要考虑:

  • 静态资源
  • flask的后端api访问

首先静态资源,这个比较简单:

1
2
3
4
5
6
7
server {
listen 80;
location / {
index index.html;
root /var/www/;
}
}

坑:不同域下前端ajax请求后端flask API

之前在本机开发时,ajax访问的domain就是一个固定的ip, 现在在docker中,肯定不能直接使用ip来访问,因为不同部署的ip是不同的, 在docker-compose中已经将nginx的80映射到外部的85端口,因此从外部直接访问flask后端api肯定是不行的,因此需要用nginx再加一个location配置,将api访问转到flask:

1
2
3
location /api/ {
proxy_pass http://flask:5000;
}

因为docker中的nginx容器是可以直接访问flask容器的,因此可以直接使用flask:5000。 这样就可以达到,比如在外部访问 ip:85/api/login 会转到http://flask:5000/api/login 就可以实现ajax访问flask 的API。

Nginx完整的app.conf:

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
location / {
index index.html;
root /var/www/;
}
location /api/ {
proxy_pass http://flask:5000;
}
}

最终的镜像打包使用docker save

总结

之前没有用docker完整的进行打包和部署,这次完整的走一遍,又将docker恶补了一遍,上面的记录更多是根据本次项目的需求做的时候遇到的问题,写的比较杂乱,单纯作为记录吧。