如何优雅的处理Python环境及程序部署

简介

随着这几年AI的火热,Python 也强势进入编程语言前三甲行列。Python 是一种跨平台的计算机程序设计语言是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越来越多被用于独立的、大型项目的开发。以上抄了一段百度百科介绍,Python 自我感觉和如今ES的标准类似,写法也类似,当然 Python 有其独有的语法特征,比如它的缩进规则等等,不满足它的规则,编译是会报错的。简洁的语法,应用性广应该是Python 热火的重要因素吧。

最近经常使用 Python 来完成一些自动化工具,代码生成工具什么的,由于第三方库的强大,很多复杂的功能找到对应的第三方库就能很快完成,同时可以实验许多很有意思的功能,比如机器学习啊,爬虫等等。好了,废话不多说,这篇文章主题不是要介绍 Python 的语法,主要是想介绍下自己在搭建环境及发布上的一些心得。

开发环境配置

Python 的程序开发,严格意义上来说其实不需要什么特别的环境,装好编译器后,你就可以在命令行环境中通过简单的命令 python run.py 来运行程序了。但为了更好的维护开发环境,我们需要一些辅助工具来帮助我们。

virtualenv

第一个要介绍的就是 virtualenv了,它能够帮助我们对于每一个独立的项目维护一个独立的包环境,简单来说A项目需要使用第三方abc库的1.0版本,而B项目需要使用abc库的2.0版本,如果全部使用系统环境来安装是没法区分的。所以我们通过 virtualenv 来给每个项目独立创建包管理环境,并且可以独立出 Python 的运行环境。通过 pip install virtualenv 安装好对应的包后,在对应的工程目录下执行 virtualenv virt 就会以默认配置创建虚拟环境了。当然我们指定额外配置,例如 --no-site-packages 不安装系统包,-p PYTHON_EXE, --python=PYTHON_EXE 指定创建的 Python 运行环境。具体配置选项还有好多,请参考官网 Reference Guide。

pipenv

实际 Python 开发,需要我们区分出开发环境和产品环境。以前我们的做法,一般都是通过 requriements.txt 文件来管理,针对不同的环境我们可能需要创建多个 requriements.txt 文件,例如 requriements-dev.txt 或者 requriements-prod.txt 什么的。有过 node.js 开发经验的小伙伴可能会很惊讶,WTF!这是什么鬼,依赖还要自己维护!不得不说,之前我们只能这么干,安装完包以后,通过 pip freeze 命令来导出项目依赖的第三方包。但是这个命令有个问题,会将目标包及其包含的其他依赖包都给导出来,这让有洁癖的我肯定受不了啊。如果我们勤奋一点,自己管理 requriements.txt 文件,那可能解决了目标包管理问题,但是目标包包含的其他依赖包会在每次重新安装的时候保持最新版本,谁TM知道最新版本会不会有问题呢,如果有个类似 npmyarn 之类的包管理工具就好了。pipenv 解决了我们这个问题,它具有以下几个特点:

  • 集成了 pipvirtualenv
  • 通过 PipfilePipfile.lock 的自动管理来替代手动管理的requriements.txt
  • 通过hash来管理包更加的准确, 安全。
  • pipenv graph 能够让你了解工程依赖关系图。
  • 通过加载.env文件简化开发流程。

使用上也非常简单,通过在工作根目录下执行 pipenv install 命令创建虚拟环境,此处是使用了集成的 virtualenv。完成以后根目录会创建 PipfilePipfile.lock 2个文件来自动管理项目所使用的包,这时候我们执行任何安装命令都会更新 PipfilePipfile.lock了。

简单的用法示例一览:

使用Python 3.7创建一个新项目:
$ pipenv --python 3.7

删除项目virtualenv:
$ pipenv --rm

安装项目的所有依赖项(包括dev):
$ pipenv install --dev

创建包含预发布的lock文件:
$ pipenv lock --pre

显示已安装的依赖项的图表关系:
$ pipenv graph

检查已安装的依赖项是否存在问题:
$ pipenv check

将本地setup.py安装到虚拟环境/ Pipfile中:
$ pipenv install -e

