热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于FlaskAngular的项目组网架构与部署

基于Raindrop网,分享项目的组网架构和部署。项目组网架构架构说明1.流项目访问分为两个流,通过nginx分两个端口暴露给外部使用:数据流:用户访问Raindrop网站。控制流

基于Raindrop网,分享项目的组网架构和部署。

项目组网架构

《基于Flask-Angular的项目组网架构与部署》

架构说明

1.流

项目访问分为两个流,通过nginx分两个端口暴露给外部使用:
数据流:用户访问Raindrop网站。
控制流:管理人员通过supervisor监控、管理服务器进程。

图中除了将程序部署在ECS上外,还使用了OSS(对象存储服务,可以理解成一个nosql数据库),主要是为了存放一些静态文件,提高访问速度。阿里的OSS还可以与CDN一起使用,同样可以提高访问速度。

2.nginx

通过nginx对外暴露两个端口,如上所述,80端口供用户访问网站,另一个端口供管理人员使用。
80端口:根据请求的url配置了方向代理,分别导向client(angular)和server(flask).
其中server通过gunicorn部署在[socket]localhost:10000
配置如下:

server {
listen 80 default_server;
# set client body size to 4M (add by dh) #
client_max_body_size 4M;
gzip_types text/plain text/css application/json application/Javascript text/xml application/xml application/xml+rss text/Javascript;
# product
root /home/raindrop/www/client/dist;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
} # access flask static folder
location /static/ {
# product
root /home/raindrop/www/server/app;
}
location /api/ {
proxy_pass http://localhost:10000/api/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Error pages
error_page 413 @413_json;
location @413_json {
default_type application/json;
return 200 '{"msg": "Request Entity Too Large(max=4M)"}';
}
}

3.gunicorn

gunicorn作为wsgi容器,用来执行flask server。
gunicorn可以使用异步socket:gevent,其本质是基于greenlet实现协程的第三方库,改善io阻塞问题,通过简单的配置就能使程序获得极高的并发处理能力。

注意:使用gunicorn != 使用gevent,如要开启gevent socket,启动gunicorn时需要增加–work-class参数,如下:

gunicorn –workers=4 –worker-class socketio.sgunicorn.GeventSocketIOWorker -b localhost:10000 wsgi:app

除了gunicorn,也可以选用uwsgi,但是有两点限制需要注意:
1.如果使用了Flask-socketio,请不要使用uwsgi,原因

Note regarding uWSGI: While this server has support for gevent and WebSocket,
there is no way to use the custom event loop needed by gevent-socketio,
so there is no directly available method for hosting Flask-SocketIO applications on it.
If you figure out how to do this please let me know!

2.如果使用异步WSGI Server,请勿使用uwsgi,原因:慎用异步 WSGI Server 运行 Flask 应用

Flask(Werkzeug)的Local Thread无法与uwsgi基于uGreen的微线程兼容,引起Local Thread工作混乱。

4.celery

在程序运行过程中会有一些比较耗时但并非紧急的工作,这些任务可以使用异步任务来处理,提高server的响应速度。
celery – Distributed Task Queue是一个第三方库,提供异步队列和任务处理功能。
Raindrop使用celery配合实现feed功能。通过flask进行配置,使用redis作为异步队列来存储任务,并将处理结果(feed activity)存储在redis中。

5.supervisor

如上所述,我们需要在服务器上运行gunicorn和celery两个进程。
显然,我们需要一个monitor来帮我们管理这两个进程,避免进程崩溃不能及时拉起,阻塞业务。
supervisor:Supervisor process control system for UNIX是一个开源monitor程序,并且内置了web管理功能,可以远程监控、重启进程。配置如下:

