feat: add support for routing an app to a specified host:port

This is useful when there is a service not managed by Dokku but should be exposed via the Dokku routing layer. As an example, some binaries (consul, nomad, vault) expose web uis, and are traditionally run on the host directly vs in a container.

Closes #4665
This commit is contained in:
Jose Diaz-Gonzalez
2021-08-06 01:29:25 -04:00
parent afd64c3e04
commit 9ecfa843f0
18 changed files with 298 additions and 7 deletions

View File

@@ -0,0 +1,15 @@
# Null Builder
> New as of 0.25.0
The `null` builder does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ip:port-combination) for more information on the aforementioned use case.
## Usage
### Detection
This builder is _never_ auto-detected. The builder _must_ be specified via the `builder:set` command:
```shell
dokku builder:set node-js-app selected null
```

View File

@@ -0,0 +1,15 @@
# Null Scheduler
> New as of 0.25.0
The `null` scheduler does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ip:port-combination) for more information on the aforementioned use case.
## Usage
### Detection
This scheduler is _never_ auto-detected. The scheduler _must_ be specified via the `config:set` command:
```shell
dokku config:set node-js-app DOCKER_SCHEDULER=null
```

View File

@@ -940,6 +940,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-get-static-listeners`
- Description: Return the network value for an app's property
- Invoked by: `internally triggered by proxy-build-config`
- Arguments: `$APP $PROCESS_TYPE`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-write-ipaddr`
- Description: Write the ipaddr for a given app index

View File

@@ -123,6 +123,40 @@ dokku network:info test-network
// TODO
```
### Routing an app to a known ip:port combination
> New as of 0.25.0
In some cases, it may be necessary to route an app to an existing `$IP:$PORT` combination. This is particularly the case for internal admin tools or services that aren't run by Dokku but have a web ui that would benefit from being exposed by Dokku. This can be done by setting a value for `static-web-lister` and running a few other commands when creating an app.
```shell
# for a service listening on:
# - ip address: 127.0.0.1
# - port: 8080
# create the app
dokku apps:create local-app
# set the builder to the null builder, which does nothing
dokku builder:set local-app selected null
# set the scheduler to the null scheduler, which does nothing
dokku config:set local-app DOKKU_SCHEDULER=null
# set the static-web-listener network property to the ip:port combination for your app.
dokku network:set local-app static-web-listener 127.0.0.1:8080
# set the port map as desired for the port specified in your static-web-listener
dokku proxy:ports-set local-app http:80:8080
# set the domains desired
dokku domains:set local-app local-app.dokku.me
dokku proxy:build-config local-app
```
Only a single `$IP:$PORT` combination can be routed to for a given app, and that `$IP:$PORT` combination _must_ be accessible to the proxy, or requests to the app may not resolve.
### Attaching an app to a network
> New as of 0.20.0, Requires Docker 1.21+

16
plugins/builder-null/commands Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
[[ " help builder-null:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
source "$PLUGIN_AVAILABLE_PATH/builder-null/help-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
case "$1" in
help | builder-null:help)
cmd-builder-null-help "$@"
;;
*)
exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
;;
esac

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-builder-null-help() {
declare desc="help command"
declare CMD="$1"
local plugin_name="builder-null"
local plugin_description="No-op builder plugin"
if [[ "$CMD" == "${plugin_name}:help" ]]; then
echo -e "Usage: dokku ${plugin_name}[:COMMAND]"
echo ''
echo "$plugin_description"
echo ''
elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
true
else
cat <<help_desc
$plugin_name, $plugin_description
help_desc
fi
}

View File

@@ -0,0 +1,4 @@
[plugin]
description = "dokku core builder-null plugin"
version = "0.24.10"
[plugin.config]

View File

@@ -1,5 +1,5 @@
SUBCOMMANDS = subcommands/create subcommands/destroy subcommands/exists subcommands/info subcommands/list subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set
TRIGGERS = triggers/core-post-deploy triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/docker-args-process-run triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report
TRIGGERS = triggers/core-post-deploy triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/docker-args-process-run triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-get-static-listeners triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report
BUILD = commands subcommands triggers
PLUGIN_NAME = network

View File

@@ -3,6 +3,7 @@ package network
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strconv"
@@ -17,18 +18,19 @@ import (
var (
// DefaultProperties is a map of all valid network properties with corresponding default property values
DefaultProperties = map[string]string{
"bind-all-interfaces": "",
"attach-post-create": "",
"attach-post-deploy": "",
"bind-all-interfaces": "",
"initial-network": "",
"static-web-listener": "",
"tld": "",
}
// GlobalProperties is a map of all valid global network properties
GlobalProperties = map[string]bool{
"bind-all-interfaces": true,
"attach-post-create": true,
"attach-post-deploy": true,
"bind-all-interfaces": true,
"initial-network": true,
"tld": true,
}
@@ -40,6 +42,10 @@ func BuildConfig(appName string) error {
return nil
}
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
return nil
}
appRoot := common.AppRoot(appName)
s, err := common.PlugnTriggerOutput("ps-current-scale", []string{appName}...)
if err != nil {
@@ -97,6 +103,22 @@ func BuildConfig(appName string) error {
// GetContainerIpaddress returns the ipaddr for a given app container
func GetContainerIpaddress(appName, processType, containerID string) (ipAddr string) {
if processType == "web" {
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
ip, _, err := net.SplitHostPort(staticWebListener)
if err == nil {
return ip
}
ip2 := net.ParseIP(staticWebListener)
if ip2 != nil {
return ip2.String()
}
return "127.0.0.1"
}
}
if b, err := common.DockerInspect(containerID, "{{ .HostConfig.NetworkMode }}"); err == nil {
if string(b[:]) == "host" {
return "127.0.0.1"
@@ -123,6 +145,17 @@ func GetContainerIpaddress(appName, processType, containerID string) (ipAddr str
// GetContainerPort returns the port for a given app container
func GetContainerPort(appName, processType string, containerID string, isHerokuishContainer bool) (port string) {
if processType == "web" {
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
_, port, err := net.SplitHostPort(staticWebListener)
if err == nil {
return port
}
return "80"
}
}
dockerfilePorts := make([]string, 0)
if !isHerokuishContainer {
configValue := config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", "")
@@ -156,6 +189,12 @@ func GetContainerPort(appName, processType string, containerID string, isHerokui
// GetListeners returns a string array of app listeners
func GetListeners(appName string, processType string) []string {
if processType == "web" {
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
return []string{staticWebListener}
}
}
appRoot := common.AppRoot(appName)
ipPrefix := fmt.Sprintf("/IP.%s.", processType)

View File

@@ -27,6 +27,7 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
"--network-global-initial-network": reportGlobalInitialNetwork,
"--network-global-tld": reportGlobalTld,
"--network-initial-network": reportInitialNetwork,
"--network-static-web-listener": reportStaticWebListener,
"--network-tld": reportTld,
"--network-web-listeners": reportWebListeners,
}
@@ -123,6 +124,10 @@ func reportInitialNetwork(appName string) string {
return common.PropertyGet("network", appName, "initial-network")
}
func reportStaticWebListener(appName string) string {
return common.PropertyGet("network", appName, "static-web-listener")
}
func reportTld(appName string) string {
return common.PropertyGet("network", appName, "tld")
}

View File

@@ -59,6 +59,10 @@ func main() {
appName := flag.Arg(0)
property := flag.Arg(1)
err = network.TriggerNetworkGetProperty(appName, property)
case "network-get-static-listeners":
appName := flag.Arg(0)
processType := flag.Arg(1)
err = network.TriggerNetworkGetStaticListeners(appName, processType)
case "network-write-ipaddr":
appName := flag.Arg(0)
processType := flag.Arg(1)

View File

@@ -140,6 +140,13 @@ func TriggerNetworkGetProperty(appName string, property string) error {
return nil
}
// TriggerNetworkGetStaticListeners fetches the static listener for the specified app/processType combination
func TriggerNetworkGetStaticListeners(appName string, processType string) error {
staticWebListener := reportStaticWebListener(appName)
fmt.Println(staticWebListener)
return nil
}
// TriggerNetworkWriteIpaddr writes the ip to disk
func TriggerNetworkWriteIpaddr(appName string, processType string, containerIndex string, ip string) error {
appRoot := common.AppRoot(appName)

View File

@@ -447,10 +447,12 @@ nginx_build_config() {
if [[ -z "$DOKKU_APP_LISTENERS" ]]; then
dokku_log_warn_quiet "No web listeners specified for $APP"
elif (is_deployed "$APP"); then
local IMAGE_TAG=$(get_running_image_tag "$APP")
local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG" 2>/dev/null)
if ! verify_image "$IMAGE" 2>/dev/null; then
dokku_log_fail "Missing image for app"
if [[ "$(plugn trigger network-get-static-listeners "$APP" "web")" == "" ]]; then
local IMAGE_TAG=$(get_running_image_tag "$APP")
local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG" 2>/dev/null)
if ! verify_image "$IMAGE" 2>/dev/null; then
dokku_log_fail "Missing image for app"
fi
fi
local NGINX_BUILD_CONFIG_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")

16
plugins/scheduler-null/commands Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
[[ " help scheduler-null:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
source "$PLUGIN_AVAILABLE_PATH/scheduler-null/help-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
case "$1" in
help | scheduler-null:help)
cmd-scheduler-null-help "$@"
;;
*)
exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
;;
esac

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-scheduler-null-help() {
declare desc="help command"
declare CMD="$1"
local plugin_name="scheduler-null"
local plugin_description="No-op scheduler plugin"
if [[ "$CMD" == "${plugin_name}:help" ]]; then
echo -e "Usage: dokku ${plugin_name}[:COMMAND]"
echo ''
echo "$plugin_description"
echo ''
elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
true
else
cat <<help_desc
$plugin_name, $plugin_description
help_desc
fi
}

View File

@@ -0,0 +1,4 @@
[plugin]
description = "dokku core scheduler-null plugin"
version = "0.24.10"
[plugin.config]

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-scheduler-null-scheduler-is-deployed() {
declare desc="checks if an app is deployed"
declare trigger="scheduler-is-deployed"
declare DOKKU_SCHEDULER="$1" APP="$2"
if [[ "$DOKKU_SCHEDULER" != "null" ]]; then
return
fi
return 0
}
trigger-scheduler-null-scheduler-is-deployed "$@"

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bats
load test_helper
setup() {
global_setup
create_app
}
teardown() {
destroy_app
global_teardown
}
# covers https://github.com/dokku/dokku/issues/4665
@test "(proxied-app) default" {
run /bin/bash -c "dokku builder:set $TEST_APP selected null"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku config:set $TEST_APP DOKKU_SCHEDULER=null"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku network:set $TEST_APP static-web-listener 127.0.0.1:8080"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:80:8080"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku proxy:build-config $TEST_APP"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku nginx:show-config $TEST_APP"
echo "output: $output"
echo "status: $status"
assert_success
assert_output_contains "server 127.0.0.1:8080;"
}