Kamal 提供零停机部署、滚动重启、资源桥接、远程构建、附属服务管理,以及使用 Docker 在生产环境部署和管理 Web 应用所需的一切。最初为 Rails 应用打造,Kamal 适用于任何可容器化的 Web 应用。

美国 37signal 公司是一家 “小而美” 的公司,员工只有几十人但是每年有数百万利润。著名的项目管理工具 Basecamp、网络框架 Ruby on Rails 都出自他们。Kamal 也是他们的作品,用 Ruby 编写并集成到 Rails 的默认工具链中。
Kamal 的主要优势有二:一是与 Git 版本管理紧密结合,一次 commit 一个镜像,出什么问题可以立即回退;二是使用 kamal-proxy,从而带来健康检查与零停机部署,新版本实例正常上线前不会影响已有的实例。

前提条件

如上所述,只要能被 Docker 打成镜像的 Web 应用都能用 Kamal 部署。同时因为 Kamal 有 Accessories 的概念,事实上让 Kamal 占据了与 Docker Compose 一样的生态位。然而,你要部署的应用还要满足一些特殊条件:

  • 你需要手动写一个 Dockerfile,使你的服务正常启动并向某个端口提供 HTTP 服务。
  • 你需要添加 GET /up 端点,该端点用于健康检查,如果应用状态正常则返回 200 OK(Kamal 最初是为 Rails 设计的,这个端点其实就是 Rails 的惯例。)

对于部署目标 VPS 也有一定要求:

  • 建议使用刚刚安装、仅配置好 SSH Key 远程连接的全新系统。
  • 发行版建议为最新版本的 Ubuntu 或 Debian。
  • SSH 开放在 22 端口且为 root 账户登录。(显然 Kamal 的默认配置并不合安全的最佳实践,但是你会需要额外的配置,下文会说明)

下面准备了一个 Node.js 的例子,与一个 PHP+MySQL 的例子来说说它的基本用法。

请准备一个域名,并在 DNS 提供商中把域名解析事先设置到你的服务器上。


首先安装 Ruby 3.2+,如果你不知道怎么安装,可以用 rbenv

然后运行:

$gem install kamal 

把 Kamal 安装到本机上。

Node.js 应用

我已经准备了一个例子: GitHub - uqsme/app-size-comparison: App sizes comparison

切换到根目录,如你所见我已经写了 Dockerfile。本应用共有三个端点,根端点是一个 SPA,/api/files 是数据源,/up 是健康检查端点。

在根目录下执行:

$kamal init 

此时会出现 config/deploy.yml,这就是 kamal 的配置文件。但是原配置过于冗长、注释繁琐,这里针对本例的简单应用,请直接清空然后复制粘贴以下内容:

service: app-size-comparison # 把kamal改成你Docker Hub的用户名,或你自定义registry的命名空间  kamal/app-size-comparison # 部署的目标服务器,即你ssh的IP或域名 servers: web: - 192.168.0.1 # 自动启用Let's Encrypt proxy: ssl: true # 换成你要部署的域名 host: lastname.uqs.me app_port: 8000 builder: arch: amd64 registry: password: KAMAL_REGISTRY_PASSWORD 

再转到.kamal/secret

# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. # Option 1: Read secrets from the environment # KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD # Option 2: Read secrets via a command # RAILS_MASTER_KEY=$(cat config/master.key) # Option 3: Read secrets via kamal secrets helpers # These will handle logging in and fetching the secrets in as few calls as possible # There are adapters for 1Password, LastPass + Bitwarden # # SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS) # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS) 

Kamal 在这个文件中存储凭据的获取方式,注释说了不仅支持环境变量、文件,还适配了密码管理器(当然也包括明文),可以直接从管理器中的条目读取。这里你需要设置你的 Docker Hub 密码,添加以下内容

KAMAL_REGISTRY_PASSWORD=你的Docker Hub密码

然后针对你的特殊情况,展开对应的部分:

一切配置完成后,运行 kamal setup

