最近在学习 Go ,而常用的内部 PaaS 平台正好也支持 Go 以及相关 Web 框架。一套 PaaS 系统支持多种语言,其中就离不开 buildpack 机制。虽然 PaaS 平台不断在升级,但是 buildpack 机制却一直保留。本文主要是一些 buildpack 资料的整理和实践。
1. PaaS 如何部署应用
无论是基于原生 Docker,还是 Kubernetes 的 PaaS 平台,都只解决了资源隔离的问题,并没有规约 App 的运行方式。一个 App 能运行起来,除了开发人员关注的代码,还有运维人员关注的配置信息。这些配置信息包括:
- 账户配置,MySQL、Redis 账户密码等。
- 服务配置,服务名、端口号、第三方服务地址,如 Ceph 等。
- 运行时依赖,Python 2、Python 3、Golang、Nodejs 等,还包括相关依赖包,如,gunicorn 等。
实际上除了提供运行时,在运行 App 之前,PaaS 还需要执行一系列脚本,完成一些必要的运行环境构建。通常,这些脚本被称之为 buildpack,放在一个公用的 Git 仓库管理。
2. buildpack
buildpack 是 Heroku 的部署机制。Heroku 是一个支持多语言的 PaaS 平台,支持 Ruby、Java、Node.js、Scala、Clojure、Python、PHP、Perl 等语言。在 Github 上,Heroku 开源了 buildpack 。大家可以根据项目即拿即用,同时,也可以定制化开发自己的 buildpack。
CloudFoundry 和 Heroku 的 buildpack 是兼容的,既可以部署在 Heroku 上,也可以部署在 CloudFoundry 上。很多的 PaaS 平台,都会使用到 buildpack 部署应用。buildpack 已经成为 PaaS 应用部署的事实标准。
3. Cloud Foundry 部署原理
Cloud Foundry 是业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务。Cloud Foundry 对之后的 PaaS 平台建设思路,具有非常重要的影响。很多团队,在构建 PaaS 时,会参考 Cloud Foundry 。
下图是 Cloud Foundry 部署 App 的完整流程。
- 用户使用 CF PUSH 命令上传应用
- CLI 告知 CCNG 创建一个应用
- CCNG 在数据库中,新增应用的记录。例如应用名称,哪一个 buildpack 等
- CLI 上传程序
- CCNG 将程序存起来
- CLI 启动应用
- 由于应用尚未部署,所以 CCNG 找一台 DEA(Droplet Execution Agent),在该 DEA 内执行 buildpack 来部署应用
- DEA 输出运行 buildpack 的信息
- buildpack 执行完毕,输出一个 DropLet文件(编译打包的结果),DEA 将该文件存起来
- DEA 将打包情况汇报给 CCNG
- CCNG 选择一个 DEA 来部署应用
- 应用在 DEA 中运行,运行结果输出到 CCNG
可以看到,buildpack 和 App 都是在一样的环境(DEA)中执行的。buildpack 非常简洁,只需要三个脚本:
- bin/detect,探测 buildpack 是否支持此应用
- bin/compile,编译脚本
- bin/release,打包脚本
4. 利用 Heroku buildpack 创建应用
Cloud Foundry 是私有云 PaaS 解决方案,Heroku 是公有云 PaaS 解决方案。Cloud Foundry 通过发展集成、培训等合作伙伴,构建了非常好的生态。Heroku 在公网上提供收费的应用部署服务,但提供一定的免费额度。
这里为了简洁,没有搭建 Cloud Foundry,而是直接使用 Heroku 提供的部署服务。
4.1 Heroku 准备
第一步,在 https://www.heroku.com/ 注册 Heroku 账户。
第二步,安装 Heroku Toolbelt 客户端。
Toolbelt 是 Heroku 的命令行工具,允许通过命令行的方式来管理 Heroku 应用,下载地址。
4.2 Django 项目准备
第一步,创建一个 Django 项目,用于部署测试。
1
2
3
| django-admin startproject herokupro
ls herokupro/
herokupro manage.py
|
第二步,根据 buildpack 的约定,在项目中新建两个文件。
- requirements.txt,App 所依赖的第三方包
- Procfile,App 启动时执行的命令
1
2
3
4
5
6
| cat herokupro/requirements.txt
django==1.8.3
gunicorn==19.7.1
whitenoise==3.3.0
cat herokupro/Procfile
web: gunicorn herokupro.wsgi
|
第三步,修改 Django 工程,以适应 gunicorn 部署
在 herokupro/wsgi.py 新增:
1
2
3
| from whitenoise.django import DjangoWhiteNoise
application = DjangoWhiteNoise(application)
|
在 herokupro/settings.py 新增
1
2
3
4
5
6
7
| BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_ROOT = os.path.join(BASE_DIR, 'assets')
STATIC_URL = '/static/'
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
|
新增 static 文件夹
1
| touch herokupro/static/keep
|
第四步(非必需),新建 runtime.txt 文件,指定 Python 版本。
在 heroku-buildpack-python 的 Github 页面,可以找到可用的 Python 版本.
1
2
| cat herokupro/runtime.txt
python-3.7.6
|
最终目录结构:
1
2
| ls herokupro/
Procfile herokupro manage.py requirements.txt runtime.txt static
|
4.3 创建 Heroku 应用
第一步,登陆 Heroku。
heroku login
第二步,创建 App 。
1
2
3
| heroku create heroku-django-app-hello
Creating ⬢ heroku-django-app-hello... done
https://heroku-django-app-hello.herokuapp.com/ | https://git.heroku.com/heroku-django-app-hello.git
|
Heroku 会给 App 分配两个地址:
第三步,提交代码并构建、部署应用。
1
2
3
4
| git init
git remote add origin https://git.heroku.com/heroku-django-app-hello.git
git add .
git commit -m "init"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| git push -u origin master
Enumerating objects: 22, done.
Counting objects: 100% (22/22), done.
Delta compression using up to 4 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (22/22), 5.23 KiB | 595.00 KiB/s, done.
Total 22 (delta 5), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
remote: -----> Installing python-3.6.7
remote: -----> Installing pip
remote: -----> Installing SQLite3
remote: -----> Installing requirements with pip
remote: Collecting django==1.8.3 (from -r /tmp/build_a54c15c00f10fa5ae49c62b5f9169306/requirements.txt (line 1))
remote: Downloading https://files.pythonhosted.org/packages/a3/e1/0f3c17b1caa559ba69513ff72e250377c268d5bd3e8ad2b22809c7e2e907/Django-1.8.3-py2.py3-none-any.whl (6.2MB)
remote: Installing collected packages: django
remote: Successfully installed django-1.8.3
remote:
remote: -----> python manage.py collectstatic --noinput
remote: 63 static files copied to '/tmp/build_a54c15c00f10fa5ae49c62b5f9169306/staticfiles'.
remote:
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 48.8M
remote: -----> Launching...
remote: Released v5
remote: https://heroku-django-app-hello.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/heroku-django-app-hello.git
* [new branch] master -> master
|
访问 Heroku 提供的 App 地址,https://heroku-django-app-hello.herokuapp.com/ :
在部署时,可能不会一次性成功。可以通过命令查看日志调试:
1
| heroku logs --tail --app heroku-django-app-hello
|
5. 参考链接