initial pass at container-level scaling
This commit is contained in:
106
dokku
106
dokku
@@ -66,53 +66,78 @@ case "$1" in
|
||||
APP="$2"; IMAGE="dokku/$APP"
|
||||
pluginhook pre-deploy $APP
|
||||
|
||||
if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then
|
||||
oldid=$(< "$DOKKU_ROOT/$APP/CONTAINER")
|
||||
fi
|
||||
|
||||
# start the app
|
||||
DOCKER_ARGS=$(: | pluginhook docker-args $APP deploy)
|
||||
DOCKER_ARGS+=$(: | pluginhook docker-args-deploy $APP)
|
||||
BIND_EXTERNAL=$(pluginhook bind-external-ip $APP)
|
||||
|
||||
is_image_buildstep_based "$IMAGE" && DOKKU_BUILDSTEP=true
|
||||
[[ -n "$DOKKU_BUILDSTEP" ]] && START_CMD="/start web"
|
||||
[[ -z "$DOKKU_BUILDSTEP" ]] && DOKKU_DOCKERFILE_PORT=$(dokku config:get $APP DOKKU_DOCKERFILE_PORT || true)
|
||||
DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
|
||||
|
||||
if [[ "$BIND_EXTERNAL" = "false" ]];then
|
||||
port=${DOKKU_DOCKERFILE_PORT:=5000}
|
||||
id=$(docker run -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD)
|
||||
ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $id)
|
||||
else
|
||||
id=$(docker run -d -p 5000 -e PORT=5000 $DOCKER_ARGS $IMAGE $START_CMD)
|
||||
port=$(docker port $id 5000 | sed 's/[0-9.]*://')
|
||||
ipaddr=127.0.0.1
|
||||
fi
|
||||
while read line || [ -n "$line" ]
|
||||
do
|
||||
PROC_NAME=${line%%=*}
|
||||
PROC_COUNT=${line#*=}
|
||||
CONTAINER_NUM=1
|
||||
|
||||
# if we can't post-deploy successfully, kill new container
|
||||
kill_new() {
|
||||
docker inspect $id &> /dev/null && docker kill $id > /dev/null
|
||||
trap - INT TERM EXIT
|
||||
kill -9 $$
|
||||
}
|
||||
while [[ $CONTAINER_NUM -le $PROC_COUNT ]];do
|
||||
DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_NAME.$CONTAINER_NUM"
|
||||
DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_NAME.$CONTAINER_NUM"
|
||||
DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_NAME.$CONTAINER_NUM"
|
||||
|
||||
# run checks first, then post-deploy hooks, which switches Nginx traffic
|
||||
trap kill_new INT TERM EXIT
|
||||
dokku_log_info1 "Running pre-flight checks"
|
||||
pluginhook check-deploy $APP $port $ipaddr $id
|
||||
trap - INT TERM EXIT
|
||||
# try old form even in case we're migrating dokku versions
|
||||
if [[ -f "$DOKKU_CONTAINER_ID_FILE" ]]; then
|
||||
oldids+=" "
|
||||
oldids+=$(< "$DOKKU_CONTAINER_ID_FILE")
|
||||
oldids+=" "
|
||||
elif [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then
|
||||
oldids+=$(< "$DOKKU_ROOT/$APP/CONTAINER")
|
||||
fi
|
||||
|
||||
# now using the new container
|
||||
echo $id > "$DOKKU_ROOT/$APP/CONTAINER"
|
||||
echo $ipaddr > "$DOKKU_ROOT/$APP/IP"
|
||||
echo $port > "$DOKKU_ROOT/$APP/PORT"
|
||||
echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$port" > "$DOKKU_ROOT/$APP/URL"
|
||||
# start the app
|
||||
DOCKER_ARGS=$(: | pluginhook docker-args $APP deploy)
|
||||
DOCKER_ARGS+=$(: | pluginhook docker-args-deploy $APP)
|
||||
BIND_EXTERNAL=$(pluginhook bind-external-ip $APP)
|
||||
|
||||
[[ -n "$DOKKU_BUILDSTEP" ]] && START_CMD="/start $PROC_NAME"
|
||||
[[ -z "$DOKKU_BUILDSTEP" ]] && DOKKU_DOCKERFILE_PORT=$(dokku config:get $APP DOKKU_DOCKERFILE_PORT || true)
|
||||
|
||||
|
||||
if [[ "$BIND_EXTERNAL" = "false" ]];then
|
||||
port=${DOKKU_DOCKERFILE_PORT:=5000}
|
||||
id=$(docker run -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD)
|
||||
ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $id)
|
||||
else
|
||||
id=$(docker run -d -p 5000 -e PORT=5000 $DOCKER_ARGS $IMAGE $START_CMD)
|
||||
port=$(docker port $id 5000 | sed 's/[0-9.]*://')
|
||||
ipaddr=127.0.0.1
|
||||
fi
|
||||
|
||||
# if we can't post-deploy successfully, kill new container
|
||||
kill_new() {
|
||||
docker inspect $id &> /dev/null && docker kill $id > /dev/null
|
||||
trap - INT TERM EXIT
|
||||
kill -9 $$
|
||||
}
|
||||
|
||||
# run checks first, then post-deploy hooks, which switches Nginx traffic
|
||||
trap kill_new INT TERM EXIT
|
||||
dokku_log_info1 "Running pre-flight checks"
|
||||
pluginhook check-deploy $APP $port $ipaddr $id
|
||||
trap - INT TERM EXIT
|
||||
|
||||
# now using the new container
|
||||
echo $id > "$DOKKU_CONTAINER_ID_FILE"
|
||||
echo $ipaddr > "$DOKKU_IP_FILE"
|
||||
echo $port > "$DOKKU_PORT_FILE"
|
||||
echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$port" > "$DOKKU_ROOT/$APP/URL"
|
||||
|
||||
# cleanup pre-migration files
|
||||
rm -f $DOKKU_ROOT/$APP/CONTAINER $DOKKU_ROOT/$APP/IP $DOKKU_ROOT/$APP/PORT
|
||||
CONTAINER_NUM=$(( CONTAINER_NUM + 1 ))
|
||||
done
|
||||
done < "$DOKKU_SCALE_FILE"
|
||||
|
||||
dokku_log_info1 "Running post-deploy"
|
||||
pluginhook post-deploy $APP $port $ipaddr
|
||||
pluginhook post-deploy $APP $port $ipaddr
|
||||
|
||||
# kill the old container
|
||||
if [[ -n "$oldid" ]]; then
|
||||
if [[ -n "$oldids" ]]; then
|
||||
# Let the old container finish processing requests, before terminating it
|
||||
WAIT="${DOKKU_WAIT_TO_RETIRE:-60}"
|
||||
dokku_log_info1 "Shutting down old container in $WAIT seconds"
|
||||
@@ -120,13 +145,14 @@ case "$1" in
|
||||
exec >/dev/null 2>/dev/null </dev/null
|
||||
trap '' INT HUP
|
||||
sleep $WAIT
|
||||
docker kill $oldid
|
||||
for oldid in $oldids; do
|
||||
docker kill $oldid
|
||||
done
|
||||
) & disown -a
|
||||
# Use trap since disown/nohup don't seem to keep child alive
|
||||
# Give child process just enough time to set the traps
|
||||
sleep 0.1
|
||||
fi
|
||||
|
||||
;;
|
||||
|
||||
cleanup)
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
# The default behavior is to wait for 5 seconds before running the first check,
|
||||
# and timeout each check to 30 minutes.
|
||||
#
|
||||
# By default, checks will be retried 5 times.
|
||||
# By default, checks will be retried 5 times.
|
||||
|
||||
# You can change these by setting WAIT, TIMEOUT and ATTEMPTS to different values, for
|
||||
# example:
|
||||
@@ -86,7 +86,7 @@ if [[ ! -s "${TMPDIR}/CHECKS" ]] ; then
|
||||
|
||||
# simple default check to see if the container stuck around
|
||||
# for more thorough checks, create a CHECKS file
|
||||
DOKKU_DEFAULT_WAIT=$((WAIT+TIMEOUT))
|
||||
DOKKU_DEFAULT_WAIT=10
|
||||
dokku_log_info1 "Waiting for $DOKKU_DEFAULT_WAIT seconds ..."
|
||||
sleep $DOKKU_DEFAULT_WAIT
|
||||
|
||||
@@ -116,15 +116,15 @@ do
|
||||
ATTEMPT=$(( ATTEMPT + 1 ))
|
||||
dokku_log_info1 "Attempt $ATTEMPT/$ATTEMPTS Waiting for $WAIT seconds ..."
|
||||
sleep $WAIT
|
||||
|
||||
|
||||
|
||||
|
||||
# -q do not use .curlrc (must come first)
|
||||
# --compressed Test compression handled correctly
|
||||
# --fail Fail on server errors (4xx, 5xx)
|
||||
# --location Follow redirects
|
||||
CURL_OPTIONS="-q --compressed --fail --location --max-time $TIMEOUT"
|
||||
|
||||
|
||||
|
||||
|
||||
exec < "$FILENAME"
|
||||
while read CHECK_URL EXPECTED ; do
|
||||
# Ignore empty lines and lines starting with #
|
||||
@@ -132,14 +132,14 @@ do
|
||||
[[ -z "$CHECK_URL" || "$CHECK_URL" =~ ^\# ]] && continue
|
||||
# Ignore variables
|
||||
[[ "$CHECK_URL" =~ ^.+= ]] && continue
|
||||
|
||||
|
||||
if [[ "$CHECK_URL" =~ ^https?: ]] ; then
|
||||
URL_PROTOCOL=${CHECK_URL%:*}
|
||||
CHECK_URL=${CHECK_URL#*:}
|
||||
else
|
||||
URL_PROTOCOL="http"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$CHECK_URL" =~ ^//.+ ]] ; then
|
||||
# To test a URL with specific host name, we still make request to localhost,
|
||||
# but we set Host header to $SEND_HOST.
|
||||
@@ -149,22 +149,22 @@ do
|
||||
UNPREFIXED=${CHECK_URL#//}
|
||||
URL_HOSTNAME=${UNPREFIXED%%/*}
|
||||
URL_PATHNAME=${UNPREFIXED#$URL_HOSTNAME}
|
||||
|
||||
|
||||
HEADERS="-H Host:$URL_HOSTNAME"
|
||||
else
|
||||
URL_HOSTNAME=localhost
|
||||
URL_PATHNAME=$CHECK_URL
|
||||
fi
|
||||
|
||||
|
||||
# This URL will show up in the messages
|
||||
LOG_URL="$URL_PROTOCOL://$URL_HOSTNAME$URL_PATHNAME"
|
||||
# And how we formulate the CURL request
|
||||
CURL_ARGS="$CURL_OPTIONS $URL_PROTOCOL://$DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT$URL_PATHNAME $HEADERS"
|
||||
|
||||
|
||||
dokku_log_verbose "CHECKS expected result:"
|
||||
dokku_log_verbose "$LOG_URL => \"$EXPECTED\""
|
||||
[[ $DOKKU_TRACE ]] && dokku_log_verbose "$ curl $CURL_ARGS"
|
||||
|
||||
|
||||
# Capture HTTP response or CURL error message
|
||||
if OUTPUT=$(curl -# $CURL_ARGS 2>&1) ; then
|
||||
# OUTPUT contains the HTTP response
|
||||
@@ -178,7 +178,7 @@ do
|
||||
FAILEDCHECKS=$(( FAILEDCHECKS + 1 ))
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [ $FAILEDCHECKS -gt 0 ]; then
|
||||
dokku_log_warn "Check attempt $ATTEMPT/$ATTEMPTS failed."
|
||||
SUCCESS=0
|
||||
|
||||
@@ -135,3 +135,11 @@ parse_args() {
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
copy_from_image() {
|
||||
local APP="$1"; local IMAGE="dokku/$APP"; local SRC_FILE="$2"; local DST_DIR="$3"; verify_app_name $APP
|
||||
|
||||
CID=$(docker run -d $IMAGE bash)
|
||||
docker cp "$CID:$SRC_FILE" "$DST_DIR"
|
||||
docker rm -f "$CID"
|
||||
}
|
||||
|
||||
@@ -34,11 +34,18 @@ case "$1" in
|
||||
WILDCARD_SSL="$DOKKU_ROOT/tls"
|
||||
SSL="$DOKKU_ROOT/$APP/tls"
|
||||
|
||||
if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then
|
||||
DOKKU_APP_LISTEN_PORT=$(< "$DOKKU_ROOT/$APP/PORT")
|
||||
fi
|
||||
if [[ -z "$DOKKU_APP_LISTEN_IP" ]] && [[ -f "$DOKKU_ROOT/$APP/IP" ]]; then
|
||||
DOKKU_APP_LISTEN_IP=$(< "$DOKKU_ROOT/$APP/IP")
|
||||
if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then
|
||||
shopt -s nullglob
|
||||
for DOKKU_APP_IP_FILE in $DOKKU_ROOT/$APP/IP.*;do
|
||||
DOKKU_APP_PORT_FILE=$(echo $DOKKU_APP_IP_FILE | sed -e "s:IP:PORT:g")
|
||||
DOKKU_APP_LISTENER_IP=$(< $DOKKU_APP_IP_FILE)
|
||||
DOKKU_APP_LISTENER_PORT=$(< $DOKKU_APP_PORT_FILE)
|
||||
|
||||
DOKKU_APP_LISTENERS+=" "
|
||||
DOKKU_APP_LISTENERS+="$DOKKU_APP_LISTENER_IP:$DOKKU_APP_LISTENER_PORT"
|
||||
DOKKU_APP_LISTENERS+=" "
|
||||
done
|
||||
shopt -u nullglob
|
||||
fi
|
||||
|
||||
[[ -f "$DOKKU_ROOT/ENV" ]] && source $DOKKU_ROOT/ENV
|
||||
@@ -100,6 +107,12 @@ EOF
|
||||
|
||||
if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then
|
||||
echo "upstream $APP { server $DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT; }" >> $NGINX_CONF
|
||||
elif [[ -n "$DOKKU_APP_LISTENERS" ]]; then
|
||||
echo "upstream $APP { " >> $NGINX_CONF
|
||||
for listener in $DOKKU_APP_LISTENERS;do
|
||||
echo " server $listener;" >> $NGINX_CONF
|
||||
done
|
||||
echo "}" >> $NGINX_CONF
|
||||
fi
|
||||
|
||||
dokku_log_info1 "Creating $SCHEME nginx.conf"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
source "$(dirname $0)/../common/functions"
|
||||
|
||||
APP="$1"; PORT="$2"; IP="$3"
|
||||
APP="$1"
|
||||
NO_VHOST=$(dokku config:get $APP NO_VHOST || true)
|
||||
|
||||
if [[ -n "$NO_VHOST" ]]; then
|
||||
@@ -11,4 +11,4 @@ elif [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then
|
||||
dokku domains:setup $APP
|
||||
fi
|
||||
|
||||
dokku nginx:build-config $APP $PORT $IP
|
||||
dokku nginx:build-config $APP
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
source "$(dirname $0)/../common/functions"
|
||||
source "$(dirname $0)/functions"
|
||||
|
||||
release_and_deploy() {
|
||||
source "$(dirname $0)/../common/functions"
|
||||
local APP="$1"; local IMAGE="dokku/$APP"
|
||||
|
||||
if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then
|
||||
if ls $DOKKU_ROOT/$APP/CONTAINER.* &> /dev/null; then
|
||||
if is_image_buildstep_based "$IMAGE"; then
|
||||
IMAGE_SOURCE_TYPE="buildstep"
|
||||
else
|
||||
@@ -97,9 +98,27 @@ case "$1" in
|
||||
done
|
||||
;;
|
||||
|
||||
ps:scale)
|
||||
[[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1
|
||||
verify_app_name "$2"
|
||||
|
||||
APP="$2"; DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
|
||||
shift 2
|
||||
|
||||
generate_scale_file "$APP"
|
||||
if [[ -z "$@" ]];then
|
||||
dokku_log_info1 "Scaling for $APP"
|
||||
dokku_log_info2 "$(< $DOKKU_SCALE_FILE)"
|
||||
else
|
||||
set_scale "$APP" "$@"
|
||||
fi
|
||||
release_and_deploy "$APP"
|
||||
;;
|
||||
|
||||
help | ps:help)
|
||||
cat && cat<<EOF
|
||||
ps <app> List processes running in app container(s)
|
||||
ps:scale <app> <proc>=<count> [<proc>=<count>] Set how many processes of a given process to run
|
||||
ps:start <app> Start app container(s)
|
||||
ps:stop <app> Stop app container(s)
|
||||
ps:rebuild <app> Rebuild an app
|
||||
|
||||
45
plugins/ps/functions
Executable file
45
plugins/ps/functions
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
source "$(dirname $0)/../common/functions"
|
||||
|
||||
generate_scale_file() {
|
||||
local APP="$1"; local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
|
||||
if [[ ! -f $DOKKU_SCALE_FILE ]]; then
|
||||
copy_from_image "$APP" "/app/DOKKU_SCALE" "$DOKKU_ROOT/$APP" || dokku_log_info1_quiet "DOKKU_SCALE not found in app image"
|
||||
if [[ ! -f $DOKKU_SCALE_FILE ]]; then
|
||||
local TMP_WORK_DIR=$(mktemp -d DOKKU_SCALE.XXXXX)
|
||||
trap 'rm -rf $TMP_WORK_DIR' EXIT
|
||||
|
||||
copy_from_image "$APP" /app/Procfile $TMP_WORK_DIR || true
|
||||
if [[ -f $TMP_WORK_DIR/Procfile ]];then
|
||||
local PROCFILE="$TMP_WORK_DIR/Procfile"
|
||||
while read line || [ -n "$line" ]
|
||||
do
|
||||
local PROC_NAME=${line%%:*}
|
||||
echo "$PROC_NAME=1" >> $DOKKU_SCALE_FILE
|
||||
done < "$PROCFILE"
|
||||
else
|
||||
echo "default=1" >> $DOKKU_SCALE_FILE
|
||||
fi
|
||||
|
||||
dokku_log_info1_quiet "New DOKKU_SCALE file generated"
|
||||
dokku_log_info1_quiet "$(< $DOKKU_SCALE_FILE)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
set_scale() {
|
||||
local APP="$1"; local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
|
||||
shift 1
|
||||
local SCALE_SETTINGS=("$@")
|
||||
for procscale in "${SCALE_SETTINGS[@]}"; do
|
||||
PROC_NAME=${procscale%%=*}
|
||||
PROC_COUNT=${procscale#*=}
|
||||
dokku_log_info1_quiet "Scaling $APP:$PROC_NAME to $PROC_COUNT"
|
||||
if (egrep -q ^${PROC_NAME}= $DOKKU_SCALE_FILE > /dev/null 2>&1);then
|
||||
sed --in-place "s:^${PROC_NAME}=.*:$PROC_NAME=$PROC_COUNT:g" $DOKKU_SCALE_FILE
|
||||
else
|
||||
echo "$PROC_NAME=$PROC_COUNT" >> $DOKKU_SCALE_FILE
|
||||
fi
|
||||
done
|
||||
}
|
||||
8
plugins/ps/pre-deploy
Executable file
8
plugins/ps/pre-deploy
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
source "$(dirname $0)/../common/functions"
|
||||
source "$(dirname $0)/functions"
|
||||
|
||||
APP="$1"
|
||||
|
||||
generate_scale_file $APP
|
||||
Reference in New Issue
Block a user