使用pip命令:
$ pipenv run pip freeze

程序运行及发布

先介绍下案例项目的结构,如下所示:

flask-app/
└─ flask-app/
   └─ templates
      └─ index.html
      └─ layout.html
   └─ __init__.py
 run.py

这个案例采用 Flask 来完成的,Flask 是一个 Python 实现的 Web 开发微框架,这里实现了一个简单的欢迎页面。

docker

使用Docker容器来部署程序,可以标准化我们的产品环境,让我们可以不用关注硬件,同时为了更好对接各大云平台,以后更好的扩展自动化测试和持续的集成/部署等,使用Docker来管理实现我们的部署方案成了首选。

根目录创建 Dockerfile,来定义如何打包及准备环境

# 以python3.7为基础镜像构建
FROM python:3.7
# 安装nginx与supervisor
RUN apt-get update && apt-get install -y nginx supervisor
# 安装gevent与gunicorn
RUN pip install gunicorn pipenv
# 解决输出可能的中文乱码
ENV PYTHONIOENCODING=utf-8

# 创建并设置工作目录
WORKDIR /project/flask-app
# 拷贝包文件到工作目录
COPY Pipfile* /project/flask-app/
# 安装包
RUN pipenv install --system

# nginx配置相关
# 删除默认的有效配置,sites-enabled 目录下的配置文件才能够真正被用户访问
RUN rm /etc/nginx/sites-enabled/default
# 将配置文件链接到sites-available目录
RUN ln -s /etc/nginx/sites-available/nginx.conf /etc/nginx/sites-enabled/nginx.conf
# 添加自定义CMD时,添加此命令到nginx全局配置中保证容器可以跟踪到进程,防止容器退出
RUN echo "daemon off;" >> /etc/nginx/nginx.conf

# supervisord配置相关
RUN mkdir -p /var/log/supervisor
# 指定容器运行启动命令
CMD ["/bin/bash"]

docker容器的实质是进程,而且是按照分层存储的,所以以上文件每一行对应 Docker 的一层,因此定义容器时,不必要的分层可能会导致容器文件过大。

apt-get update && apt-get install -y nginx supervisor 保证了每次执行都会安装最新包,我们预计通过 nginx 做网站的反向代理,supervisor 进程管理工具来管理容器进程。WORKDIR 创建工作目录,之后所有工作目录都已此目录为根目录。我们这里没有将实际的项目文件复制到容器中,而是准备采用挂载的方式。

docker-compose

根目录创建 docker-compose.yml

version: "3.3"

services:
  web:
    stdin_open: true
    tty: true
    # 根据Dockerfile生成镜像文件
    build: .
    image: nginx-gunicorn-flask:latest
    # 挂载代码到容器对应目录
    volumes:
      - ./flaskr:/project/flask-app/flaskr
      - ./run.py:/project/flask-app/run.py
      - ./nginx.conf:/etc/nginx/sites-available/nginx.conf
      - ./gunicorn.conf:/etc/supervisor/conf.d/gunicorn.conf
      - ./supervisord.conf:/etc/supervisor/conf.d/supervisord.conf
    # 主机与容器端口映射
    ports:
      - "80:80"
    command: "/usr/bin/supervisord"

docker-compose 作为Docker三剑客之一,主要用于管理多个容器,例如我们这里的nginx 可以作为单独的 webserver容器独立出来,如果有数据库也可以单独创建容器。我们这里主要完成了代码挂载及进程管理supervisord的启动等配置。

分别再创建gunicorn.conf

[program:gunicorn]
command=gunicorn --workers=3 run:app -b localhost:5000
directory=/project/flask-app

nginx.conf

server {
    listen 80;
    location / {
        proxy_pass http://localhost:5000/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    location ^~ /static/ {
        root /project/flask-app/;
    }
}

nginx.conf

[supervisord]
nodaemon=true

[program:nginx]
command=/usr/sbin/nginx

如果本地已经安装好了docker,那么可以直接运行docker-compose up命令执行创建镜像,运行容器等操作。如下图所示:

你可能感兴趣的:(如何优雅的处理Python环境及程序部署)