在 Fly.io 部署 Rails 应用
这几天我把 GeekNote 的部署环境迁移到了 Fly.io,我觉得这是一个值得推荐的平台,所以写这篇日志介绍一下。
为什么选择 Fly.io
Fly.io 是一个基于容器的部署平台,致力于在全球各个区域提供贴近最终用户的部署环境。
跟传统的基于 VM 的云服务相比,基于容器的的部署平台不需要管理底层操作系统,能确保应用在开发和生产环境运行在同一环境,并且可以轻松缩放。
跟其他容器部署平台相比(例如 Heroku),Fly.io 提供了更多的部署区域(例如中国香港),提供数据持久层(Volume),自带虚拟私有网络(Heroku 企业版内容),更慷慨的免费政策,可以说是在很多层面是一个更好的 Heroku。
由于 Fly.io 的容器的扩展非常灵活,按秒计价,所以它在价格上也有不少优势。GeekNote 之前部署在某云的香港机房,每月花费 400 元。由于某云是以月租方式购买服务器,即使资源有很多闲置我也没有动力调整配置。在迁移到 Fly.io 后,根据情况降低了服务器配置,再扣去 Fly.io 的免费额度,每个月预计花费 50 元左右。
如果香港机房可以满足你的应用需求,那么 Fly.io 会是一个不错的选择。
Rails 部署教程
Fly.io 自身的文档已经提供了 Ruby 应用部署和 Docker 应用部署。但官方文档没有完整介绍部署一个 Rails 应用的过程,在此记录和分享一下我的经验。
容器化
为了部署在 fly.io,需要先把 Rails 应用容器化。虽然 Fly.io 也提供像 Heroku buildpack 那样基于代码的部署方式,但是 buildpack 不一定满足你的需求,使用容器才是最好的利用容器部署平台的方式。
关于如何将 Rails 应用容器化,可以看我之前写的系列文章《Rails on Docker》。
💡 实际上,Fly.io 并不用 docker 运行应用,而是用 Firecracker VM,以实现更好的环境隔离。Docker 镜像只是作为一个标准化的镜像压缩包。详情请看Docker without Docker。
这里假设我们有一个 Rails 应用,项目目录下已经有 Dockerfile 文件,用 docker 编译该文件会得到这个应用的容器镜像。
/myapp
| ...
\ Dockerfile
这个应用需要四个进程或服务:Web,Postgres,Redis,Worker,其中 Web 和 Worker 都使用 Rails 应用的镜像,只是运行的命令不同。
在 Fly.io 中,进程或服务都以“app”为单位进行管理,我们需要创建 4 个 app。
安装 flyctl
Fly.io 的大多数操作需要使用命令行,所以先要安装命令行工具 flyctl。
安装方式可以参考官方文档:https://fly.io/docs/getting-started/installing-flyctl/ 。
对于 Mac 来说,可以通过 brew 安装:
$ brew install superfly/tap/flyctl
注册 fly.io
在 Fly.io 创建应用之前先要注册,如果之前没有注册可以通过命令行执行:
$ flyctl auth signup
它会打开浏览器进入注册流程,注册完毕后会自己跳回到命令行通过验证。
如果之前已经注册过了,可以执行:
$ flyctl auth login
要注意的是为了避免免费资源被滥用,fly.io 要求注册之后填写信用卡完成验证。当然,这样也便于转化付费用户。
注册完成后,我们就可以正式开始部署应用。
Web
创建应用
首先来创建 web app,在项目目录下执行命令:
$ flyctl launch
命令行会提示几个问题:
Creating app in /path/to/project
Scanning source code
Detected a Dockerfile app
? App Name (leave blank to use an auto-generated name):
? Select organization: Name (personal)
? Select region: hkg (Hong Kong)
Created app app-slug in organization personal
Wrote config file fly.toml
? Would you like to deploy now? No
其中 App Name 需要是全局唯一的,而且之后不能更改,同时会作为 app 的子域名,推荐用 app-name
这样的格式。如果只是尝试一下,也可以直接回车自动生成。其他选项按照需要选择即可。最后一步问是否现在部署,选择 No,因为在启动前还需要修改配置,设置环境变量,创建数据库等。
运行 launch
之后会生成一个 fly.toml
的配置文件。由于我们后面还要创建 app,为了不影响之后的操作,将它改名为 fly/web.toml
。
💡 对于 flyctl,如果当前目录下有 fly.toml,它会读取里面的 app-name 作为默认 app-name;如果没有,则需要用
--app APP_NAME
参数指定要操作的 app。
fly/web.toml
里面需要修改一些内容以符合 Rails 应用的需求。修改的部分如下:
[experimental]
# 在镜像内要执行的命令
cmd = ["bin/rails", "server"]
[[services]]
# Rails 进程监听的端口
internal_port = 3000
[deploy]
# 每次部署后执行数据迁移
release_command = "bin/rails db:migrate"
其他配置的含义可以看官方文档:https://fly.io/docs/reference/configuration/
设置环境变量和 secrets
Rails 运行过程需要设置一些环境变量,例如 SECRET_KEY_BASE
等。这时候可以使用 Fly.io 的 secrets 机制,设置的 secrets 变量会自动转换为容器内的环境变量。用以下命令设置 secrets:
$ flyctl secrets set NAME=VALUE NAME2=VALUE2 --app WEB_APP_NAME
每次修改 secrets 后会触发一次部署,为了节省时间可以一次设置多个变量。
我们也可以用一行一个变量的方式将变量写在文件里,然后用从文件读取的方式导入变量:
$ flyctl secrets import --app WEB_APP_NAME < .env.production
设置完后先放着,接下来创建数据库。
Postgres
现在来创建 Postgres app。命令行输入:
$ flyctl pg create
跟创建 web app 时一样,flyctl 会询问几个问题:
flyctl pg create
? App Name:
? Select organization: Name (personal)
? Select region: hkg (Hong Kong)
For pricing information visit: https://fly.io/docs/about/pricing/#postgresql-clusters
? Select configuration: Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
Creating postgres cluster in organization personal
……
跟创建 web app 不同的是,询问完后会立即执行部署,毕竟也没什么需要设置的。
部署完毕后命令行会输出 postgres 的管理员账号密码,可以就找个安全的地方记录一下。
下一步是把数据库挂载到 web app,输入以下命令:
$ flyctl pg attach --postgres-app PG_APP_NAME --app WEB_APP_NAME
执行操作后,fly 会在 postgres 里面创建一个 database 和有对应读写权限的 dbuser,然后自动把连接信息设置到 web app 的环境变量 DATABASE_URL 内,所以这个变量不需要自己设置。
更多 postgres 的管理信息,可以查看文档 https://fly.io/docs/reference/postgres/ 。
Redis
现在来创建 Redis app。对于 Redis,fly 没有提供像 Postgres 那么自动化的管理脚本,需要自己创建应用和设置持久化。
运行以下命令:
$ flyctl launch --image flyio/redis:6.2.6
这里指定了镜像 flyio/redis:6.2.6
作为部署镜像,而不是从 Dockerfile 构建。
创建应用后,flyctl 会创建一个新的 fly.toml 文件,为了不影响之后的操作,将它改名为 fly/redis.toml
。
里面的 [[services]]
相关的内容是不需要的,将它删除,因为这个应用不需要对外提供服务。
为了让 redis 数据持久化,需要给它挂载一个 volume,用以下命令创建 volume:
$ flyctl volumes create redis_data --size 1 --app REDIS_APP_NAME
然后在 fly.redis.toml 内添加以下内容:
[[mounts]]
destination = "/data"
source = "redis_data"
加上可选的系统检测端口:
[metrics]
port = 9091
path = "/metrics"
设置 Redis 密码:
$ flyctl secrets set REDIS_PASSWORD=MY_REDIS_PASSWORD --app REDIS_APP_NAME
部署 Redis 应用:
$ flyctl deploy -c fly/redis.toml
将连接参数设置到 web app 的环境变量:
$ flyctl secrets set REDIS_URL=redis://:MY_REDIS_PASSWORD@REDIS_APP_NAME.internal:6379/0
现在部署 Redis 应用:
$ flyctl deploy -c fly/redis.toml
部署 Web 应用
准备工作已经完成,现在可以部署 Web 应用,输入以下命令
$ flyctl deploy -c fly/web.toml
如果部署顺利,之后就可以用以下命令打开应用:
$ flyctl open -a WEB_APP_NAME
你也可以直接在浏览器打开 https://WEB_APP_NAME.fly.dev
。
Worker
不要忘了还有一个 Worker app,它跟 Web app 使用同一个镜像,只是启动方式不同。
再新建一个 app:
$ flyctl launch
把新生成的 fly.toml
改名为 fly/worker.toml
,修改内容,加上这一行:
[experimental]
cmd = ["bundle", "exec", "sidekiq"]
同时删掉 [[services]]
的内容,因为不需要对外服务。
接着设置环境变量:
$ flyctl secrets set SECRET_KEY_BASE=... DATABASE_URL=... REDIS_URL=.. --app WORKER_APP_NAME
💡 由于重复输入 ENV 很麻烦,我建议用前面导入 .env 文件的方式设置。但要注意不要把 .env 提交到 git 里。
然后部署应用:
$ flyctl deploy -c fly/worker.toml
其他
现在已经完成了部署一个 Rails 应用的过程。还有一些问题可能要处理,例如:
- 自定义域名:https://fly.io/docs/app-guides/custom-domains-with-fly/
- 通过 GitHub Action 实现持续集成:https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
- 缩放和自动缩放: https://fly.io/docs/reference/scaling/
这些可以通过官方文档再深入了解,由于文章篇幅在此不再叙述。
Fly.io 还是个年轻的公司,团队很小,CEO 都还在做客服。不过这个平台的体验很接近我理想中的容器部署平台,别的容器部署服务依然很烂,Fly.io 让我眼前一亮。我把网站迁移到 Fly.io,也是想见证它的成长。如果这让你也产生了兴趣,不妨自己尝试一下。