部署 Django 项目背后的原理:为什么需要 Nginx 和 Gunicron这些东西?

相信用过 Django 的同学一定会被 “Very easy to setup” 惊艳到。只要一行命令,就可以在 admin 界面看到一个完整的登陆注册。但是到了部署的时候,你一定会被网上复杂的部署教程搞的头晕,为啥本地开发这么简单,到了服务器却需要又是 Nginx,又是 uWSGI 这种东西呢?

摘自The Full Stack Python Guide to Deployments 一书

本文试图解释这些程序在一个 Web 服务中扮演的角色,为什么部署 Python web程序需要它们。本文不介绍如何部署 Django 应用等,这一类教程网上有很多,读者可自行搜索。其中 Nginx 和 Apache 算是一类的,可以替换。Gunicorn 和 uWSGI 的角色是类似的。同理最后端的 Web 框架是一类的,比如 Django 和 Flask。本文的内容替换以上任意一个应该也适用。

如果你读了网上任意一篇教程,你应该知道一个完整的部署应该类似这样:

最后后面的 App 我们知道,只要 runserver 就可以访问了,但是 Django 的文档明确说明:

DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through security audits or performance tests. (And that’s how it’s gonna stay. We’re in the business of making Web frameworks, not Web servers, so improving this server to be able to handle a production environment is outside the scope of Django.)

即 Django 做的事情只是一个框架,不会去关心一些安全问题、HTTP 的性能问题等。所以我们需要一个专业的 HTTP 服务器。这就出现了 Nginx 或 Apache。那么如何将 HTTP 服务器和我们的应用连接起来呢?动态网站问世的时候,就出现了 CGI 协议。注意这是一个协议,定义了HTTP 服务器如何通过后端的应用获取动态内容。可以简单的理解成 HTTP 服务器通过 CGI 协议调用后端应用吧!WSGI 可以理解成 Python 的 CGI。uWSGI 和 Gunicorn 是这种 WSGI 的一些实现。通过像 uWSGI 和 Gunicorn 的实现,Nginx 这样的通用 web 服务器就可以和 uWSGI 来沟通了。那 Django 呢?上文说到,Django 是一个 Framework,而不是一个 Web Server,Django 只是帮你去写 Web 程序的代码,真正运行的其实是 uWSGI 进程 (而并没有一个 django 进程)。就出现了上面提到的三层的部署。

但是,为什么我们还需要 Nginx 呢?这些 WSGI 程序本身不是也提供访 HTTP 问吗?

是的,uWSGI 和 Gunicorn 本身就是一个便携的 web 服务器了,但是我们一般还是在它们前面档一个更加专业的 HTTP 服务器。Nginx 作为一个经过更长时间验证的 HTTP 服务器来说,它有很多 uWSGI 没有支持的 feature。比如:

  1. 处理静态资源更加优秀,E-Tag 的设置,Gzip 压缩等
  2. 处理网络连接,降低网络负载。例如 reuqest_buffering ,假如部署一个 uWSGI 程序,如果有慢的请求存在,uWSGI 必须等待整个 HTTP 请求发过来之后才开始处理请求。但是如果前置 Nginx,那么 Nginx 会帮你收到整个 HTTP 请求之后才交给 uWSGI 处理,也就是说,uWSGI获得的永远会是完整的 HTTP 请求,不会占用一个线程在等待。(为什么慢请求不会占用 Nginx 一个线程但是会占用 uWSGI 的一个线程,这个是因为 Nginx 是基于 epoll 的异步模型,而 uWSGI 是同步模型,这里面要详细了解的话,就要去搜索和阅读很多东西了)
  3. 甚至缓存动态的内容,例如将博客的首页缓存 5 分钟
  4. 作为一个负载均衡的前置,这样每一层的实例数量可以横向扩展
  5. Nginx 本身是作为一个服务存在的,几乎在任何 Linux 版本上安装之后都可以用 initd 启动,就像 MySQL 那样。uWSGI 一般是需要自己将其配置成服务的。(虽然前置了 Nginx 依然是需要配置 uWSGI)
  6. ……

此外,这样分开的好处还是得,到达 uWSGI 和 Gunicorn 的请求的情况变得简单了很多,Nginx 处理了一层,将过滤和处理之后的请求交给 uWSGI 或 Gunicorn。这使得这些 WSGI 程序的实现简单了一些,简化了开发的工作。专业的事情交给专业的人去做。

