Skip to content
Merged
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
33 changes: 33 additions & 0 deletions lib/kamal/configuration/docs/proxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ proxy:
# HTTPS traffic), you can disable this redirect by setting `ssl_redirect: false`:
ssl_redirect: false

# SSL staging
#
# When automatic SSL is enabled, use the Let's Encrypt staging environment
# for certificate provisioning, so you can test your SSL configuration
# without running into Let's Encrypt's production rate limits. Certificates
# issued by the staging environment are not trusted by browsers.
#
# Defaults to `false`:
ssl_staging: true

# Forward headers
#
# Whether to forward the `X-Forwarded-For` and `X-Forwarded-Proto` headers.
Expand Down Expand Up @@ -108,6 +118,29 @@ proxy:
# specify --strip-path-prefix=false
strip_path_prefix: false

# Read-only targets
#
# kamal-proxy can split traffic between the deployed (writer) targets and a
# set of read-only targets, e.g. app instances backed by database replicas.
# Read requests are routed to the read targets; write requests always go to
# the writers.
#
# Targets are host:port addresses reachable from the proxy.
read_targets:
- 192.168.0.2:3000
- 192.168.0.3:3000

# Whether to route WebSocket traffic to read targets, when available.
#
# Defaults to `false`:
read_target_websockets: true

# How long, in seconds, a client's reads stick to the writer after it makes
# a write, so clients always read their own writes.
#
# Defaults to 1 second:
writer_affinity_timeout: 10

# Healthcheck
#
# When deploying, the proxy will by default hit `/up` once every second until we hit
Expand Down
4 changes: 4 additions & 0 deletions lib/kamal/configuration/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def deploy_options
{
host: hosts,
tls: ssl? ? true : nil,
"tls-staging": proxy_config["ssl_staging"] ? true : nil,
"tls-certificate-path": container_tls_cert,
"tls-private-key-path": container_tls_key,
"deploy-timeout": seconds_duration(config.deploy_timeout),
Expand All @@ -79,6 +80,9 @@ def deploy_options
"health-check-timeout": seconds_duration(proxy_config.dig("healthcheck", "timeout")),
"health-check-path": proxy_config.dig("healthcheck", "path"),
"target-timeout": seconds_duration(proxy_config["response_timeout"]),
"read-target": proxy_config["read_targets"].presence,
"read-target-websockets": proxy_config["read_target_websockets"] ? true : nil,
"writer-affinity-timeout": seconds_duration(proxy_config["writer_affinity_timeout"]),
"buffer-requests": proxy_config.fetch("buffering", { "requests": true }).fetch("requests", true),
"buffer-responses": proxy_config.fetch("buffering", { "responses": true }).fetch("responses", true),
"buffer-memory": proxy_config.dig("buffering", "memory"),
Expand Down
49 changes: 49 additions & 0 deletions test/configuration/proxy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,55 @@ class ConfigurationProxyTest < ActiveSupport::TestCase
end
end

test "ssl_staging, read_targets and writer affinity flags in deploy_options" do
@deploy[:proxy] = {
"ssl" => true, "host" => "example.com",
"ssl_staging" => true,
"read_targets" => [ "192.168.0.2:3000", "192.168.0.3:3000" ],
"read_target_websockets" => true,
"writer_affinity_timeout" => 10
}
options = config.proxy.deploy_options
assert_equal true, options[:"tls-staging"]
assert_equal [ "192.168.0.2:3000", "192.168.0.3:3000" ], options[:"read-target"]
assert_equal true, options[:"read-target-websockets"]
assert_equal "10s", options[:"writer-affinity-timeout"]
end

test "ssl_staging, read_targets and writer affinity flags absent when not configured" do
@deploy[:proxy] = { "ssl" => true, "host" => "example.com" }
options = config.proxy.deploy_options
assert_not options.key?(:"tls-staging")
assert_not options.key?(:"read-target")
assert_not options.key?(:"read-target-websockets")
assert_not options.key?(:"writer-affinity-timeout")
end

test "ssl_staging and read_target_websockets false emit no flags" do
@deploy[:proxy] = { "ssl" => true, "host" => "example.com", "ssl_staging" => false, "read_target_websockets" => false }
options = config.proxy.deploy_options
assert_not options.key?(:"tls-staging")
assert_not options.key?(:"read-target-websockets")
end

test "read_targets in deploy command args" do
@deploy[:proxy] = { "host" => "example.com", "read_targets" => [ "192.168.0.2:3000" ] }
args = config.proxy.deploy_command_args(target: "abc123:80")
assert_includes args.join(" "), "--read-target=\"192.168.0.2:3000\""
end

test "invalid types for ssl_staging, read_targets and writer affinity keys" do
{
"ssl_staging" => "yes",
"read_targets" => "192.168.0.2:3000",
"read_target_websockets" => 1,
"writer_affinity_timeout" => "10"
}.each do |key, value|
@deploy[:proxy] = { "host" => "example.com", key => value }
assert_raises(Kamal::ConfigurationError, "expected #{key}=#{value.inspect} to be rejected") { config.proxy }
end
end

private
def config
Kamal::Configuration.new(@deploy)
Expand Down