[inet_http_server]
# web 管理端口,通过nginx暴露给远端用户
port=127.0.0.1:51000
[program:raindrop]
# 程序启动命令
command = gunicorn --workers=4 --worker-class socketio.sgunicorn.GeventSocketIOWorker -b localhost:10000 wsgi:app
directory = /home/raindrop/www/server
user = dh
stopwaitsecs=60
stdout_logfile = /var/log/raindrop/supervisor-raindrop.log
redirect_stderr = true
[program:celery]
command = celery -P gevent -A wsgi.celery worker
directory = /home/raindrop/www/server
user = dh
stopwaitsecs=60
stdout_logfile = /var/log/raindrop/supervisor-celery.log
redirect_stderr = true

6.数据库

mysql:网站主要数据存储在关系型数据库中
redis:缓存mysql数据 + celery异步队列

服务器

使用阿里云的服务器
ECS:部署服务端程序
OSS:存储前端静态文件(速度很快,对前端体验改善很大,采用angular框架强烈推荐使用)

组件

nginx: HTTP Server
angularjs: client框架
flask:server框架
gunicorn: web 容器
celery: 异步任务处理
supervisor: monitor
redis: 数据缓存 + 任务队列
mysql: 数据库

本地开发环境与工具

ubuntu12.04 64: 操作系统
virtualenv: python虚拟环境(隔离项目开发环境,不用担心包冲突了)
vagrant: 基于virtualbox的本地虚拟环境(环境可以导入导出,团队开发必备)
gulp: angular工程打包工具
pip: python包管理工具
fabric: 基于python的远程脚本(自动化部署神器)

打包部署

基于Ubuntu 12.0.4,其它系统安装命令(apt-get 等)请自行修改。

繁琐VS自动化

《基于Flask-Angular的项目组网架构与部署》

  1. 如果是第一次部署,需要初始化ECS服务器,安装基本工具:
    nginx, supervisor, redis, mysql, pip, virtualenv

  2. 打包项目代码

  3. 发布静态文件到OSS服务器

  4. 发布项目代码到ECS服务器,安装server依赖的包

  5. 修改mysql, nginx, supervisor配置文件

  6. 拉起所需进程

然后各种apt-get install, scp, tar, cp, mv,不得不说,这是一个烦人且毫无技术含量的工作,干过几次后基本就可以摔键盘了。
不过,有繁琐的地方,一定有自动化。其实完成上面这些工作,三条命令足以:

fab init_env
fab build
fab deploy

这要感谢Fabric:Simple, Pythonic remote execution and deployment项目,封装了非常简洁的远程操作命令。

Fabric

使用Fabric,只需要编写一个fabile.py脚本,在启动定义init_env, build, deploy三个任务:

# -*- coding: utf-8 -*-
import os, re, hashlib
from termcolor import colored
from datetime import datetime
from fabric.api import *
from fabric.contrib.files import exists
class FabricException(Exception):
pass
env.abort_exception = FabricException
# 服务器地址,可以有多个,依次部署:
env.hosts = [
'user@120.1.1.1'
]
env.passwords = {
'user@120.1.1.1:22':'123456'
}
# sudo用户为root:
env.sudo_user = 'root'
# mysql
db_user = 'root'
db_password = '123456'
_TAR_FILE = 'raindrop.tar.gz'
_REMOTE_TMP_DIR = '/tmp'
_REMOTE_BASE_DIR = '/home/raindrop'
_ALIYUN_OSS = {
'endpoint' : 'oss-cn-qingdao.aliyuncs.com',
'bucket' : 'yourbucketname',
'accessKeyId' : 'youraccessKeyId' ,
'accessKeySecret': 'youraccessKeySecret'
}
def build():
'''
必须先打包编译client,再打包整个项目
'''
with lcd(os.path.join(os.path.abspath('.'), 'client')):
local('gulp build') # 上传静态文件到oss服务器,并修改index.html中对静态文件的引用
with lcd(os.path.join(os.path.abspath('.'), 'client/dist')):
with lcd('scripts'):
for file in _list_dir('./'):
if oss_put_object_from_file(file, local('pwd', capture=True) + '/' + file):
_cdnify('../index.html', file)
with lcd('styles'):
for file in _list_dir('./'):
if oss_put_object_from_file(file, local('pwd', capture=True) + '/' + file):
_cdnify('../index.html', file)

