aslan-server/.deploy/prod-deploy/deploy-likei-services.sh

365 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -euo pipefail
BASE_DIR="${BASE_DIR:-/opt/aslan-deploy}"
SRC_DIR="${SRC_DIR:-$BASE_DIR/source/likei-services}"
KUBECONFIG="${KUBECONFIG:-$BASE_DIR/kubeconfig}"
LOCK_FILE="${LOCK_FILE:-$BASE_DIR/deploy.lock}"
NAMESPACE="${NAMESPACE:-prod}"
MODE="${MODE:-preload}"
SKIP_BUILD="${SKIP_BUILD:-0}"
USE_GIT_SOURCE="${USE_GIT_SOURCE:-0}"
GIT_REMOTE_URL="${GIT_REMOTE_URL:-git@gitea.haiyihy.com:hy/aslan-server.git}"
GIT_REF="${GIT_REF:-aslan_prod}"
MAVEN_GOALS="${MAVEN_GOALS:-clean package}"
MAVEN_PROFILE="${MAVEN_PROFILE:-prod}"
MAVEN_EXTRA_ARGS="${MAVEN_EXTRA_ARGS:-}"
BUILD_TS="${BUILD_TS:-$(date +%Y%m%dv%H%M%S)}"
IMAGE_REGISTRY="${IMAGE_REGISTRY:-tuokemi-con-registry.ap-southeast-1.cr.aliyuncs.com}"
IMAGE_PROJECT="${IMAGE_PROJECT:-atyou-prod}"
BASE_IMAGE="tuokemi-con-registry.ap-southeast-1.cr.aliyuncs.com/public_mirror_images/eclipse-temurin:17-jdk-jammy"
FALLBACK_BASE_IMAGE="${FALLBACK_BASE_IMAGE:-eclipse-temurin:17-jdk-jammy}"
usage() {
cat <<'EOF'
Usage:
deploy-likei-services.sh [--mode preload|push|build-only|status] [--skip-build] [--fast] [gateway|wallet|order|other|external|console ...]
Examples:
/opt/aslan-deploy/deploy-likei-services.sh status
/opt/aslan-deploy/deploy-likei-services.sh --mode preload wallet
/opt/aslan-deploy/deploy-likei-services.sh --mode preload order
/opt/aslan-deploy/deploy-likei-services.sh --mode preload gateway order
/opt/aslan-deploy/deploy-likei-services.sh --mode preload other external console
/opt/aslan-deploy/deploy-likei-services.sh --fast wallet
USE_GIT_SOURCE=1 GIT_REF=aslan_prod /opt/aslan-deploy/deploy-likei-services.sh --mode preload other
MODE=push ALIYUN_USER=xxx ALIYUN_PASS=xxx /opt/aslan-deploy/deploy-likei-services.sh other
Notes:
- preload mode builds the image on this server, imports it into each TKE node's containerd, then updates the deployment.
- push mode follows the original Jenkins flow and requires registry credentials in ALIYUN_USER/ALIYUN_PASS.
- USE_GIT_SOURCE=1 clones or resets source from GIT_REMOTE_URL/GIT_REF before building.
EOF
}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "missing command: $1" >&2
exit 2
}
}
acquire_deploy_lock() {
need_cmd flock
exec 9>"$LOCK_FILE"
# Only one deploy should mutate source, build images, preload nodes, and roll deployments at a time.
flock -n 9 || {
echo "another deploy is already running: $LOCK_FILE" >&2
exit 5
}
}
validate_git_ref() {
if [[ ! "$GIT_REF" =~ ^[A-Za-z0-9._/-]{1,80}$ || "$GIT_REF" == -* || "$GIT_REF" == *..* ]]; then
echo "invalid GIT_REF: $GIT_REF" >&2
exit 2
fi
}
ensure_base_image() {
if docker image inspect "$BASE_IMAGE" >/dev/null 2>&1; then
return
fi
if docker pull "$BASE_IMAGE"; then
return
fi
log "base image not available from primary registry, pulling $FALLBACK_BASE_IMAGE"
docker pull "$FALLBACK_BASE_IMAGE"
docker tag "$FALLBACK_BASE_IMAGE" "$BASE_IMAGE"
}
update_source_from_git() {
if [[ "$USE_GIT_SOURCE" != "1" ]]; then
return
fi
need_cmd git
validate_git_ref
mkdir -p "$(dirname "$SRC_DIR")"
if [[ ! -d "$SRC_DIR/.git" ]]; then
rm -rf "$SRC_DIR"
log "git clone $GIT_REMOTE_URL ($GIT_REF) -> $SRC_DIR"
git clone --branch "$GIT_REF" "$GIT_REMOTE_URL" "$SRC_DIR"
return
fi
log "git reset source to origin/$GIT_REF from $GIT_REMOTE_URL"
git -C "$SRC_DIR" remote set-url origin "$GIT_REMOTE_URL"
git -C "$SRC_DIR" fetch origin "$GIT_REF"
git -C "$SRC_DIR" checkout -B "$GIT_REF" "origin/$GIT_REF"
git -C "$SRC_DIR" reset --hard "origin/$GIT_REF"
}
service_module() {
case "$1" in
gateway) echo "rc-gateway" ;;
wallet) echo "rc-service/rc-service-wallet/wallet-start" ;;
order) echo "rc-service/rc-service-order/order-start" ;;
other) echo "rc-service/rc-service-other/other-start" ;;
external) echo "rc-service/rc-service-external/external-start" ;;
console) echo "rc-service/rc-service-console/console-start" ;;
*) echo "unsupported service: $1" >&2; exit 2 ;;
esac
}
service_dir() {
case "$1" in
gateway) echo "rc-gateway" ;;
wallet) echo "rc-service/rc-service-wallet" ;;
order) echo "rc-service/rc-service-order" ;;
other) echo "rc-service/rc-service-other" ;;
external) echo "rc-service/rc-service-external" ;;
console) echo "rc-service/rc-service-console" ;;
*) echo "unsupported service: $1" >&2; exit 2 ;;
esac
}
image_repo() {
case "$1" in
gateway) echo "$IMAGE_REGISTRY/$IMAGE_PROJECT/gateway" ;;
wallet) echo "$IMAGE_REGISTRY/$IMAGE_PROJECT/wallet" ;;
order) echo "$IMAGE_REGISTRY/$IMAGE_PROJECT/order" ;;
other) echo "$IMAGE_REGISTRY/$IMAGE_PROJECT/other" ;;
external) echo "$IMAGE_REGISTRY/$IMAGE_PROJECT/external" ;;
console) echo "$IMAGE_REGISTRY/$IMAGE_PROJECT/console" ;;
*) echo "unsupported service: $1" >&2; exit 2 ;;
esac
}
build_service() {
local svc="$1"
local module
local dir
local repo
local tag
local image
module="$(service_module "$svc")"
dir="$(service_dir "$svc")"
repo="$(image_repo "$svc")"
tag="${svc}-${BUILD_TS}"
image="${repo}:${tag}"
cd "$SRC_DIR"
if [[ "$SKIP_BUILD" != "1" ]]; then
log "maven package $svc ($module)"
# shellcheck disable=SC2086
# MAVEN_GOALS=package keeps the existing target cache for code-only fast builds; use the default clean package for safer full builds.
mvn $MAVEN_GOALS -pl "$module" -am -P "$MAVEN_PROFILE" -Dmaven.test.skip=true $MAVEN_EXTRA_ARGS
fi
ensure_base_image
log "docker build $image"
docker build \
--build-arg "SERVICE_VERSION=${IMAGE_PROJECT}:${tag}" \
-f "$dir/Dockerfile" \
-t "$image" \
"$dir"
docker tag "$image" "${repo}:latest"
case "$MODE" in
build-only)
log "build-only completed for $svc: $image"
;;
push)
push_image "$svc" "$image" "$repo"
rollout "$svc" "$image"
;;
preload)
ensure_preload_pull_policy "$svc"
preload_image_to_nodes "$svc" "$image"
rollout "$svc" "$image"
;;
*)
echo "unsupported MODE: $MODE" >&2
exit 2
;;
esac
}
push_image() {
local svc="$1"
local image="$2"
local repo="$3"
if [[ -n "${ALIYUN_USER:-}" && -n "${ALIYUN_PASS:-}" ]]; then
log "docker login registry for $svc"
printf '%s' "$ALIYUN_PASS" | docker login --username "$ALIYUN_USER" --password-stdin tuokemi-con-registry.ap-southeast-1.cr.aliyuncs.com
fi
log "docker push $image"
docker push "$image"
docker push "${repo}:latest"
}
preload_image_to_nodes() {
local svc="$1"
local image="$2"
local safe_tag="${image//[^A-Za-z0-9_.-]/-}"
local tar_path="$BASE_DIR/artifacts/${safe_tag}.tar"
mkdir -p "$BASE_DIR/artifacts"
log "docker save $image -> $tar_path"
docker save "$image" -o "$tar_path"
mapfile -t nodes < <(kubectl --kubeconfig "$KUBECONFIG" get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
if [[ "${#nodes[@]}" -eq 0 ]]; then
echo "no kubernetes nodes found" >&2
exit 3
fi
for node in "${nodes[@]}"; do
preload_image_to_node "$svc" "$image" "$tar_path" "$node"
done
}
ensure_preload_pull_policy() {
local svc="$1"
local policy
policy="$(kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" get "deployment/$svc" \
-o "jsonpath={.spec.template.spec.containers[?(@.name=='$svc')].imagePullPolicy}")"
# Preload mode imports images into node containerd; Always-pull pods would bypass that cache and fail.
if [[ "$policy" == "Always" ]]; then
echo "deployment/$svc uses imagePullPolicy=Always; preload mode requires IfNotPresent or unset" >&2
exit 4
fi
}
preload_image_to_node() {
local svc="$1"
local image="$2"
local tar_path="$3"
local node="$4"
local pod="image-preload-${svc}-${node//[^a-zA-Z0-9-]/-}-$(date +%s)"
local manifest
manifest="$(mktemp)"
cat >"$manifest" <<EOF
apiVersion: v1
kind: Pod
metadata:
name: ${pod}
namespace: ${NAMESPACE}
spec:
nodeName: "${node}"
restartPolicy: Never
hostPID: true
tolerations:
- operator: Exists
containers:
- name: preload
image: docker:26.1.3-dind
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "sleep 3600"]
securityContext:
privileged: true
volumeMounts:
- name: containerd
mountPath: /run/containerd/containerd.sock
volumes:
- name: containerd
hostPath:
path: /run/containerd/containerd.sock
type: Socket
EOF
log "create preload pod $pod on node $node"
kubectl --kubeconfig "$KUBECONFIG" apply -f "$manifest" >/dev/null
rm -f "$manifest"
if ! kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" wait --for=condition=Ready "pod/$pod" --timeout=180s >/dev/null; then
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" describe "pod/$pod" || true
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" delete pod "$pod" --ignore-not-found=true >/dev/null
return 1
fi
log "copy image tar to $pod"
if ! kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" cp "$tar_path" "$pod:/tmp/image.tar" -c preload >/dev/null; then
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" delete pod "$pod" --ignore-not-found=true >/dev/null
return 1
fi
log "import $image into node $node"
if ! kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" exec "$pod" -c preload -- \
ctr --address /run/containerd/containerd.sock -n k8s.io images import /tmp/image.tar >/dev/null; then
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" delete pod "$pod" --ignore-not-found=true >/dev/null
return 1
fi
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" delete pod "$pod" --ignore-not-found=true >/dev/null
}
rollout() {
local svc="$1"
local image="$2"
log "set image deployment/$svc $svc=$image"
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" set image "deployment/$svc" "$svc=$image"
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" rollout status "deployment/$svc" --timeout=600s
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" get deploy "$svc" -o wide
}
status() {
kubectl --kubeconfig "$KUBECONFIG" -n "$NAMESPACE" get deploy gateway wallet order other external console -o wide
kubectl --kubeconfig "$KUBECONFIG" get nodes -o wide
}
main() {
while [[ "$#" -gt 0 ]]; do
case "$1" in
--mode)
[[ -n "${2:-}" ]] || { echo "--mode requires an argument" >&2; exit 2; }
MODE="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=1
shift
;;
--fast)
# Code-only wallet changes can keep Maven's target cache instead of forcing clean package.
MAVEN_GOALS=package
shift
;;
-h|--help)
usage
exit 0
;;
*)
break
;;
esac
done
if [[ "${1:-}" == "status" || "$MODE" == "status" ]]; then
need_cmd kubectl
[[ -f "$KUBECONFIG" ]] || { echo "missing kubeconfig: $KUBECONFIG" >&2; exit 2; }
status
exit 0
fi
need_cmd java
need_cmd mvn
need_cmd docker
need_cmd kubectl
[[ -f "$KUBECONFIG" ]] || { echo "missing kubeconfig: $KUBECONFIG" >&2; exit 2; }
acquire_deploy_lock
update_source_from_git
local services=("$@")
if [[ "${#services[@]}" -eq 0 ]]; then
# 保留既有无参数发布范围gateway/order 只在显式指定时发布,避免老运维命令意外扩大生产变更面。
services=(other external console)
fi
for svc in "${services[@]}"; do
build_service "$svc"
done
}
main "$@"