当然,并不是所有的项目都需要这么复杂的部署,有一个可选的是将 WSGI 程序嫁接到 HTTP 服务器上,比如 Apache httpd + mod_wsgi, Nginx + mod_uwsgi 等。

参考资料:

  1. 一份比较不错的部署指南
  2. Why do I need Nginx and something like Gunicorn?
  3. Why do I need nginx when I have uWSGI
 

辅助Django开发的一些隐藏资源(文档)

距离上次使用 Django 还是一年前了,最近又回到 Django 上来,慢慢读它的文档,发现了1年之前马马虎虎使用 Django 的时候没有发现的好东西。

Admin可以自动生成文档

大家都知道 Django 可以自动根据你的 Model 生成 admin,但是我还是第一次知道这个 admin 可以生成文档呢。效果如下,可以展示项目的 App 中可以用的 Tags,Model 等。

Admin自动生成的文档首页

对我来说,比较有用的是可以展示某个 Model 的所有可以用的属性,这样就不用翻代码了,一目了然。尤其是对于没有 Model 之间的关系的 related_name ,有一些没有自己定义,即使自己定义了可能也比较乱。通过这个文档来看,这个 Model 上面的可调用的东西就很清晰了。

安装方法:

  1. 将 django.contrib.admindocs 添加到 INSTALLED_APPS
  2. 在 urls.py 中添加  path('admin/doc/', include('django.contrib.admindocs.urls'))
  3. 安装 http://docutils.sourceforge.net/ (pip install docutils)

更多安装信息可以参考 Django文档

Classy Class-Based Views网站

去年写了一篇文章介绍 Django 的 Class-based-View,但是 Django 的 View 看起来比较杂乱,也比较多(好像一旦用上了多继承,就会搞得这样)。想要一个 View 或 Mixin 的时候,经常怀疑自己选择的这个是不是最合适的。

http://ccbv.co.uk 这个网站可以清楚地展示 Django(可以选择 Django 的版本) 所有可以用的 View。每个 View 的详情页展示了这个 View 的属性和可以用的方法(如果用 dir的话,你可能会被显示出的信息吓到)。并且可以查看方法的文档和源码,也可以跳转到 Django 在 Github 的源代码。

还能画出继承关系的类图,详见恨晚。

 

《罪与罚》摘录

去年去参加了 WEPLAY 游戏展,会展上《寂静岭》的音乐制作人山冈晃先生接受采访的时候说,《寂静岭》音乐的灵感来自于一本俄罗斯小说《罪与罚》,所以我决定去读一下这本书。

花了半年时间才读完这本书,读完之后发现,这个情节和游戏中的剧情有很微妙的关系。《罪与罚》中主线讲述的是大学生罗佳迫于生活的穷困,内心又有着伟大的思想,认为杀死一个蝼蚁一般的老太婆,获得短期内的钱来供自己这段时间的发展,对人类社会来说是有益的,就像拿破仑一样。然而,内心是有这样的想法,但是实践起来却畏手畏脚,最终鬼使神差践行了自己的计划,良心却备受谴责。经历了很长时间的煎熬,最后去警察局自首。我认为罗佳的悲剧来自于年纪轻轻却不甘于平庸,急切地想有非常舒适的环境去发展自己的矛盾。现实中的自己无法满足自己对自己的期望,从而对自己产生了恨,表现地对自己的妹妹和妈妈冷漠,对自己的处境也漠不关心。

除了主人公罗佳,书中还描述了很多人的悲剧。无法自制经常跑去饮酒的小官吏,硬生生断送的自己的前程,还有原本幸福的家庭;命途多舛的官吏妻子,被逼卖淫的女儿;被世俗嘲笑,被富婆包养的斯维德里盖洛夫,即使放弃了一切也换不到杜尼娅的芳心,最终自杀;对儿子百般溺爱,精神失常的罗佳的妈妈。

这本书所描述的悲剧如此突出,我觉得很大部分原因是作者陀思妥耶夫斯基喜欢让人物的感情剧烈变化,文章有打断的独白和对话,用了很多语气词,惊叹词,使得这些人物的台词感染力非常强,很容易代入。

