Flynn 平台实战指南:从 Git 部署、水平扩展到多进程应用的完整流程
Flynn 平台实战指南:从 Git 部署、水平扩展到多进程应用的完整流程
本文是一份以 Flynn 平台为背景的端到端实战指南,围绕 docs/content/basics.md 的核心脉络展开:从一个 Go 示例应用出发,完整演示如何在 Flynn 上完成创建应用、关联 PostgreSQL、通过 git push 部署、水平扩展、查看日志、发布新版本、自定义域名路由、添加多进程类型以及运行一次性任务。读完本文,你将掌握 Flynn 最核心的日常操作链路,并理解这些命令背后在 controller、router、host 等组件中的实际实现。
前置条件
本指南假设你满足以下两个条件:
- 已经有一个运行中的 Flynn 集群,并且已经配置好
flynn命令行工具。如果还没有,请先阅读 安装指南 完成集群搭建与 CLI 配置。 - 使用默认域名
demo.localflynn.com(这是安装 Vagrant 演示环境的默认配置)。如果你使用的是自己的域名,请把文中所有demo.localflynn.com替换为你 bootstrap 阶段通过CLUSTER_DOMAIN设置的值。
flynn CLI 的所有命令都通过 docopt 风格的定义注册,例如 cli/app.go、cli/ps.go、cli/scale.go 等文件中的 register(...) 调用,你可以在仓库的 cli 目录下查看每个子命令的完整用法说明。
部署第一个应用(Deploy)
我们将部署一个 Go 示例应用:它启动一个极简 HTTP 服务器,并连接一个 PostgreSQL 数据库(这是一个带命中的计数器应用,每次 HTTP 请求都会把计数写入数据库并返回当前值)。
1. 克隆示例应用仓库
$ git clone <go-flynn-example 示例仓库>
该示例应用自带 Procfile,声明了 web 进程类型,属于标准的 Heroku 风格应用结构。
2. 创建 Flynn 应用
$ cd go-flynn-example
$ flynn create example
Created example
flynn create 命令做了两件事(对应 cli/app.go 中的 runCreate 实现):
- 调用 controller 的
CreateApp接口在 Flynn 集群中注册名为example的应用; - 如果当前处于 Git 仓库内,会自动添加(或替换)一个名为
flynn的 Git remote,指向该应用的接收地址。
验证 remote 是否添加成功:
$ git remote -v
flynn https://git.demo.localflynn.com/example.git (push)
flynn https://git.demo.localflynn.com/example.git (fetch)
origin https://github.com/flynn/nodejs-flynn-example.git (fetch)
origin https://github.com/flynn/nodejs-flynn-example.git (push)
注意:
flynn create还支持-r/--remote自定义 remote 名称(传空字符串则完全不创建 remote)、-y/--yes跳过 remote 已存在时的确认提示;不传应用名时会随机生成一个名字。
创建应用时,Flynn 会自动为应用生成一条默认 HTTP 路由,把 example.demo.localflynn.com 指向 example-web 服务:
$ flynn route
ROUTE SERVICE ID STICKY LEADER PATH
http:example.demo.localflynn.com example-web http/2e37467e-08fc-47e5-853b-4f0574cb6871 false false /
默认路由的实现逻辑在 cli/route.go 中:HTTP 路由默认关联的服务名是 <应用名>-web(mustApp() + "-web"),路由的 ID 以 http/ 为前缀,输出表格中 STICKY(cookie 粘性路由)、LEADER(仅路由到 leader 实例)、PATH 分别对应路由的粘性、leader 与路径匹配配置。
3. 添加 PostgreSQL 数据库资源
示例应用依赖 PostgreSQL,用 flynn resource add 直接开通:
$ flynn resource add postgres
Created resource 320f38ba-36bc-40ce-97e5-dad1b5c3bd20 and release c7b793ca-b7b1-4da0-bd1d-4ed95c1b52e8.
这一步的底层流程在 cli/resource.go 中:CLI 向 controller 发送 ProvisionResource 请求,由对应 provider(这里是 postgres)实际完成数据库实例的创建;随后把返回的 Env 环境变量注入应用,setEnv 会生成一个新的 release(所以输出里同时有 resource ID 和 release ID)。
查看应用当前的环境变量:
$ flynn env
DATABASE_URL=postgres://84abab8e4000453fe2e1ce3f4f04392a:80f9191af9ae6c7890e4caae54990255@leader.postgres.discoverd:5432/7f02ee75fe57cb70fe1e5d9afc37935c
FLYNN_POSTGRES=postgres
PGDATABASE=7f02ee75fe57cb70fe1e5d9afc37935c
PGHOST=leader.postgres.discoverd
PGPASSWORD=80f9191af9ae6c7890e4caae54990255
PGUSER=84abab8e4000453fe2e1ce3f4f04392a
这些变量值得留意:
DATABASE_URL是完整的连接串,host 是leader.postgres.discoverd—— 这是 Flynn 内置服务发现(discoverd)给出的域名,保证应用总能连接到 PostgreSQL 集群的当前 leader;- 除了
DATABASE_URL,Flynn 还按 PostgreSQL 原生约定注入PGHOST、PGPORT、PGUSER、PGPASSWORD、PGDATABASE等变量,因此绝大多数驱动/ORM 无需额外配置即可直连; flynn env的实现见 cli/env.go,支持set、unset、get子命令,还可以用-t <proc>指定只对某个进程类型读写环境变量。
4. 推送代码完成部署
一切就绪后,把代码推送到 flynn remote 即可触发部署:
$ git push flynn master
Counting objects: 728, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (451/451), done.
Writing objects: 100% (728/728), 933.29 KiB | 0 bytes/s, done.
Total 728 (delta 215), reused 728 (delta 215)
-----> Building example...
-----> Go app detected
-----> Checking Godeps/Godeps.json file.
-----> Installing go1.6.3... done
-----> Running: go install -v -tags heroku .
-----> Discovering process types
Procfile declares types -> web
-----> Compiled slug size is 3.6M
-----> Creating release...
=====> Scaling initial release to web=1
-----> Waiting for initial web job to start...
=====> Initial web job started
=====> Application deployed
To https://git.1.localflynn.com/example.git
* [new branch] master -> master
这次 git push 背后是一条完整的构建链:gitreceive 服务收到 push 后调用 gitreceive/receiver/flynn-receive.go 中的接收器,它会用 CONTROLLER_KEY 连接 controller,检出代码、交给 builder 识别语言并编译(上面输出中的 "Go app detected" 即构建阶段)、生成 slug、创建 release,最后自动把初始 release 扩展到 web=1 并等待首个 web job 启动。部署完成后,直接用默认路由访问:
$ curl http://example.demo.localflynn.com
Hello from Flynn on port 8080 from container db0440f7-19b4-4369-b79e-7a48dba415c2
Hits = 1
响应中的 container db0440f7-... 是实际处理请求的容器 ID,Hits = 1 说明计数器已经写入 PostgreSQL 并从数据库中读回。
水平扩展(Scale)
应用通过根目录下的 Procfile 声明自己的进程类型。示例应用声明了一个 web 进程类型,执行的命令是 go-flynn-example:
$ cat Procfile
web: go-flynn-example
新创建的应用如果带有 web 进程类型,默认只启动 1 个 web 进程。用 flynn ps 查看:
$ flynn ps
ID TYPE STATE CREATED RELEASE COMMAND
flynn-db0440f7-19b4-4369-b79e-7a48dba415c2 web up About a minute ago ccd1aa34-77f7-4b7c-9772-e5d39a9f2d1e /runner/init start web
flynn ps 的实现见 cli/ps.go:它调用 controller 的 JobList 拉取该应用的全部 job,按创建时间排序后输出表格;默认只显示 up 和 pending 状态的 job,还支持 -a/--all(显示全部)、-c/--command(显示命令列)、-q/--quiet(只输出 ID)、-t/--type(按进程类型过滤)。
把 web 进程扩展到 3 个:
$ flynn scale web=3
scaling web: 1=>3
09:33:51.730 ==> web 4ef91e4b-d0c3-4e3f-931b-6db3b551dcd9 pending
09:33:51.733 ==> web ccd3aff7-80b3-46b4-a95f-006bfceb80c6 pending
09:33:51.743 ==> web flynn-ccd3aff7-80b3-46b4-a95f-006bfceb80c6 starting
09:33:51.751 ==> web flynn-4ef91e4b-d0c3-4e3f-931b-6db3b551dcd9 starting
09:33:52.129 ==> web flynn-4ef91e4b-d0c3-4e3f-931b-6db3b551dcd9 up
09:33:52.171 ==> web flynn-ccd3aff7-80b3-46b4-a95f-006bfceb80c6 up
scale completed in 464.957638ms
flynn scale 的参数格式是 TYPE=COUNT<a href="https://link.gitcode.com/i/ef6b4444487b80980260a8cbf6a5abb5" target="_blank">,KEY=VAL...](详见 [cli/scale.go),有几个要点:
- 可以一次调整多个进程类型,例如
flynn scale web=2 worker=5; COUNT之后可以跟逗号分隔的 host tag 键值对,用于把进程调度到打了特定标签的主机上,例如web=3,active=true表示把 3 个 web 进程分布在 tag 为active=true的主机上;只写KEY不写值等价于KEY=true;- 不传任何参数时,
flynn scale显示当前各进程类型的数量; -n/--no-wait不等待扩缩容事件完成,-r/--release指定要调整的 release,-a/--all列出所有 release 的非零 formation(不能与扩缩容参数同时使用)。
CLI 会根据集群 API 版本选择两条底层路径(cli/scale.go):新版集群走 ScaleAppRelease(scale request),旧版集群回退到 PutFormation + 监听 job 事件的方式;两者都会实时打印每个 job 的 pending/starting/up/down 状态迁移。
再次查看进程:
$ flynn ps
ID TYPE STATE CREATED RELEASE COMMAND
flynn-db0440f7-19b4-4369-b79e-7a48dba415c2 web up 2 minutes ago ccd1aa34-77f7-4b7c-9772-e5d39a9f2d1e /runner/init start web
flynn-4ef91e4b-d0c3-4e3f-931b-6db3b551dcd9 web up 16 seconds ago ccd1aa34-77f7-4b7c-9772-e5d39a9f2d1e /runner/init start web
flynn-ccd3aff7-80b3-46b4-a95f-006bfceb80c6 web up 16 seconds ago ccd1aa34-77f7-4b7c-9772-e5d39a9f2d1e /runner/init start web
连续发起多次 HTTP 请求,可以看到 router 把请求负载均衡到三个进程,并且每次 Hits 都在增长,证明所有进程共享同一个 PostgreSQL 数据库:
$ curl http://example.demo.localflynn.com
Hello from Flynn on port 8080 from container db0440f7-19b4-4369-b79e-7a48dba415c2
Hits = 2
$ curl http://example.demo.localflynn.com
Hello from Flynn on port 8080 from container 4ef91e4b-d0c3-4e3f-931b-6db3b551dcd9
Hits = 3
$ curl http://example.demo.localflynn.com
Hello from Flynn on port 8080 from container ccd3aff7-80b3-46b4-a95f-006bfceb80c6
Hits = 4
$ curl http://example.demo.localflynn.com
Hello from Flynn on port 8080 from container 4ef91e4b-d0c3-4e3f-931b-6db3b551dcd9
Hits = 5
查看日志(Logs)
用 flynn log 查看应用内所有进程的 stdout/stderr 输出:
$ flynn log
2016-07-26T13:32:05.987763Z app[web.flynn-db0440f7-19b4-4369-b79e-7a48dba415c2]: hitcounter listening on port 8080
2016-07-26T13:33:52.370073Z app[web.flynn-4ef91e4b-d0c3-4e3f-931b-6db3b551dcd9]: hitcounter listening on port 8080
2016-07-26T13:33:52.402620Z app[web.flynn-ccd3aff7-80b3-46b4-a95f-006bfceb80c6]: hitcounter listening on port 8080
flynn log 的实现见 cli/log.go,它从 logaggregator 拉取日志流,默认按 时间戳 来源[进程类型.jobID]: 消息 的格式输出。常用的选项包括:
-f/--follow:持续跟随输出新日志(类似tail -f);-n/--number=<lines>:最多返回最近 N 行(下文 Release 一节会用到);-t/--process-type=<type>:只显示某个进程类型的日志(多进程一节会用到);-j/--job=<id>:只显示某个 job 的日志;-r/--raw-output:输出原始日志消息,不带前缀;-s/--split-stderr:把 stderr 行输出到本机 stderr;-i/--init:把 containerinit(容器初始化)日志输出到 stderr。
发布新版本(Release)
Flynn 中"发布新版本"就是提交代码并 push 到 Flynn:每次成功的 push 都会产生一个新的 release,scheduler 会按照当前各进程类型的 scale 配置用新 release 滚动替换旧进程。
修改 main() 函数,在开头加一行输出:
fmt.Println("I've made a change!")
提交并推送:
$ git add main.go
$ git commit -m "Add log message"
$ git push flynn master
推送成功后,之前的 3 个 web 进程会被替换为基于新 release 的 3 个新进程:
$ flynn ps
ID TYPE STATE CREATED RELEASE COMMAND
flynn-8f61a0f9-0582-474c-a996-1bec7d496f2a web up 6 seconds ago 677a8a2b-f67d-4e50-8712-1a1524a23b6f /runner/init start web
flynn-f863b79a-d2b2-44d6-807b-1b508d758a8b web up 6 seconds ago 677a8a2b-f67d-4e50-8712-1a1524a23b6f /runner/init start web
flynn-1f6b3c21-3b6f-4dc0-86b3-4bfb9481b71a web up 6 seconds ago 677a8a2b-f67d-4e50-8712-1a1524a23b6f /runner/init start web
注意 RELEASE 列已经全部变为新 release ID(677a8a2b-...)。用 -n 6 查看最近 6 行日志,可以看到新进程都输出了新增的日志消息:
$ flynn log -n 6
2016-07-26T13:37:01.634234Z app[web.flynn-1f6b3c21-3b6f-4dc0-86b3-4bfb9481b71a]: I've made a change!
2016-07-26T13:37:01.634509Z app[web.flynn-f863b79a-d2b2-44d6-807b-1b508d758a8b]: I've made a change!
2016-07-26T13:37:01.653521Z app[web.flynn-1f6b3c21-3b6f-4dc0-86b3-4bfb9481b71a]: hitcounter listening on port 8080
2016-07-26T13:37:01.654673Z app[web.flynn-f863b79a-d2b2-44d6-807b-1b508d758a8b]: hitcounter listening on port 8080
2016-07-26T13:37:01.666323Z app[web.flynn-8f61a0f9-0582-474c-a996-1bec7d496f2a]: I've made a change!
2016-07-26T13:37:01.677524Z app[web.flynn-8f61a0f9-0582-474c-a996-1bec7d496f2a]: hitcounter listening on port 8080
自定义域名路由(Routes)
创建应用时,web 进程类型会自动获得一条默认 HTTP 路由,域名是默认路由域名的子域(如 example.demo.localflynn.com)。如果要使用其他域名,需要手动添加路由。
假设你有一个域名 example.com,已通过 DNS 指向你的 Flynn 集群(例如把 example.com 做 CNAME 指向 example.demo.localflynn.com)。添加路由:
$ flynn route add http example.com
http/74b05faf-c062-42f2-8ffe-678cfa3c061b
此时应用应该拥有两条路由:
$ flynn route
ROUTE SERVICE ID STICKY LEADER PATH
http:example.com example-web http/74b05faf-c062-42f2-8ffe-678cfa3c061b false false /
http:example.demo.localflynn.com example-web http/2e37467e-08fc-47e5-853b-4f0574cb6871 false false /
访问 example.com 即可命中 web 进程:
$ curl http://example.com
Hello from Flynn on port 8080 from container 8f61a0f9-0582-474c-a996-1bec7d496f2a
Hits = 6
flynn route 的完整能力见 cli/route.go:
- 路由类型分
http和tcp两种:HTTP 路由按域名(可带路径,如flynn route add http example.com/path/)分发,TCP 路由按端口分发; -s/--service指定路由指向的服务名,默认为<应用名>-web;- HTTP 路由支持
-c/--tls-cert+-k/--tls-key配置 PEM 编码的 TLS 证书(传-表示从 stdin 读取),配置后协议列会显示https; --sticky开启基于 cookie 的粘性路由,--leader开启仅路由到 leader 实例的模式,--disable-keep-alives关闭 router 与后端之间的 keep-alive;flynn route update <id>可以修改已有路由,flynn route remove <id>删除路由。
有了多条路由后,你可以让应用根据 HTTP Host 头(此时可能是 example.demo.localflynn.com 或 example.com)返回不同的内容。
多进程应用(Multiple Processes)
前面的例子只有一个 web 进程类型,但 Flynn 应用可以声明多个进程类型,并且每个类型可以独立扩缩容。常见的用法是把 Web 服务和后台任务(worker、clock、队列消费者等)分开。
添加一个每秒打印一次当前时间的 clock 命令。新建目录 clock,写入 clock/main.go:
package main
import (
"fmt"
"time"
)
func main() {
for t := range time.NewTicker(time.Second).C {
fmt.Println(t)
}
}
在 Procfile 中增加 clock 进程类型:
clock: clock
提交并推送:
$ git add clock/ Procfile
$ git commit -m "Add clock service"
$ git push flynn master
把 clock 扩展到 1 个进程,然后查看所有进程(注意 web 的 3 个进程和 clock 的 1 个进程现在都基于同一个新 release):
$ flynn scale clock=1
$ flynn ps
ID TYPE STATE CREATED RELEASE COMMAND
flynn-5a2b2364-cfb6-411e-86f8-af9298994f09 clock up 13 seconds ago 7f81b96d-3834-4eed-8b05-9ce66cc07b54 /runner/init start clock
flynn-c0b01f0f-4236-4d01-94eb-fe8c16f3dc0e web up 13 seconds ago 7f81b96d-3834-4eed-8b05-9ce66cc07b54 /runner/init start web
flynn-7453bc70-6b79-4776-9f32-c57506ba28f6 web up 13 seconds ago 7f81b96d-3834-4eed-8b05-9ce66cc07b54 /runner/init start web
flynn-4fc38d7d-06d8-4285-a225-ae0cdeb58e03 web up 13 seconds ago 7f81b96d-3834-4eed-8b05-9ce66cc07b54 /runner/init start web
用 -t 按进程类型过滤日志,只看 clock 的输出:
$ flynn log -t clock
2016-07-26T13:47:11.217109Z app[clock.flynn-5a2b2364-cfb6-411e-86f8-af9298994f09]: 2016-07-26 13:47:11.216175147 +0000 UTC
2016-07-26T13:47:12.221625Z app[clock.flynn-5a2b2364-cfb6-411e-86f8-af9298994f09]: 2016-07-26 13:47:12.221232786 +0000 UTC
2016-07-26T13:47:13.217002Z app[clock.flynn-5a2b2364-cfb6-411e-86f8-af9298994f09]: 2016-07-26 13:47:13.21673476 +0000 UTC
2016-07-26T13:47:14.216574Z app[clock.flynn-5a2b2364-cfb6-411e-86f8-af9298994f09]: 2016-07-26 13:47:14.216298339 +0000 UTC
2016-07-26T13:47:15.216373Z app[clock.flynn-5a2b2364-cfb6-411e-86f8-af9298994f09]: 2016-07-26 13:47:15.21610766 +0000 UTC
clock 是后台任务类型,不会自动获得 HTTP 路由;这也正是 Flynn 进程类型设计的意义:需要对外服务的类型(如 web)自动接入 router,不需要暴露端口的类型(如 clock/worker)则只按需扩缩容与收集日志。
运行一次性任务(Run)
Flynn 可以在新容器里生成一个交互式一次性进程,常用于调试、数据库迁移、执行管理脚本等场景:
$ flynn run bash
flynn run 的实现见 cli/run.go,支持丰富的选项:
-d/--detached:后台运行,不连接 IO 流(隐含开启日志);-r <release>:指定运行的 release(默认是应用当前 release);-l/--enable-log:把输出发送到日志流;--limits <limits>:为一次性 job 设置资源限制(格式见flynn limit -h),如 CPU、内存;--profiles=<profiles>:应用 job profile(逗号分隔);--mounts-from <proc>:从指定进程类型复制 volume 挂载;- 当 stdin/stdout 都是终端时会自动开启 TTY,并正确转发窗口大小(COLUMNS/LINES/TERM 环境变量)与 SIGINT/SIGTERM 信号,因此
flynn run里可以运行vim之类的交互式程序。
CLI 通过 RunJobAttached 建立 attach 连接,把本地 stdin 转发给远端 job、接收远端 stdout/stderr;如果是 slug 类应用,命令会自动加上 /runner/init 前缀(见 cli/run.go)。
总结
本文围绕 Flynn 应用生命周期走完了一条完整链路:
- 部署:
flynn create创建应用并自动配置 Git remote 与默认路由,flynn resource add postgres注入数据库连接环境变量,git push flynn master触发构建、生成 release 并启动web=1; - 扩展:通过
Procfile声明进程类型,用flynn scale独立扩缩容各类型,router 自动对多个 web 进程做负载均衡; - 观测:
flynn ps查看进程状态,flynn log(支持-n、-t、-f等过滤与跟随)查看聚合日志; - 迭代:再次
git push即发布新 release,所有进程按原 scale 配置滚动替换; - 路由:
flynn route add http绑定自定义域名,支持 TLS、sticky、leader 等高级选项; - 多样化进程:为应用添加 clock/worker 等后台进程类型并独立管理;
- 运维:
flynn run起一次性交互容器执行调试或迁移任务。
如果想深入理解这些命令背后的组件实现,建议继续阅读仓库中的 controller(API 与调度)、router(HTTP/TCP 路由)、gitreceive(Git 推送接收)与 logaggregator(日志聚合)等模块;集成测试示例可以参考 test/test_git_deploy.go 中对 Git 部署全流程的自动化验证。