diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index 6c51c774d..d21858459 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -18,6 +18,11 @@ def boot(name, prepare: true) execute *accessory.ensure_env_directory upload! accessory.secrets_io, accessory.secrets_path, mode: "0600" execute *accessory.run + + 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 @@ -75,6 +80,10 @@ def start(name) on(hosts) do 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) + execute *accessory.deploy(target: target) + end end end end @@ -87,6 +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 + + if accessory.running_proxy? + 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 end diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 0c1b9009c..3519a4886 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -1,9 +1,13 @@ 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, :publish_args, :env_args, :volume_args, :label_args, :option_args, - :secrets_io, :secrets_path, :env_directory, + :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, to: :accessory_config + delegate :proxy_container_name, to: :config + def initialize(config, name:) super(config) @@ -107,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 6d8f44c60..78e9b9048 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 diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index 804a15029..afd0f856c 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -3,7 +3,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] @@ -18,6 +18,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 @@ -100,6 +102,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 diff --git a/lib/kamal/configuration/docs/accessory.yml b/lib/kamal/configuration/docs/accessory.yml index 86d49b77a..e81cf3919 100644 --- a/lib/kamal/configuration/docs/accessory.yml +++ b/lib/kamal/configuration/docs/accessory.yml @@ -90,3 +90,93 @@ accessories: # They are not created or copied before mounting: volumes: - /path/to/mysql-logs:/var/log/mysql + + # 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 diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index f3d71ffd3..68603203d 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" + } } } } @@ -158,6 +161,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\" --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 + + test "remove" do + assert_equal \ + "docker exec kamal-proxy kamal-proxy remove custom-busybox", + new_command(:busybox).remove.join(" ") + end + private def new_command(accessory) Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory) diff --git a/test/configuration/accessory_test.rb b/test/configuration/accessory_test.rb index 2615dab60..0a9c452f9 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" } } } @@ -152,4 +155,9 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase test "options" do assert_equal [ "--cpus", "\"4\"", "--memory", "\"2GB\"" ], @config.accessory(:redis).option_args end + + test "proxy" do + assert @config.accessory(:monitoring).running_proxy? + assert_equal [ "monitoring.example.com" ], @config.accessory(:monitoring).proxy.hosts + end end