读完这本书,也没有什么很特别的想法,就是感受到了这么多悲剧。生者还是好好活着,多考虑一下别人,小心翼翼的,生活太容易破碎了。

以下是我在 Kindle 上划出的一些摘录。

 

这种理论把芸芸众生人为地分为“非凡的人”和“平凡的人”两类:“一类是低级的人(平凡的人),也就是说,可以称之为仅仅是繁殖同类的材料;另一类是真正意义上的人,也就是具有天赋和才干,能在自己所处的社会提出新见解的人。”

个人究竟该以何种方式与途径发展自己的个性呢?

但他犯罪后良心不安,被他蔑视的道德规范暗暗地惩罚着他的灵魂,他噩梦连连,疑神疑鬼,惶惶不可终日,几次打算到警察分局去投案自首。这样,这部小说所写的真正惩罚就是道德与良心的惩罚,它所描绘的是发生在主人公内心深处的道德和心理斗争。

“那是由于没有别的人可找,也没有别的路可走啊!不是吗,任何人总得至少有一条路可走啊。因为有时候一个人必须有条路可走!

此外,要了解任何一个人,都必须一步一步地细心观察,才不致产生错误和偏见,否则,以后纠正错误和消除成见就十分困难了。

啊。当然,即使只吃黑面包只喝白开水,她也不会出卖自己的灵魂,也不会因为耽于舒适而放弃精神方面的自由;

啊,必要时我们会压制我们的道德情感,并且把自由、宁静,甚至良心,所有的一切,都拿到旧货市场上去拍卖。连生命也毫不顾惜!只要我们热爱的人能够幸福。

一种新的、无法克服的感觉以每分钟逐渐增强之势控制了他:这就是对劈面相逢的、环绕四周的一切都怀着某种无比强烈的、几乎是生理性的反感,一种持续不断的、怒气冲冲的、恨之入骨的反感。他憎恶劈面相逢的一切人,——憎恶他们的面孔、步伐、举止。假如有谁来与他攀谈,他简直要啐他一脸唾沫,可能还会咬他一口……

科学却告诉我们:首先你应该只爱自己,因为世界上的一切都以个人利益为基础。你只爱你自己,那么你就会把自己的事情办妥,你的长上衣也就会完整如一。经济学的真理进一步告诉我们,社会上办得好的私人事业越多,也就是说完整如一的长上衣越多,社会的基础就越牢固,公共事业也就会办得越发兴旺。

“您的那个讲师在莫斯科受审时被问到为何伪造有奖债券时,答道:‘大家都千方百计致富,所以我也想快速发财。’原话我记不太清了,但意思就是不劳而获,尽快地大发横财!大家都习惯于坐享其成,以别人的思想为思想,吃别人的现成饭。哈,伟大的时刻来临了,每个人都露出了自己的本性,都在看用什么法子发财……”

他只知道一点:“这一切必须就在今天结束,一次性地结束,立即结束;否则他决不回家,因为他不愿意如此活着。”如何结束?用什么法子结束?对此他毫不知晓,甚至不愿加以考虑。他驱走了这个念头,因为这个念头让他苦恼不已。他只是感觉到并且知道,一切总归都必须改变,这样变或者那样变,“不管怎样变都行”,他怀着天不怕地不怕、无可动摇的自信和决心反复喃喃着这句话。

“没什么可锁的人是幸福的,对吗?”他笑盈盈地对索尼娅说。

比方说,哪怕是人类的立法者和规章制度的创立者,从远古时代,直至后来的莱喀古士[17]、梭伦[18]、穆罕默德[19]、拿破仑等等,无一例外,都是罪犯,唯一的原因在于,他们在制定新法规的同时,也就破坏了世所公认的、神圣不可侵犯的、代代相传的古老法规,而且,当然啰,他们也不会面对流血而停步不前,只要流血(有时流的完全是无辜的、为维护古代的法规而英勇献身者的鲜血)能帮助他们成功。尤其令人注目的是,这些人类的恩人和规章制度的创立者,大多数都是血流成河的特别可怕的屠夫。

第一类人永远是现在的主人,第二类人则是未来的主人。第一类人保存这世界,增殖人口;第二类人则推动世界向前发展,并引导它奔向目的地。无论是第一类人还是第二类人,都有完全相同的生存权利。

