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多个视频,全部看完要好久,一起看的话大家可以互通质量,节省时间啊。

 

分布式定时任务的重复执行问题

定时执行某项任务是非常常见的一个需求,简单的 crontabl 就可以完成。我们最近的一个 Django 项目中用了 Apscheduler 来调度定时任务,遇到了一些问题。

首先,Apscheduler 定位是一个依赖于你的应用的任务调度库,非常轻巧:

That APScheduler is not a daemon or service itself, nor does it come with any command line tools. It is primarily meant to be run inside existing applications.

Apscheduler 是跟随你的应用来启动的,没有持久化,没有命令行操作任务的工作,运行在应用的内部(这其中很挫的一个问题是,每次运行 Django manage.py 都会启动这玩意,甚至跑 migrate 都会启动,不过这很可能是我们的用法有问题)。如果一些小巧的项目用起来倒是不错,但是如果需要对任务进行管理和控制,就有很多限制了。

Apscheduler 分布式的问题

我们遇到的问题是:Gunicorn 部署 Django 的时候会起来 4 个进程实例,我们 Django 应用内的 Apscheduler 也会跟着起来 4 个,那么遇到定时制定任务的时候,每个任务都会执行 4 次。这就很蛋疼了。

我们先是用了一个临时的解决方案,就是让任务的执行有幂等性。任务是拉去一部分数据到 MySQL 数据库中,那么就在数据库建立一些 Uniqe 索引,后面重复执行的任务放不进去。这样的做法显然会浪费一些资源,但是从结果上看是可以保证正确的,数据肯定不会被重复存储。缺点是每个任务都要考虑到同步的问题,或是类似的唯一索引,或是加锁。

然后又有了一种解决方案,manage.py 在运行的时候使用一个文件锁,这样无论有多少个 Django 在运行,都只会有一个 Apscheduler 存在。这样这个问题就可以被忽略了,大家可以再污染实际的定时任务逻辑了。可这种锁这对本地的多个 Django 有效,如果分布式部署 Django ,那么问题又来了,每一个机器的多个 Django 中都会存在一个 Apscheduler。对于解决单机多个 Apscheduler 的方案中,还有一个比较灵巧的,但是稍微复杂一些。原理是让 Gunicorn 先载入 app 然后再 fork 进程,载入 app 的时候开启一个 scheduler 线程,fork 的时候就不会再开线程了。这样每一个 Django 中都有 worker 可以用(jobstore 必须不能使用内存,要用外部可共享的),但是只有一个 scheduler 存在)。具体的步骤如下:

  1. Gunicorn 启动的命令带上 --preload 参数,先载入 app 再 fork 进程,这样下面的代码只会在 master 执行一次:
  2. jobstore 选择非 memory 的选项。否则只有一个 worker 在跑(和 scheduler 在同一个 Django 中的那个),使用外部的 jobstore 就可以让所有的 worker 去跑 scheduler 了
  3. Scheduler 选择 BackgroundScheduler 。因为 BackgroundScheduler 的实现是,调用 start() 会开一个线程来调度。这样我们在 master 进程 load app 的时候会调用一次,Gunicorn fork 出来不会再调用,所以就一直有一个线程存在。

Celery 的解决方案

Celery 作为一个分布式的任务队列,解决方案就比较简单了:直接就只有一个 scheduler 存在,它调度任务,多个 worker 来执行。

You have to ensure only a single scheduler is running for a schedule at a time, otherwise you’d end up with duplicate tasks. Using a centralized approach means the schedule doesn’t have to be synchronized, and the service can operate without using locks.

既然只有一个 scheduler ,那么周期任务既不需要同步,也不需要加锁。缺点就是一个单点。(部署 scheduler 的地方需要提供一个可读写的文件让 scheduler 保存最后一次执行的任务,这个 scheduler 其实是有状态的。)

对于可用性要求不是特别高、周期性任务实时性要求不是特别高的项目来说,其实也够用了。而且我认为 celery 应该会实现类似调度失败重试的机制,不至于和 crontab 那么简陋。

其实我设想的任务调度应该是多个 scheduler 对于某任务、某时间生成一个唯一 token,然后拿 token 去队列里放任务。应该可以解决分布式的问题,即高可用又是幂等的。

 

其余的一些方案:

  1. http://elasticjob.io/
 

健康茁壮成长的Python项目需要什么?

Python语言以简单易懂,上手难度低著称,遗憾的是,很多人保持着轻松上手后的水平,写了很多“面条式的”代码(我也写过很多)。一个项目如果想要写好,以可持续的方式发展,就不能和写练习的脚本一样,除了编程功底,一些保证项目质量的工具也必不可少。不然公司就像是个小作坊一样,以野路子的方式开发,浪费大量的人力,甚至把刚接触 Python 的新人带歪了。本文总结通常一个可持续交付的 Python 项目应该如何开发。这方便的知识也可以通过逛 Github,看一下一些经典的项目(例如 requests,django,sentry)是如何解决这些问题的。

1. 单元测试

王垠曾经说过:

