Files
dokku/plugins/scheduler-docker-local/scheduler-deploy
Jose Diaz-Gonzalez 0786954d34 feat: schedule related images for cleanup when retiring containers
If the containers no longer exist and the images are unused, we should remove the images as well.

Note that images may sometimes stick around for a little while, depending on the retire schedule and the wait time specified for the container.

Closes #4104
2020-11-26 03:21:45 -05:00

224 lines
9.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/checks/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions"
trigger-scheduler-docker-local-scheduler-deploy() {
declare desc="deploys an image tag for a given application"
declare trigger="scheduler-deploy"
declare DOKKU_SCHEDULER="$1" APP="$2" IMAGE_TAG="$3"
if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then
return
fi
rm -f "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/failed-containers"
local DOCKER_RUN_LABEL_ARGS="--label=com.dokku.app-name=$APP"
local DOKKU_DOCKER_STOP_TIMEOUT DOKKU_HEROKUISH DOKKU_NETWORK_BIND_ALL IMAGE
DOKKU_HEROKUISH=false
IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG")
verify_app_name "$APP"
plugn trigger pre-deploy "$APP" "$IMAGE_TAG"
is_image_herokuish_based "$IMAGE" "$APP" && DOKKU_HEROKUISH=true
local IMAGE_SOURCE_TYPE="dockerfile"
[[ "$DOKKU_HEROKUISH" == "true" ]] && IMAGE_SOURCE_TYPE="herokuish"
local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
local oldids=$(get_app_container_ids "$APP")
DOKKU_NETWORK_BIND_ALL="$(plugn trigger network-get-property "$APP" bind-all-interfaces)"
DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
[[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}"
DOKKU_START_CMD="$(config_get "$APP" DOKKU_START_CMD || true)"
local line
local PROC_TYPE
local PROC_COUNT
local CONTAINER_INDEX
while read -r line || [[ -n "$line" ]]; do
[[ "$line" =~ ^#.* ]] && continue
line="$(strip_inline_comments "$line")"
PROC_TYPE=${line%%=*}
PROC_COUNT=${line#*=}
CONTAINER_INDEX=1
if [[ "$(is_app_proctype_checks_disabled "$APP" "$PROC_TYPE")" == "true" ]]; then
dokku_log_info1 "zero downtime is disabled for app ($APP.$PROC_TYPE). stopping currently running containers"
local cid proctype_oldids="$(get_app_running_container_ids "$APP" "$PROC_TYPE" 2>/dev/null)"
for cid in $proctype_oldids; do
dokku_log_info2 "stopping $APP.$PROC_TYPE ($cid)"
# Disable the container restart policy
"$DOCKER_BIN" container update --restart=no "$cid" &>/dev/null || true
# shellcheck disable=SC2086
"$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$cid" &>/dev/null
# remove cid from oldids to skip the old container finish processing
oldids="$(remove_val_from_list "$cid" "$oldids" " ")"
done
fi
while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do
local cid=""
local port=""
local ipaddr=""
local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX"
local DYNO="$PROC_TYPE.$CONTAINER_INDEX"
# start the app
local DOCKER_ARGS
DOCKER_ARGS=$(: | plugn trigger docker-args-deploy "$APP" "$IMAGE_TAG" "$PROC_TYPE" "$CONTAINER_INDEX")
DOCKER_ARGS+=" -e DYNO=$DYNO "
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-deploy "$APP" "$IMAGE_SOURCE_TYPE" "$IMAGE_TAG" "$PROC_TYPE" "$CONTAINER_INDEX")
[[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true "
declare -a ARG_ARRAY
eval "ARG_ARRAY=($DOCKER_ARGS)"
local START_CMD
[[ "$DOKKU_HEROKUISH" == "true" ]] && START_CMD="/start $PROC_TYPE"
[[ -n "$DOKKU_START_CMD" ]] && START_CMD="$DOKKU_START_CMD"
if [[ "$PROC_TYPE" == "web" ]]; then
ports=($(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$CONTAINER_INDEX"))
local DOKKU_DOCKER_PORT_ARGS=""
local DOKKU_PORT=""
for p in "${ports[@]}"; do
if [[ ! "$p" =~ .*udp.* ]]; then
DOKKU_PORT=${DOKKU_PORT:="$p"}
fi
DOKKU_DOCKER_PORT_ARGS+=" -p $p "
done
START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH" "$DOKKU_PORT")
if [[ "$DOKKU_NETWORK_BIND_ALL" == "false" ]]; then
# shellcheck disable=SC2086
cid=$("$DOCKER_BIN" container create --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE --label=com.dokku.dyno=$DYNO "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init --env PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
else
# shellcheck disable=SC2086
cid=$("$DOCKER_BIN" container create --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE --label=com.dokku.dyno=$DYNO "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init $DOKKU_DOCKER_PORT_ARGS --env PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
fi
else
START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH")
# shellcheck disable=SC2086
cid=$("$DOCKER_BIN" container create --label=com.dokku.container-type=deploy --label=com.dokku.process-type=$PROC_TYPE --label=com.dokku.dyno=$DYNO "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS --init --env PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
fi
plugn trigger post-container-create "app" "$cid" "$APP" "deploy" "$PROC_TYPE"
"$DOCKER_BIN" container start "$cid" >/dev/null || true
ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$cid")
port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$cid" "$DOKKU_HEROKUISH")
kill_new() {
declare desc="wrapper function to kill newly started app container"
declare CID="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3"
mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP"
echo "${CID} ${PROC_TYPE}.${CONTAINER_INDEX}" >>"${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/failed-containers"
"$DOCKER_BIN" container inspect "$CID" &>/dev/null && {
# Disable the container restart policy
"$DOCKER_BIN" container update --restart=no "$CID" &>/dev/null || true
"$DOCKER_BIN" container stop "$CID" >/dev/null && "$DOCKER_BIN" container kill "$CID" &>/dev/null
}
trap - INT TERM EXIT
kill -9 $$
}
# run checks first, then post-deploy hooks, which switches proxy traffic
trap 'kill_new $cid $PROC_TYPE $CONTAINER_INDEX' INT TERM EXIT
if [[ "$(is_app_proctype_checks_disabled "$APP" "$PROC_TYPE")" == "false" ]]; then
dokku_log_info1 "Attempting pre-flight checks ($PROC_TYPE.$CONTAINER_INDEX)"
plugn trigger check-deploy "$APP" "$cid" "$PROC_TYPE" "$port" "$ipaddr"
fi
trap - INT TERM EXIT
# now using the new container
[[ -n "$cid" ]] && echo "$cid" >"$DOKKU_CONTAINER_ID_FILE"
[[ -n "$ipaddr" ]] && plugn trigger network-write-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr"
[[ -n "$port" ]] && plugn trigger network-write-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port"
# cleanup pre-migration files
rm -f "$DOKKU_ROOT/$APP/CONTAINER" "$DOKKU_ROOT/$APP/IP" "$DOKKU_ROOT/$APP/PORT"
local CONTAINER_INDEX=$((CONTAINER_INDEX + 1))
done
# cleanup when we scale down
if [[ "$PROC_COUNT" == 0 ]]; then
local CONTAINER_IDX_OFFSET=0
else
local CONTAINER_IDX_OFFSET=$((PROC_COUNT + 1))
fi
local container_state_filetype
for container_state_filetype in CONTAINER IP PORT; do
cd "$DOKKU_ROOT/$APP"
find . -maxdepth 1 -name "$container_state_filetype.$PROC_TYPE.*" -printf "%f\n" | sort -t . -k 3 -n | tail -n +$CONTAINER_IDX_OFFSET | xargs rm -f
done
done <"$DOKKU_SCALE_FILE"
dokku_log_info1 "Running post-deploy"
plugn trigger core-post-deploy "$APP" "$port" "$ipaddr" "$IMAGE_TAG"
plugn trigger post-deploy "$APP" "$port" "$ipaddr" "$IMAGE_TAG"
# kill the old container
if [[ -n "$oldids" ]]; then
if [[ -z "$DOKKU_WAIT_TO_RETIRE" ]]; then
local DOKKU_APP_DOKKU_WAIT_TO_RETIRE=$(config_get "$APP" DOKKU_WAIT_TO_RETIRE || true)
local DOKKU_GLOBAL_DOKKU_WAIT_TO_RETIRE=$(config_get --global DOKKU_WAIT_TO_RETIRE || true)
local DOKKU_WAIT_TO_RETIRE=${DOKKU_APP_DOKKU_WAIT_TO_RETIRE:="$DOKKU_GLOBAL_DOKKU_WAIT_TO_RETIRE"}
fi
# Let the old container finish processing requests, before terminating it
local WAIT="${DOKKU_WAIT_TO_RETIRE:-60}"
dokku_log_info1 "Shutting down old containers in $WAIT seconds"
local oldid
for oldid in $oldids; do
dokku_log_verbose_quiet "$oldid"
plugn trigger scheduler-register-retired "$APP" "$oldid" "$WAIT"
done
(
exec >/dev/null 2>/dev/null </dev/null
trap '' INT HUP
sleep "$WAIT"
for oldid in $oldids; do
# Disable the container restart policy
"$DOCKER_BIN" container update --restart=no "$oldid" &>/dev/null || true
# Attempt to stop, if that fails, then force a kill as docker seems
# to not send SIGKILL as the docs would indicate. If that fails, move
# on to the next.
# shellcheck disable=SC2086
"$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$oldid" \
|| "$DOCKER_BIN" container kill "$oldid" \
|| plugn trigger retire-container-failed "$APP" "$oldid" # plugin trigger for event logging
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
}
fn-scheduler-docker-local-extract-start-cmd() {
declare APP="$1" PROC_TYPE="$2" START_CMD="$3" DOKKU_HEROKUISH="$4" PORT="$5"
local DOKKU_DOCKERFILE_START_CMD DOKKU_PROCFILE_START_CMD START_CMD
if [[ "$DOKKU_HEROKUISH" != "false" ]]; then
echo "$START_CMD"
return
fi
DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true)
DOKKU_PROCFILE_START_CMD=$(plugn trigger procfile-get-command "$APP" "$PROC_TYPE" "$PORT")
START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD}
echo "$START_CMD"
}
trigger-scheduler-docker-local-scheduler-deploy "$@"