· Pavel Tuma · Nástroje  · 8 min čtení

Testovací jízda s Kamal deploy

S vydáním Ruby on Rails 8 se Kamal deploy dostal do popředí. Pojďme se na to podívat.

S vydáním Ruby on Rails 8 se Kamal deploy dostal do popředí. Pojďme se na to podívat.

Co je Kamal

Kamal je univerzální nástroj pro nasazování webových aplikací kamkoliv, od holého železa až po cloudové virtuální stroje. Byl vytvořen ve 37signals, aby jim pomohl odejít od drahých cloudových služeb na jejich vlastní servery. Původně byl vytvořen pro Ruby on Rails, ale funguje pro aplikace, které lze kontejnerizovat, což je v podstatě cokoliv.

Jak jsem se k tomu dostal

Jedním z návrhů Kamalu je spravovat nasazení a přitom se vyhnout složitosti Kubernetes. Kubernetes se v poslední době stal super přehnaně propagovaný se všemi obvyklými příznaky - organizace se chtějí přesunout na Kubernetes bez ohledu na to, zda je vhodný pro jejich úlohy nebo zda potřebují takovou flexibilitu při škálování. Když migrují na Kubernetes, nepoužívají ho správně a chybí jim zdroje nebo odbornost. Vidíme, že naši klienti často nepotřebují Kubernetes pro své aplikace, takže jsem byl zvědavý, zda by Kamal mohl být životaschopnou alternativou ke kontejnerovým službám ve veřejných cloudech jako AWS ECS, Azure Container Apps, nebo k platformám jako služba jako Heroku nebo Render. Existuje ještě jeden konkrétní scénář, který na trhu vidíme, kde by Kamal mohl také poskytnout výhodu jednoduchosti - používání kontejnerizovaných aplikací v on-premise infrastruktuře.

Testovací nastavení a rozsah

Rozhodl jsem se otestovat Kamal ve dvou scénářích:

  1. Dvě Ruby on Rails aplikace nasazené na flotilu AWS EC2 instancí
  2. Nasazení statické webové stránky na VM hostovaný na VMWare hypervisoru

Otázky, na které jsem chtěl prostřednictvím tohoto testování odpovědět, byly:

  • Jak snadné je nastavení a použití?
  • Je schopen spravovat více aplikací na stejném “clusteru” serverů?
  • Dokáže spolehlivě spravovat TLS certifikáty automaticky?
  • Jaké jsou možnosti pro vestavěné proxy a zpracování příchozího provozu?
  • Jak provádět jednorázové úlohy?

Záměrně jsem vyloučil testování jedné z funkcí Kamalu nazvané accessories. Je to schopnost nasadit podpůrné služby aplikací jako databáze, cache atd. společně s aplikacemi. PostgreSQL databáze, Redis cache a RabbitMQ fronty byly relevantní pro Rails aplikace. Rozhodl jsem se to netestovat, protože Redis a RabbitMQ již nejsou relevantní s Ruby on Rails 8. Pro cachování a fronty Rails poskytuje vestavěné náhrady - Solid Cache a Solid Queue, obě s databázovým backendem. To zanechává pouze databázi na stole. A s databází považuji použití spravované služby ve většině případů (jako AWS RDS) za nejlepší možnost.

Hladké části

Instalace a základní použití

Není zde moc co říct pro část instalace “lidského operátora”. S Rails máte vše nastaveno ihned po vytvoření nové aplikace. Pro přinesení vlastní aplikace jednoduše následujte super krátký průvodce začátkem a můžete začít.

Jakmile máte Kamal nainstalovaný, máte k dispozici příkaz kamal. Použití přes CLI je docela přímočaré, ačkoliv příkazy mohou být někdy trochu kryptické, dokud se s nimi neseznámíte. Jako v případě, kdy byl vyžadován upgrade běžícího kamal-proxy kvůli upgradu samotného Kamalu. A máte: kamal proxy restart, kamal proxy boot a kamal proxy reboot vedle kamal proxy start a kamal proxy stop příkazů. Musíte jít do dokumentace, abyste zjistili, který vám dá kontejner založený na nové verzi image.

Kamal tvrdí, že na něj můžete jednoduše hodit hromadu obyčejných, nekonfigurovaných serverů a provede jakoukoli konfiguraci, která je nutná. Předpokládám, že to znamená, že může nainstalovat a nakonfigurovat Docker Engine na cílovém systému. Velmi rychle jsem to otestoval a zjistil, že to dělá pomocí oficiální instalace Docker Engine balíčků. To by pravděpodobně omezilo, které distribuce lze použít na ty podporované. Na druhou stranu můžete volně použít systém, kde je Docker engine nainstalován. A to jsem udělal použitím nejnovější Fedora Core.

Zpracování úloh