我喜欢把编程比喻成开赛车,而测试就是放在路边用来防撞的轮胎护栏……

在没有测试的项目中写代码,就感觉在没有护栏的跑道上开车,心里很没有安全感。尤其是代码需要重构的时候。要么你要浪费时间每次都自己手动跑一下程序测试一下(测试也是自动化的一种),要么就要自信,相信自己的改动是绝对正确的。

Github 上面的代码就很方便了,有很多公开的服务可以使用,比如 travis,codecov.io 可以用。

私有项目就有点复杂了,需要自己搭建测试环境。之前用过 bitbucket,用我们自己开发的 CI badwolf 。这个是基于 docker 的,项目中有一个 Dockerfile,badwolf 负责以下一些工作:

  1. 对 PR 和 master 的代码在 Docker 中进行单元测试
  2. 对评论 “ci rebuild” 的时候重新从 Dockerfile build 一个image
  3. 3个人 approve PR 之后自动将 PR 合并

CI 要面对一个问题:测试环境和执行测试的命令如何分离?

Travis 是每次都会执行所有的命令,每次推送都会 install 依赖,运行测试。这样的好处是配置简单,配置环境和跑测试用一个文件就够了,清晰明了。缺点是每次都重新安装,速度慢,浪费资源。

badwolf 是只有 rebuild 的时候才会重新构建镜像,否则一直用同一个镜像来测试。这样的缺点是要制定 Dockerfile,并且要处理好环境,每次测试不能有副作用。优点是速度快。

现在我们用的是 Gitlab。Gitlab 有了 Runner 的概念,其实是更适合大团队一起。只要配置好了 shared runner,所有的项目都可以用,runner 跑完了一个去跑下一个,FIFO很公平,也节省了资源。缺点是,我觉得 Gitlab 是配置CI最复杂的,需要自己部署好 Docker 仓库,然后制定 .gitlab-ci.yml文件。

Gitlab其实比较灵活,你可以在Docker提供一个最基本的 Liunx 环境,在 yml 配置文件中写 install 的东西,每次 CI 都去安装。也可以将环境打包在后者,每次需要更新依赖就要重新 build image,但是 push 代码的时候速度快很多。个人偏爱后者。

2. 代码覆盖率

有了单元测试,如何说服同事保证写测试呢?靠价值观肯定是不行的。

对开源项目来说,有很棒的 codecov 可以用,能生成所有代码每一行跑的次数,覆盖率,changes 带来的覆盖率改变,这些都是最基本的功能了,还有什么 Github 机器人,pipeline(如果 PR 下降了覆盖率,或者新加的代码没有覆盖就不能 merge)更是酷炫。

但是对于私有项目来说,要么给钱,要么自己搞。badwolf 这种自己写的CI比较土,我们是直接在项目的最后打印出来 coverage.py 的输出结果,虽然土,但是有用。覆盖率的保证也需要我们的同事在对不好的 PR 下面留言: “testcase please!”

Gitlab 其实也好不了哪里去,除了打在 log 的最后,在它的 project settings 里面还有一个功能,写一个正则表达式,将覆盖率匹配出来显示。还是土了点,至少有点用吧。另外也可以用 coverage.py 生成的 html 报告推送到 pages 上面,这里有一篇教程,不过那样你又要去维护一个 pages 服务器(还要注意不对外网开放)。

3. 代码质量检查

PEP8,flake8,pylint 傻傻分不清楚?看这里吧

这种东西,一开始会挺痛苦的,后面对团队收益很多。

4. 保护master

组织任何人向 master push 代码。比较理想的情况是,develop 实时部署到 staging 情况,定期发布到 master。

5. issue追踪系统

类似 Github 的 issue,记录 bug 等未解决的问题,现在除了 bitbucket 丑的基本不能用,其余很多都不错的。

6. code review

对于开源项目或者团队合作来说,review 是非常重要的。

很多人觉得 review 太浪费时间了,业务这么紧,没空搞。但是我觉得这是去了解别人工作的一个好机会,并且互相学习,进步特别快。尤其是对一个刚毕业的同学来说,review几乎是快速成长的一条捷径。review由于合并速度,占用大家的时间等原因,慢是慢了一些。但是节省了很多潜在的时间,利远大于弊。

暂时想到这么多,里面每一个项目如果细开去说还有很多细节问题。甚至每一个点都够成立一个技术公司了。

一定要做正确的事情,不要被业务逼得去走歪路。

 

Git 在不同的项目使用不同的author

安装好 Git 的时候,每个人都会设置全局的账户:

公司的 Git 账户和个人的 Git 账户不一样,可以在项目中设置此项目的 Author :

这样私人账户每次都要设置,可以设置一条 Git 的 alias ,这样每次需要将这个仓库的 author 设置成私人账户,只要敲一下 git private 就可以了。

其实 git config --global 命令是修改的一个文件,$HOME/.gitconfig 例如我的文件如下:

同之前提到的 myrc 项目一样,gitconfig 这个文件我也是用 git 来追踪的,去一个新环境只要安装好这个项目就可以回到自己熟悉的 git 了。