# 注意在oss上配置跨域规则,否则fonts文件无法加载
# !!修改fonts文件夹请放开此段程序!!
# with lcd('fonts'):
# for file in _list_dir('./'):
# oss_put_object_from_file('fonts/%s' % file, local('pwd', capture=True) + '/' + file)
with lcd(os.path.join(os.path.abspath('.'), 'server')):
local('pip freeze > requirements/common.txt')
excludes = ['oss', 'distribute']
[local('sed -i -r -e \'/^.*' + exclude + '.*$/d\' "requirements/common.txt"') for exclude in excludes]
local('python setup.py sdist')
# e.g command: fab deploy:'',Fasle
# 注意命令两个参数间不要加空格
def deploy(archive='', needPut='True'):
if archive is '':
filename = '%s.tar.gz' % local('python setup.py --fullname', capture=True).strip()
archive = 'dist/%s' % filename
else:
filename = archive.split('/')[-1] tmp_tar = '%s/%s' % (_REMOTE_TMP_DIR, filename)
if eval(needPut):
# 删除已有的tar文件:
run('rm -f %s' % tmp_tar)
# 上传新的tar文件:
put(archive, _REMOTE_TMP_DIR)
# 创建新目录:
newdir = 'raindrop-%s' % datetime.now().strftime('%y-%m-%d_%H.%M.%S')
with cd(_REMOTE_BASE_DIR):
sudo('mkdir %s' % newdir)

# 重置项目软链接:
with cd(_REMOTE_BASE_DIR):
# 解压到新目录:
with cd(newdir):
sudo('tar -xzvf %s --strip-compOnents=1' % tmp_tar)
# 保存上传文件
if exists('www/server/app/static/upload/images/', use_sudo=True):
sudo('cp www/server/app/static/upload/images/ %s/server/app/static/upload/ -r' % newdir)
sudo('rm -f www')
sudo('ln -s %s www' % newdir)
with cd(_REMOTE_BASE_DIR):
with prefix('source %s/env/local/bin/activate' % _REMOTE_BASE_DIR):
sudo('pip install -r www/server/requirements/common.txt')
# 启动服务
with cd('www'):
# mysql
sudo('cp etc/my.cnf /etc/mysql/')
sudo('restart mysql')
# monitor
sudo('cp etc/rd_super.conf /etc/supervisor/conf.d/')
sudo('supervisorctl stop celery')
sudo('supervisorctl stop raindrop')
sudo('supervisorctl reload')
sudo('supervisorctl start celery')
sudo('supervisorctl start raindrop')

