今年8月得知了Heroku将要在今年11月彻底下线免费服务,我只有少量服务放在Heroku上,所以没有太在意,直到10月感觉到不得不开始做迁移相关的工作了,于是开始着手准备,并有了这篇文章。
这篇文章纯属是事后记录的一些流水账,可能缺少截图和代码片段,全当是作为一个故事看,可能不具备参考性。
为什么选择PaaS服务
从很久以前开始接触“虚拟主机”这个行业开始,我经历了虚拟主机、VPS、云主机、PaaS、容器等时代——从最早我不需要有任何运维成本的虚拟主机,需要完全自己运维的VPS主机,又回归到不太需要自己运维的PaaS服务,就像兜了一个圈。
PaaS全名平台即服务(Platform as a Service),意思是云平台提供了一套部署“框架”,可以将你编写的代码通过它的方式一键部署在云上,并且给你提供网络、存储、日志等常用的服务,解决了开发人员的运维问题,我们只需要写好代码提交,后期所有的自动化部署、发布、运维、扩展、备份等逻辑都可以让云平台帮我完成。
其实容器与k8s的思想也是脱胎于此,现在看来,容器就是将这样的需求标准化成为了一套规范。相对来说,容器的可定制化程度更高,也算是PaaS服务在2015年以后的逐步进化。
其实我一直认为,早期的虚拟主机发展到后面就是PaaS,PaaS继续发展就是现在的容器即服务(CaaS)。所以,本文所谓的PaaS的概念,并不局限在一两个名词之中,而是泛指这一类基于某种特定的框架来自动化部署的平台。
早期著名的PaaS平台有谷歌的GAE、新浪的SAE、Heroku等,PaaS这个概念能很快打入开发群体之中的关键原因之一是免费。在2015年左右,正是GAE、SAE等平台最火的时候, 我记得曾经有不少教程教大家如何部署应用到GAE,甚至有的翻墙工具也直接部署在GAE上。
发展到今天,新浪的SAE早已半入土,谷歌Cloud SAE因为高昂的学习成本与产品设计,也被我抛弃了。
我记得之前部署过服务在GAE上,但因为用错了环境导致没有满足Free Tier资格仍然被扣费。其后台管理与付费逻辑十分难懂,在账单出来前你很难知道自己在用的服务价格,我开始抵触这类收费标准不明确的后付费产品,就怕哪天早上起床突然发现房子被人收走了。
对于个人开发者来说,相对比较友好的平台是Heroku,它提供了每个月550到1000小时的免费运行时间,同时还有免费的PostgreSQL和Redis服务,搭配我常用的Django环境是绝配。
对于一个全时间段运行的应用来说,一个月需要跑24 x 30 = 720个小时,免费额度完全没有问题。但免费套餐最大的缺点就是在不活跃30分钟后会被自动停止,等下次有人请求的时候会再重启启动,这会导致这个请求消耗更多时间。
免费套餐非常适合部署一些演示站点、不活跃的站点等,我在2018年做Code-Breaking的时候将官网部署在了Heroku:
这一部署就是4年,直到2022年10月我将其迁移到fly.io,它都静静地运行在那里,无需我任何的管理,也没有任何错误。这确实满足了我当时选择Heroku的所有需求——对于一个短期内使用的临时性站点,我要做的就是尽可能减少后期的管理成本,只要能一直运行就行。
想一下,这可能是存活时间最久的CTF站点之一了。
当然,我前文也说了,Heroku在8月宣布要下线免费套餐。这可能是PaaS服务最大的风险,来自于平台政策的变化,而Heroku最便宜的套餐也是一小时0.01美刀,合计7刀一个月,还不算PostgreSQL和Redis数据库的费用。
为什么选择fly.io
巧的是前段时间看到一个推特博文介绍了一个平台叫fly.io:
我了解了一下fly,这是一家来自美国的初创公司。其提供的免费服务如下:
- 3个1核256MB内存的环境,用于运行应用
- 3G的Volume,可以用于部署数据库等
- 160GB每月的流量
其实即使不考虑免费套餐,其定价也比较亲民,不用担心免费额度用超了以后的价格问题:
于是我就决定将code-breaking迁移到fly.io上,作为Heroku的替代。
迁移代码到fly.io
fly.io由于诞生在容器时代,所以本身就是基于容器的,有额外学习成本。
先安装其客户端flyctl并进行认证,然后来到code-breaking项目根目录,执行flyctl launch
。此时,flyctl会根据当前项目的结构识别出待部署应用类型,比如我的是Django:
按照指引完成配置,此时会创建Web应用和一个数据库应用(PostgreSQL),并生成两个文件:
- Dockerfile
- fly.toml
其中Dockerfile就是将会运行这个应用的镜像模板,里面已经简单帮我实现了大部分部署Django的代码,在基础上稍稍改动就可以直接使用。
fly.toml是fly.io的配置文件,没有特殊的需求的情况下不用修改。
然后我执行flyctl deploy
部署。默认情况下,flyctl会将当前项目发送到云端,在云端的一个虚拟机里进行镜像编译部署。不过,在成功编译几次后我就无法使用远程编译了,执行flyctl deploy
无法连接上虚拟机,会出现“Failed to start remote builder heartbeat”的错误,使用flyctl deploy --local-only
改成本地编译可以解决这个问题。
部署结束后,fly.io会提供一个二级域名,通过code-breaking.fly.dev即可访问刚才部署的应用。
当然,这只是第一步工作,后面我还需要解决域名和持久化数据的问题:
- 静态资源的部署
- 数据库的迁移
- 域名与证书绑定
静态资源的部署
初始化时生成的fly.toml文件中有关于volume的配置:
[[statics]]
guest_path = "/app/public"
url_prefix = "/static/"
意思就是/app/public
这个目录下的文件将会作为静态资源,在Web中的URL前缀是/static/
。所以,我只需修改Django配置即可:
STATIC_URL = '/static/'
STATIC_ROOT = '/app/public'
数据库的迁移
数据库需要从Heroku迁移到fly.io,我选择使用Navicat手工来做这件事,首先就是连接Heroku和fly.io的PostgreSQL数据库。
Heroku的数据库是对公网开放的,我们只需要拿到它的地址与账号密码,执行如下命令:
heroku pg:credentials -a [appname]
此时会输出一个数据库连接URL,其中会包含地址、端口和账号密码。
而fly.io的数据库是一个内网地址,我需要使用flyctl自带的proxy功能将其转发到本地再进行连接。在创建数据库的时候flyctl会打印其内网地址和账号密码,然后通过proxy命令来将端口转发出来:
flyctl proxy 5432:5432 code-breaking-db.internal -a code-breaking-db
再使用Navicat直接连接本地的5432端口即可,后续数据迁移操作,直接在Navicat里进行。
域名与证书绑定
默认情况下,部署完成的应用将会分配一个二级域名,但我明显需要绑定自己的域名。方法是来到官网后台Certificates页面,点击“Add certificate”,此时它会要求你添加三个解析:
其实这个地方的逻辑非常不清晰,明明是绑定域名,但这个功能却叫“Certificates”,以至于让我在这一块浪费了很多时间。
fly.io给我们的IP实际上是一个共享IP,是通过HTTPS证书中的hostname或HTTP头里的Host来确认访问者要访问的主机,我猜这也是为什么这个功能叫“Certificates”的原因,在不添加证书的情况下我们无法正确通过自己的域名访问到应用。
所以,来到Cloudflare中,添加好这几个解析记录,其中_acme-challenge
最好是DNS Only:
然后回到“Certificates”页面点击check again,验证成功后即绑定好了域名。
此时我们再通过Code-Breaking就正常访问到了Code-Breaking的官网,全部的部署完成。
体验与感受
完整部署下来,可以明显感觉到fly.io的成熟程度远远落后于heroku,其flyctl客户端仍然存在一些Bug,文档也不齐全。虽说我轻描淡写地描述了我整个部署的过程,但实际上其中踩坑无数,甚至远程编译的Bug在触发后就再也没解决过,官方社区也多是问题鲜有靠谱答案。
就功能上来看,fly.io也比较少,只能说刚好满足我现在的需要。
其优点就是具有免费的额度,价格也便宜,对于个人开发者来说也很友好。而且相比于heroku来说,fly.io仅是一个初创公司,仍具有很大的想象力,相信现有的这些不足也会慢慢解决与完善。
最后,在写文的时候看到fly.io的招聘页面,这家公司Base在芝加哥但大部分员工属于远程办公。我在招聘列表里也看到了安全工程师的岗位:https://fly.io/jobs/security-engineer/,$120k to $200k USD 年薪对于远程工作来讲还是很有吸引力的,有兴趣的同学可以关注一下。