如果你是使用Django,那基本上可以认为你放弃了前后端完全分离的开发方式,否则Django自带的模板引擎和路由对你没有任何用处,你应当使用更轻量级的类似Flask+React等来做开发。

在选择框架时,任何时候都应当考虑使用一个大而全的框架,再加一些小的框架,而不要使用很多小框架。

Django的文档非常详细,一定要顺着Documentation、Tutorials、Topic guides、How-to guides认真全部看过一遍。我下面说的会更偏工程化,尽量会遵循最佳实践,使得效率更高。如果有更好的实现,请告诉我。

对于开发,还可以看看, 软件开发最佳实践:代码安全我们需要打什么疫苗 有些内容已经旧了过时了。

使用Ansible做自动化部署

自动化部署的优点

开发人员可以设置自动化,以便跨环境置备、部署和管理计算基础架构。从一个通用框架可靠一致地部署多层应用,配置所需的服务。

通过实现流程自动化,在测试和生产环境之间更快地移动软件。这样就可以在整个软件交付周期实现可重复的可靠部署,从而支持 DevOps 并管理 CI/CD 管道。

使用 Ansible 自动化平台来自动化执行基础架构置备与编排、系统更新和补丁安装、软件安装。通过 Ansible Playbook 创建并运行可复用的基础架构即代码(IaC),从而自动执行更广泛的工作流,比如将应用完全部署到生产环境。获取实时任务状态更新,并且监控和解决基础架构中的问题。

借助安全漏洞修复、策略和治理以及内容管理工具提高运维效率,从而进一步推动自动化。

你需要设置防火墙,安装Apache,mod-wsgi,增加站点,加固系统和所有组件。安装MySQL,加用户和数据库,定期更新,每次都一条条敲命令的方法没错,而使用自动化部署可以提高效率,减少开发和生产之间产生偏差的风险,并以IaC的理念管理基础设施。

Ansible自身的安装不使用操作系统自带的包,使用pip安装最新版。这里会有一个死循环,虽然推荐一个项目只跑在一台虚拟机里面,实际上你不需要去隔离Python环境,但是还是建议每个项目都应当有一个venv,减少组件的污染,venv是使用Ansible建立的,而Ansible又需要一开始就可用,所以这里用了3个Python环境,一个是操作系统级的;一个是用户级的,只放Ansible、ansible-lint等IDE需要的组件;最后一个是venv,给项目使用。

所以使用如下命令安装Ansible,并且跑Ansible的时候不要source到项目的venv。

sudo apt install python3-pip
python3 -m pip install ansible --user
python3 -m pip install ansible-lint --user

编辑器的选择Visual Studio Code

编辑器一定是选择VSC,为了保证测试环境和正式环境完全一致,建议在虚拟机内做开发,并且使用Remote插件WSL或者SSH远程开发。把一些必须的插件加入Recommendation。

推荐好用的编辑器Visual Studio Code

源代码版本控制Git

Git通过在VSC安装gitlens插件管理。Git的Clone使用HTTPS并且不使用用户名和密码,只使用Project Access Tokens。通过

git config credential.helper store

将PAT保存在~/.git-credentials里,这样子可以确保即使密码泄露,也只会影响一个项目。正式部署的环境,可以给个权限较低的PAT,比如只读。

.gitignore不要自己写,从互联网下载别人已经写好的.gitignore模板。

各种Linting,基线核查

什么是Linting

在计算机科学中,lint是一种工具程序的名称,它用来标记源代码中,某些可疑的、不具结构性(可能造成bug)的段落。它是一种静态程序分析工具,最早适用于C语言,在UNIX平台上开发出来。后来它成为通用术语,可用于描述在任何一种计算机程序语言中,用来标记源代码中有疑义段落的工具。

Lint是一种静态程序代码分析工具,一般在开发的时候使用,并且在Peer Code Review之前。自动化代码检查和代码格式化可以让Code Review更加高效。Lint一般会包括很多规则,类似Python是运行时编译的语言,很多错误得等到运行时才会提示,使用了Lint工具,就可以让你在开发时就可以解决包括语法、变量名错误等问题。Lint一般也会提供代码风格检查,确保整个团队使用一致的代码风格。

总之你要用各种检查工具来检查你写出的每段代码,过的筛子越多越安全。

在整个Django项目里,你会遇到各种不同的文件类型,比如Markdown、Python、Django、Ansible、HTML、JavaScript等等,全部应当都有Linting,并且treat warnings as errors!

我一般使用的各类Linting包括:davidanson.vscode-markdownlint、redhat.ansible、ansible-lint、flake8、monosans.djlint、ESLint。VSC会将Linting的结果放在PROBLEMS里,确保PROBLESM没有任何一个错误,如果确定要忽略某个特定的rule,不应当全局忽略,而应使用行忽略模式。

各种format工具

代码格式化也非常重要,写代码不需要个性化,团队应选择一种代码风格并且通过格式化或者提交前检查来确保。还有其他一些辅助工具,比如Python的import顺序自动调整,我使用ms-python.isort来自动sort imports。HTML,使用monosans.djlint来格式化。

永远使用最新的组件

只使用包管理器来安装所有组件。

所有用到相关的组件都应当是最新版,操作系统打开自动更新,并且经常性更新组件到最新版。

