Kamal 2 部署笔记:kamal-proxy 如何切换流量,deploy.yml 怎么填
项目信息
- 仓库:
- 官网与文档:
- kamal-proxy 源码:
- 当前版本 2.12.0,MIT 许可。项目原名 MRSK。
最初是为 Rails 应用写的,任何能被 Docker 容器化的 web 应用都能用。
定位
从裸机到云 VM,用 Docker 部署 web 应用,做到零停机。kamal-proxy 负责在容器之间无缝切换请求,SSHKit 负责在多台服务器上执行命令。
设计取舍
不走 K8s/Swarm 那套声明式状态 reconcile,走的是和 Capistrano 一样的命令式(imperative)路线,可以理解成 Capistrano for Containers。服务器不需要提前精心准备:Ruby 或其它依赖都打包进 Docker 镜像,全新 Ubuntu 服务器加一个 SSH key,写进服务器列表,Kamal 会自动装 Docker 并跑起来。
相比自建 Kubernetes,Kamal 简单得多,做的事情看得见,本质就是调用基础的 Docker 命令。Heroku/Fly/Render 这类云托管平台好用但会绑定,Kamal 想在没有商业平台加价的前提下,把部署体验做到接近。37signals 用它把 HEY 等应用从云迁回自有机房。
kamal-proxy
Kamal 2 自带一个专用反向代理 kamal-proxy,是一个独立的 Go 二进制,替代应用前面单独跑的 Nginx 或 Caddy 进程。
部署时 kamal-proxy 先把连接 hold 住,等新容器起来、健康检查通过后原子切换流量,不丢请求。老容器上的在途请求会被 drain(默认 30 秒)之后才发 SIGTERM。请求如果超过这个窗口(大文件上传、报表),可能被切断,可以调大 proxy 下的 stop_wait_time:
stop_wait_time: 120
kamal-proxy run 启动代理;kamal-proxy deploy service1 --target web-1:3000 注册目标。健康检查失败会中止部署并返回非零退出码。TLS 方面,--tls 自动申请并续期证书,--tls-on-demand-url 支持按需签发。run 的选项也可以通过环境变量传入(HTTP_PORT / KAMAL_PROXY_HTTP_PORT 等)。
安装与初始化
gem install kamal
kamal init
kamal init 会生成 config/deploy.yml、.kamal/secrets、Dockerfile。
config/deploy.yml
service: myapp
image: ghcr.io/yourorg/myapp
servers:
web:
hosts:
- 192.0.2.10
- 192.0.2.11
job:
hosts:
- 192.0.2.10
cmd: bin/jobs
proxy:
ssl: true
host: myapp.example.com
app_port: 3000
healthcheck:
path: /up
interval: 2
timeout: 5
# stop_wait_time: 120
registry:
server: ghcr.io
username: yourorg
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
RAILS_ENV: production
PORT: "3000"
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
accessories:
sidekiq:
image: ghcr.io/yourorg/myapp
hosts: [192.0.2.10]
cmd: "bundle exec sidekiq"
env:
secret: [RAILS_MASTER_KEY, DATABASE_URL, REDIS_URL]
boot:
limit: 10
pause: 0
几个字段的说明:
proxy.host填域名,HTTPS 由 Let's Encrypt 自动签发。用 Cloudflare 时把 SSL/TLS 加密模式设为 Full。app_port要和容器里应用监听的端口一致,默认代理连容器的 80。- 构建默认用最后一次 git commit 作为来源。初期调试可以设
context: .直接用工作目录里未提交的改动,稳妥之后删掉这行。构建默认在部署机上做远程构建,也可以指定独立的 builder。 - 多目的地:
kamal deploy -d staging会额外读取并合并config/deploy.staging.yml。
第一次部署
kamal setup。第一台服务器、第一个应用会装 Docker、建 kamal 网络、启动 kamal-proxy;之后的应用会跳过这些步骤,直接用已经存在的 proxy。
kamal deploy 的执行顺序
- Build:构建镜像并推到 registry
- Lock:在主服务器上取部署锁,并发部署会被中止
- Pull:各主机拉新镜像
- 跑 boot 命令,例如
kamal app exec --reuse 'bin/rails db:migrate' - 启动新容器(与老容器并存)
- 健康检查:kamal-proxy 轮询 health path,直到返回 200 或超时
- 切换流量:原子路由到新容器,老容器上的在途请求 drain
- 停老容器:SIGTERM,超时后 SIGKILL
- 释放锁
多台 web 主机时,3–8 步默认并行。要做真正的逐台滚动部署,用 --hosts 指定:
kamal deploy --hosts 192.0.2.10
kamal deploy --hosts 192.0.2.11
超时换算:有效启动窗口是 healthcheck.interval × healthcheck.timeout(上面例子是 2×5)。外层 boot.limit 是总墙钟预算(秒),要覆盖最坏的启动时间加上数据库迁移时间。
accessories 与单机多应用
accessories 用来跑长期存在的边车(db、cache、worker)。Rails 7.1 起自带 /up 健康端点,检查 Active Record、Action Cable adapter、Active Storage。
同一台服务器上跑多个应用时,每个应用各自一份 config/deploy.yml 和各自的 service/host,但只跑一个 kamal-proxy 实例(由第一个部署的应用安装)。查看 proxy 上注册的服务:
ssh USER@SERVER
docker exec kamal-proxy kamal-proxy list
输出列:Service / Host / Target / State / TLS。
Kamal 目前不能做重定向,www 到非 www 的跳转要在应用侧做;任何需要暴露 HTTP 端点的 accessory 其实应该做成一个独立应用。
文档
- 安装:
- 配置:
- 命令:
- 源码:
- kamal-proxy: