Merge pull request #864 from progrium/860-mh-bind-to-internal-ip

bind docker container to internal port if using vhosts
This commit is contained in:
Jose Diaz-Gonzalez
2015-01-05 19:58:18 -05:00
8 changed files with 163 additions and 9 deletions

View File

@@ -136,3 +136,25 @@ dokku domains:clear myapp
# remove a custom domain from app
dokku domains:remove myapp example.com
```
### Container network interface binding
> New as of 0.3.13
The deployed docker container running your app's web process will bind to either the internal docker network interface (i.e. `docker inspect --format '{{ .NetworkSettings.IPAddress }}' $CONTAINER_ID`) or an external interface (i.e. 0.0.0.0) depending on dokku's VHOST configuration. Dokku will attempt to bind to the internal docker network interface unless you specifically set NO_VHOST for the given app or your dokku installation is not setup to use VHOSTS (i.e. $DOKKU_ROOT/VHOST or $DOKKU_ROOT/HOSTNAME is set to an IPv4 or IPv6 address)
```shell
# container bound to docker interface
root@dokku:~/dokku# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1b88d8aec3d1 dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute goofy_albattani
root@dokku:~/dokku# docker inspect --format '{{ .NetworkSettings.IPAddress }}' goofy_albattani
172.17.0.6
# container bound to all interfaces (previous default)
root@dokku:/home/dokku# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp nostalgic_tesla
```

20
dokku
View File

@@ -82,8 +82,20 @@ case "$1" in
# start the app
DOCKER_ARGS=$(: | pluginhook docker-args $APP deploy)
id=$(docker run -d -p 5000 -e PORT=5000 $DOCKER_ARGS $IMAGE /bin/bash -c "/start web")
port=$(docker port $id 5000 | sed 's/[0-9.]*://')
BIND_EXTERNAL=$(pluginhook bind-external-ip $APP)
if [[ "$BIND_EXTERNAL" = "false" ]];then
port=5000
id=$(docker run -d -e PORT=$port $DOCKER_ARGS $IMAGE /bin/bash -c "/start web")
ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $id)
echo $ipaddr > "$DOKKU_ROOT/$APP/IP"
else
id=$(docker run -d -p 5000 -e PORT=5000 $DOCKER_ARGS $IMAGE /bin/bash -c "/start web")
port=$(docker port $id 5000 | sed 's/[0-9.]*://')
[[ -f "$DOKKU_ROOT/$APP/IP" ]] && rm -f "$DOKKU_ROOT/$APP/IP"
fi
# if we can't post-deploy successfully, kill new container
function kill_new {
@@ -95,7 +107,7 @@ case "$1" in
# run checks first, then post-deploy hooks, which switches Nginx traffic
trap kill_new INT TERM EXIT
echo "-----> Running pre-flight checks"
pluginhook check-deploy $id $APP $port
pluginhook check-deploy $id $APP $port ${ipaddr:-localhost}
# now using the new container
echo $id > "$DOKKU_ROOT/$APP/CONTAINER"
@@ -103,7 +115,7 @@ case "$1" in
echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$port" > "$DOKKU_ROOT/$APP/URL"
echo "-----> Running post-deploy"
pluginhook post-deploy $APP $port
pluginhook post-deploy $APP $port $ipaddr
trap - INT TERM EXIT
# kill the old container

View File

@@ -1,6 +1,21 @@
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
RE_IPV4="([0-9]{1,3}[\.]){3}[0-9]{1,3}"
RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" # TEST: 1:2:3:4:5:6:7:8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|" # TEST: 1:: 1:2:3:4:5:6:7::
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" # TEST: 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" # TEST: 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" # TEST: 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" # TEST: 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" # TEST: 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" # TEST: 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" # TEST: ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|" # TEST: fe08::7:8%eth0 fe08::7:8%1 (link-local IPv6 addresses with zone index)
RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|" # TEST: ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}" # TEST: 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33
case "$1" in
domains)
[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1
@@ -23,7 +38,7 @@ case "$1" in
else
VHOST=$(< "$DOKKU_ROOT/HOSTNAME")
fi
if [[ "$VHOST" =~ ([0-9]{1,3}[\.]){3}[0-9]{1,3} ]];then
if [[ "$VHOST" =~ $RE_IPV4 ]] || [[ "$VHOST" =~ $RE_IPV6 ]];then
echo "ip found as hostname. disabling vhost support"
[[ ! $(grep -q NO_VHOST "$DOKKU_ROOT/$APP/ENV") ]] && echo "export NO_VHOST='1'" >> "$DOKKU_ROOT/$APP/ENV"
else

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
APP="$1"
set +e; NO_VHOST=$(dokku config:get $APP NO_VHOST); set -e
RE_IPV4="([0-9]{1,3}[\.]){3}[0-9]{1,3}"
RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" # TEST: 1:2:3:4:5:6:7:8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|" # TEST: 1:: 1:2:3:4:5:6:7::
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" # TEST: 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" # TEST: 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" # TEST: 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" # TEST: 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" # TEST: 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" # TEST: 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" # TEST: ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|" # TEST: fe08::7:8%eth0 fe08::7:8%1 (link-local IPv6 addresses with zone index)
RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|" # TEST: ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}" # TEST: 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33
if [[ -f "$DOKKU_ROOT/VHOST" ]];then
GLOBAL_VHOST=$(< "$DOKKU_ROOT/VHOST")
else
GLOBAL_VHOST=$(< "$DOKKU_ROOT/HOSTNAME")
fi
if [[ -n "$NO_VHOST" ]]; then
echo true # bind to external ip. VHOST is disabled for this app
elif [[ "$GLOBAL_VHOST" =~ $RE_IPV4 ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV6 ]]; then
echo true # bind to external ip. GLOBAL_VHOST is somehow an IPv4 or IPv6 address
elif [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then
echo false # bind to docker ip. this app has a vhost defined
else
echo false
fi

View File

@@ -15,7 +15,7 @@ restart_nginx () {
case "$1" in
nginx:build-config)
APP="$2"; PORT="$3"
APP="$2"; PORT="$3"; IP="${4:-127.0.0.1}"
[[ -z "$PORT" ]] && PORT=$(< "$DOKKU_ROOT/$APP/PORT")
VHOST_PATH="$DOKKU_ROOT/$APP/VHOST"
WILDCARD_SSL="$DOKKU_ROOT/tls"
@@ -68,7 +68,7 @@ EOF
NOSSL_SERVER_NAME=$(echo $NONSSL_VHOSTS $SSL_VHOSTS| tr '\n' ' ')
echo "-----> Creating $SCHEME nginx.conf"
echo "upstream $APP { server 127.0.0.1:$PORT; }" > $DOKKU_ROOT/$APP/nginx.conf
echo "upstream $APP { server $IP:$PORT; }" > $DOKKU_ROOT/$APP/nginx.conf
eval "cat <<< \"$(< $NGINX_CONF)\" >> $DOKKU_ROOT/$APP/nginx.conf"
echo "-----> Running nginx-pre-reload"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
APP="$1"; PORT="$2"
APP="$1"; PORT="$2"; IP="$3"
set +e; NO_VHOST=$(dokku config:get $APP NO_VHOST); set -e
@@ -11,4 +11,4 @@ elif [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then
dokku domains:setup $APP
fi
dokku nginx:build-config $APP $PORT
dokku nginx:build-config $APP $PORT $IP

68
tests/unit/ports.bats Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bats
load test_helper
setup() {
[[ -f "$DOKKU_ROOT/VHOST" ]] && cp -f "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak"
[[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -f "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak"
}
teardown() {
destroy_app
[[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST"
[[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME"
}
@test "port exposure (with global VHOST)" {
echo "dokku.me" > "$DOKKU_ROOT/VHOST"
deploy_app
CONTAINER_ID=$(docker ps --no-trunc| grep dokku/$TEST_APP | grep "start web" | awk '{ print $1 }')
run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep '[0-9]*'"
echo "output: "$output
echo "status: "$status
assert_failure
}
@test "port exposure (without global VHOST and real HOSTNAME)" {
rm "$DOKKU_ROOT/VHOST"
echo "dokku.me" > "$DOKKU_ROOT/HOSTNAME"
deploy_app
CONTAINER_ID=$(docker ps --no-trunc| grep dokku/$TEST_APP | grep "start web" | awk '{ print $1 }')
run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep '[0-9]*'"
echo "output: "$output
echo "status: "$status
assert_failure
}
@test "port exposure (with NO_VHOST set)" {
deploy_app
dokku config:set $TEST_APP NO_VHOST=1
CONTAINER_ID=$(docker ps --no-trunc| grep dokku/$TEST_APP | grep "start web" | awk '{ print $1 }')
run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep '[0-9]*'"
echo "output: "$output
echo "status: "$status
assert_success
}
@test "port exposure (without global VHOST and IPv4 address as HOSTNAME)" {
rm "$DOKKU_ROOT/VHOST"
echo "127.0.0.1" > "$DOKKU_ROOT/HOSTNAME"
deploy_app
CONTAINER_ID=$(docker ps --no-trunc| grep dokku/$TEST_APP | grep "start web" | awk '{ print $1 }')
run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep '[0-9]*'"
echo "output: "$output
echo "status: "$status
assert_success
}
@test "port exposure (without global VHOST and IPv6 address as HOSTNAME)" {
rm "$DOKKU_ROOT/VHOST"
echo "fda5:c7db:a520:bb6d::aabb:ccdd:eeff" > "$DOKKU_ROOT/HOSTNAME"
deploy_app
CONTAINER_ID=$(docker ps --no-trunc| grep dokku/$TEST_APP | grep "start web" | awk '{ print $1 }')
run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep '[0-9]*'"
echo "output: "$output
echo "status: "$status
assert_success
}

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env bash
# constants
DOKKU_ROOT=${DOKKU_ROOT:=~dokku}
TEST_APP=my-cool-guy-test-app
# test functions