# nginx
sudo('cp etc/rd_nginx.conf /etc/nginx/sites-available/')
# ln -f —-如果要建立的链接名已经存在,则删除之
sudo('ln -sf /etc/nginx/sites-available/rd_nginx.conf /etc/nginx/sites-enabled/default')
sudo('nginx -s reload')
def init_env():
sudo('aptitude update')
sudo('aptitude safe-upgrade')
# sudo('apt-get install nginx')
sudo('aptitude install python-software-properties')
sudo('add-apt-repository ppa:nginx/stable')
sudo('aptitude update')
sudo('apt-get install nginx')
sudo('apt-get install supervisor')
sudo('apt-get install redis-server')
sudo('apt-get install mysql-server')
sudo('apt-get install python-pip python-dev build-essential')
sudo('pip install virtualenv')
run('mkdir /var/log/raindrop -p')
with cd ('/home/raindrop'):
sudo('virtualenv env')
def oss_put_object_from_file(key, file_path):
from oss.oss_api import *
oss = OssAPI(_ALIYUN_OSS['endpoint'], _ALIYUN_OSS['accessKeyId'], _ALIYUN_OSS['accessKeySecret'])
res = oss.put_object_from_file(_ALIYUN_OSS['bucket'], key, file_path)
return res.status == 200 and True or False
def _expand_path(path):
print path
return '"$(echo %s)"' % path
def sed(filename, before, after, limit='', backup='.bak', flags=''):
# Characters to be escaped in both
for char in "/'":
before = before.replace(char, r'\%s' % char)
after = after.replace(char, r'\%s' % char)
# Characters to be escaped in replacement only (they're useful in regexen
# in the 'before' part)
for char in "()":
after = after.replace(char, r'\%s' % char)
if limit:
limit = r'/%s/ ' % limit
cOntext= {
'script': r"'%ss/%s/%s/%sg'" % (limit, before, after, flags),
'filename': _expand_path(filename),
'backup': backup
}
# Test the OS because of differences between sed versions
with hide('running', 'stdout'):
platform = local("uname")
if platform in ('NetBSD', 'OpenBSD', 'QNX'):
# Attempt to protect against failures/collisions
hasher = hashlib.sha1()
hasher.update(env.host_string)
hasher.update(filename)
context['tmp'] = "/tmp/%s" % hasher.hexdigest()
# Use temp file to work around lack of -i
expr = r"""cp -p %(filename)s %(tmp)s \
&& sed -r -e %(script)s %(filename)s > %(tmp)s \
&& cp -p %(filename)s %(filename)s%(backup)s \
&& mv %(tmp)s %(filename)s"""
else:
context['extended_regex'] = '-E' if platform == 'Darwin' else '-r'
expr = r"sed -i%(backup)s %(extended_regex)s -e %(script)s %(filename)s"
command = expr % context
return local(command)
def _cdnify(index_file, cdn_file):
sed(index_file, '([^<]*(src|href)=")[^<]*' + cdn_file + '"', '\\1http://' + _ALIYUN_OSS['bucket'] + '.' + _ALIYUN_OSS['endpoint'] + '/' + cdn_file + '"')
def _list_dir(dir=None, access=lcd, excute=local):
"""docstring for list_dir"""
if dir is None:
return []
with access(dir):
string = excute("for i in *; do echo $i; done", capture=True)
files = string.replace("\r","").split("\n")
return files

通过fabric自动化部署有两点需要注意:
1.安装mysql时,设置的密码不能生效,需要登到服务器上手动设置一下:

mysql -u root
use mysql;
update user set password=PASSWORD('123456') where User='root';
flush privileges;
quit;

2.服务器上的用户需要自己手动创建。

gulp

在build任务中,使用gulp build打包client的angular代码。
这里先不多说,有需要请参考github项目generator-gulp-angular

setup

setup是python自己的打包工具。
build任务中,最后使用python setup.py sdist命令把整个工程打包成一个tar.gz文件,上传到服务器。
使用这个工具需要编写如下两个文件:
setup.py:打包脚本

#!/usr/bin/env python
from setuptools import setup, find_packages
import server
try:
long_description = open('README.md').read()
except:
long_description = server.__description__
REQUIREMENTS = []
exclude_lib = ['oss', 'distribute']
for lib in open("server/requirements/common.txt").readlines():
for exclude in exclude_lib:
if lib.lower() not in exclude:
REQUIREMENTS.append(lib)
setup(
name='raindrop',
url='https://www.yudianer.com',
version=server.__version__,
author=server.__author__,
author_email=server.__email__,
description=server.__description__,
long_description=long_description,
license=server.__license__,
packages=find_packages(),
zip_safe=False,
platforms='any',
install_requires=REQUIREMENTS
)

MANIFEST.in:指定打包哪些文件夹

recursive-include etc *
recursive-include client/dist *
recursive-include server/app *
recursive-include server/requirements *
recursive-exclude server *.pyc
prune server/app/static/upload/images
prune server/env
prune server/tests

本文由raindrop网码农撰写。欢迎转载,但请注明出处。


