Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy for accessories #981

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
14 changes: 14 additions & 0 deletions lib/kamal/cli/accessory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 9 additions & 1 deletion lib/kamal/commands/accessory.rb
Original file line number Diff line number Diff line change
@@ -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,
:network_args, :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)
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion lib/kamal/commands/app.rb
Original file line number Diff line number Diff line change
@@ -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 ]

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
15 changes: 14 additions & 1 deletion lib/kamal/configuration/accessory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
89 changes: 89 additions & 0 deletions lib/kamal/configuration/docs/accessory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,92 @@ accessories:
# Defaults to kamal:
network: custom

# 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
17 changes: 16 additions & 1 deletion test/commands/accessory_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
Expand Down Expand Up @@ -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\" --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)
Expand Down
8 changes: 8 additions & 0 deletions test/configuration/accessory_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
"options" => {
"cpus" => "4",
"memory" => "2GB"
},
"proxy" => {
"host" => "monitoring.example.com"
}
}
}
Expand Down Expand Up @@ -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.hosts
end
end