Z hlediska se ukázalo, že Kamal je wrapper kolem příkazu docker - když provedete kamal příkaz, je “přeložen” na odpovídající Docker příkaz nebo příkazy a ty jsou provedeny na konkrétním nebo všech hostitelských serverech. V log výstupu můžete vidět prováděné docker příkazy. Pokud znáte log výstup Capistrano, log výstup se tomu podobá. To je pěkné a pomáhá vám ladit vaši konfiguraci, pokud je to nutné.

Shledal jsem užitečným, že existují konkrétní kamal příkazy pro kroky v procesu nasazení i jeden agregační příkaz pro provedení celého procesu. Můžete spustit kamal deploy pro provedení všeho, nebo můžete samostatně spustit příkazy pro sestavení a odeslání image aplikace do repozitáře, stažení image na servery a zřízení kontejnerů.

Nasazení jsou bez výpadků, protože kamal-proxy zvládá přepínání provozu mezi starým kontejnerem a novým. Pro to se používají standardní health checks. Konfigurace je v Kamalově konfiguračním souboru snadná.

Více nasazených aplikací není problém - jednoduše zahrňte stejné hostitelské servery do více aplikací a jsou nasazeny Kamalem. Jediná menší nevýhoda je, že najednou máte do určité míry stejnou konfiguraci ve více repozitářích aplikací a můžete ovládat každou konkrétní aplikaci při spouštění kamal app [subcommand] pouze z toho konkrétního adresáře. Samozřejmě, pokud nenapíšete --config-file= a neukážete na odpovídající Kamal konfigurační soubor.

Smíšený pytel

Povinné přihlášení do registru kontejnerů

Když se používá soukromý registr kontejnerů, jako AWS ECR v mém případě, jsou vyžadovány přihlašovací údaje, aby bylo možné volat jakýkoli Kamal příkaz, včetně těch, které registr nepoužívají. Nejsem si jistý, zda je to vyžadováno Docker Engine, ale bylo to rozptýlení poskytnout je v případě, že jsem jen chtěl vidět logy z kamal-proxy při řešení problémů.

Zpracování TLS certifikátů

Rychle se ukázalo, že Kamal může automaticky zpracovávat TLS certifikáty pomocí Let’s Encrypt pouze v případě, že nasazení jsou na jeden server. Naštěstí existuje způsob, jak říct kamal-proxy, aby použil vlastní certifikáty v případě, že je aplikace nasazena na více serverů. S více servery musí být před nimi load balancer, a považuji šifrování mezi load balancerem a Kamal hosty za klíčové pro dosažení zero-trust architektury.

Také jsem zažil problémy při vydávání certifikátu pro jeden server, ale ukázalo se, že to mohl být problém na straně Let’s Encrypt, protože tyto problémy se objevovaly a najednou mizely. Projevovaly se v logách jako:

2025-04-29T12:56:28.635918620Z {"time":"2025-04-29T12:56:28.635730333Z","level":"INFO","msg":"Request","host":"kpoc-app1.zonio.net","port":80,"path":"/.well-known/acme-challenge/i2_BMdRH6c1BZVkL1pYOjTRKxiwSYQpul0_CQhdTmaE","request_id":"71c6b7be-7d4d-4e65-a66f-a91bd34b1ffb","status":200,"service":"","target":"","duration":20586,"method":"GET","req_content_length":0,"req_content_type":"","resp_content_length":87,"resp_content_type":"","client_addr":"23.178.112.211","client_port":"55495","remote_addr":"23.178.112.211","user_agent":"Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)","proto":"HTTP/1.1","scheme":"http","query":""}
2025-04-29T12:56:33.735203318Z {"time":"2025-04-29T12:56:33.734788723Z","level":"INFO","msg":"http: TLS handshake error from 178.255.168.1:65264: acme/autocert: unable to satisfy \"https://acme-v02.api.letsencrypt.org/acme/authz/2369242787/512897904057\" for domain \"kpoc-app1.zonio.net\": no viable challenge type found"}

DNS challenge požadavek od Let’s Encrypt obdržel HTTP 200 a zdá se, že byl úspěšně splněn nejprve. Ale chyba byla vyhozena při pokusu o přístup k aplikaci přes HTTPS později.

Těžká místa

Konfigurace hostitelských serverů

Při konfiguraci Docker registru, který by Kamal měl používat, existuje možnost použití Ruby ERB v konfiguračním souboru. Dokumentace ukazuje příklad pro použití AWS ECR:

registry:
  server: <your aws account id>.dkr.ecr.<your aws region id>.amazonaws.com
  username: AWS
  password: <%= %x(aws ecr get-login-password) %>

Hodnoty mohly být převzaty z provádění příkazů (jak je ukázáno pro hodnotu hesla), z proměnných prostředí nebo odjinud. Očekával jsem, že stejný přístup bude možný pro konfiguraci hostitelských serverů a poskytl bych IP adresy k použití z OpenTofu výstupu. Ale ukázalo se, že to není možné (alespoň k verzi Kamal 2.5.3). Hmmm, nápad na PR 🤔