这将:

  1. 通过 SSH 连接到服务器(默认使用 root,通过 SSH 密钥认证)。
  2. 在任何尚未安装 Docker 的服务器上安装 Docker(使用 get.docker.com):这需要通过 SSH 获取 root 权限。
  3. 在本地和远程登录镜像仓库。
  4. 使用应用根目录下的标准 Dockerfile 构建镜像。
  5. 将镜像推送到镜像仓库。
  6. 从镜像仓库拉取镜像到服务器。
  7. 确保 kamal-proxy 正在运行,并在 80 和 443 端口接收流量。
  8. 启动一个与当前 Git 版本哈希对应的应用版本的新容器。
  9. 告诉 kamal-proxy,一旦新容器对 GET /up 返回 200 OK,就将流量路由到该容器。
  10. 停止运行旧版本应用的上一个容器。
  11. 清理未使用的镜像和已停止的容器,防止服务器磁盘被占满。

稍后访问你的域名,或者 lastname.uqs.me,你应该就能看到一张图表,证明我们的 SPA 启动成功了。

接下来,假设你又进行了开发,请转到 app/src/index.html,找到这两行注释:

<!-- <h1>Here it is!</h1>
<p>I just changed the content.<p> -->

选中,用 Ctrl+/ 组合键取消注释。
Kamal 对 Git 有很好的集成,所以如果你有什么更改,可以在 git 提交后就立马上线。下面请 git add --all .git commit -m "changed something" 完成提交,然后使用 kamal deploy 命令。

$kamal deploy 

完成后,刷新你的网站,你会发现标题与内容如期呈现了。

PHP 应用

下面以异次元发卡系统为例。首先克隆源码:

$git clone https://github.com/lizhipay/acg-faka 

然后初始化 Kamal

$cd acg-faka $kamal init 

为了用 Docker 部署,编写 Dockerfile。由于有.htaccess,可以考虑用 fpm+apache 类型的,这里用 shinsenter 的:

FROM shinsenter/php:8.2-fpm-apache

COPY . /var/www/html/

WORKDIR /var/www/html

这里我还是直接提供完善的脚本 config/deploy.yml

service: acg-faka # 把kamal改成你Docker Hub的用户名,或你自定义registry的命名空间  kamal/acg-faka # 部署的目标服务器,即你ssh的IP或域名 servers: web: - 192.168.0.1 # 自动启用Let's Encrypt proxy: ssl: true # 换成你要部署的域名 host: demo.uqs.me # fpm-apache脚本默认开80 app_port: 80 healthcheck: path: / builder: arch: amd64 registry: password: KAMAL_REGISTRY_PASSWORD accessories: db:  mysql:8 # 假设是同一机器 host: 192.168.0.1 # 避免暴露公网 port: "127.0.0.1:3307:3306" # 要引入数据库容器的环境变量 # 都是mysql容器特有的设置 # 详见:https://hub.docker.com/_/mysql#environment-variables env: # 明文 clear: # 允许远程访问 MYSQL_ROOT_HOST: '%' # 库名 MYSQL_DATABASE: production # 需要保密的 secret: - MYSQL_ROOT_PASSWORD 

首先要注意的是健康检查,上面说了 Kamal 要求 GET /up 返回 200 OK,但这只是 Rails 的惯例,往往其他应用就会 404,所以这里先把检查端点改成首页。
然后,发卡系统是一个数据库驱动的应用。一般情况下将网站与数据库同时部署,我们会考虑 Docker Compose,现在 Kamal 有 Accessories 机制,事实上就与前者等效了。
而且,Accessories 与主服务都在相同的 docker 网络下,可以直接用 <镜像名>-<accessory名> 代替 IP,来实现容器间的互通了。
在部署前,你仍要提供密码,不仅有 registry 的密码,还有你想让数据库使用的密码。.kamal/secret

KAMAL_REGISTRY_PASSWORD=<你的registry密码>
MYSQL_ROOT_PASSWORD=mysql112233

配置完成后,提交一次 git commit,运行:

$git add --all . $git commit -m "setup kamal" $kamal setup 

稍后,访问 demo.uqs.me,可以看到安装界面。在连接数据库时,可以这样写:


第一个字段是数据库所在主机,可以填 acg-faka-db,Docker 的域名解析就会转到数据库所在容器。第二个字段数据库名、第三个账户、第四个密码都是通过环境变量定义的。最后一个是表名前缀,但是 docker 部署的一个特点就是自带隔离,所以这个字段在这种部署模式下意义不大了。
最后看一下安装好的发卡网:


使用 kamal details 命令查看容器情况:

更多资料

官网
kamal 加速 - Ruby China
Kamal 的讨论与问题汇总 - Ruby China


📌 转载信息
原作者:
tistest
转载时间:
2026/1/1 15:50:53