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 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/kamal/cli/accessory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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 @@ -73,6 +78,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 @@ -85,6 +94,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
2 changes: 1 addition & 1 deletion lib/kamal/cli/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def stop
version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
endpoint = capture_with_info(*app.container_id_for_version(version)).strip
if endpoint.present?
execute *app.remove(target: endpoint), raise_on_non_zero_exit: false
execute *app.remove, raise_on_non_zero_exit: false
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,
: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
16 changes: 0 additions & 16 deletions lib/kamal/commands/app/proxy.rb

This file was deleted.

16 changes: 16 additions & 0 deletions lib/kamal/commands/proxy/exec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Kamal::Commands::Proxy::Exec
delegate :proxy_container_name, to: :config

def deploy(target:)
proxy_exec :deploy, service_name, *proxy_deploy_command_args(target: target)
end

def remove
proxy_exec :remove, service_name
end

private
def proxy_exec(*command)
docker :exec, proxy_container_name, "kamal-proxy", *command
end
end
15 changes: 14 additions & 1 deletion lib/kamal/configuration/accessory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
85 changes: 85 additions & 0 deletions lib/kamal/configuration/docs/accessory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,88 @@ accessories:
# They are not created or copied before mounting
volumes:
- /path/to/mysql-logs:/var/log/mysql

# 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
4 changes: 0 additions & 4 deletions lib/kamal/configuration/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ def deploy_command_args(target:)
optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options)
end

def remove_command_args(target:)
optionize({ target: "#{target}:#{app_port}" })
end

def merge(other)
self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config)
end
Expand Down
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 @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions test/commands/app_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ class CommandsAppTest < ActiveSupport::TestCase

test "remove" do
assert_equal \
"docker exec kamal-proxy kamal-proxy remove app-web --target \"172.1.0.2:80\"",
new_command.remove(target: "172.1.0.2").join(" ")
"docker exec kamal-proxy kamal-proxy remove app-web",
new_command.remove.join(" ")
end


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 @@ -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.host
end
end
4 changes: 2 additions & 2 deletions test/integration/app_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class AppTest < IntegrationTest

kamal :app, :stop

assert_app_is_down
assert_app_not_found

kamal :app, :start

Expand Down Expand Up @@ -48,7 +48,7 @@ class AppTest < IntegrationTest

kamal :app, :remove

assert_app_is_down
assert_app_not_found
assert_app_directory_removed
end
end
6 changes: 6 additions & 0 deletions test/integration/integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ def assert_app_is_down
assert_equal "502", response.code
end

def assert_app_not_found
response = app_response
debug_response_code(response, "404")
assert_equal "404", response.code
end

def assert_app_is_up(version: nil, app: @app)
response = app_response(app: app)
debug_response_code(response, "200")
Expand Down