Image pro cron úlohy

Možná to bylo příliš vysoké očekávání nebo jen obecné nepochopení založené na tom, jak jobs fungují v Kubernetes, ale předpokládal jsem, že by existoval způsob, jak specifikovat jiný image pro jednorázové úlohy. To není případ, protože existuje pouze jeden image sestavený a bude použit pro jakékoli Kamal role definované v konfiguračním souboru. Takže pro pravidelné spouštění rake úlohy jsem skončil s přidáním cronu do image aplikace, vytvořením wrapperu, aby cron logoval výstup do dockeru, a nakonfigurováním ho pro spuštění rake. Konfiguraci lze převzít z diffů níže, pokud má někdo zájem.

diff --git a/Dockerfile b/Dockerfile
index 4de88ee..c6f27ac 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,8 +16,9 @@ WORKDIR /rails

 # Install base packages
 RUN apt-get update -qq && \
-    apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
-    rm -rf /var/lib/apt/lists /var/cache/apt/archives
+    apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client cron && \
+    rm -rf /var/lib/apt/lists /var/cache/apt/archives && \
+    rm -rf /etc/cron.*/*

 # Set production environment
 ENV RAILS_ENV="production" \
diff --git a/bin/cron-executor.sh b/bin/cron-executor.sh
new file mode 100755
index 0000000..2c547e9
--- /dev/null
+++ b/bin/cron-executor.sh
@@ -0,0 +1,8 @@
+#!/bin/bash -e
+
+PATH=$PATH:/usr/local/bin
+
+cd /rails
+
+echo "CRON: ${@}" >/proc/1/fd/1 2>/proc/1/fd/2
+exec "${@}" >/proc/1/fd/1 2>/proc/1/fd/2
diff --git a/config/crontab b/config/crontab
new file mode 100644
index 0000000..f6eba30
--- /dev/null
+++ b/config/crontab
@@ -0,0 +1 @@
+* * * * * /rails/bin/cron-executor.sh bin/rails db:version
diff --git a/config/deploy.yml b/config/deploy.yml
index f8260c6..41f1f11 100644
--- a/config/deploy.yml
+++ b/config/deploy.yml
@@ -7,7 +7,15 @@ image: remastermedia/rails8_kamal_poc
 # Deploy to these servers.
 servers:
   web:
-    - 54.236.57.179
+    hosts:
+      - 54.236.57.179
+  cron:
+    hosts:
+      - 54.236.57.179
+    cmd:
+      bash -c "(env && cat config/crontab) | crontab - && cron -f -L 2"
+    options:
+      user: root
   # job:
   #   hosts:
   #     - 192.168.0.1

Závěr

Celkově se zdá, že Kamal je životaschopnou možností pro nasazení aplikací, když nastavení nevyžaduje funkce a škálu složitějších alternativ jako Kubernetes nebo veřejné cloudové kontejnerové služby. Možná budou muset být učiněny nějaké kompromisy, ale jsou vyváženy cenou infrastruktury. Nejsem schopen vyhodnotit Kamal z hlediska spolehlivosti, protože zatím nebylo žádné produkční použití. Formulace znamená, že když budete číst tento příspěvek, bude podáván z infrastruktury nasazené Kamalem. K současnosti, při jeho přípravě, jsem zažil pouze jednu poruchu, když kamal-proxy zastavil z neznámého důvodu. Log ukazuje pouze:

2025-05-29T22:11:46.809160748Z {"time":"2025-05-29T22:11:46.806896428Z","level":"INFO","msg":"Server stopped"}

když bylo nasazení dokončeno před tím. A pak když byl vydán příkaz kamal proxy logs, obnovil se sám.

2025-05-30T09:47:26.874603248Z {"time":"2025-05-30T09:47:26.873943355Z","level":"INFO","msg":"Restored saved state","path":"/home/kamal-proxy/.config/kamal-proxy/kamal-proxy.state"}
2025-05-30T09:47:26.876984049Z {"time":"2025-05-30T09:47:26.876741477Z","level":"INFO","msg":"Server started","http":80,"https":443}

Každopádně, Kamal stojí za to, abyste ho vyzkoušeli.

AKTUALIZACE 2025-06-24

Našel jsem příčinu, proč se Kamal proxy náhle zastavil bez jakékoli log zprávy, jak je popsáno výše. Příčinou bylo, že jsem použil Fedora Core OS, které se automaticky aktualizuje. A když aktualizace vyžaduje restart, pak je ve výchozím nastavení provedena. Takže to pouze poslalo SIGTERM Kamal proxy a nezanechalo žádnou log zprávu, protože vše bylo v pořádku. Po restartu se Docker kontejnery s Kamal komponentami nespustily automaticky.

Naštěstí docela přímočaré vysvětlení 😅

Zpět na blog