Kamal 2 部署工具完全指南¶
为什么要学 Kamal¶
37signals 出品,Rails 之父背书:Kamal(原名 MRSK)由 DHH(Ruby on Rails 创建者)和 37signals 团队开发,用于部署 HEY.com 和 Basecamp。它代表了"回归简单"的部署哲学——不需要 Kubernetes,不需要 PaaS,直接 SSH 到服务器部署 Docker 容器。
零停机部署,无需 K8s:Kamal 使用 Traefik 反向代理实现滚动部署(Rolling Deploy)。新容器启动并通过健康检查后,流量才切换过去,旧容器优雅退出。你获得了 K8s 级别的零停机部署,但复杂度只有它的 1/10。
任何语言、任何框架:虽然 Kamal 出自 Ruby 社区,但它本质上就是一个 Docker 部署工具。只要你的应用能打包成 Docker 镜像,不管是 Node.js、Python、Go、Rust 还是 PHP,都能用 Kamal 部署。
直接 SSH,无中间层:Kamal 通过 SSH 直接操作目标服务器。没有 Agent、没有 Daemon、不需要在服务器上安装额外软件(除了 Docker)。部署的可审计性和可调试性极强。
Accessories 管理附属服务:数据库、Redis、搜索引擎等附属服务可以在 Kamal 配置中声明和管理,与主应用一起部署和维护。
核心概念详解¶
Kamal 是什么(白话解释)¶
假设你租了几台服务器,想把你的 Web 应用部署上去。传统方式:
- SSH 到每台服务器
- 拉取最新代码
- 构建 Docker 镜像
- 停掉旧容器
- 启动新容器
- 配置 Nginx
- 祈祷不出问题
Kamal 把这个过程自动化了。你写一个配置文件(config/deploy.yml),然后运行 kamal deploy。Kamal 自动:
- 在 CI 或本地构建 Docker 镜像
- 推送到 Registry
- SSH 到每台服务器
- 拉取新镜像
- 启动新容器
- 等健康检查通过
- 将流量切到新容器
- 停掉旧容器
核心架构¶
开发者本地 / CI
│
├── kamal build → Docker Image → Push to Registry
│
└── kamal deploy (via SSH)
↓
┌───────────────────────────────────────────┐
│ 服务器 1 │
│ ┌──────────┐ ┌──────────┐ │
│ │ Traefik │→│ App v2 │ (新容器) │
│ │ (代理) │ │ (健康✓) │ │
│ └──────────┘ └──────────┘ │
│ ┌──────────┐ │
│ │ App v1 │ (旧容器,待退) │
│ └──────────┘ │
└───────────────────────────────────────────┘
┌───────────────────────────────────────────┐
│ 服务器 2(同样的滚动部署) │
└───────────────────────────────────────────┘
┌───────────────────────────────────────────┐
│ 服务器 3(Accessories: DB, Redis) │
└───────────────────────────────────────────┘
关键概念¶
| 概念 | 说明 |
|---|---|
| Service | 你的应用(Web、Worker 等) |
| Destination | 部署目标环境(staging/production) |
| Registry | Docker 镜像仓库 |
| Proxy | Kamal Proxy(基于 Traefik 的轻量代理) |
| Accessories | 附属服务(数据库、Redis 等) |
| Builder | 构建配置(本地/远程/多架构) |
| Healthcheck | 健康检查(确认新容器正常才切流量) |
| Hooks | 部署生命周期钩子 |
Kamal vs 其他部署方案对比¶
| 特性 | Kamal | Kubernetes | Docker Compose | Coolify |
|---|---|---|---|---|
| 复杂度 | 低 | 极高 | 低 | 低 |
| 零停机 | 原生支持 | 原生支持 | 需手动 | 支持 |
| 自动扩缩 | 手动 | 自动 | 手动 | 手动 |
| 学习曲线 | 低 | 极高 | 极低 | 低 |
| 多服务器 | 支持 | 核心特性 | 不支持 | 支持 |
| 配置方式 | YAML | YAML+大量资源 | YAML | Web UI |
| 健康检查 | 内置 | 内置 | 基础 | 基础 |
| SSL | Traefik 自动 | Ingress | 手动 | 自动 |
| 回滚 | 一键 | 自动 | 手动 | 手动 |
| 适合规模 | 中小型 | 大型 | 开发环境 | 中小型 |
| 运维成本 | 低 | 高 | 最低 | 低 |
| 服务发现 | Traefik | CoreDNS | Docker网络 | Traefik |
安装与配置¶
安装 Kamal¶
# Ruby gem 安装
gem install kamal
# 或通过 Docker(不需要 Ruby)
alias kamal='docker run -it --rm -v "${PWD}:/workdir" -v "/run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock" -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/basecamp/kamal:latest'
# 验证
kamal version
初始化项目¶
这会创建以下文件:
config/
└── deploy.yml # 主配置文件
.kamal/
├── hooks/ # 部署钩子
│ ├── docker-setup
│ ├── pre-connect
│ ├── pre-build
│ ├── pre-deploy
│ └── post-deploy
└── secrets # 密钥文件
配置文件详解¶
# config/deploy.yml
service: my-app
image: myuser/my-app
servers:
web:
hosts:
- 192.168.1.100
- 192.168.1.101
labels:
traefik.http.routers.my-app.rule: Host(`app.example.com`)
options:
memory: 512m
cpus: "0.5"
worker:
hosts:
- 192.168.1.102
cmd: bin/jobs
proxy:
ssl: true
host: app.example.com
app_port: 3000
healthcheck:
path: /up
interval: 3
timeout: 5
registry:
server: ghcr.io
username: myuser
password:
- KAMAL_REGISTRY_PASSWORD
builder:
arch: amd64
# 或多架构
# multiarch: true
# 或远程构建
# remote:
# arch: amd64
# host: ssh://builder@build-server
env:
clear:
RAILS_ENV: production
NODE_ENV: production
DB_HOST: 192.168.1.200
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
- REDIS_URL
- SECRET_KEY_BASE
accessories:
db:
image: postgres:16
host: 192.168.1.200
port: "127.0.0.1:5432:5432"
env:
clear:
POSTGRES_DB: my_app_production
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
options:
memory: 1g
redis:
image: redis:7-alpine
host: 192.168.1.200
port: "127.0.0.1:6379:6379"
directories:
- data:/data
# 多环境支持
# config/deploy.staging.yml 可以覆盖上面的配置
密钥管理¶
# .kamal/secrets 文件(不要提交到 Git)
KAMAL_REGISTRY_PASSWORD=ghp_xxxxxxxxxxxx
RAILS_MASTER_KEY=xxxxxxxxxxxx
DATABASE_URL=postgresql://user:pass@db/app
REDIS_URL=redis://redis:6379/0
SECRET_KEY_BASE=xxxxxxxxxxxx
POSTGRES_PASSWORD=xxxxxxxxxxxx
# 或使用环境变量 / 1Password / Vault
# .kamal/secrets 支持 ERB 模板
KAMAL_REGISTRY_PASSWORD=<%= `op read "op://Vault/Registry/password"`.strip %>
快速上手:5 分钟最小示例¶
准备工作¶
- 一台 Linux 服务器(Ubuntu 22.04),SSH 可访问
- Docker Hub 或 GitHub Container Registry 账号
- 应用有 Dockerfile
Dockerfile¶
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
EXPOSE 3000
HEALTHCHECK CMD curl -f http://localhost:3000/up || exit 1
CMD ["node", "dist/index.js"]
配置¶
# config/deploy.yml
service: my-app
image: myuser/my-app
servers:
web:
hosts:
- your-server-ip
proxy:
host: app.yourdomain.com
ssl: true
app_port: 3000
registry:
username: myuser
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
NODE_ENV: production
部署¶
# 首次设置(安装 Docker、配置 Traefik)
kamal setup
# 后续部署
kamal deploy
# 查看状态
kamal details
# 查看日志
kamal app logs
# 回滚
kamal rollback
进阶用法¶
场景一:多服务器 Rolling Deploy¶
servers:
web:
hosts:
- 10.0.0.1
- 10.0.0.2
- 10.0.0.3
options:
memory: 1g
cpus: "1"
# Kamal 逐台服务器部署:
# 1. 在 10.0.0.1 启动新容器
# 2. 健康检查通过后切换流量
# 3. 停掉旧容器
# 4. 在 10.0.0.2 重复...
# 5. 在 10.0.0.3 重复...
# 全程零停机
场景二:Web + Worker + 定时任务¶
servers:
web:
hosts:
- 10.0.0.1
- 10.0.0.2
worker:
hosts:
- 10.0.0.3
cmd: node dist/worker.js
scheduler:
hosts:
- 10.0.0.3
cmd: node dist/scheduler.js
场景三:多环境部署¶
# config/deploy.yml(生产环境)
service: my-app
image: myuser/my-app
servers:
web:
hosts: [10.0.1.1, 10.0.1.2]
proxy:
host: app.example.com
# config/deploy.staging.yml(覆盖配置)
servers:
web:
hosts: [10.0.2.1]
proxy:
host: staging.example.com
env:
clear:
NODE_ENV: staging
场景四:部署钩子¶
# .kamal/hooks/pre-deploy
#!/bin/sh
echo "Running pre-deploy checks..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed! Aborting deployment."
exit 1
fi
# .kamal/hooks/post-deploy
#!/bin/sh
echo "Post-deploy: running migrations..."
kamal app exec "npx prisma migrate deploy"
echo "Notifying team..."
curl -X POST "$SLACK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"text\": \"Deployed ${KAMAL_VERSION} to production\"}"
场景五:Accessories 管理¶
accessories:
db:
image: postgres:16
host: 10.0.0.10
port: "127.0.0.1:5432:5432"
env:
clear:
POSTGRES_DB: app_production
POSTGRES_USER: app
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
options:
memory: 2g
redis:
image: redis:7-alpine
host: 10.0.0.10
port: "127.0.0.1:6379:6379"
directories:
- data:/data
cmd: redis-server --appendonly yes
search:
image: getmeili/meilisearch:latest
host: 10.0.0.10
port: "127.0.0.1:7700:7700"
env:
secret:
- MEILI_MASTER_KEY
directories:
- data:/meili_data
# 管理 Accessories
kamal accessory boot db # 启动数据库
kamal accessory reboot redis # 重启 Redis
kamal accessory logs search # 查看搜索服务日志
kamal accessory remove db # 移除数据库
场景六:远程构建(CI 集成)¶
# GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
- run: gem install kamal
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- run: kamal deploy
env:
KAMAL_REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
场景七:资源限制与健康检查¶
servers:
web:
hosts: [10.0.0.1]
options:
memory: 512m
cpus: "0.5"
log-opt: max-size=50m
proxy:
host: app.example.com
ssl: true
app_port: 3000
healthcheck:
path: /up
interval: 3 # 每3秒检查一次
timeout: 5 # 超时5秒
# 默认:7次成功后才切流量
场景八:常用操作命令¶
# 部署
kamal deploy # 完整部署流程
kamal redeploy # 快速重新部署(跳过构建)
# 应用管理
kamal app start # 启动应用
kamal app stop # 停止应用
kamal app restart # 重启应用
kamal app details # 查看详情
kamal app logs # 查看日志
kamal app logs -f # 跟踪日志
kamal app exec "command" # 在容器中执行命令
# 代理管理
kamal proxy reboot # 重启 Traefik
# 回滚
kamal rollback # 回滚到上一个版本
kamal rollback [version] # 回滚到指定版本
# 环境变量
kamal env push # 推送更新的环境变量
# 服务器管理
kamal server bootstrap # 初始化服务器(安装 Docker)
# Audit
kamal audit # 查看部署审计日志
常见问题与排错¶
问题一:SSH 连接失败¶
# 确保 SSH key 已配置
ssh-add -l
# 测试 SSH 连接
ssh root@your-server-ip
# Kamal 默认使用 root 用户
# 如需其他用户,在 deploy.yml 中设置
# ssh:
# user: deploy
问题二:Docker 构建失败¶
问题三:健康检查不通过¶
问题四:部署回滚¶
问题五:磁盘空间不足¶
问题六:如何查看部署日志¶
参考资源¶
- 官方文档:https://kamal-deploy.org/
- GitHub:https://github.com/basecamp/kamal
- DHH 介绍 Kamal:https://world.hey.com/dhh (搜索 "Kamal")
- Kamal Handbook:https://kamal-deploy.org/docs/installation
- 37signals 部署实践:https://dev.37signals.com/
- Ruby on Rails Guides(Kamal 章节):https://guides.rubyonrails.org/
- Kamal Discord:https://discord.gg/kamal