推荐阅读
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 本文深入探讨了NoSQL数据库的四大主要类型:键值对存储、文档存储、列式存储和图数据库。NoSQL(Not Only SQL)是指一系列非关系型数据库系统,它们不依赖于固定模式的数据存储方式,能够灵活处理大规模、高并发的数据需求。键值对存储适用于简单的数据结构;文档存储支持复杂的数据对象;列式存储优化了大数据量的读写性能;而图数据库则擅长处理复杂的关系网络。每种类型的NoSQL数据库都有其独特的优势和应用场景,本文将详细分析它们的特点及应用实例。 ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • 您的数据库配置是否安全?DBSAT工具助您一臂之力!
    本文探讨了Oracle提供的免费工具DBSAT,该工具能够有效协助用户检测和优化数据库配置的安全性。通过全面的分析和报告,DBSAT帮助用户识别潜在的安全漏洞,并提供针对性的改进建议,确保数据库系统的稳定性和安全性。 ... [详细]
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
  • 在 Axublog 1.1.0 版本的 `c_login.php` 文件中发现了一个严重的 SQL 注入漏洞。该漏洞允许攻击者通过操纵登录请求中的参数,注入恶意 SQL 代码,从而可能获取敏感信息或对数据库进行未授权操作。建议用户尽快更新到最新版本并采取相应的安全措施以防止潜在的风险。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 深入探索HTTP协议的学习与实践
    在初次访问某个网站时,由于本地没有缓存,服务器会返回一个200状态码的响应,并在响应头中设置Etag和Last-Modified等缓存控制字段。这些字段用于后续请求时验证资源是否已更新,从而提高页面加载速度和减少带宽消耗。本文将深入探讨HTTP缓存机制及其在实际应用中的优化策略,帮助读者更好地理解和运用HTTP协议。 ... [详细]
  • 作为软件工程专业的学生,我深知课堂上教师讲解速度之快,很多时候需要课后自行消化和巩固。因此,撰写这篇Java Web开发入门教程,旨在帮助初学者更好地理解和掌握基础知识。通过详细记录学习过程,希望能为更多像我一样在基础方面还有待提升的学员提供有益的参考。 ... [详细]
  • 本文详细介绍了使用 Python 进行 MySQL 和 Redis 数据库操作的实战技巧。首先,针对 MySQL 数据库,通过 `pymysql` 模块展示了如何连接和操作数据库,包括建立连接、执行查询和更新等常见操作。接着,文章深入探讨了 Redis 的基本命令和高级功能,如键值存储、列表操作和事务处理。此外,还提供了多个实际案例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 在CentOS上部署和配置FreeSWITCH
    在CentOS系统上部署和配置FreeSWITCH的过程涉及多个步骤。本文详细介绍了从源代码安装FreeSWITCH的方法,包括必要的依赖项安装、编译和配置过程。此外,还提供了常见的配置选项和故障排除技巧,帮助用户顺利完成部署并确保系统的稳定运行。 ... [详细]
  • 智能制造数据综合分析与应用解决方案
    在智能制造领域,生产数据通过先进的采集设备收集,并利用时序数据库或关系型数据库进行高效存储。这些数据经过处理后,通过可视化数据大屏呈现,为生产车间、生产控制中心以及管理层提供实时、精准的信息支持,助力不同应用场景下的决策优化和效率提升。 ... [详细]
  • NoSQL数据库,即非关系型数据库,有时也被称作Not Only SQL,是一种区别于传统关系型数据库的管理系统。这类数据库设计用于处理大规模、高并发的数据存储与查询需求,特别适用于需要快速读写大量非结构化或半结构化数据的应用场景。NoSQL数据库通过牺牲部分一致性来换取更高的可扩展性和性能,支持分布式部署,能够有效应对互联网时代的海量数据挑战。 ... [详细]
  • 在 Ubuntu 中遇到 Samba 服务器故障时,尝试卸载并重新安装 Samba 发现配置文件未重新生成。本文介绍了解决该问题的方法。 ... [详细]
author-avatar
冰忆ch
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有