“有良心的人一旦意识到自己的错误,就会深感痛苦。这也是对他的一种苦役之外的惩罚。”

追求进步,并跻身于“我们的年轻的一代”的行列——完全是出于一时的青春激情。他是那些数不胜数、各式各样的言行庸俗、思想幼稚、志大才疏而又刚愎自用者之一,这类人对于最流行的时髦思想必定是顷刻间便趋之若鹜,紧紧依附,为的是立即把它庸俗化,并在一瞬间把他们有时竭诚效劳的一切漫画化。

也许,在这件事上起决定作用的是那种特殊的穷人的自尊心。就是因为这种自尊心,千千万万的穷人每逢日常生活中人人必须遵守的某些社会习俗时,都会倾箱倒箧地把自己节衣缩食积攒起来的一点点钱全都花光,只是为了证明自己“毫不逊色于别人”,以及不让那些人对他们横加“指责”。

你们还不知道,还不知道,这是怎样的一颗心灵,这是怎样的一个姑娘!她竟会偷钱,她!如果你们需要的话,她为了济人之难会脱下自己身上的最后一件衣服,光着脚去把它卖掉,再把钱送给你们,瞧啊她就是这样一个姑娘!因为我的孩子啼饥号寒,她甚至去领了黄色执照,她完全是为了我们才去出卖自己的呀!……哎哟,死鬼,死鬼呀!哎哟,死鬼,死鬼呀!你看见了吗?你看见了吗?这就是为你办的丧后酬客宴!上帝啊!你们倒是保护保护她呀,究竟为什么光是站着呢!罗季昂·罗曼诺维奇!您为什么不出来主持正义啊?难道连您都不相信她?你们连她的一个小指头都抵不上,你们所有的人,所有的人,所有的人,所有的人!上帝啊!最后只好请您保护她了!”

你瞧:你已经知道,我母亲几乎是一无所有。妹妹侥幸受了些教育,也注定只能东奔西跑当家庭教师。她们把一切希望都寄托在我的身上。我已经上了大学,但在大学里我无法维持自己的生活,迫不得已只好暂时退学。假如我就这样勉强拖下去,那么十年二十年之后(如果天从人愿,情况好转),我仍然有希望当上一名教师,或者成为一个官吏,可以拿到一千卢布的年薪……(他似乎是倒背如流)然而到那个时候,母亲早已因为操劳和愁苦而憔悴不堪了,我还是不能使她感到安慰,而妹妹……唉,妹妹的情况也许更糟!……难道竟然可以一辈子对这一切都漠不关心,置若罔闻,把母亲忘记到九霄云外,让妹妹低首下心地,譬如说吧,任人侮辱?为了什么?是不是为了在埋葬她们以后,挣钱去养活其他的人——妻子和孩子,而以后又一文不名地让他们活在世上餐风饮露?唔……唔,于是我下定决心要占有老太婆的钱,把它们用做我最近几年的费用,不再使母亲担忧受苦,用这些钱维持我大学的生活,实现我大学毕业后最初的一些计划——大刀阔斧、不遗余力地彻底改变这一切,为自己开创一个崭新的前程,踏上一条独立自主的新路……唔……唔,我要说的就全在这里了……唔,当然喽,我杀死了这个老太婆,——我做了一件坏事……唉,不说了!”

一百只兔子永远拼不成一匹马,一百个疑点永远拼不成一个证据,有句英国谚语不是这样说的吗?不过这仅仅是一种理智的说法,可是一时冲动起来的感情是难以控制的。

其实,要对某些人做出公正的评判,就必须预先放弃一些先入的偏见,改变那种对待我们周围的人和事的习惯态度。

最后,我采取了一个征服女人的心的最高明、最有效的绝招,这个绝招永远都不会使人失望,对任何人都绝对管用,无一例外。这个绝招是众所周知的,它就是阿谀奉承。世界上最难的事是开诚相见,而最容易的事是阿谀奉承。开诚相见,只要有百分之一的虚假,那么马上就会出现不和谐,而麻烦就会随之而来。至于阿谀奉承,哪怕从头至尾都是虚假,但还是令人感到高兴,听起来不会不舒服;哪怕觉得肉麻,但还是觉得舒服。不管阿谀奉承是如何肉麻,其中肯定至少有一半使人觉得真实。

