基於Docker部署Flask服務容器

上一篇我們介紹了Flask框架,並在開發環境運行了一個簡單的本地應用。而在實際生產環境中,Flask往往用來做獨立的應用部署,例如Web網站,或者接口服務。我們這裏,就嘗試基於Docker部署一個Flask的服務容器。

 

1 準備工作

在CentOS下安裝Flask和Docker,這裏就不贅述了,我版本分別是:

Python 3.6
Flask 1.1.2
Werkzeug 1.0.1
Docker version 17.09.1-ce, build 19e2cf6

 

2 應用代碼

創建一個目錄,並在該目錄下分別創建文件及子目錄,結構如下:

./
├── Dockerfile        #docker製作文件
└── app
    ├── app
    │   ├── main.py   #應用主入口文件
    │   ├── static    #目錄,用於存放靜態文件
    │   └── templates #模板,用於存放視圖模板
    └── uwsgi.ini     #uwsgi配置文件

我們分別來看一下

2.1 main.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Description

Date: 2021/3/15 11:50 上午
"""

app = Flask(__name__)


@app.route('/', methods=['GET'])
def hello():
    return 'Hello World From Flask!'


if __name__ == '__main__':
    # Only for debugging while developing
    app.run(host='0.0.0.0', port=8080)

這裏很簡單,就是返回一個字符串。

2.2 uwsgi.ini

[uwsgi]
module = app.main
callable = app
master = true
processes = 16

分別解釋一下

  • module:加載一個WSGI模塊,這裏加載的是與uwsgi.ini文件同級的app目錄下的main.py這個模塊;
  • callable:uWSGI加載的模塊()中哪個應用變量將被調用, 對應上面main.py文件中,Flask所創建的應用變量app;
  • master:是否啓動主進程,來管理其他進程,其它的uwsgi進程都是master進程的子進程,如果kill master進程,相當於重啓所有的uwsgi進程;
  • processes:啓動的進程數;

其他常用配置

  • threads:每個進程啓動的線程數。
  • workers:啓動的worker數,和processes效果相同;
  • daemonize = /var/log/myapp_uwsgi.log  使進程在後臺運行,並將日誌打到指定的日誌文件或者udp服務器
  • max-requests = 5000   爲每個工作進程設置請求數的上限。當一個工作進程處理的請求數達到這個值,那麼該工作進程就會被回收重用(重啓)。你可以使用這個選項來默默地對抗內存泄漏
  • reload-mercy = 8:設置在平滑的重啓(直到接收到的請求處理完才重啓)一個工作子進程中,等待這個工作結束的最長秒數。這個配置會使在平滑地重啓工作子進程中,如果工作進程結束時間超過了8秒就會被強行結束(忽略之前已經接收到的請求而直接結束)

更多參數,可詳見官方文檔

2.3 Dockerfile

# 基礎鏡像
FROM tiangolo/uwsgi-nginx-flask:python3.6-alpine3.7

COPY app /app

EXPOSE 8080

 

3 製作鏡像

在項目目錄(我這裏是test),執行命令

docker build -t test_image .

輸出

Sending build context to Docker daemon  14.34kB
Step 1/3 : FROM tiangolo/uwsgi-nginx-flask:python3.6-alpine3.7
python3.6-alpine3.7: Pulling from tiangolo/uwsgi-nginx-flask
48ecbb6b270e: Pull complete
692f29ee68fa: Pull complete
f75fc7ac1098: Pull complete
c30e40bb471c: Pull complete
51a8cc25b36b: Pull complete
074ffb62a7a7: Pull complete
4adf1a1570c9: Pull complete
b85c5544f47f: Pull complete
4b692052c830: Pull complete
6c397707523a: Pull complete
8f024010f4fc: Pull complete
71a2c0a6f4c0: Pull complete
84153fe0f140: Pull complete
2f415e7b7d03: Pull complete
861b676195c0: Pull complete
7a0bf11a12e0: Pull complete
39035b694307: Pull complete
b99491632f33: Pull complete
562bd63da354: Pull complete
270a925c68d3: Pull complete
e2a1178baba1: Pull complete
Digest: sha256:5e13ef9c28578290a825b42a863d223a4b02b8e8d101e18d0ea463bb45878103
Status: Downloaded newer image for tiangolo/uwsgi-nginx-flask:python3.6-alpine3.7
 ---> cdec3b0d8f20
Step 2/3 : COPY app /app
 ---> 57b55cb280d5
Step 3/3 : EXPOSE 8080
 ---> Running in 059033350a5a
 ---> 7c09a8fe084c
Removing intermediate container 059033350a5a
Successfully built 7c09a8fe084c
Successfully tagged test_image:latest

確認下

docker images

輸出

REPOSITORY         TAG             IMAGE ID            CREATED             SIZE
test_image         latest          7c09a8fe084c        2 hours ago         189MB

 

4 測試

4.1 運行鏡像

docker run -it --rm -p 8081:8080 test_image

這裏將主機的8081端口的請求,轉發映射到容器的8080端口。輸出

Checking for script in /app/prestart.sh
Running script /app/prestart.sh
Running inside /app/prestart.sh, you could add migrations to this file, e.g.:

#! /usr/bin/env sh

# Let the DB start
sleep 10;
# Run migrations
alembic upgrade head

/usr/lib/python2.7/site-packages/supervisor/options.py:298: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.
  'Supervisord is running as root and it is searching '
2021-03-15 07:43:42,808 CRIT Supervisor running as root (no user in config file)
2021-03-15 07:43:42,808 INFO Included extra file "/etc/supervisor.d/supervisord.ini" during parsing
2021-03-15 07:43:42,815 INFO RPC interface 'supervisor' initialized
2021-03-15 07:43:42,815 CRIT Server 'unix_http_server' running without any HTTP authentication checking
2021-03-15 07:43:42,816 INFO supervisord started with pid 1
2021-03-15 07:43:43,818 INFO spawned: 'nginx' with pid 10
2021-03-15 07:43:43,820 INFO spawned: 'uwsgi' with pid 11
[uWSGI] getting INI configuration from /app/uwsgi.ini
[uWSGI] getting INI configuration from /etc/uwsgi/uwsgi.ini

;uWSGI instance configuration
[uwsgi]
cheaper = 2
processes = 16
ini = /app/uwsgi.ini
module = app.main
callable = app
master = true
plugin = python3
ini = /etc/uwsgi/uwsgi.ini
socket = /tmp/uwsgi.sock
chown-socket = nginx:nginx
chmod-socket = 664
hook-master-start = unix_signal:15 gracefully_kill_them_all
need-app = true
die-on-term = true
show-config = true
;end of configuration

*** Starting uWSGI 2.0.17 (64bit) on [Mon Mar 15 07:43:43 2021] ***
compiled with version: 6.4.0 on 27 March 2018 12:43:27
os: Linux-3.10.0.514.26.2.el7.x86_64 #4 SMP Wed Aug 16 17:09:53 CST 2017
nodename: 737e365a4225
machine: x86_64
clock source: unix
pcre jit disabled
detected number of CPU cores: 4
current working directory: /app
detected binary path: /usr/sbin/uwsgi
your processes number limit is 62742
your memory page size is 4096 bytes
detected max file descriptor number: 1024000
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uwsgi socket 0 bound to UNIX address /tmp/uwsgi.sock fd 3
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
Python version: 3.6.9 (default, Oct 17 2019, 12:14:22)  [GCC 6.4.0]
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0x7fdba6baaf40
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 1239640 bytes (1210 KB) for 16 cores
*** Operational MODE: preforking ***
WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x7fdba6baaf40 pid: 11 (default app)
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 11)
spawned uWSGI worker 1 (pid: 13, cores: 1)
spawned uWSGI worker 2 (pid: 14, cores: 1)
running "unix_signal:15 gracefully_kill_them_all" (master-start)...
2021-03-15 07:43:45,067 INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2021-03-15 07:43:45,067 INFO success: uwsgi entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

看到最後面,nginx 和 uwsgi 都啓動成功了。確認一下

docker ps -a

CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS          PORTS                                     NAMES
5c5c761a8a0b   test_image    "/entrypoint.sh /s..."   5 seconds ago   Up 4 seconds    80/tcp, 443/tcp, 0.0.0.0:8081->8080/tcp   awesome_hodgkin

可以看到,這裏的PORTS,80和443都暴露出來了,外面的8081指向容器內的8080端口。

4.2 請求

使用curl命令

curl -v GET 127.0.0.1:8081

輸出

* About to connect() to 127.0.0.1 port 8081 (#0)
*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 8081 (#0)
> PUT / HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.13.1.0 zlib/1.2.3 libidn/1.18 libssh2/1.2.2
> Host: 127.0.0.1:8081
> Accept: */*
> Content-Length: 16
> Content-Type: application/x-www-form-urlencoded
>
* Closing connection #0
* Failure when receiving data from the peer
curl: (56) Failure when receiving data from the peer

報CURL 56錯誤 ,接收網絡數據錯誤。

4.3 排查

我們進到容器裏面看看

docker exec -it 5c5c761a8a0b sh

發現app目錄下,多了幾個文件,目錄結構變成了:

./
├── app
│   ├── app
│   │   ├── __pycache__               #應用生成的緩存文件目錄
│   │   │   └── main.cpython-36.pyc   #應用主入口文件的緩存文件
│   │   └── main.py                   #應用主入口文件
│   ├──
├── main.py             #項目入口
├── prestart.sh
├── supervisord.pid
└── uwsgi.ini           #uwsgi配置文件

其中與app目錄同級的,多了一個main.py文件,內容如下

import sys

from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello():
    version = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
    message = "Hello World from Flask in a uWSGI Nginx Docker container with Python {} (default)".format(
        version
    )
    return message


if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True, port=80)

看起來,這個文件把Dockerfile所指定暴露的8080端口給屏蔽了,只對外暴露80端口了。

我們改一下端口,重新啓動試一下。

docker run -it --rm -p 8081:80 test_image

 查看容器

CONTAINER ID    IMAGE         COMMAND                  CREATED         STATUS          PORTS                                     NAMES
c2e2c0edbe4c    test_image    "/entrypoint.sh /s..."   22 seconds ago  Up 20 seconds   443/tcp, 8080/tcp, 0.0.0.0:8081->80/tcp   quizzical_jang

 發現,這裏確實暴露了443和8080端口,同時把主機的8081端口指向到容器內部的80端口。

再請求一下

curl -v GET 127.0.0.1:8081

返回 

* About to connect() to 127.0.0.1 port 8081 (#0)
*   Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 8081 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.13.1.0 zlib/1.2.3 libidn/1.18 libssh2/1.2.2
> Host: 127.0.0.1:8081
> Accept: */*
> Content-Length: 0
> Content-Type: application/text
>
< HTTP/1.1 200 OK
< Server: nginx/1.15.3
< Date: Mon, 15 Mar 2021 08:23:28 GMT
< Content-Type: application/text
< Content-Length: 24
< Connection: keep-alive
<
"Hello World From Flask!"

成功返回了main.py文件中return的字符串。同時鏡像運行也輸出

[pid: 19|app: 0|req: 1/1] 172.30.0.1 () {36 vars in 539 bytes} [Mon Mar 15 08:23:28 2021] GET / => generated 24 bytes in 4 msecs (HTTP/1.1 200) 2 headers in 71 bytes (1 switches on core 0)
172.30.0.1 - - [15/Mar/2021:08:23:28 +0000] "GET / HTTP/1.1" 200 24 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.13.1.0 zlib/1.2.3 libidn/1.18 libssh2/1.2.2" "-"

說明我們啓動容器的時候,uwsgi自動把對外的端口改成了80和443,外部端口只能映射到這兩個端口,請求才能順利到達代碼層。而Dockerfile中的EXPOSE命令似乎沒有起作用。

至此,我們就用Docker把一個簡單的Flask應用作爲服務容器進行了部署。

後續的關於鏡像推送和部署的,請參考之前的docker相關文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章