在线上部署python应用,有两个难题,一是不同的应用依赖的环境不同,比如python版本,依赖包/模块/库的版本;二是要安装的服务器太多的话从pypi下载速度慢,并且容易失败。本文介绍我在工作中是如何解决这两个问题的,向大家分享批量安装python应用的一些经验。
注:以下操作的前提是保证测试环境和线上环境的操作系统相同。
将python应用制作成包
一个典型的python应用的目录结构如下:
» tree foo
.
├── foo
│ ├── __init__.py
│ └── app.py
└── setup.py
foo/app.py
from __future__ import print_function
import sys
import requests
def download(url):
return requests.get(url).text
def main():
if(len(sys.argv) != 2):
sys.exit('Usage:\nfoo url')
print(download(sys.argv[1]), end='')
if __name__ == '__main__':
main()
foo/__init__.py
from app import *
setup.py
"""A setuptools based setup module.
See:
https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject
"""
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
setup(
name='foo',
# Versions should comply with PEP440.
version='0.1.0',
description='A sample Python project',
# Author details
author='Yanxurui',
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
# Specify the Python versions you support here.
'Programming Language :: Python :: 2.7',
],
# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
packages=find_packages(exclude=['tests']),
# Alternatively, if you want to distribute just a my_module.py, uncomment
# this:
# py_modules=["my_module"],
# List run-time dependencies here. These will be installed by pip when
# your project is installed.
install_requires=['requests'],
# To provide executable scripts, use entry points in preference to the
# "scripts" keyword. Entry points provide cross-platform support and allow
# pip to create the appropriate form of executable for the target platform.
entry_points={
'console_scripts': [
'foo=foo:main',
],
},
)
从setup.py可以看出该应用包含一个包和一个可执行文件,并且依赖requests库。 使用entry_points创建了可执行文件foo,它调用foo包的main函数,该函数定义在app.py中。制作成package的优点是源码包含在site-packages中,不需要单独安装,并且生成的可执行文件开头包含了python的路径,不需要手动指定。
传统的安装方法是:
git clone https://github.com/yanxurui/foo.git
cd foo
pip install .
pip install -e .
。
这种方式在大部分的情况下工作得很好的,然而当你需要安装或部署python应用到上百台服务器的时候,就会出现本文开头所讨论的两个难题。
使用pyenv+virtualenv搭建python环境
pyenv解决了不同版本python的并存问题,virtualenv/venv使得不同的应用可以创建独立的依赖环境。
以CentOS为例,分别在线上环境和测试环境执行以下步骤搭建python环境:
新建用户py
为了避免使用root账号运行python应用,我们新建一个用户py:
useradd py
pwd py # set password for py
su py
安装pyenv+virtualenv
pyenv是一堆shell脚本,通过修改PATH路径来拦截python、pip等命令。pyenv-virtualenv是pyenv的插件,用来管理virtualenv或venv创建的多个虚拟环境,如果不创建env,该插件不依赖于virtualenv也可以切换env。
以下是参考它们github的文档的安装步骤:
# download
git clone https://github.com/pyenv/pyenv.git ~/.pyenv
git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
# expose pyenv command-line utility
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
# enable shims and auto-activation of virtualenvs
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\n eval "$(pyenv virtualenv-init -)"\nfi' >> ~/.bashrc
# restart shell
exec "$SHELL"
安装python
安装你要使用的python版本,比如安装当前最新版的python2
pyenv install 2.7.14
PYENV_ROOT/versions
目录,因此不会影响系统自带的python。
注:如果编译失败,已经下载的源码会被删除,可以手动下载到缓存$(pyenv root)/cache
目录:
wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tar.xz -P $PYENV_ROOT/cache
pyenv install 2.7.14
离线安装python应用
做法是先在测试环境将python应用安装到自己的virtualenv,然后将整个虚拟环境直接copy到线上环境对应python的envs目录。
测试环境:安装应用并打包env
创建virtualenv(假设使用的python是2.7.14):
cd foo
pyenv virtualenv 2.7.14 foo-env
pyenv local foo-env
在测试环境安装python应用和依赖包:
pip install .
将虚拟环境制作成压缩包并传到线上服务器:
tar -czPf foo-env.tar.gz ~/.pyenv/versions/2.7.14/envs/foo-env ~/.pyenv/versions/foo-env
scp foo-env.tar.gz py@192.168.3.234:~/
-P
选项会保留绝对路径,解压的时候也需要指定该选项。打包的时候包括了指向虚拟环境的软连接。
线上环境:安装env
cd
tar -xzPf foo-env.tar.gz
pyenv rehash
pyenv rehash
会为可执行文件foo重新生成shim(垫片),shim位于$(pyenv root)/shims/目录,有意思的是所有可执行文件的shim的内容完全一样。
启动应用可以直接使用全路径
~/.pyenv/versions/foo-env/bin/foo http://example.com
mkdir foo
cd foo
pyenv local foo-env
foo http://example.com