“罪行?什么罪行?”他突然出人意外地狂叫,“我杀死的是一只可恶的、有害的虱子,一个对任何人都没有益处的放高利贷的老太婆,一个穷人的吸血鬼,杀了她可以赎四十桩罪行,这也算是犯罪吗?我可不认为这是罪行,也没有想去赎罪。为什么大家都指着我说:‘犯罪,犯罪!’直到现在我才明白,我的胆怯是非常愚蠢的,现在我已决定去受这种不应该受到的耻辱!我这样做,只是由于自己的卑鄙和无能,也许还由于这个……波尔菲里建议的好处!……”

但是他对自己进行了严格的审判,他那变得冷酷无情的良心,在他以往的行为中没有发现任何特别严重的罪过,除了人人都免不了的一般的失误以外。

 

Gitlab自动部署方案(Systemd+nc)

Gitlab 的 CI 还是比较有好的,只要配置好一些 Shared Runner,所有的项目都可以共享这些 Runner 来跑 CI 了,这样比较节省资源,这些 Runner 勤勤恳恳地跑完了一个就去跑下一个。

但是 CD 自动部署稍稍有些麻烦,Gitlab 这边能做到的就是跑完所有的测试之后发送一个 Curl 通知,告诉 Build 所有的测试通过了,可以开始部署了。.gitlab-ci.yml 文件类似下面:

这样 Runner 在所有的 test stage 执行完了之后,就会执行 deploy 这个 stage。

最后 deploy 这个地方只写了一个 curl ,那么接收 Gitlab 的通知,然后执行部署这个步骤就有些麻烦了。可能需要一个 HTTP 服务监听端口,在网上找了一些方案,有用 PHP 写的脚本,也有用 Python 写的,但是这样就有了更多的依赖要维护了。

找了一圈,发现 netcat 这个小巧的工具就能实现监听端口->响应请求->执行脚本部署的需求。

下面的命令可以一直监听 9999 端口,有请求的话就响应 echo 的内容。然后结束命令。

配合 && 连接起来,就可以做到通过 Curl 触发部署操作了。

通过 systemd,可以将这个脚本管理起来,让它永远重启,这样一次部署之后,马上就可以重新监听,等待下一次部署命令。注意要添加 StartLimitInterval ,限制一下执行的频率。最终的 systemd service 如下:

这样就可以实现每次向 master push 代码,自动测试成功并且马上推送到测试环境中。 update.sh 脚本的最后可以加一个 Curl 命令向钉钉或者 slack 发送提醒。

 

PyCon2018 Review (Part 1)

1. Python 依赖管理的未来?

像 npm 这种工具一般都会产生 .lock 文件的,将版本锁死。Python 并没有这种 lock 文件,这样的话我们部署的时候就有不确定性,可能最终的环境会和预期不一致(What do you want 和 What do you need 的问题)。有一种方案就是 pip freeze > requirements.txt ,但是这种导出的依赖是扁平的,你不是一个包是你项目的依赖,还是你所依赖的 Flask 的依赖的依赖。

所以就有了 Pipenv,使用它来安装,就会产生一个 Pipfile,放着人类可读的依赖包,每次安装之后就会自动产生一个 .lock 文件,是一个 json 格式的文件,对机器处理很友好。每一个依赖都带有 sha256 hash,可以将最终部署的环境锁定。此外,Pipenv 还带有很多非常 Nice 的功能,比如画依赖图、管理 venv、切换 Python 版本等等。需要注意的是,这和 setup.py 是完全不一样的东西,一个是应用的依赖管理,一个是库的打包(这个好问题是最后一位观众提出的)。

推荐指数:5 (讲了Python打包的历史,现场带有Nice的演示,所有的问题都有满意的回答,演讲思路清晰)

演讲者:Kenneth Reitz(requests作者,应该所有的 Pythoner 都知道这个人吧),演讲5天前刚从 Heroku 跳槽到 DigitalOcean。

相关项目:pipPipenv

Youtube:Kenneth Reitz – Pipenv: The Future of Python Dependency Management – PyCon 2018

2. importlib.resources in Python3.7

