Replace Traefik with mproxy
[mproxy](https://github.com/kevinmcconnell/mproxy) is a custom minimal proxy designed specifically for Kamal. It has two big advantages over Traefik: 1. Imperative deployments - we tell it to switch from container A to container B, and it waits for container B to start then switches. No need to poll for health checks ourselves or mess around with forcing health checks to fail. 2. Support for multiple apps - as much as possible, configuration is supplied at runtime by the deploy command, allowing us to have multiple apps share an instance of mproxy without conflicting config.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
require_relative "integration_test"
|
||||
|
||||
class AccessoryTest < IntegrationTest
|
||||
class IntegrationAccessoryTest < IntegrationTest
|
||||
test "boot, stop, start, restart, logs, remove" do
|
||||
kamal :envify
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require_relative "integration_test"
|
||||
|
||||
class AppTest < IntegrationTest
|
||||
class IntegrationAppTest < IntegrationTest
|
||||
test "stop, start, boot, logs, images, containers, exec, remove" do
|
||||
kamal :envify
|
||||
|
||||
|
||||
3
test/integration/docker/deployer/app/.kamal/hooks/post-proxy-reboot
Executable file
3
test/integration/docker/deployer/app/.kamal/hooks/post-proxy-reboot
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "Rebooted proxy on ${KAMAL_HOSTS}"
|
||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/post-proxy-reboot
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
echo "Rebooted Traefik on ${KAMAL_HOSTS}"
|
||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/post-traefik-reboot
|
||||
3
test/integration/docker/deployer/app/.kamal/hooks/pre-proxy-reboot
Executable file
3
test/integration/docker/deployer/app/.kamal/hooks/pre-proxy-reboot
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "Rebooting proxy on ${KAMAL_HOSTS}..."
|
||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-proxy-reboot
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
echo "Rebooting Traefik on ${KAMAL_HOSTS}..."
|
||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-traefik-reboot
|
||||
@@ -6,4 +6,5 @@ ARG COMMIT_SHA
|
||||
RUN echo $COMMIT_SHA > /usr/share/nginx/html/version
|
||||
RUN mkdir -p /usr/share/nginx/html/versions && echo "version" > /usr/share/nginx/html/versions/$COMMIT_SHA
|
||||
RUN mkdir -p /usr/share/nginx/html/versions && echo "hidden" > /usr/share/nginx/html/versions/.hidden
|
||||
RUN echo "Up!" > /usr/share/nginx/html/up
|
||||
|
||||
|
||||
@@ -21,11 +21,8 @@ builder:
|
||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||
healthcheck:
|
||||
cmd: wget -qO- http://localhost > /dev/null || exit 1
|
||||
traefik:
|
||||
args:
|
||||
accesslog: true
|
||||
accesslog.format: json
|
||||
image: registry:4443/traefik:v2.10
|
||||
proxy:
|
||||
image: registry:4443/dmcbreen/mproxy:latest
|
||||
accessories:
|
||||
busybox:
|
||||
service: custom-busybox
|
||||
@@ -33,4 +30,4 @@ accessories:
|
||||
cmd: sh -c 'echo "Starting busybox..."; trap exit term; while true; do sleep 1; done'
|
||||
roles:
|
||||
- web
|
||||
stop_wait_time: 1
|
||||
stop_wait_time: 5
|
||||
|
||||
@@ -19,7 +19,7 @@ push_image_to_registry_4443() {
|
||||
|
||||
install_kamal
|
||||
push_image_to_registry_4443 nginx 1-alpine-slim
|
||||
push_image_to_registry_4443 traefik v2.10
|
||||
push_image_to_registry_4443 dmcbreen/mproxy latest
|
||||
push_image_to_registry_4443 busybox 1.36.0
|
||||
|
||||
# .ssh is on a shared volume that persists between runs. Clean it up as the
|
||||
|
||||
@@ -44,10 +44,10 @@ class IntegrationTest < ActiveSupport::TestCase
|
||||
deployer_exec(:kamal, *commands, **options)
|
||||
end
|
||||
|
||||
def assert_app_is_down
|
||||
def assert_app_is_down(response_code: "503")
|
||||
response = app_response
|
||||
debug_response_code(response, "502")
|
||||
assert_equal "502", response.code
|
||||
debug_response_code(response, "503")
|
||||
assert_equal "503", response.code
|
||||
end
|
||||
|
||||
def assert_app_is_up(version: nil)
|
||||
@@ -96,8 +96,8 @@ class IntegrationTest < ActiveSupport::TestCase
|
||||
def assert_200(response)
|
||||
code = response.code
|
||||
if code != "200"
|
||||
puts "Got response code #{code}, here are the traefik logs:"
|
||||
kamal :traefik, :logs
|
||||
puts "Got response code #{code}, here are the proxy logs:"
|
||||
kamal :proxy, :logs
|
||||
puts "And here are the load balancer logs"
|
||||
docker_compose :logs, :load_balancer
|
||||
puts "Tried to get the response code again and got #{app_response.code}"
|
||||
@@ -124,8 +124,8 @@ class IntegrationTest < ActiveSupport::TestCase
|
||||
def debug_response_code(app_response, expected_code)
|
||||
code = app_response.code
|
||||
if code != expected_code
|
||||
puts "Got response code #{code}, here are the traefik logs:"
|
||||
kamal :traefik, :logs
|
||||
puts "Got response code #{code}, here are the proxy logs:"
|
||||
kamal :proxy, :logs
|
||||
puts "And here are the load balancer logs"
|
||||
docker_compose :logs, :load_balancer
|
||||
puts "Tried to get the response code again and got #{app_response.code}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require_relative "integration_test"
|
||||
|
||||
class LockTest < IntegrationTest
|
||||
class IntegrationLockTest < IntegrationTest
|
||||
test "acquire, release, status" do
|
||||
kamal :envify
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require_relative "integration_test"
|
||||
|
||||
class MainTest < IntegrationTest
|
||||
class IntegrationMainTest < IntegrationTest
|
||||
test "envify, deploy, redeploy, rollback, details and audit" do
|
||||
kamal :envify
|
||||
assert_local_env_file "SECRET_TOKEN=1234"
|
||||
@@ -9,7 +9,7 @@ class MainTest < IntegrationTest
|
||||
|
||||
first_version = latest_app_version
|
||||
|
||||
assert_app_is_down
|
||||
assert_app_is_down response_code: "502"
|
||||
|
||||
kamal :deploy
|
||||
assert_app_is_up version: first_version
|
||||
@@ -31,11 +31,11 @@ class MainTest < IntegrationTest
|
||||
assert_app_is_up version: first_version
|
||||
|
||||
details = kamal :details, capture: true
|
||||
assert_match /Traefik Host: vm1/, details
|
||||
assert_match /Traefik Host: vm2/, details
|
||||
assert_match /Proxy Host: vm1/, details
|
||||
assert_match /Proxy Host: vm2/, details
|
||||
assert_match /App Host: vm1/, details
|
||||
assert_match /App Host: vm2/, details
|
||||
assert_match /traefik:v2.10/, details
|
||||
assert_match /dmcbreen\/mproxy:latest/, details
|
||||
assert_match /registry:4443\/app:#{first_version}/, details
|
||||
|
||||
audit = kamal :audit, capture: true
|
||||
@@ -76,7 +76,7 @@ class MainTest < IntegrationTest
|
||||
assert_equal({ user: "root", port: 22, keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
|
||||
assert_equal({ "multiarch" => false, "args" => { "COMMIT_SHA" => version } }, config[:builder])
|
||||
assert_equal [ "--log-opt", "max-size=\"10m\"" ], config[:logging]
|
||||
assert_equal({ "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "cord"=>"/tmp/kamal-cord", "log_lines" => 50, "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1" }, config[:healthcheck])
|
||||
assert_equal({ "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "log_lines" => 50, "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1" }, config[:healthcheck])
|
||||
end
|
||||
|
||||
test "setup and remove" do
|
||||
|
||||
65
test/integration/proxy_test.rb
Normal file
65
test/integration/proxy_test.rb
Normal file
@@ -0,0 +1,65 @@
|
||||
require_relative "integration_test"
|
||||
|
||||
class IntegrationProxyTest < IntegrationTest
|
||||
test "boot, reboot, stop, start, restart, logs, remove" do
|
||||
kamal :envify
|
||||
|
||||
kamal :proxy, :boot
|
||||
assert_proxy_running
|
||||
|
||||
output = kamal :proxy, :reboot, capture: true
|
||||
assert_proxy_running
|
||||
assert_hooks_ran "pre-proxy-reboot", "post-proxy-reboot"
|
||||
assert_match /Rebooting proxy on vm1,vm2.../, output
|
||||
assert_match /Rebooted proxy on vm1,vm2/, output
|
||||
|
||||
output = kamal :proxy, :reboot, :"--rolling", capture: true
|
||||
assert_proxy_running
|
||||
assert_hooks_ran "pre-proxy-reboot", "post-proxy-reboot"
|
||||
assert_match /Rebooting proxy on vm1.../, output
|
||||
assert_match /Rebooted proxy on vm1/, output
|
||||
assert_match /Rebooting proxy on vm2.../, output
|
||||
assert_match /Rebooted proxy on vm2/, output
|
||||
|
||||
kamal :proxy, :boot
|
||||
assert_proxy_running
|
||||
|
||||
# Check booting when booted doesn't raise an error
|
||||
kamal :proxy, :stop
|
||||
assert_proxy_not_running
|
||||
|
||||
# Check booting when stopped works
|
||||
kamal :proxy, :boot
|
||||
assert_proxy_running
|
||||
|
||||
kamal :proxy, :stop
|
||||
assert_proxy_not_running
|
||||
|
||||
kamal :proxy, :start
|
||||
assert_proxy_running
|
||||
|
||||
kamal :proxy, :restart
|
||||
assert_proxy_running
|
||||
|
||||
logs = kamal :proxy, :logs, capture: true
|
||||
assert_match %r["level":"INFO","msg":"Server started","http":80,"https":443], logs
|
||||
|
||||
kamal :proxy, :remove
|
||||
assert_proxy_not_running
|
||||
|
||||
kamal :env, :delete
|
||||
end
|
||||
|
||||
private
|
||||
def assert_proxy_running
|
||||
assert_match %r[registry:4443/dmcbreen/mproxy:latest "mproxy run"], proxy_details
|
||||
end
|
||||
|
||||
def assert_proxy_not_running
|
||||
refute_match %r[registry:4443/dmcbreen/mproxy:latest "mproxy run"], proxy_details
|
||||
end
|
||||
|
||||
def proxy_details
|
||||
kamal :proxy, :details, capture: true
|
||||
end
|
||||
end
|
||||
@@ -1,65 +0,0 @@
|
||||
require_relative "integration_test"
|
||||
|
||||
class TraefikTest < IntegrationTest
|
||||
test "boot, reboot, stop, start, restart, logs, remove" do
|
||||
kamal :envify
|
||||
|
||||
kamal :traefik, :boot
|
||||
assert_traefik_running
|
||||
|
||||
output = kamal :traefik, :reboot, "-y", capture: true
|
||||
assert_traefik_running
|
||||
assert_hooks_ran "pre-traefik-reboot", "post-traefik-reboot"
|
||||
assert_match /Rebooting Traefik on vm1,vm2.../, output
|
||||
assert_match /Rebooted Traefik on vm1,vm2/, output
|
||||
|
||||
output = kamal :traefik, :reboot, :"--rolling", "-y", capture: true
|
||||
assert_traefik_running
|
||||
assert_hooks_ran "pre-traefik-reboot", "post-traefik-reboot"
|
||||
assert_match /Rebooting Traefik on vm1.../, output
|
||||
assert_match /Rebooted Traefik on vm1/, output
|
||||
assert_match /Rebooting Traefik on vm2.../, output
|
||||
assert_match /Rebooted Traefik on vm2/, output
|
||||
|
||||
kamal :traefik, :boot
|
||||
assert_traefik_running
|
||||
|
||||
# Check booting when booted doesn't raise an error
|
||||
kamal :traefik, :stop
|
||||
assert_traefik_not_running
|
||||
|
||||
# Check booting when stopped works
|
||||
kamal :traefik, :boot
|
||||
assert_traefik_running
|
||||
|
||||
kamal :traefik, :stop
|
||||
assert_traefik_not_running
|
||||
|
||||
kamal :traefik, :start
|
||||
assert_traefik_running
|
||||
|
||||
kamal :traefik, :restart
|
||||
assert_traefik_running
|
||||
|
||||
logs = kamal :traefik, :logs, capture: true
|
||||
assert_match /Traefik version [\d.]+ built on/, logs
|
||||
|
||||
kamal :traefik, :remove
|
||||
assert_traefik_not_running
|
||||
|
||||
kamal :env, :delete
|
||||
end
|
||||
|
||||
private
|
||||
def assert_traefik_running
|
||||
assert_match /traefik:v2.10 "\/entrypoint.sh/, traefik_details
|
||||
end
|
||||
|
||||
def assert_traefik_not_running
|
||||
assert_no_match /traefik:v2.10 "\/entrypoint.sh/, traefik_details
|
||||
end
|
||||
|
||||
def traefik_details
|
||||
kamal :traefik, :details, capture: true
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user