前言
如果是普通 Phoenix 页面,我对“部署”这两个字的心理负担其实没那么大。
无非就是那几件老活:
- 配环境变量
- 跑迁移
- 起服务
- 反代到域名
页面能开,接口能回,日志里没飘红,基本就能先喘口气。
但 LiveView 不一样。
它表面看也是 Phoenix 应用,骨子里却更像一群一直挂着线的会话进程。你本地开发时,开一个浏览器、切两个标签页、对着 localhost 自娱自乐,那种体验很容易让人产生一种错觉:
“这不就跟普通 Web 项目差不多吗?顶多多个 WebSocket。”
这句话的问题在于,前半句像安慰,后半句像陷阱。
因为一到线上,你就会发现那个“顶多”的分量非常重:
- 反向代理要不要正确升级 WebSocket
- 长连接会不会被负载均衡器闲着没事掐掉
- 多实例之后,用户下一次重连会不会落到另一台机器
- 你存在进程内存里的那点临时状态,到底算不算数
我后来对 LiveView 部署有了一个很主观,但很实用的判断标准:
不是服务跑起来就算部署完成,而是网络抖一下、实例切一下、用户多一点,它还别立刻露馅。
这篇我不想写成“保姆级部署教程”。那种一步一步敲命令的内容,官方文档和平台文档都比我严谨。我更想聊的是另一件事:
LiveView 到底适合怎么部署,以及我为什么后来越来越觉得,选平台只是表层,真正的分水岭是你有没有把 WebSocket 生产环境当回事。
1. 我第一次把 LiveView 往线上扔,才发现“能跑”跟“能活”差得有点远
我最早真是很天真。
看到 Phoenix 能 mix release,我心里就已经在庆祝了。因为这套路径对后端很友好:
- 应用打成 release
- 带上
runtime.exs - 数据库 URL、密钥这些走环境变量
- 起服务时顺手跑个 migration
这套动作本身没毛病。问题是,它只解决了“进程启动”。
而 LiveView 的线上体验,很多时候坏就坏在“进程明明已经启动了,但连接层开始给你表演”。
我印象最深的一次,是页面能开,静态内容也正常,结果一操作 LiveView 页面,偶发性卡住。不是必现,不是直接报 500,也不是全站挂掉,就是那种最烦人的状态:
看上去像小毛病,查起来像灵异事件。
最后翻出来,根因并不高深:
- 代理层的 WebSocket 升级配置不完整
- 空闲连接超时太短
- 我自己又把一些临时状态想得过于理所当然
那次之后我才真正把“LiveView 部署”拆成了两层:
1.1 第一层,是应用怎么发上去
也就是:
- 用 Fly.io
- 用 Gigalixir
- 还是自己控服务器
这层决定的是你用什么方式把 release 跑起来。
1.2 第二层,是连接和状态怎么活下去
也就是:
- WebSocket 链路稳不稳
- 多实例时状态会不会飘
- 断线重连以后,页面能不能优雅恢复
这层才是 LiveView 真正有性格的地方。
我后面再看各种“Phoenix 部署很简单”的说法,基本都会下意识补一句:
对普通请求来说,是。对 LiveView 来说,得看你有没有把第二层也算进去。
2. 先说结论:平台没有唯一正确答案,但场景差得非常大
如果硬让我先给一个不那么政治正确的观点,我会这么说:
- 个人项目、demo、想先把东西发出去看看,
Fly.io很顺手 - 想省点基础设施脑细胞,又希望它长得像“给 Phoenix 准备好的托管平台”,
Gigalixir会更省心 - 真到业务项目、私有部署、合规要求、成本可控这些关键词出来时,我最后还是更信
mix release + systemd + Nginx
这不是说自建一定高级,也不是说托管平台不靠谱。
而是 LiveView 这种东西,一旦进入真实环境,平台选择就不只是“哪个一键部署更快”,而是下面这些问题谁更重要:
- 我到底想省的是哪一类时间
- 我能不能接受平台抽象帮我挡住细节,也顺便挡住排查入口
- 我是不是迟早要面对多实例、连接治理、反向代理和系统监控
这几个问题不想清楚,最后就特别容易陷入一种经典局面:
前期嫌自建麻烦,后期嫌平台不透明。
我自己就来回摇摆过。
3. Fly.io:它确实适合把 LiveView 很快地推到公网,但别把“顺手”误会成“什么都替你想好了”
Fly.io 这些年在 Phoenix / LiveView 圈子里很常见,不是没道理。
它最讨喜的地方,是非常契合 Elixir 这种“release 打包好、服务自己能跑”的路子。很多时候你只要把 Dockerfile 和配置理顺,剩下就是:
fly launch
fly deploy
那种体验对第一次把 LiveView 项目发到公网的人来说,很有诱惑力。因为它确实会给你一种感觉:
“终于不是我一个人跟 Linux 配置文件搏斗了。”
3.1 我为什么觉得它适合系列项目、demo、早期验证
原因很现实:
- 起步快
- 跟 Phoenix release 配合自然
- HTTPS、域名、基础运行环境这类活,平台替你扛了不少
- 对“先上线看看”的目标特别友好
如果你现在手里是一个聊天室、协作白板、后台管理台这类 LiveView 项目,重点还是想尽快验证在线效果,那 Fly.io 的确很香。
我自己很喜欢它的一点是,它逼着我把应用打包这件事做规范。
比如一个比较常见的 Docker 入口,大概会长这样:
CMD ["/app/bin/server"]
配合 release,应用启动方式比较统一,心智负担没那么散。
3.2 但它最容易让人产生一个误会:好像部署难题已经结束了
这就是 Fly.io 最“温柔也最危险”的地方。
它太容易让第一次上手的人觉得:
“看吧,LiveView 部署也没多复杂。”
可平台帮你把应用托起来,不等于它帮你把状态边界也想清楚了。
尤其是下面这几类坑,真到后面还是会回来找你:
- 你把本地文件、上传临时文件、进程内缓存,当成了稳定状态
- 你开始跑多个实例,但业务逻辑默认“用户大概率还在原来那台机器上”
- 你以为页面断线重连只是用户无感刷新,实际上 socket 上那点临时 assign 已经变天了
LiveView 很多页面在本地之所以显得格外丝滑,是因为本地开发环境帮你把“不稳定性”藏起来了。
一旦部署到真正的分布式环境里,任何依赖“这一条连接最好别乱跑”的设计,后面都容易心虚。
3.3 我对 Fly.io 的真实态度是:适合上手,但别顺手把架构判断也一起外包了
如果你问我,它值不值得用?
值。
如果你问我,它能不能替代你对 LiveView 运行模型的理解?
不能,而且这个错觉一旦形成,后面排查问题会格外难受。
我现在对 Fly.io 的使用心态比较朴素:
- 它很适合把项目快速发出去
- 它也很适合验证 LiveView 在公网下的真实表现
- 但一旦我开始在意可观测性、连接治理、成本边界和部署策略,我就不会再把“平台够省事”当成唯一标准
4. Gigalixir:如果说 Fly.io 像“我先把你带上路”,那它更像“我尽量别让你碰太多路边零件”
我对 Gigalixir 的印象一直挺明确:它对 Phoenix / Elixir 项目足够友好,而且那种友好不是“能跑”,而是“很多细节它默认就知道你大概要什么”。
这种平台的好处,是你不需要太早进入运维人格。
说白了就是,不用那么快把自己变成:
- 半个 Linux 管理员
- 半个网络工程师
- 半个日志平台搬运工
对于想把注意力放在产品和业务上的人,这种体验很有吸引力。
4.1 它最适合哪种状态下的我
不是“特别懂部署”的我。
而是下面这种我:
- 我已经知道 LiveView 不是普通静态页面
- 但我暂时不想亲自盯着 Nginx、systemd、证书续期、进程守护这些事
- 我希望平台对 Phoenix 的理解别太浅
这时候 Gigalixir 的价值就比较明显。
它给我的感受有点像老派 PaaS,那种“你专心写应用,我尽量别让你被基础设施反复教育”的思路,在 Phoenix 这条线上是成立的。
4.2 它的代价也很直接:你省下来的,不只是麻烦,还有控制力
我一直觉得,托管平台最公平的一点就是,它从不白给。
你省掉了下面这些麻烦:
- 系统服务编排
- 反向代理细节
- 一部分运行环境管理
你通常也会同时失去一部分东西:
- 对底层配置更细的掌控
- 出问题时“想往里再看一层”的自由
- 某些成本和资源策略上的主动权
这不是 Gigalixir 一家的问题,这是托管平台的通用物理规律。
所以我后来给自己定了个很简单的判断:
如果我现在最贵的是开发时间,平台托管就划算。
如果我现在最贵的是长期稳定性、资源边界和排查透明度,自建往往更踏实。
5. 真到稍微像样一点的生产环境,我还是更信 mix release + systemd + Nginx
这套方案不性感,但很实在。
而且它有个特别大的优点:出了问题,基本都还能继续往下查。
这句话听起来很朴素,但真做过线上排查的人应该懂,它几乎就是安全感本身。
5.1 我为什么后来越来越偏向这套
因为 LiveView 项目一旦开始沾上这些词:
- 内网部署
- 自有服务器
- 数据不方便放外面
- 需要更明确的监控和日志入口
- 团队对 Linux 和 Nginx 并不陌生
那 mix release + systemd + Nginx 往往就会从“老办法”变成“正合适”。
Phoenix release 这一层其实挺干净,核心启动动作也不复杂:
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix compile
MIX_ENV=prod mix release
_build/prod/rel/my_app/bin/my_app start
我喜欢这套方案,不是因为它命令少,而是因为它的边界清楚:
- release 负责应用
- systemd 负责守护进程
- Nginx 负责入口流量和 WebSocket 反代
谁出问题,先怀疑谁,脑子不会太乱。
5.2 systemd 这层不花哨,但真的很能扛
我现在如果用自建部署,基本都会老老实实给 release 配一个 service。
像这样:
[Unit]
Description=MyApp Phoenix LiveView
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/srv/my_app
Environment=PHX_SERVER=true
Environment=PORT=4000
EnvironmentFile=/etc/my_app.env
ExecStart=/srv/my_app/bin/my_app start
ExecStop=/srv/my_app/bin/my_app stop
Restart=always
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
这里我后来最在意的反而不是 Restart=always,而是 LimitNOFILE 这种看起来很不起眼的东西。
原因特别现实:LiveView 是长连接模型,连接一多,文件描述符上限、进程数、系统 socket 资源这些东西,都会比传统“请求来一下就走”的页面更早撞墙。
很多人以为线上扛不住,是应用代码慢。
结果往下一查,先撞到的是系统层的天花板。
这种情况很像你以为自己在研究架构,实际上先被门框绊了一跤。
5.3 Nginx 这层,表面只是反代,实际上最会给 LiveView 下绊子
我对 LiveView 部署最大的态度转变,就是从“WebSocket 配一下就行”变成了“代理层就是核心参与者”。
普通 Phoenix 页面对 Nginx 的要求没那么高,HTTP 代理对了,大多数时候就差不多。
但 LiveView 页面只要要靠 WebSocket 持续交互,Nginx 就不能再被当成一个只会转发请求的路由工。
下面这类配置,我现在基本都会先看:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 600s;
}
}
这里面最容易被低估的,不是 Upgrade,而是超时。
你本地开发时,LiveView 长连接看起来像空气一样自然,是因为中间没人管它。线上一旦经过代理层、负载均衡层、CDN 或网关,空闲超时、心跳周期、断线重连策略这些事,都会开始有存在感。
最烦的不是它直接挂。
最烦的是它挂得若有若无:
- 页面还开着
- 静态内容还在
- 用户点一下才发现交互没了
这种故障特别像表面和平,实际上屋里已经断电。
6. 真正会在线上咬人的,不是 mix release,而是 WebSocket 的生产配置
这一段我想单独拎出来讲,因为它就是这篇的重点。
如果只让我留一句最想强调的话,那就是:
LiveView 部署的关键,不在于你用了哪个平台,而在于你有没有把 WebSocket 当成一等公民。
下面这三个点,是我觉得最该认真看待的。
6.1 sticky session:它不一定永远是硬性要求,但很多场景下,不开真的会让人怀疑人生
这事最容易吵起来。
因为从原理上说,LiveView 断线后是可以重连、重新挂载的。只要你的状态设计是健康的,重连到另一台机器也不是世界末日。
问题在于,很多真实项目并没有“健康到那么理想”。
比如这些状态:
- 正在进行中的上传
- 只存在于 socket assign 里的临时 UI 状态
- 某些还没来得及落库、只靠进程内存维持的过渡数据
这时候如果连接频繁漂到别的实例,用户体验就会很魔幻。
所以我现在对 sticky session 的态度很务实:
- 理论上,不是所有 LiveView 项目都必须靠它活
- 实践上,只要你有明显的长会话特征、上传流程或较重的瞬时状态,它就很值得开
它不是让架构变正确的魔法。
它更像是承认现实:在你把所有状态都设计得足够可迁移之前,先别让连接乱跳。
6.2 连接数上限:你以为 LiveView 很轻,系统会提醒你“轻是有单位的”
Elixir 进程确实轻,这话没错。
但“轻”不等于“无限”。
一个 LiveView 页面背后就是一条连接、一个会话、一组进程协作,再叠加 Presence、PubSub、数据库池、上传、后台任务,资源消耗很快就不是“单个请求”那个量级了。
所以我现在看线上容量,不太会只盯应用本身,还会顺手看这些东西:
- Erlang VM 的进程和内存表现
- 数据库连接池大小
- 服务器文件描述符限制
- Nginx 的
worker_connections - 负载均衡器对并发连接和空闲连接的限制
这块最坑人的地方是,问题通常不会以“你连接数爆了”这么诚实的方式出现。
它更常见的样子是:
- 部分用户偶发掉线
- 峰值时交互延迟突然变大
- Presence 更新变慢
- 某些页面开始莫名重连
如果你只盯业务日志,经常会有一种“看着都还行,但用户怎么就是不对劲”的感觉。
6.3 心跳和超时:很多线上事故,本质上只是中间层觉得你太安静了
这点特别像成年人社交。
你不说话,对方就默认你走了。
WebSocket 链路也是一样。LiveView 自己有心跳机制,但中间层未必愿意一直耐心等你。所以我现在碰到长连接环境,都会下意识确认:
- 代理层空闲超时是多少
- 云负载均衡空闲超时是多少
- 心跳周期和代理超时是不是明显打架
- 断线后前端重连体验是不是能接受
很多“用户说页面偶尔卡死”的问题,最后并不是什么高端 bug,就是链路上某一层觉得这条连接太久没动静,顺手给它判了死刑。
这类问题最气人的地方在于,它经常不是稳定复现。
于是你白天看监控,岁月静好;晚上用户一多,开始随机抽奖。
7. 如果非要我现在给一个选择建议,我会这么选
我不想搞那种“唯一正确答案”的表格,但如果让我站在今天这个时间点,按真实工程感受给建议,大概是这样:
7.1 我只是想先把项目发出去,验证 LiveView 在线上到底成不成立
我会先选 Fly.io。
原因很简单,不折腾,启动快,离“真的在公网跑起来”这件事最近。
它特别适合处理一个很现实的问题:
很多人不是不会写 LiveView,而是一直没把它真正放到公网环境里挨过打。
7.2 我想少碰基础设施,但又不想完全自己搭
我会认真看 Gigalixir。
它的优势在于,对 Phoenix / Elixir 这条线更懂一点,不需要我过早进入“服务器巡检员”状态。
7.3 我要的是长期稳定、环境可控、排查透明
我大概率会选:
mix release + systemd + Nginx
如果还有多节点需求,那就继续把节点发现、PubSub、数据库、监控体系补齐,而不是指望平台抽象帮我顺便把所有问题都消化掉。
我后来越来越相信一件事:
LiveView 项目越往后走,真正值钱的不是“一键发版”,而是“我知道这条连接、这个状态、这次重连到底发生了什么”。
8. 我现在上线前最怕漏掉的,反而不是代码
如果你让我说一个最真实的变化,那就是我现在发 LiveView 应用前,最紧张的常常不是业务逻辑,而是这些“看起来不像功能”的东西:
- WebSocket 升级头有没有完整透传
- 代理和负载均衡的超时是不是合理
- 连接数、文件描述符、数据库池有没有明显短板
- 多实例时状态是不是过度依赖单机内存
- 断线重连以后,页面是不是还能把自己找回来
这些东西平时很不抢戏,所以最容易被拖到最后。
但它们一旦出问题,用户根本不会关心你后台用了 LiveView 还是 Turbo 还是别的什么名词。用户只会说一句很朴素的话:
“这个页面怎么老是看着没坏,但用着别扭。”
而那句“看着没坏”,往往就是最难查的开始。
总结
这期我真正想说清楚的,不是某个平台的部署步骤,而是一个更底层的判断:
LiveView 的部署难点,从来不只是把 Phoenix 跑起来,而是把长连接、状态恢复和代理层一起当成系统的一部分。
所以如果把这篇压成三句话,我会这么留:
Fly.io适合快,适合验证,适合先把项目扔到公网看看真实表现Gigalixir适合省心,适合不想太早手搓基础设施的人- 自建服务器最麻烦,但只要项目开始认真对待稳定性和可控性,它往往也是最踏实的