目前来说,如果你想在你的库或者应用中添加静态文件(类似分词这种元数据的话),方法是放到一个目录中,然后在代码中拼出来路径,打开并读取这个文件。首先这样代码冗长(虽然Python来写只需要5行左右)但是依然很长,并且如果你的程序是从 zip 文件中执行的话,那么就有问题了,你打开文件的时候并不会有这个系统路径存在。

所以 LinkedIn 的 Warsaw (这个库主要是他们开发的)给出了一个 Talk 介绍 Python3.7 中新增的 importlib.resource 库的解决方案:在静态文件所在的文件夹添加一个 __init__.py 文件,那么这个文件夹就变成了一个“module”,使用 importlib.resources 中只要是可以 import 的 module,那么这个 module 中的静态文件就可以通过 importlib 来读取。解决了 zip 文件的情况,还提供了很多 API,还有一些效率上的提升。以后的程序中感觉用这种方式加载静态文件比较靠谱。

推荐指数:3

演讲者:Warsaw from Linkedin

相关项目:https://github.com/linkedin/shivimportlib.resources

Youtube: Barry Warsaw – Get your resources faster, with importlib.resources – PyCon 2018

3. Pythoner为什么要了解Systemd?

Instagram 是业界有名的 Python 用户,他们的博客经常会发表干货,演讲也通常价值很高。不过这个 talk 感觉没有发挥出 Instagram 一贯的水平啊,嘉宾嗓子有点沙哑,加上口音,听懂的难度有点大,理解万岁哈哈。

内容是关于 Systemd 的,教你如何将 Python 程序作为系统的 service 来管理,涉及了服务管理的小历史,这部分是一个不错的 Systemd 入门教程吧。后半部分推销了 facebook 的一个项目。

pystemd 是一个 libsystemd 的 Python wrapper,提供了很多不错的 feature,例如运行一个命令时,将整个系统变成 Read-only 的;或者提供一个与系统的 tmp 相隔离的 tmp; 或者提供一个网络的 firewall,限制只有特定的 IP 可以访问等;或者限制 CPU 和内存等资源的使用,之前我想实现一个多租户的 Python 环境,这个库看起来可以满足我大部分的需求。当然,基本也有所有 systemd 的相关操作。后面 Demo 部分很不错,虽然最后一个貌似失败了。

推荐指数:4

演讲者:Alvaro Leiva Geisse from Instagram & Facebook

相关项目:systemd,pystemd

Youtube:Alvaro Leiva Geisse – Systemd: why you should care as a Python developer – PyCon 2018

4. 命令行UI也需要设计

这是去年的 PyCon 的视频,最近从“稍后观看”里面找到的,发现自己还没看完,就顺带看了。

黑乎乎的命令行并不是没有UI了,相反,UI的复杂程度不亚于GUI,也需要精心的设计。我使用 PostgreSQL 和 MySQL 从没依赖过 GUI, 因为 mycli 和 pgcli 实在是太方便了。命令历史,自动补全,颜色,拿什么 GUI 跟我换我都不换!这两款项目的作者来自 Amjith Ramanujam 来自 Netflix,演讲也很精彩。

命令行不像GUI,GUI上面如果有什么新 feature,就会有一个新的选项或者 icon,用户就知道了。但是命令行呢,要么花时间读手册,要么你有个不错的朋友跟你说一些技巧,不然你很难发现一些实用的 feature。这里演讲者举了几个例子,有时候你要想办法让用户见到这些 nice feature。

第二点,注意力要放在用户身上,首先想到怎么用最好,而不是首先想到难以实现。这里作者提到实现 mycli 的时候,要做到根据数据库的数据自动提示,但是市面上并没有开源的 SQL 引擎,要自己写一个工作量和难度实在太大了,但是作者也实现了。

第三点,配置是罪恶之源,只留给用户去配置一些主观的东西,比如颜色。其他的不要去让用户决定。

最后就是一段演示了,展示了怎么写命令行GUI,虽然写了十几行而已,但是已经完爆很多 REPL 了。

推荐指数: 4

演讲者:Amjith Ramanujam from Netflix

相关项目:myclipgclifish-shellbpythonprompt_toolkit

Youtube:Amjith Ramanujam Awesome Command Line Tools PyCon 2017

 先发这些吧,后面遇到好看的继续更,大家如果看到质量高的也欢迎推荐。2018年貌似有170多个视频,全部看完要好久,一起看的话大家可以互通质量,节省时间啊。