G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현 G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건) G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST) G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직 G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트 G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트 G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트 G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트 G-10: core/push_notify.py + routers/push.py + PushSubscription 모델 G-11: approvals 다중승인 (위임/서명/기한초과/마감연장) G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh 하네스: guardia-orchestrator 확장기능 Phase 반영 봇명령어: /sr /status /license /bulk 슬래시 명령어 추가 설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
160 lines
5.4 KiB
Bash
160 lines
5.4 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# 헬스체크 스크립트 (HTTP / TCP)
|
|
# Jenkins Stage 7: Health Check에서 호출
|
|
# 사용: health_check.sh --url <URL> [--timeout 60] [--retries 3] [--interval 10]
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
CHECK_URL=""
|
|
CHECK_TYPE="http" # http | tcp
|
|
TCP_HOST=""
|
|
TCP_PORT=""
|
|
TIMEOUT="${HEALTH_TIMEOUT:-60}"
|
|
RETRIES="${HEALTH_RETRIES:-3}"
|
|
INTERVAL="${HEALTH_INTERVAL:-10}"
|
|
EXPECT_STATUS="${EXPECT_STATUS:-200}"
|
|
LOG_DIR="${WORKSPACE:-/tmp}/build-logs"
|
|
HC_LOG="${LOG_DIR}/healthcheck-$(date +%Y%m%d-%H%M%S).log"
|
|
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
|
log() { echo -e "${GREEN}[HEALTH]${NC} $*" | tee -a "${HC_LOG}"; }
|
|
err() { echo -e "${RED}[ERROR]${NC} $*" | tee -a "${HC_LOG}"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "${HC_LOG}"; }
|
|
|
|
mkdir -p "${LOG_DIR}"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--url) CHECK_URL="$2"; CHECK_TYPE="http"; shift 2 ;;
|
|
--host) TCP_HOST="$2"; CHECK_TYPE="tcp"; shift 2 ;;
|
|
--port) TCP_PORT="$2"; CHECK_TYPE="tcp"; shift 2 ;;
|
|
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
--retries) RETRIES="$2"; shift 2 ;;
|
|
--interval) INTERVAL="$2"; shift 2 ;;
|
|
--expect) EXPECT_STATUS="$2";shift 2 ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
# ── HTTP 헬스체크 ────────────────────────────────────────────────────────────
|
|
check_http() {
|
|
log "HTTP 헬스체크: ${CHECK_URL}"
|
|
log " 예상 상태코드: ${EXPECT_STATUS}"
|
|
log " 타임아웃: ${TIMEOUT}초 / 재시도: ${RETRIES}회"
|
|
|
|
local attempt=0
|
|
while [[ ${attempt} -lt ${RETRIES} ]]; do
|
|
attempt=$((attempt + 1))
|
|
log " 시도 ${attempt}/${RETRIES}..."
|
|
|
|
local http_code
|
|
local response_time_ms
|
|
local start_ns
|
|
start_ns=$(date +%s%N 2>/dev/null || echo 0)
|
|
|
|
http_code=$(curl \
|
|
--silent \
|
|
--output /dev/null \
|
|
--write-out "%{http_code}" \
|
|
--max-time "${TIMEOUT}" \
|
|
--connect-timeout 10 \
|
|
--location \
|
|
"${CHECK_URL}" 2>/dev/null || echo "000")
|
|
|
|
local end_ns
|
|
end_ns=$(date +%s%N 2>/dev/null || echo 0)
|
|
if [[ "${start_ns}" != "0" ]]; then
|
|
response_time_ms=$(( (end_ns - start_ns) / 1000000 ))
|
|
else
|
|
response_time_ms="N/A"
|
|
fi
|
|
|
|
log " 응답: HTTP ${http_code} (${response_time_ms}ms)"
|
|
|
|
if [[ "${http_code}" == "${EXPECT_STATUS}" ]]; then
|
|
log "✅ 헬스체크 통과: HTTP ${http_code}"
|
|
return 0
|
|
elif [[ "${http_code}" == "000" ]]; then
|
|
warn " 연결 실패 (타임아웃 또는 접속 거부)"
|
|
else
|
|
warn " 예상과 다른 응답: ${http_code} (예상: ${EXPECT_STATUS})"
|
|
fi
|
|
|
|
if [[ ${attempt} -lt ${RETRIES} ]]; then
|
|
log " ${INTERVAL}초 후 재시도..."
|
|
sleep "${INTERVAL}"
|
|
fi
|
|
done
|
|
|
|
err "헬스체크 실패: ${RETRIES}회 모두 실패"
|
|
return 1
|
|
}
|
|
|
|
# ── TCP 헬스체크 ────────────────────────────────────────────────────────────
|
|
check_tcp() {
|
|
[[ -z "${TCP_HOST}" ]] && { err "--host 파라미터가 필요합니다"; exit 1; }
|
|
[[ -z "${TCP_PORT}" ]] && { err "--port 파라미터가 필요합니다"; exit 1; }
|
|
|
|
log "TCP 헬스체크: ${TCP_HOST}:${TCP_PORT}"
|
|
|
|
local attempt=0
|
|
while [[ ${attempt} -lt ${RETRIES} ]]; do
|
|
attempt=$((attempt + 1))
|
|
log " 시도 ${attempt}/${RETRIES}..."
|
|
|
|
if timeout "${TIMEOUT}" bash -c \
|
|
"echo >/dev/tcp/${TCP_HOST}/${TCP_PORT}" 2>/dev/null; then
|
|
log "✅ TCP 연결 성공: ${TCP_HOST}:${TCP_PORT}"
|
|
return 0
|
|
fi
|
|
|
|
warn " TCP 연결 실패: ${TCP_HOST}:${TCP_PORT}"
|
|
if [[ ${attempt} -lt ${RETRIES} ]]; then
|
|
log " ${INTERVAL}초 후 재시도..."
|
|
sleep "${INTERVAL}"
|
|
fi
|
|
done
|
|
|
|
err "TCP 헬스체크 실패: ${RETRIES}회 모두 실패"
|
|
return 1
|
|
}
|
|
|
|
# ── 복합 헬스체크 ─────────────────────────────────────────────────────────────
|
|
check_composite() {
|
|
# HTTP + 추가 TCP 포트 확인
|
|
check_http || return 1
|
|
|
|
if [[ -n "${TCP_HOST}" ]] && [[ -n "${TCP_PORT}" ]]; then
|
|
check_tcp || return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ── 메인 ─────────────────────────────────────────────────────────────────────
|
|
main() {
|
|
log "=== 헬스체크 시작: $(date) ==="
|
|
|
|
case "${CHECK_TYPE}" in
|
|
http)
|
|
[[ -z "${CHECK_URL}" ]] && { err "--url 파라미터가 필요합니다"; exit 1; }
|
|
check_http
|
|
;;
|
|
tcp)
|
|
check_tcp
|
|
;;
|
|
composite)
|
|
check_composite
|
|
;;
|
|
*)
|
|
err "알 수 없는 헬스체크 유형: ${CHECK_TYPE}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
log "=== 헬스체크 통과 ==="
|
|
}
|
|
|
|
main "$@"
|