From 70d2c71734c6778fa22d91e48a91a641b9129c48 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Thu, 31 Oct 2024 10:50:48 +0400 Subject: [PATCH 01/12] Added commands to deploy accessory to kamal-proxy --- lib/kamal/cli/accessory.rb | 3 +++ lib/kamal/commands/accessory.rb | 18 +++++++++++++++++- lib/kamal/configuration/accessory.rb | 15 ++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index d707820aa..885890491 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -18,6 +18,7 @@ def boot(name, prepare: true) execute *accessory.ensure_env_directory upload! accessory.secrets_io, accessory.secrets_path, mode: "0600" execute *accessory.run + execute *accessory.deploy if accessory.running_proxy? end end end @@ -75,6 +76,7 @@ def start(name) on(hosts) do execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug execute *accessory.start + execute *accessory.deploy if accessory.running_proxy? end end end @@ -87,6 +89,7 @@ def stop(name) on(hosts) do execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug execute *accessory.stop, raise_on_non_zero_exit: false + # execute *accessory.remove if accessory.running_proxy? end end end diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 9abb6dfc0..9068ba9b5 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -2,8 +2,10 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base attr_reader :accessory_config delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd, :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args, - :secrets_io, :secrets_path, :env_directory, + :secrets_io, :secrets_path, :env_directory, :running_proxy?, to: :accessory_config + delegate :proxy_container_name, to: :config + def initialize(config, name:) super(config) @@ -38,6 +40,16 @@ def info docker :ps, *service_filter end + def deploy + target = container_id_for(container_name: service_name, only_running: true) + proxy_exec :deploy, service_name, *accessory_config.proxy.deploy_command_args(target: target) if target + end + + def remove + target = container_id_for(container_name: service_name, only_running: true) + proxy_exec :remove, service_name, *accessory_config.proxy.remove_command_args(target: target) if target + end + def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil) pipe \ @@ -110,4 +122,8 @@ def ensure_env_directory def service_filter [ "--filter", "label=service=#{service_name}" ] end + + def proxy_exec(*command) + docker :exec, proxy_container_name, "kamal-proxy", *command + end end diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index aeb5f334f..2203e64c5 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -5,7 +5,7 @@ class Kamal::Configuration::Accessory delegate :argumentize, :optionize, to: Kamal::Utils - attr_reader :name, :accessory_config, :env + attr_reader :name, :accessory_config, :env, :proxy def initialize(name, config:) @name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name] @@ -20,6 +20,8 @@ def initialize(name, config:) config: accessory_config.fetch("env", {}), secrets: config.secrets, context: "accessories/#{name}/env" + + # initialize_proxy if running_proxy? end def service_name @@ -106,6 +108,17 @@ def cmd accessory_config["cmd"] end + def running_proxy? + @accessory_config["proxy"].present? + end + + def initialize_proxy + @proxy = Kamal::Configuration::Proxy.new \ + config: config, + proxy_config: accessory_config["proxy"], + context: "accessories/#{name}/proxy" + end + private attr_accessor :config From 4c778de2d93ef1b5e8e7e71174003b7467d43d80 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Thu, 31 Oct 2024 10:51:51 +0400 Subject: [PATCH 02/12] Added tests for accessory configuration with proxy --- lib/kamal/cli/accessory.rb | 2 +- lib/kamal/configuration/accessory.rb | 2 +- lib/kamal/configuration/docs/accessory.yml | 84 ++++++++++++++++++++++ test/configuration/accessory_test.rb | 8 +++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index 885890491..e37e41169 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -89,7 +89,7 @@ def stop(name) on(hosts) do execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug execute *accessory.stop, raise_on_non_zero_exit: false - # execute *accessory.remove if accessory.running_proxy? + execute *accessory.remove if accessory.running_proxy? end end end diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index 2203e64c5..2728607d5 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -21,7 +21,7 @@ def initialize(name, config:) secrets: config.secrets, context: "accessories/#{name}/env" - # initialize_proxy if running_proxy? + initialize_proxy if running_proxy? end def service_name diff --git a/lib/kamal/configuration/docs/accessory.yml b/lib/kamal/configuration/docs/accessory.yml index e77bf754f..f7e78c062 100644 --- a/lib/kamal/configuration/docs/accessory.yml +++ b/lib/kamal/configuration/docs/accessory.yml @@ -98,3 +98,87 @@ accessories: # Defaults to kamal: network: custom + # Proxy + # + proxy: + # Host + # + # The hosts that will be used to serve the app. The proxy will only route requests + # to this host to your app. + # + # If no hosts are set, then all requests will be forwarded, except for matching + # requests for other apps deployed on that server that do have a host set. + host: foo.example.com + + # App port + # + # The port the application container is exposed on + # + # Defaults to 80 + app_port: 3000 + + # SSL + # + # kamal-proxy can provide automatic HTTPS for your application via Let's Encrypt. + # + # This requires that we are deploying to a one server and the host option is set. + # The host value must point to the server we are deploying to and port 443 must be + # open for the Let's Encrypt challenge to succeed. + # + # Defaults to false + ssl: true + + # Response timeout + # + # How long to wait for requests to complete before timing out, defaults to 30 seconds + response_timeout: 10 + + # Healthcheck + # + # When deploying, the proxy will by default hit /up once every second until we hit + # the deploy timeout, with a 5 second timeout for each request. + # + # Once the app is up, the proxy will stop hitting the healthcheck endpoint. + healthcheck: + interval: 3 + path: /health + timeout: 3 + + # Buffering + # + # Whether to buffer request and response bodies in the proxy + # + # By default buffering is enabled with a max request body size of 1GB and no limit + # for response size. + # + # You can also set the memory limit for buffering, which defaults to 1MB, anything + # larger than that is written to disk. + buffering: + requests: true + responses: true + max_request_body: 40_000_000 + max_response_body: 0 + memory: 2_000_000 + + # Logging + # + # Configure request logging for the proxy + # You can specify request and response headers to log. + # By default, Cache-Control, Last-Modified and User-Agent request headers are logged + logging: + request_headers: + - Cache-Control + - X-Forwarded-Proto + response_headers: + - X-Request-ID + - X-Request-Start + + # Forward headers + # + # Whether to forward the X-Forwarded-For and X-Forwarded-Proto headers. + # + # If you are behind a trusted proxy, you can set this to true to forward the headers. + # + # By default kamal-proxy will not forward the headers the ssl option is set to true, and + # will forward them if it is set to false. + forward_headers: true diff --git a/test/configuration/accessory_test.rb b/test/configuration/accessory_test.rb index f52209026..9440eca65 100644 --- a/test/configuration/accessory_test.rb +++ b/test/configuration/accessory_test.rb @@ -63,6 +63,9 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase "options" => { "cpus" => "4", "memory" => "2GB" + }, + "proxy" => { + "host" => "monitoring.example.com" } } } @@ -161,4 +164,9 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase @deploy[:accessories]["mysql"]["network"] = "database" assert_equal [ "--network", "database" ], @config.accessory(:mysql).network_args end + + test "proxy" do + assert @config.accessory(:monitoring).running_proxy? + assert_equal "monitoring.example.com", @config.accessory(:monitoring).proxy.host + end end From f4b7c886fbf1fa4c662cc2b3dd22a66c8664d36d Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Fri, 27 Sep 2024 12:24:54 +0400 Subject: [PATCH 03/12] Added tests for accessory deploy and remove commands --- lib/kamal/cli/accessory.rb | 17 ++++++++++++++--- lib/kamal/commands/accessory.rb | 10 ++++------ test/commands/accessory_test.rb | 17 ++++++++++++++++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index e37e41169..70aa208cb 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -18,7 +18,11 @@ def boot(name, prepare: true) execute *accessory.ensure_env_directory upload! accessory.secrets_io, accessory.secrets_path, mode: "0600" execute *accessory.run - execute *accessory.deploy if accessory.running_proxy? + + if accessory.running_proxy? + target = accessory.container_id_for(container_name: accessory.service_name, only_running: true) + execute *accessory.deploy(target: target) + end end end end @@ -76,7 +80,10 @@ def start(name) on(hosts) do execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug execute *accessory.start - execute *accessory.deploy if accessory.running_proxy? + if accessory.running_proxy? + target = container_id_for(container_name: service_name, only_running: true) + execute *accessory.deploy(target: target) + end end end end @@ -89,7 +96,11 @@ def stop(name) on(hosts) do execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug execute *accessory.stop, raise_on_non_zero_exit: false - execute *accessory.remove if accessory.running_proxy? + + if accessory.running_proxy? + target = accessory.container_id_for(container_name: accessory.service_name, only_running: true) + execute *accessory.remove(target: target) + end end end end diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 9068ba9b5..e002b28d0 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -40,14 +40,12 @@ def info docker :ps, *service_filter end - def deploy - target = container_id_for(container_name: service_name, only_running: true) - proxy_exec :deploy, service_name, *accessory_config.proxy.deploy_command_args(target: target) if target + def deploy(target:) + proxy_exec :deploy, service_name, *accessory_config.proxy.deploy_command_args(target: target) end - def remove - target = container_id_for(container_name: service_name, only_running: true) - proxy_exec :remove, service_name, *accessory_config.proxy.remove_command_args(target: target) if target + def remove(target:) + proxy_exec :remove, service_name, *accessory_config.proxy.remove_command_args(target: target) end diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 1befd9e64..9909de3a1 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -39,7 +39,10 @@ class CommandsAccessoryTest < ActiveSupport::TestCase "busybox" => { "service" => "custom-busybox", "image" => "busybox:latest", - "host" => "1.1.1.7" + "host" => "1.1.1.7", + "proxy" => { + "host" => "busybox.example.com" + } } } } @@ -166,6 +169,18 @@ class CommandsAccessoryTest < ActiveSupport::TestCase new_command(:mysql).remove_image.join(" ") end + test "deploy" do + assert_equal \ + "docker exec kamal-proxy kamal-proxy deploy custom-busybox --target \"172.1.0.2:80\" --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", + new_command(:busybox).deploy(target: "172.1.0.2").join(" ") + end + + test "remove" do + assert_equal \ + "docker exec kamal-proxy kamal-proxy remove custom-busybox --target \"172.1.0.2:80\"", + new_command(:busybox).remove(target: "172.1.0.2").join(" ") + end + private def new_command(accessory) Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory) From aa2906086a458e607f669b04c6e20ae9180a0992 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Fri, 27 Sep 2024 12:38:12 +0400 Subject: [PATCH 04/12] Added host to the expected accessory deploy command result --- test/commands/accessory_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 9909de3a1..4da4669a4 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -171,7 +171,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase test "deploy" do assert_equal \ - "docker exec kamal-proxy kamal-proxy deploy custom-busybox --target \"172.1.0.2:80\" --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", + "docker exec kamal-proxy kamal-proxy deploy custom-busybox --target \"172.1.0.2:80\" --host \"busybox.example.com\" --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", new_command(:busybox).deploy(target: "172.1.0.2").join(" ") end From 86657b0172e68460aeb70ed3e4856cdbe2cc594a Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Thu, 31 Oct 2024 10:56:43 +0400 Subject: [PATCH 05/12] Fixed kamal-proxy remove command --- lib/kamal/cli/accessory.rb | 4 ++-- lib/kamal/commands/accessory.rb | 8 ++++---- test/commands/accessory_test.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index 70aa208cb..4cebed91b 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -98,8 +98,8 @@ def stop(name) execute *accessory.stop, raise_on_non_zero_exit: false if accessory.running_proxy? - target = accessory.container_id_for(container_name: accessory.service_name, only_running: true) - execute *accessory.remove(target: target) + target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip + execute *accessory.remove if target end end end diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index e002b28d0..4db107514 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -2,7 +2,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base attr_reader :accessory_config delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd, :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args, - :secrets_io, :secrets_path, :env_directory, :running_proxy?, + :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, to: :accessory_config delegate :proxy_container_name, to: :config @@ -41,11 +41,11 @@ def info end def deploy(target:) - proxy_exec :deploy, service_name, *accessory_config.proxy.deploy_command_args(target: target) + proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target) end - def remove(target:) - proxy_exec :remove, service_name, *accessory_config.proxy.remove_command_args(target: target) + def remove + proxy_exec :remove, service_name end diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 4da4669a4..8a8f929ad 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -177,7 +177,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase test "remove" do assert_equal \ - "docker exec kamal-proxy kamal-proxy remove custom-busybox --target \"172.1.0.2:80\"", + "docker exec kamal-proxy kamal-proxy remove custom-busybox", new_command(:busybox).remove(target: "172.1.0.2").join(" ") end From 4d8241ebab9036aa0d6839a5372b506134d17e20 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Wed, 9 Oct 2024 12:33:22 +0400 Subject: [PATCH 06/12] Fixed kamal-proxy remove command --- test/commands/accessory_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 8a8f929ad..633cfc0a5 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -178,7 +178,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase test "remove" do assert_equal \ "docker exec kamal-proxy kamal-proxy remove custom-busybox", - new_command(:busybox).remove(target: "172.1.0.2").join(" ") + new_command(:busybox).remove.join(" ") end private From 006fa0de17817d74b1471517db6fbf624340c327 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Sat, 28 Sep 2024 20:56:13 +0400 Subject: [PATCH 07/12] Extracted proxy commands to a module --- lib/kamal/commands/accessory.rb | 14 ++++++-------- lib/kamal/commands/app.rb | 10 +++++++++- lib/kamal/commands/{app/proxy.rb => proxy/exec.rb} | 6 +++--- 3 files changed, 18 insertions(+), 12 deletions(-) rename lib/kamal/commands/{app/proxy.rb => proxy/exec.rb} (54%) diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 4db107514..cc72da8ea 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -1,4 +1,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base + include Kamal::Commands::Proxy::Exec + attr_reader :accessory_config delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd, :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args, @@ -40,14 +42,6 @@ def info docker :ps, *service_filter end - def deploy(target:) - proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target) - end - - def remove - proxy_exec :remove, service_name - end - def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil) pipe \ @@ -117,6 +111,10 @@ def ensure_env_directory end private + def proxy_deploy_command_args(target:) + proxy.deploy_command_args(target: target) + end + def service_filter [ "--filter", "label=service=#{service_name}" ] end diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 6c4df0e4a..5bbbdb516 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -1,5 +1,5 @@ class Kamal::Commands::App < Kamal::Commands::Base - include Assets, Containers, Execution, Images, Logging, Proxy + include Assets, Containers, Execution, Images, Logging, Kamal::Commands::Proxy::Exec ACTIVE_DOCKER_STATUSES = [ :running, :restarting ] @@ -76,6 +76,14 @@ def ensure_env_directory end private + def service_name + role.container_prefix + end + + def proxy_deploy_command_args(target:) + role.proxy.deploy_command_args(target: target) + end + def latest_image_id docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'" end diff --git a/lib/kamal/commands/app/proxy.rb b/lib/kamal/commands/proxy/exec.rb similarity index 54% rename from lib/kamal/commands/app/proxy.rb rename to lib/kamal/commands/proxy/exec.rb index 777a4aaf5..ac6f30a9b 100644 --- a/lib/kamal/commands/app/proxy.rb +++ b/lib/kamal/commands/proxy/exec.rb @@ -1,12 +1,12 @@ -module Kamal::Commands::App::Proxy +module Kamal::Commands::Proxy::Exec delegate :proxy_container_name, to: :config def deploy(target:) - proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target) + proxy_exec :deploy, service_name, *proxy_deploy_command_args(target: target) end def remove - proxy_exec :remove, role.container_prefix + proxy_exec :remove, service_name end private From 92046247526f2cd768e3577851db27e7b73237a5 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Mon, 30 Sep 2024 13:07:05 +0400 Subject: [PATCH 08/12] Removed duplicated method --- lib/kamal/commands/accessory.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index cc72da8ea..552a5c3bf 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -118,8 +118,4 @@ def proxy_deploy_command_args(target:) def service_filter [ "--filter", "label=service=#{service_name}" ] end - - def proxy_exec(*command) - docker :exec, proxy_container_name, "kamal-proxy", *command - end end From f52826b2d6e39d6fe50936ec1fbd9df0100d15d9 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Wed, 9 Oct 2024 14:06:38 +0400 Subject: [PATCH 09/12] Updated accessory proxy to support hosts option --- lib/kamal/configuration/docs/accessory.yml | 7 ++++++- test/commands/accessory_test.rb | 2 +- test/configuration/accessory_test.rb | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/kamal/configuration/docs/accessory.yml b/lib/kamal/configuration/docs/accessory.yml index f7e78c062..a22cdeb0f 100644 --- a/lib/kamal/configuration/docs/accessory.yml +++ b/lib/kamal/configuration/docs/accessory.yml @@ -101,14 +101,19 @@ accessories: # Proxy # proxy: - # Host + # Hosts # # The hosts that will be used to serve the app. The proxy will only route requests # to this host to your app. # # If no hosts are set, then all requests will be forwarded, except for matching # requests for other apps deployed on that server that do have a host set. + # + # Specify one of `host` or `hosts`. host: foo.example.com + hosts: + - foo.example.com + - bar.example.com # App port # diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 633cfc0a5..b9bcca7e3 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -171,7 +171,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase test "deploy" do assert_equal \ - "docker exec kamal-proxy kamal-proxy deploy custom-busybox --target \"172.1.0.2:80\" --host \"busybox.example.com\" --deploy-timeout \"30s\" --drain-timeout \"30s\" --buffer-requests --buffer-responses --log-request-header \"Cache-Control\" --log-request-header \"Last-Modified\" --log-request-header \"User-Agent\"", + "docker exec kamal-proxy kamal-proxy deploy custom-busybox --target=\"172.1.0.2:80\" --host=\"busybox.example.com\" --deploy-timeout=\"30s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\"", new_command(:busybox).deploy(target: "172.1.0.2").join(" ") end diff --git a/test/configuration/accessory_test.rb b/test/configuration/accessory_test.rb index 9440eca65..d15a48ad3 100644 --- a/test/configuration/accessory_test.rb +++ b/test/configuration/accessory_test.rb @@ -167,6 +167,6 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase test "proxy" do assert @config.accessory(:monitoring).running_proxy? - assert_equal "monitoring.example.com", @config.accessory(:monitoring).proxy.host + assert_equal [ "monitoring.example.com" ], @config.accessory(:monitoring).proxy.hosts end end From 14068b32b1a4a6114a94f4b64186d04401f46e59 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Thu, 21 Nov 2024 22:38:06 +0400 Subject: [PATCH 10/12] Added alias to accessories proxy configuration example --- lib/kamal/configuration/docs/accessory.yml | 87 +--------------------- 1 file changed, 1 insertion(+), 86 deletions(-) diff --git a/lib/kamal/configuration/docs/accessory.yml b/lib/kamal/configuration/docs/accessory.yml index a22cdeb0f..fab2989f4 100644 --- a/lib/kamal/configuration/docs/accessory.yml +++ b/lib/kamal/configuration/docs/accessory.yml @@ -101,89 +101,4 @@ accessories: # Proxy # proxy: - # Hosts - # - # The hosts that will be used to serve the app. The proxy will only route requests - # to this host to your app. - # - # If no hosts are set, then all requests will be forwarded, except for matching - # requests for other apps deployed on that server that do have a host set. - # - # Specify one of `host` or `hosts`. - host: foo.example.com - hosts: - - foo.example.com - - bar.example.com - - # App port - # - # The port the application container is exposed on - # - # Defaults to 80 - app_port: 3000 - - # SSL - # - # kamal-proxy can provide automatic HTTPS for your application via Let's Encrypt. - # - # This requires that we are deploying to a one server and the host option is set. - # The host value must point to the server we are deploying to and port 443 must be - # open for the Let's Encrypt challenge to succeed. - # - # Defaults to false - ssl: true - - # Response timeout - # - # How long to wait for requests to complete before timing out, defaults to 30 seconds - response_timeout: 10 - - # Healthcheck - # - # When deploying, the proxy will by default hit /up once every second until we hit - # the deploy timeout, with a 5 second timeout for each request. - # - # Once the app is up, the proxy will stop hitting the healthcheck endpoint. - healthcheck: - interval: 3 - path: /health - timeout: 3 - - # Buffering - # - # Whether to buffer request and response bodies in the proxy - # - # By default buffering is enabled with a max request body size of 1GB and no limit - # for response size. - # - # You can also set the memory limit for buffering, which defaults to 1MB, anything - # larger than that is written to disk. - buffering: - requests: true - responses: true - max_request_body: 40_000_000 - max_response_body: 0 - memory: 2_000_000 - - # Logging - # - # Configure request logging for the proxy - # You can specify request and response headers to log. - # By default, Cache-Control, Last-Modified and User-Agent request headers are logged - logging: - request_headers: - - Cache-Control - - X-Forwarded-Proto - response_headers: - - X-Request-ID - - X-Request-Start - - # Forward headers - # - # Whether to forward the X-Forwarded-For and X-Forwarded-Proto headers. - # - # If you are behind a trusted proxy, you can set this to true to forward the headers. - # - # By default kamal-proxy will not forward the headers the ssl option is set to true, and - # will forward them if it is set to false. - forward_headers: true + ... \ No newline at end of file From f367ca8ea56dc2622cde668822a64df1673c849f Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Thu, 21 Nov 2024 23:07:55 +0400 Subject: [PATCH 11/12] Replaced Kamal::Commands::Proxy::Exec with Kamal::Commands::App::Proxy and Kamal::Commands::Accessory::Proxy --- lib/kamal/commands/accessory.rb | 6 +----- .../{proxy/exec.rb => accessory/proxy.rb} | 4 ++-- lib/kamal/commands/app.rb | 10 +--------- lib/kamal/commands/app/proxy.rb | 16 ++++++++++++++++ 4 files changed, 20 insertions(+), 16 deletions(-) rename lib/kamal/commands/{proxy/exec.rb => accessory/proxy.rb} (71%) create mode 100644 lib/kamal/commands/app/proxy.rb diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 552a5c3bf..281b87138 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -1,5 +1,5 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base - include Kamal::Commands::Proxy::Exec + include Proxy attr_reader :accessory_config delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd, @@ -111,10 +111,6 @@ def ensure_env_directory end private - def proxy_deploy_command_args(target:) - proxy.deploy_command_args(target: target) - end - def service_filter [ "--filter", "label=service=#{service_name}" ] end diff --git a/lib/kamal/commands/proxy/exec.rb b/lib/kamal/commands/accessory/proxy.rb similarity index 71% rename from lib/kamal/commands/proxy/exec.rb rename to lib/kamal/commands/accessory/proxy.rb index ac6f30a9b..195a321bf 100644 --- a/lib/kamal/commands/proxy/exec.rb +++ b/lib/kamal/commands/accessory/proxy.rb @@ -1,8 +1,8 @@ -module Kamal::Commands::Proxy::Exec +module Kamal::Commands::Accessory::Proxy delegate :proxy_container_name, to: :config def deploy(target:) - proxy_exec :deploy, service_name, *proxy_deploy_command_args(target: target) + proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target) end def remove diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 5bbbdb516..6c4df0e4a 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -1,5 +1,5 @@ class Kamal::Commands::App < Kamal::Commands::Base - include Assets, Containers, Execution, Images, Logging, Kamal::Commands::Proxy::Exec + include Assets, Containers, Execution, Images, Logging, Proxy ACTIVE_DOCKER_STATUSES = [ :running, :restarting ] @@ -76,14 +76,6 @@ def ensure_env_directory end private - def service_name - role.container_prefix - end - - def proxy_deploy_command_args(target:) - role.proxy.deploy_command_args(target: target) - end - def latest_image_id docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'" end diff --git a/lib/kamal/commands/app/proxy.rb b/lib/kamal/commands/app/proxy.rb new file mode 100644 index 000000000..777a4aaf5 --- /dev/null +++ b/lib/kamal/commands/app/proxy.rb @@ -0,0 +1,16 @@ +module Kamal::Commands::App::Proxy + delegate :proxy_container_name, to: :config + + def deploy(target:) + proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target) + end + + def remove + proxy_exec :remove, role.container_prefix + end + + private + def proxy_exec(*command) + docker :exec, proxy_container_name, "kamal-proxy", *command + end +end From eee47d10eee7738a18f6a9acb846949d373c8a53 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Tue, 26 Nov 2024 13:34:51 +0400 Subject: [PATCH 12/12] Added an integration test for proxied accessory using Busybox and netcat --- lib/kamal/cli/accessory.rb | 4 +- test/integration/docker/deployer/Dockerfile | 2 + .../app_with_proxied_accessory/Dockerfile | 9 +++ .../config/deploy.yml | 44 +++++++++++++ .../app_with_proxied_accessory/default.conf | 17 +++++ test/integration/proxied_accessory_test.rb | 63 +++++++++++++++++++ 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 test/integration/docker/deployer/app_with_proxied_accessory/Dockerfile create mode 100644 test/integration/docker/deployer/app_with_proxied_accessory/config/deploy.yml create mode 100644 test/integration/docker/deployer/app_with_proxied_accessory/default.conf create mode 100644 test/integration/proxied_accessory_test.rb diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index 4cebed91b..4de8832b9 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -20,7 +20,7 @@ def boot(name, prepare: true) execute *accessory.run if accessory.running_proxy? - target = accessory.container_id_for(container_name: accessory.service_name, only_running: true) + target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip execute *accessory.deploy(target: target) end end @@ -81,7 +81,7 @@ def start(name) execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug execute *accessory.start if accessory.running_proxy? - target = container_id_for(container_name: service_name, only_running: true) + target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip execute *accessory.deploy(target: target) end end diff --git a/test/integration/docker/deployer/Dockerfile b/test/integration/docker/deployer/Dockerfile index c71328619..c25747a2c 100644 --- a/test/integration/docker/deployer/Dockerfile +++ b/test/integration/docker/deployer/Dockerfile @@ -20,6 +20,7 @@ COPY *.sh . COPY app/ app/ COPY app_with_roles/ app_with_roles/ COPY app_with_traefik/ app_with_traefik/ +COPY app_with_proxied_accessory/ app_with_proxied_accessory/ RUN rm -rf /root/.ssh RUN ln -s /shared/ssh /root/.ssh @@ -30,6 +31,7 @@ RUN git config --global user.name "Deployer" RUN cd app && git init && git add . && git commit -am "Initial version" RUN cd app_with_roles && git init && git add . && git commit -am "Initial version" RUN cd app_with_traefik && git init && git add . && git commit -am "Initial version" +RUN cd app_with_proxied_accessory && git init && git add . && git commit -am "Initial version" HEALTHCHECK --interval=1s CMD pgrep sleep diff --git a/test/integration/docker/deployer/app_with_proxied_accessory/Dockerfile b/test/integration/docker/deployer/app_with_proxied_accessory/Dockerfile new file mode 100644 index 000000000..0e6237df4 --- /dev/null +++ b/test/integration/docker/deployer/app_with_proxied_accessory/Dockerfile @@ -0,0 +1,9 @@ +FROM registry:4443/nginx:1-alpine-slim + +COPY default.conf /etc/nginx/conf.d/default.conf + +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 diff --git a/test/integration/docker/deployer/app_with_proxied_accessory/config/deploy.yml b/test/integration/docker/deployer/app_with_proxied_accessory/config/deploy.yml new file mode 100644 index 000000000..bdb547aeb --- /dev/null +++ b/test/integration/docker/deployer/app_with_proxied_accessory/config/deploy.yml @@ -0,0 +1,44 @@ +service: app_with_proxied_accessory +image: app_with_proxied_accessory +servers: + - vm1 +env: + clear: + CLEAR_TOKEN: 4321 + CLEAR_TAG: "" + HOST_TOKEN: "${HOST_TOKEN}" +asset_path: /usr/share/nginx/html/versions +proxy: + host: 127.0.0.1 +registry: + server: registry:4443 + username: root + password: root +builder: + driver: docker + arch: <%= Kamal::Utils.docker_arch %> + args: + COMMIT_SHA: <%= `git rev-parse HEAD` %> +accessories: + busybox: + service: custom-busybox + image: registry:4443/busybox:1.36.0 + cmd: sh -c 'echo "Starting busybox..."; trap exit term; while true; do sleep 1; done' + roles: + - web + netcat: + service: netcat + image: registry:4443/busybox:1.36.0 + cmd: > + sh -c 'echo "Starting netcat..."; while true; do echo -e "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello Ruby" | nc -l -p 80; done' + roles: + - web + port: 12345:80 + proxy: + host: netcat + ssl: false + healthcheck: + interval: 1 + timeout: 1 + path: "/" + diff --git a/test/integration/docker/deployer/app_with_proxied_accessory/default.conf b/test/integration/docker/deployer/app_with_proxied_accessory/default.conf new file mode 100644 index 000000000..e37a9bc1f --- /dev/null +++ b/test/integration/docker/deployer/app_with_proxied_accessory/default.conf @@ -0,0 +1,17 @@ +server { + listen 80; + listen [::]:80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/test/integration/proxied_accessory_test.rb b/test/integration/proxied_accessory_test.rb new file mode 100644 index 000000000..10f3cff89 --- /dev/null +++ b/test/integration/proxied_accessory_test.rb @@ -0,0 +1,63 @@ +require_relative "integration_test" + +class ProxiedAccessoryTest < IntegrationTest + test "boot, stop, start, restart, logs, remove" do + @app = "app_with_proxied_accessory" + + kamal :deploy + + kamal :accessory, :boot, :netcat + assert_accessory_running :netcat + assert_netcat_is_up + + kamal :accessory, :stop, :netcat + assert_accessory_not_running :netcat + assert_netcat_not_found + + kamal :accessory, :start, :netcat + assert_accessory_running :netcat + assert_netcat_is_up + + kamal :accessory, :restart, :netcat + assert_accessory_running :netcat + assert_netcat_is_up + + kamal :accessory, :remove, :netcat, "-y" + assert_accessory_not_running :netcat + assert_netcat_not_found + end + + private + def assert_accessory_running(name) + assert_match /registry:4443\/busybox:1.36.0 "sh -c 'echo \\"Start/, accessory_details(name) + end + + def assert_accessory_not_running(name) + assert_no_match /registry:4443\/busybox:1.36.0 "sh -c 'echo \\"Start/, accessory_details(name) + end + + def accessory_details(name) + kamal :accessory, :details, name, capture: true + end + + def assert_netcat_is_up + response = netcat_response + debug_response_code(response, "200") + assert_equal "200", response.code + end + + def assert_netcat_not_found + response = netcat_response + debug_response_code(response, "404") + assert_equal "404", response.code + end + + def netcat_response + uri = URI.parse("http://127.0.0.1:12345/up") + http = Net::HTTP.new(uri.host, uri.port) + request = Net::HTTP::Get.new(uri) + request["Host"] = "netcat" + + http.request(request) + end +end