94 lines
3.1 KiB
Bash
94 lines
3.1 KiB
Bash
#!/usr/bin/env sh
|
|||
|
|
set -eu
|
||
|
|
|
||
|
|
if [ "$#" -ne 4 ]; then
|
||
|
|
echo "用法: $0 <服务名> <镜像仓库> <镜像标签> <Compose 文件>" >&2
|
||
|
|
exit 2
|
||
|
|
fi
|
||
|
|
|
||
|
|
SERVICE="$1"
|
||
|
|
IMAGE_REPOSITORY="$2"
|
||
|
|
IMAGE_TAG="$3"
|
||
|
|
COMPOSE_FILE="$4"
|
||
|
|
IMAGE="$IMAGE_REPOSITORY:$IMAGE_TAG"
|
||
|
|
REGISTRY_HOST="${IMAGE_REPOSITORY%%/*}"
|
||
|
|
SSH_TARGET="${TARGET_SSH_TARGET:?TARGET_SSH_TARGET 未设置}"
|
||
|
|
SSH_KEY="${TARGET_SSH_KEY:?TARGET_SSH_KEY 未设置}"
|
||
|
|
SSH_KNOWN_HOSTS="${TARGET_SSH_KNOWN_HOSTS:?TARGET_SSH_KNOWN_HOSTS 未设置}"
|
||
|
|
REMOTE_DIR="${TARGET_DEPLOY_DIR:?TARGET_DEPLOY_DIR 未设置}"
|
||
|
|
: "${REGISTRY_USER:?REGISTRY_USER 未设置}"
|
||
|
|
: "${REGISTRY_PASSWORD:?REGISTRY_PASSWORD 未设置}"
|
||
|
|
|
||
|
|
run_ssh() {
|
||
|
|
ssh -i "$SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=yes \
|
||
|
|
-o UserKnownHostsFile="$SSH_KNOWN_HOSTS" -o ConnectTimeout=10 "$SSH_TARGET" "$@"
|
||
|
|
}
|
||
|
|
|
||
|
|
run_scp() {
|
||
|
|
scp -i "$SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=yes \
|
||
|
|
-o UserKnownHostsFile="$SSH_KNOWN_HOSTS" -o ConnectTimeout=10 "$@"
|
||
|
|
}
|
||
|
|
|
||
|
|
run_ssh install -d -m 750 "$REMOTE_DIR"
|
||
|
|
REMOTE_COMPOSE="$REMOTE_DIR/docker-compose.$SERVICE.yml"
|
||
|
|
run_scp "$COMPOSE_FILE" "$SSH_TARGET:$REMOTE_COMPOSE.tmp"
|
||
|
|
run_ssh sh -s -- "$REMOTE_COMPOSE.tmp" "$REMOTE_COMPOSE" <<'REMOTE_PREPARE'
|
||
|
|
set -eu
|
||
|
|
compose_tmp="$1"
|
||
|
|
compose_target="$2"
|
||
|
|
chmod 644 "$compose_tmp"
|
||
|
|
mv -f "$compose_tmp" "$compose_target"
|
||
|
|
REMOTE_PREPARE
|
||
|
|
|
||
|
|
printf '%s' "$REGISTRY_PASSWORD" | run_ssh docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST"
|
||
|
|
trap 'run_ssh docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true' EXIT HUP INT TERM
|
||
|
|
run_ssh docker pull "$IMAGE"
|
||
|
|
|
||
|
|
run_ssh sh -s -- "$REMOTE_DIR" "$SERVICE" "$IMAGE_REPOSITORY" "$IMAGE_TAG" <<'REMOTE_DEPLOY'
|
||
|
|
set -eu
|
||
|
|
remote_dir="$1"
|
||
|
|
service="$2"
|
||
|
|
image_repository="$3"
|
||
|
|
image_tag="$4"
|
||
|
|
compose_file="docker-compose.$service.yml"
|
||
|
|
project_name="firstleg-prod-$service"
|
||
|
|
cd "$remote_dir"
|
||
|
|
export IMAGE_REPOSITORY="$image_repository" IMAGE_TAG="$image_tag"
|
||
|
|
|
||
|
|
compose() {
|
||
|
|
docker compose --project-name "$project_name" -f "$compose_file" "$@"
|
||
|
|
}
|
||
|
|
|
||
|
|
container_id="$(compose ps --all --quiet "$service" | head -n 1)"
|
||
|
|
previous_image=""
|
||
|
|
if [ -n "$container_id" ]; then
|
||
|
|
previous_image="$(docker inspect --format '{{.Config.Image}}' "$container_id")"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if compose up -d --no-deps --pull never "$service" && \
|
||
|
|
compose exec -T "$service" wget -qO- http://127.0.0.1/healthz >/dev/null; then
|
||
|
|
echo "生产前端部署成功: $image_repository:$image_tag"
|
||
|
|
compose ps "$service"
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "生产前端健康检查失败,准备回滚" >&2
|
||
|
|
compose logs --tail=100 "$service" >&2 || true
|
||
|
|
case "$previous_image" in
|
||
|
|
"$image_repository:"*) ;;
|
||
|
|
'') echo "首次部署无旧镜像可回滚" >&2; exit 1 ;;
|
||
|
|
*) echo "旧镜像不属于当前应用,拒绝回滚: $previous_image" >&2; exit 1 ;;
|
||
|
|
esac
|
||
|
|
previous_tag="${previous_image#"$image_repository:"}"
|
||
|
|
if [ "$previous_tag" = "$image_tag" ]; then
|
||
|
|
echo "旧镜像与新镜像一致,无法回滚" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
docker pull "$previous_image"
|
||
|
|
export IMAGE_TAG="$previous_tag"
|
||
|
|
compose up -d --no-deps --pull never "$service"
|
||
|
|
compose exec -T "$service" wget -qO- http://127.0.0.1/healthz >/dev/null || true
|
||
|
|
echo "已回滚至: $previous_image" >&2
|
||
|
|
exit 1
|
||
|
|
REMOTE_DEPLOY
|