Flynn 平台实战指南:从 Git 部署、水平扩展到多进程应用的完整流程

原创2026-09-26 09:53:151,603 阅读
文章标签:云原生微服务容器编排运维

Flynn 平台实战指南:从 Git 部署、水平扩展到多进程应用的完整流程

本文是一份以 Flynn 平台为背景的端到端实战指南,围绕 docs/content/basics.md 的核心脉络展开:从一个 Go 示例应用出发,完整演示如何在 Flynn 上完成创建应用、关联 PostgreSQL、通过 git push 部署、水平扩展、查看日志、发布新版本、自定义域名路由、添加多进程类型以及运行一次性任务。读完本文,你将掌握 Flynn 最核心的日常操作链路,并理解这些命令背后在 controller、router、host 等组件中的实际实现。

前置条件

本指南假设你满足以下两个条件:

  1. 已经有一个运行中的 Flynn 集群,并且已经配置好 flynn 命令行工具。如果还没有,请先阅读 安装指南 完成集群搭建与 CLI 配置。
  2. 使用默认域名 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 应用生命周期走完了一条完整链路:

  1. 部署:flynn create 创建应用并自动配置 Git remote 与默认路由,flynn resource add postgres 注入数据库连接环境变量,git push flynn master 触发构建、生成 release 并启动 web=1;
  2. 扩展:通过 Procfile 声明进程类型,用 flynn scale 独立扩缩容各类型,router 自动对多个 web 进程做负载均衡;
  3. 观测:flynn ps 查看进程状态,flynn log(支持 -n、-t、-f 等过滤与跟随)查看聚合日志;
  4. 迭代:再次 git push 即发布新 release,所有进程按原 scale 配置滚动替换;
  5. 路由:flynn route add http 绑定自定义域名,支持 TLS、sticky、leader 等高级选项;
  6. 多样化进程:为应用添加 clock/worker 等后台进程类型并独立管理;
  7. 运维:flynn run 起一次性交互容器执行调试或迁移任务。

如果想深入理解这些命令背后的组件实现,建议继续阅读仓库中的 controller(API 与调度)、router(HTTP/TCP 路由)、gitreceive(Git 推送接收)与 logaggregator(日志聚合)等模块;集成测试示例可以参考 test/test_git_deploy.go 中对 Git 部署全流程的自动化验证。

登录后查看全文
flynn