【开发实战】Kamal - 与 Docker Compose 同一生态位的部署工具
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密码
然后针对你的特殊情况,展开对应的部分:
使用 ghcr.io、阿里云等三方 registry以阿里云仓库服务为例,在
config/deploy.yml添加以下内容:registry: # 把这两行添加回去 server: xxxx-xxxxxxxxxxxxxxx.cn-hangzhou.personal.cr.aliyuncs.com username: uqsme password: - KAMAL_REGISTRY_PASSWORD
ssh 使用非 22 端口在
config/deploy.yml添加:ssh: port: 12345
ssh 使用非 root 用户Kamal 无法自动在目标上安装 Docker。你需要手动安装 Docker 与 Git:
#curl -fsSL https://get.docker.com -o- | bash -然后,在
config/deploy.yml添加:ssh: user: your_username
一切配置完成后,运行 kamal setup。
这将:
- 通过 SSH 连接到服务器(默认使用 root,通过 SSH 密钥认证)。
- 在任何尚未安装 Docker 的服务器上安装 Docker(使用 get.docker.com):这需要通过 SSH 获取 root 权限。
- 在本地和远程登录镜像仓库。
- 使用应用根目录下的标准 Dockerfile 构建镜像。
- 将镜像推送到镜像仓库。
- 从镜像仓库拉取镜像到服务器。
- 确保 kamal-proxy 正在运行,并在 80 和 443 端口接收流量。
- 启动一个与当前 Git 版本哈希对应的应用版本的新容器。
- 告诉 kamal-proxy,一旦新容器对
GET /up返回200 OK,就将流量路由到该容器。- 停止运行旧版本应用的上一个容器。
- 清理未使用的镜像和已停止的容器,防止服务器磁盘被占满。
稍后访问你的域名,或者 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