pip使用safety、pip-check来检查所有pip组件的最新版和安全补丁。将整个项目需要使用到的pip组件写入requirements.txt,并且尽量不写版本号,requirements不使用pip freeze来生成,只写自己要用的组件,如果组件有依赖关系自动安装了别的组件让系统处理,保持requirements干净整洁。

pip升级的方法:

pip list -o --format columns | cut -d' ' -f1 | xargs -n1 pip install -U

JavaScript包有多个选择,可使用npm来管理。package.json类似requirements.txt,只写自己必须的组件,package-lock.json作为辅助,可不进入git。经常运行如下命名保证JavaScript组件均为最新版。

npm outdated
npm update

项目的目录结构

项目的总体目录应当在/var/www外面,比如就放入~/内。为了让www-data可以读取到~/的内容,可将www-data加入用户组或单独加权限,并且处理好SELinux相关的权限控制问题。

Django有静态资源,在开发时,静态资源是跟项目在一起的,在正式部署时,你可以将静态资源独立部署到另外一个服务器,也可以部署在同一台,如果是同一台,应当将静态资源通过collectstatic移动到settings.py设定的STATIC_ROOT内,这个目录一般在/var/www里。

上传文件资源也应当放在/var/www外面。

开发、测试和部署环境的区分

有些包只在开发时使用,比如Django Debug Toolbar,在正式环境是不需要的。有些配置参数根据环境会有变化,比如生产环境的DEBUG一定不能打开,所以需要隔离多个环境。

涉及到环境的分离技术手段有多个,一个是Ansible的,一个是Django的settings和requirements.txt,你可以建立一个目录,并且将不同的配置文件放入,然后通过传递环境参数来启用不同的,或者使用Ansible来控制,比如在Ansible的ini内,你可以指定哪台机器跑什么样的环境。

将密码放在Git目录外面。一个常见的问题是,很多人不小心把密码硬编码在源代码里,或者已经做了分离,但是不小心放到了Git内。任何时候都不能将密码放入Git内,否则删除非常麻烦。密码也可以交由Vault来管理。

不同环境同步的问题

经常我们必须将正式环境数据同步到测试环境,将同步操作规范化,脚本化。这里涉及到可能必须dump数据库,rsync上传文件。永远只从正式环境覆盖数据到测试环境,通过一些判断来确保这一点,以免在打开多个运维窗口时命令串了。为了安全,同步应当只从正式环境发起,让测试环境对正式环境开放防火墙端口。

使用mod_wsgi deamon mode部署

Apache2 Ansible模板片段。

WSGIDaemonProcess {{ project_name }} python-home={{ project_home_path }}/.venv python-path={{ project_home_path }}
WSGIProcessGroup {{ project_name }}

WSGIScriptAlias / {{ project_home_path }}/{{ project_name }}/wsgi.py

项目的路径不能硬编码在源代码里,默认项目生成的wsgi.py不需要去更改。

全局常量的设置

你可以在settings.py内设置常量并且在各个地方使用,settings内定义的常量系统会自动维护不同环境覆盖问题,但是这会导致一个问题是,你无法在编辑时得到检查,比如你常量名写错了,只有运行时才会提示错误。所以另外一个建议是自定义一个const.py,在里面放入常量。如果是有些需要动态更改的配置,可以使用constance模块在数据库内维护。

时间的存储

任何时候都应当使用UTC保存时间,并且在显示的时候根据时区需要显示不同的时间。Django自带了USE_TZ、USE_I18N、USE_L18N,全部应当设置为True。USE_TZ将在Django5开始默认值设置为True。

SQL语句优化

因为是使用ORM来写,所以你基本上不需要写任何SQL语句,但是你一定要知道底层生成的所有SQL语句,可以使用Django Debug Toolbar来查看。一个常见的问题是,有时候一个简单的页面,里面有个for循环,去获取类外键关联的数据,会导致实际上一个SQL语句可以解决,变成生成成百上千条SQL语句,导致页面加载速度非常慢,一般使用select_related、prefetch_related可以解决这个问题。

管理和调试技巧

admin目录路径设置成随机字符串进行隐藏并且限制IP访问。如果有必要,可以实现模拟任何用户的功能,但是需要非常谨慎评估模拟操作的发起限制。对于一些类似发送邮件、统一身份认证和一些耗时较长和依赖外部系统的操作,可以使用一些Mock。

项目进入维护阶段的定期维护

当项目稳定后,不再有大的开发后,也应当定期进行维护,大公司的坏习惯不能学。应当在测试环境定期升级所有组件,并且测试正确后,在正式环境实施。

只是要常用的操作,都应当脚本化,纳入Ansible管理,做到不需要思考,拷贝黏贴代码即可。这会花费一点时间,但是对于未来运维是非常省事的。一般一个项目要跟着一辈子。应当做到要更新操作系统、中间件、所以依赖包全部只跑一个脚本即可。在项目顶级目录里面新建一个目录叫tools,在里面放所有的脚本。

对于操作系统的大版本升级,应当是全新安装并且重新部署而不是原地升级。如果Django本身有大的breaking change,可考虑使用最新版生成骨架代码再将旧代码贴入的方式进行升级。