This commit is contained in:
Executable
+174
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
log() {
|
||||
printf '{"event":"%s","environment":"%s","stack":"%s","releaseId":"%s","commitSha":"%s"}\n' \
|
||||
"$1" "${ENVIRONMENT:-unknown}" "${STACK_NAME:-unknown}" "${RELEASE_ID:-unknown}" "${COMMIT_SHA:-unknown}"
|
||||
}
|
||||
|
||||
require_var() {
|
||||
local name="$1"
|
||||
if [ -z "${!name:-}" ]; then
|
||||
printf 'Required environment variable %s is missing\n' "$name" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
require_var ENVIRONMENT
|
||||
require_var STACK_NAME
|
||||
require_var RELEASE_ID
|
||||
require_var COMMIT_SHA
|
||||
require_var WEB_IMAGE
|
||||
require_var PUBLIC_HOST
|
||||
require_var TRAEFIK_NETWORK
|
||||
|
||||
REMOTE_DIR="${REMOTE_DEPLOY_DIR:-/tmp/videoreg-deploy}"
|
||||
LOCK_FILE="${RELEASE_LOCK_FILE:-/tmp/${STACK_NAME}-${ENVIRONMENT}.lock}"
|
||||
|
||||
exec 9>"$LOCK_FILE"
|
||||
if ! flock -n 9; then
|
||||
echo "Another deployment is already running for ${STACK_NAME}/${ENVIRONMENT}" >&2
|
||||
exit 75
|
||||
fi
|
||||
|
||||
cleanup_release_images() {
|
||||
if [ "${DEPLOY_CLEANUP_FAILED_IMAGES:-true}" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Cleaning up unused release image after failed deploy." >&2
|
||||
docker image rm "$WEB_IMAGE" >/dev/null 2>&1 || true
|
||||
|
||||
if [ -n "${COMMIT_SHA:-}" ]; then
|
||||
docker image prune -af --filter "label=org.opencontainers.image.revision=${COMMIT_SHA}" >&2 || true
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_on_exit() {
|
||||
local status=$?
|
||||
trap - EXIT
|
||||
|
||||
if [ "$status" -ne 0 ]; then
|
||||
cleanup_release_images
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap cleanup_on_exit EXIT
|
||||
|
||||
docker_disk_paths() {
|
||||
local docker_root
|
||||
docker_root="$(docker info --format '{{.DockerRootDir}}' 2>/dev/null || true)"
|
||||
|
||||
if [ -n "$docker_root" ] && [ -d "$docker_root" ]; then
|
||||
printf '%s\n' "$docker_root"
|
||||
fi
|
||||
|
||||
if [ -d /var/lib/containerd ]; then
|
||||
printf '%s\n' /var/lib/containerd
|
||||
fi
|
||||
}
|
||||
|
||||
prune_docker_before_pull() {
|
||||
if [ "${DEPLOY_PRUNE_BEFORE_PULL:-false}" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local prune_until="${DEPLOY_PRUNE_UNTIL:-168h}"
|
||||
echo "Pruning unused Docker images older than ${prune_until} before pulling release image." >&2
|
||||
docker image prune -af --filter "until=${prune_until}" >&2 || true
|
||||
docker builder prune -af --filter "until=${prune_until}" >&2 || true
|
||||
}
|
||||
|
||||
check_docker_disk_space() {
|
||||
local min_mb="${DEPLOY_MIN_FREE_MB:-2048}"
|
||||
if [[ ! "$min_mb" =~ ^[0-9]+$ ]]; then
|
||||
echo "DEPLOY_MIN_FREE_MB must be an integer MiB value, got: ${min_mb}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
local min_kb=$((min_mb * 1024))
|
||||
local failed=0
|
||||
local path available_kb available_mb
|
||||
|
||||
while IFS= read -r path; do
|
||||
available_kb="$(df -Pk "$path" | awk 'NR == 2 { print $4 }')"
|
||||
if [ -z "$available_kb" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$available_kb" -lt "$min_kb" ]; then
|
||||
available_mb=$((available_kb / 1024))
|
||||
echo "Docker storage path ${path} has ${available_mb} MiB free; need at least ${min_mb} MiB before pulling release image." >&2
|
||||
failed=1
|
||||
fi
|
||||
done < <(docker_disk_paths | awk '!seen[$0]++')
|
||||
|
||||
if [ "$failed" -ne 0 ]; then
|
||||
docker system df >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
wait_service() {
|
||||
local service="$1"
|
||||
local timeout="${DEPLOY_WAIT_TIMEOUT_SECONDS:-300}"
|
||||
local started_at
|
||||
started_at="$(date +%s)"
|
||||
|
||||
while true; do
|
||||
local update_state
|
||||
update_state="$(docker service inspect --format '{{if .UpdateStatus}}{{.UpdateStatus.State}}{{end}}' "$service" 2>/dev/null || true)"
|
||||
if [ "$update_state" = "rollback_started" ] || [ "$update_state" = "rollback_completed" ] || [ "$update_state" = "paused" ]; then
|
||||
docker service ps --no-trunc "$service" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local desired running failed
|
||||
desired="$(docker service inspect --format '{{if .Spec.Mode.Replicated}}{{.Spec.Mode.Replicated.Replicas}}{{else}}1{{end}}' "$service")"
|
||||
running="$(docker service ps "$service" --filter desired-state=running --format '{{.CurrentState}}' | grep -c '^Running' || true)"
|
||||
failed="$(docker service ps "$service" --no-trunc --format '{{.CurrentState}} {{.Error}}' | grep -Ec 'Rejected|Failed|No such image|unhealthy' || true)"
|
||||
|
||||
if [ "$failed" -gt 0 ]; then
|
||||
docker service ps --no-trunc "$service" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$running" -ge "$desired" ]; then
|
||||
log "service_converged"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$(( $(date +%s) - started_at ))" -ge "$timeout" ]; then
|
||||
docker service ps --no-trunc "$service" >&2 || true
|
||||
echo "Timed out waiting for ${service} convergence" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
log deploy_start
|
||||
|
||||
if [ -n "${REGISTRY:-}" ] && [ -n "${REGISTRY_USERNAME:-}" ] && [ -n "${REGISTRY_PASSWORD:-}" ]; then
|
||||
printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY" --username "$REGISTRY_USERNAME" --password-stdin
|
||||
fi
|
||||
|
||||
prune_docker_before_pull
|
||||
check_docker_disk_space
|
||||
|
||||
docker image pull "$WEB_IMAGE"
|
||||
|
||||
export ENVIRONMENT STACK_NAME RELEASE_ID COMMIT_SHA WEB_IMAGE PUBLIC_HOST TRAEFIK_NETWORK
|
||||
export REGION="${REGION:-}" TEAM_ID="${TEAM_ID:-}" WEB_REPLICAS="${WEB_REPLICAS:-1}"
|
||||
export WEB_PLACEMENT_CONSTRAINT="${WEB_PLACEMENT_CONSTRAINT:-node.role == manager}"
|
||||
export TRAEFIK_ENTRYPOINTS="${TRAEFIK_ENTRYPOINTS:-websecure}" TRAEFIK_TLS="${TRAEFIK_TLS:-true}"
|
||||
export VIDEOREG_DB_MEMORY_TTL_MS="${VIDEOREG_DB_MEMORY_TTL_MS:-3600000}"
|
||||
export VIDEOREG_NEXT_CACHE_REVALIDATE_SECONDS="${VIDEOREG_NEXT_CACHE_REVALIDATE_SECONDS:-3600}"
|
||||
|
||||
docker stack deploy --with-registry-auth --prune -c "$REMOTE_DIR/docker-stack.yml" "$STACK_NAME"
|
||||
wait_service "${STACK_NAME}_web"
|
||||
|
||||
log deploy_converged
|
||||
Reference in New Issue
Block a user