정말 귀찮은데 하기싫은데 어쩔수 없이 해야하는 일이있다.
사내 개발자가 능력 부족으로 만들지 못한다고 선언한 모니터링 툴이다.
아니 뭐 스카우터같은 APM 써라~ 라고 할수도 있지만
APM... 그거 뭐 데이터독 연결해서 로그수집 제대로 해서 로그서버 따박따박 들어와서 슬랙 연동하고
이런 정상적인 기업이나 가능한거지 우리같은 좋소는 힘들다(내가 다해야해서 불가능함)
그래서 어떻게 하는가
1. 상부의 허가를 받는다
2. AI에게 외주를 준다.
우선 결과만 보자면 잘만들어졌다.
대신 15초마다 서버에 질의하는 미친 DDOS가 되버렸다.
그나마 자사솔루션이니까... 이건 좀 개선방법을 찾아봐야겠음.
application 단 내부 질의하기 싫은데 외부에 음.... 1px 짜리 이미지 박아야하나
테스트 개발하고
실제 모니터링 서비스..
개발자 및 개발을 하기 싫은 나는 Python으로 가볍게 아주 가볍게 만들꺼다
필요한건 딱 두개 파일이다.
data/app.py(백엔드), data/templates/index.html(프론트)
[report@SERVER data]$ vi app.py
from flask import Flask, render_template, jsonify
import threading
import time
import requests
from datetime import datetime
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True # 템플릿 자동 리로드 활성화
app.config['JSON_AS_ASCII'] = False # JSON 응답에서 비-ASCII 문자 허용
# 설정값: 기존 서비스 목록 유지
SERVICES_TO_CHECK = {
"국제흰쌀밥알리기조직위원회": "https://globalriceanybody.or.kr/images/login-id.png",
}
# 각 서비스의 동적 페이지 설정 (DB 연결 확인용)
DYNAMIC_PAGES = {
"국제흰쌀밥알리기조직위원회": "https://globalriceanybody.or.kr/passBy/login.do",
}
# DB 오류 감지 패턴 설정
DB_ERROR_PATTERN = "오류가 발생했습니다" # 공통 오류 메시지 패턴
CHECK_INTERVAL = 15 # 15초마다 체크
FAILURE_THRESHOLD = 3 # 3회 연속 실패 시 알림 발생
# 상태 변수
status_history = {service_name: [] for service_name in SERVICES_TO_CHECK} # 서비스별 상태 기록
consecutive_failures = {service_name: 0 for service_name in SERVICES_TO_CHECK} # 서비스별 연속 실패 횟수
is_system_down = {service_name: False for service_name in SERVICES_TO_CHECK} # 서비스별 다운 상태
status_lock = threading.Lock() # 스레드 안전을 위한 락
max_history_size = 100 # 서비스당 최대 기록 보관 수
def check_db_connection(service_name):
"""DB 연결 상태를 확인하는 헬퍼 함수
Args:
service_name: 확인할 서비스 이름
Returns:
튜플: (db_status, db_connection_error, error_type)
- db_status: 상태 설명 문자열
- db_connection_error: 오류 있음 여부(True/False)
- error_type: 오류 유형 코드
"""
try:
# 서비스에 동적 페이지가 정의되어 있지 않으면 확인 불가
if service_name not in DYNAMIC_PAGES:
return "확인 불가", False, None
dynamic_url = DYNAMIC_PAGES[service_name]
dynamic_response = requests.get(dynamic_url, timeout=5)
# 응답 코드에 따른 분류
if dynamic_response.status_code == 404:
# 404는 경로 오류로 분류
return "경로 오류", True, "path_error"
elif dynamic_response.status_code != 200:
# 200이 아닌 다른 HTTP 오류
return f"HTTP {dynamic_response.status_code}", True, "http_error"
# 응답 내용에 오류 메시지가 있는지 확인
if DB_ERROR_PATTERN in dynamic_response.text:
# DB 연결 오류 발견
return "연결 오류", True, "db_error"
# 모든 검사 통과 - 정상 상태
return "정상", False, None
except requests.RequestException as e:
# 요청 자체가 실패한 경우
return f"접속 불가: {str(e)[:50]}", True, "connection_error"
def check_service(service_name, url):
"""개별 서비스 헬스체크 수행 - OR 로직 기반 최적화된 흐름"""
global consecutive_failures, is_system_down, status_history
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
# 1. 정적 리소스(이미지) 확인 - 첫 번째 검사
start_time = time.time()
static_response = requests.get(url, timeout=5)
response_time = time.time() - start_time
# 서버가 응답하지 않는 경우 - 가장 심각한 문제 먼저 확인
if static_response.status_code != 200:
with status_lock:
consecutive_failures[service_name] += 1
status = {
'timestamp': timestamp,
'status_code': static_response.status_code,
'message': f"HTTP {static_response.status_code} 오류",
'response_time': f"{response_time:.3f}초",
'success': False,
'db_status': '확인 불가', # 서버가 다운되면 DB 확인 불가
'error_type': 'server_down',
'consecutive_failures': consecutive_failures[service_name]
}
with status_lock:
if consecutive_failures[service_name] >= FAILURE_THRESHOLD and not is_system_down[service_name]:
status['alert'] = f"시스템 다운! {consecutive_failures[service_name]}회 연속 실패"
is_system_down[service_name] = True
# 서버는 응답하지만 다른 문제가 있는지 확인
else:
# 2. DB 연결 상태 확인 - 두 번째 검사
db_status, db_connection_error, error_type = check_db_connection(service_name)
# DB 연결에 문제가 있는 경우
if db_connection_error:
with status_lock:
consecutive_failures[service_name] += 1
# 오류 유형에 따른
error_message = '알 수 없는 오류'
if error_type == 'db_error':
error_message = 'DB 연결 오류'
elif error_type == 'path_error':
error_message = '경로 오류'
elif error_type == 'http_error':
error_message = f"HTTP 오류: {db_status}"
elif error_type == 'connection_error':
error_message = '접속 불가'
status = {
'timestamp': timestamp,
'status_code': 200, # 서버 자체는 정상
'message': error_message,
'response_time': f"{response_time:.3f}초",
'success': False, # DB 연결 오류는 실패로 간주
'db_status': db_status,
'error_type': error_type,
'consecutive_failures': consecutive_failures[service_name]
}
with status_lock:
if consecutive_failures[service_name] >= FAILURE_THRESHOLD and not is_system_down[service_name]:
alert_message = "시스템 다운!"
if error_type == 'db_error':
alert_message = "DB 연결 문제!"
elif error_type == 'path_error':
alert_message = "경로 오류!"
status['alert'] = f"{alert_message} {consecutive_failures[service_name]}회 연속 실패"
is_system_down[service_name] = True
# 3. 모든 검사를 통과 - 모든 것이 정상인 경우
else:
status = {
'timestamp': timestamp,
'status_code': 200,
'message': 'OK',
'response_time': f"{response_time:.3f}초",
'success': True,
'db_status': db_status,
'error_type': None,
'consecutive_failures': consecutive_failures[service_name]
}
with status_lock:
if consecutive_failures[service_name] > 0:
status['message'] = f"{consecutive_failures[service_name]}회 실패 후 복구됨"
# 성공 시 카운터 초기화
consecutive_failures[service_name] = 0
if is_system_down[service_name]:
status['alert'] = "시스템이 복구되었습니다!"
is_system_down[service_name] = False
except requests.RequestException as e:
# 연결 자체가 실패한 경우
with status_lock:
consecutive_failures[service_name] += 1
status = {
'timestamp': timestamp,
'status_code': 0,
'message': f"연결 오류: {str(e)[:100]}",
'response_time': "N/A",
'success': False,
'db_status': '확인 불가',
'error_type': 'connection_error',
'consecutive_failures': consecutive_failures[service_name]
}
with status_lock:
if consecutive_failures[service_name] >= FAILURE_THRESHOLD and not is_system_down[service_name]:
status['alert'] = f"시스템 다운! {consecutive_failures[service_name]}회 연속 실패"
is_system_down[service_name] = True
# 상태 기록 업데이트
with status_lock:
status_history[service_name].append(status)
# 최근 기록만 유지
if len(status_history[service_name]) > max_history_size:
status_history[service_name].pop(0)
return status
def perform_health_check():
"""모든 서비스에 대한 헬스체크 수행"""
results = {}
for service_name, url in SERVICES_TO_CHECK.items():
results[service_name] = check_service(service_name, url)
return results
def health_check_worker():
"""백그라운드에서 정기적으로 헬스체크 수행"""
while True:
perform_health_check()
time.sleep(CHECK_INTERVAL)
@app.route('/')
def index():
"""메인 모니터링 페이지 렌더링"""
try:
return render_template('index.html',
services=SERVICES_TO_CHECK,
interval=CHECK_INTERVAL,
threshold=FAILURE_THRESHOLD)
except UnicodeDecodeError as e:
return f"템플릿 파일 인코딩 오류: {str(e)}<br>templates 폴더의 index.html 파일이 UTF-8로 저장되었는지 확인하세요."
@app.route('/api/status')
def status():
"""현재 상태 및 기록을 제공하는 API 엔드포인트"""
with status_lock:
# 각 서비스별 현재 상태 구성
current_statuses = {}
for service_name in SERVICES_TO_CHECK:
if status_history[service_name]:
current_statuses[service_name] = status_history[service_name][-1]
return jsonify({
'services': current_statuses,
'history': status_history,
'config': {
'urls': SERVICES_TO_CHECK,
'dynamic_pages': DYNAMIC_PAGES,
'interval': CHECK_INTERVAL,
'threshold': FAILURE_THRESHOLD
}
})
def start_background_worker():
"""백그라운드 헬스체크 스레드 시작"""
worker_thread = threading.Thread(target=health_check_worker, daemon=True)
worker_thread.start()
if __name__ == '__main__':
# 백그라운드 헬스체크 워커 시작
start_background_worker()
# Flask 앱 실행
app.run(debug=True, host='0.0.0.0', port=15000)
vi index.html
<!DOCTYPE html>
<html>
<head>
<title>국제흰쌀밥알리기조직위원회 사이트 모니터링</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: 'Malgun Gothic', '맑은 고딕', sans-serif;
margin: 0;
padding: 20px;
background-color: #f8f9fa;
color: #333;
}
.container {
width: 98%;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.header-container h1 {
margin: 0;
padding: 0;
border-bottom: none;
}
.next-check-top {
font-size: 1em;
color: #007bff;
font-weight: 500;
background-color: #f8f9fa;
padding: 6px 12px;
border-radius: 4px;
border: 1px solid #dee2e6;
}
#services-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 40px;
margin-top: 20px;
}
@media (max-width: 768px) {
#services-container {
grid-template-columns: 1fr;
}
}
.service-panel {
padding: 25px;
border-radius: 8px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
min-height: 150px;
border: 2px solid #007bff;
}
.service-panel:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.service-panel.selected {
box-shadow: 0 0 0 2px #007bff;
border-width: 3px;
}
.service-name {
font-weight: bold;
font-size: 1.4em;
margin-bottom: 10px;
}
.service-url {
font-size: 0.9em;
color: #6c757d;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 12px;
}
.service-status-info {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.status-indicator {
display: inline-block;
width: 14px;
height: 14px;
border-radius: 50%;
margin-right: 8px;
}
.status-indicator.online {
background-color: #28a745;
}
.status-indicator.warning {
background-color: #ffc107;
}
.status-indicator.offline {
background-color: #dc3545;
}
.status-text {
font-size: 1.1em;
font-weight: 500;
}
.status-code {
font-size: 2.0em; /* 2.5em에서 축소 */
font-weight: bold;
padding: 10px 10px; /* 10px 20px에서 축소 */
border-radius: 8px; /* 8px에서 약간 줄임 */
color: white;
position: absolute;
bottom: 15px;
right: 15px;
}
.code-200 {
background-color: #28a745;
}
.code-error {
background-color: #dc3545;
}
.code-other {
background-color: #ffc107;
color: #333;
}
.code-db-error {
background-color: #dc3545;
}
.status-panel {
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.status-online {
background-color: #d4edda;
border-left: 5px solid #28a745;
}
.status-warning {
background-color: #fff3cd;
border-left: 5px solid #ffc107;
}
.status-offline {
background-color: #f8d7da;
border-left: 5px solid #dc3545;
}
.status-unknown {
background-color: #e2e3e5;
border-left: 5px solid #6c757d;
}
.status-info {
flex-grow: 1;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
box-shadow: 0 2px 3px rgba(0,0,0,0.1);
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
}
tr:hover {
background-color: #f8f9fa;
}
.success-row {
border-left: 4px solid #28a745;
}
.warning-row {
border-left: 4px solid #ffc107;
}
.failure-row {
border-left: 4px solid #dc3545;
}
.badge {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
font-size: 0.85em;
font-weight: 600;
}
.badge-success {
background-color: #d4edda;
color: #155724;
}
.badge-warning {
background-color: #fff3cd;
color: #856404;
}
.badge-danger {
background-color: #f8d7da;
color: #721c24;
}
.config-info {
background-color: #e9ecef;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 0.9em;
}
.flex-space-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.alert-message {
padding: 10px;
margin-top: 10px;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 4px;
color: #721c24;
font-weight: bold;
}
.recovery-message {
padding: 10px;
margin-top: 10px;
background-color: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 4px;
color: #155724;
font-weight: bold;
}
.service-details-section {
margin-top: 40px;
padding-top: 30px;
border-top: 1px solid #eee;
}
.response-time {
font-size: 1.1em;
margin-top: 10px;
display: block;
}
.service-alert {
margin-top: 5px;
font-size: 0.85em;
font-weight: bold;
}
.service-alert.error-alert {
color: #dc3545;
}
.service-alert.recovery-alert {
color: #28a745;
}
/* 개발 환경 표시 헤더 스타일 */
.dev-indicator {
background-color: #17a2b8;
color: white;
padding: 5px 10px;
border-radius: 4px;
margin-left: 10px;
font-size: 0.8em;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="header-container">
<h1>SaaS 국제흰쌀밥알리기조직위원회 모니터링</span></h1>
<div id="next-check" class="next-check-top">다음 점검: 계산 중...</div>
</div>
<div class="config-info">
<div><strong>점검 주기:</strong> <span>{{ interval }}</span>초</div>
<div><strong>알림 임계값:</strong> <span>{{ threshold }}</span>회 연속 실패</div>
</div>
<div id="services-container">
<!-- JavaScript로 동적 생성됨 -->
</div>
<div id="service-details" class="service-details-section">
<div class="flex-space-between">
<h2>서비스 상세 내역: <span id="selected-service-name">로딩 중...</span></h2>
</div>
<table>
<thead>
<tr>
<th>시간</th>
<th>상태 코드</th>
<th>메시지</th>
<th>응답 시간</th>
<th>연속 실패</th>
</tr>
</thead>
<tbody id="history-body">
<tr>
<td colspan="5" style="text-align: center;">데이터 로딩 중...</td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
// API 호출 간격을 백엔드 데이터 수집 주기와 맞춤 (15초)
const REFRESH_INTERVAL = 15000; // 15초마다 API 호출
let nextCheckTime = 0;
const FAILURE_THRESHOLD = {{ threshold }};
let selectedService = null;
// URL을 잘라주는 함수 추가
function getTruncatedUrl(url) {
// URL에서 도메인만 추출 (프로토콜 포함)
const domainMatch = url.match(/^https?:\/\/[^\/]*/);
if (domainMatch) {
return domainMatch[0] + '/'; // 도메인 + 슬래시만 반환
}
return url; // 매치가 안되면 원래 URL 반환
}
async function fetchStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
if (data.services) {
createOrUpdateServicePanels(data.services, data.config.urls);
if (!selectedService && Object.keys(data.services).length > 0) {
selectedService = Object.keys(data.services)[0];
}
if (selectedService && data.history[selectedService]) {
updateServiceDetails(selectedService, data.history[selectedService]);
// 개선된 타이밍 계산
const interval = data.config.interval;
// 다음 체크까지 남은 시간 계산 (항상 양수가 되도록 보장)
nextCheckTime = Math.max(1, interval - (Date.now() / 1000) % interval);
}
}
} catch (error) {
console.error('상태 가져오기 오류:', error);
document.getElementById('selected-service-name').textContent = 'API 연결 오류';
}
}
function createOrUpdateServicePanels(services, urls) {
const container = document.getElementById('services-container');
if (container.children.length === 0) {
Object.entries(urls).forEach(([serviceName, url]) => {
const panel = document.createElement('div');
panel.id = `service-panel-${serviceName}`;
panel.className = 'service-panel';
panel.onclick = () => selectService(serviceName);
panel.innerHTML = `
<div class="service-name">${serviceName}</div>
<div class="service-url">${getTruncatedUrl(url)}</div>
<div class="service-status-info">
<span class="status-indicator"></span>
<span class="status-text"></span>
</div>
<div class="service-details">
<span class="response-time"></span>
</div>
<div class="service-alert"></div>
<div class="status-code"></div>
`;
container.appendChild(panel);
});
}
Object.entries(services).forEach(([serviceName, status]) => {
updateServicePanel(serviceName, status);
});
}
function updateServicePanel(serviceName, status) {
const panel = document.getElementById(`service-panel-${serviceName}`);
if (!panel) return;
const statusIndicator = panel.querySelector('.status-indicator');
const statusText = panel.querySelector('.status-text');
const responseTime = panel.querySelector('.response-time');
const statusCode = panel.querySelector('.status-code');
const alertBox = panel.querySelector('.service-alert');
if (status.success) {
panel.className = 'service-panel status-online';
statusIndicator.className = 'status-indicator online';
statusText.textContent = '온라인';
} else if (status.consecutive_failures >= FAILURE_THRESHOLD) {
panel.className = 'service-panel status-offline';
statusIndicator.className = 'status-indicator offline';
statusText.textContent = '다운';
} else {
panel.className = 'service-panel status-warning';
statusIndicator.className = 'status-indicator warning';
statusText.textContent = '경고';
}
responseTime.textContent = status.response_time;
// DB 오류 특별 처리
if (status.status_code === "DB 오류") {
statusCode.textContent = "DB ERR";
statusCode.className = 'status-code code-db-error';
} else {
statusCode.textContent = status.status_code === 0 ? 'ERR' : `HTTP ${status.status_code}`;
statusCode.className = status.status_code === 200 ? 'status-code code-200' :
status.status_code === 0 ? 'status-code code-error' :
'status-code code-other';
}
if (status.alert) {
if (status.alert.includes('복구')) {
alertBox.textContent = status.alert;
alertBox.className = 'service-alert recovery-alert';
} else {
alertBox.textContent = status.alert;
alertBox.className = 'service-alert error-alert';
}
} else {
alertBox.textContent = '';
alertBox.className = 'service-alert';
}
if (serviceName === selectedService) {
panel.classList.add('selected');
} else {
panel.classList.remove('selected');
}
}
function selectService(serviceName) {
selectedService = serviceName;
document.querySelectorAll('.service-panel').forEach(panel => {
panel.classList.remove('selected');
});
const selectedPanel = document.getElementById(`service-panel-${serviceName}`);
if (selectedPanel) {
selectedPanel.classList.add('selected');
}
document.getElementById('selected-service-name').textContent = serviceName;
fetchStatus();
}
function updateServiceDetails(serviceName, history) {
document.getElementById('selected-service-name').textContent = serviceName;
updateHistoryTable(history);
}
function updateHistoryTable(history) {
const historyBody = document.getElementById('history-body');
let tableHtml = '';
if (history && history.length > 0) {
// 최근 5개 항목으로 제한 (최신순 정렬 후 처음 5개만 선택)
history.slice().reverse().slice(0, 5).forEach(entry => {
const rowClass = entry.success ? 'success-row' :
(entry.consecutive_failures >= FAILURE_THRESHOLD ? 'failure-row' : 'warning-row');
// DB 오류 특별 처리
let statusBadge;
if (entry.status_code === "DB 오류") {
statusBadge = `<span class="badge badge-danger">DB 오류</span>`;
} else {
statusBadge = entry.success ?
`<span class="badge badge-success">HTTP ${entry.status_code}</span>` :
(entry.consecutive_failures >= FAILURE_THRESHOLD ?
`<span class="badge badge-danger">${entry.status_code === 0 ? 'ERROR' : 'HTTP ' + entry.status_code}</span>` :
`<span class="badge badge-warning">${entry.status_code === 0 ? 'ERROR' : 'HTTP ' + entry.status_code}</span>`);
}
tableHtml += `
<tr class="${rowClass}">
<td>${entry.timestamp}</td>
<td>${statusBadge}</td>
<td>${entry.message}</td>
<td>${entry.response_time}</td>
<td>${entry.consecutive_failures}</td>
</tr>
`;
});
} else {
tableHtml = '<tr><td colspan="5" style="text-align: center;">데이터가 없습니다.</td></tr>';
}
historyBody.innerHTML = tableHtml;
}
// 카운트다운 타이머 함수 개선 - 음수 값 및 -0초 표시 문제 해결
function updateNextCheckCountdown() {
// 반올림하여 부동 소수점 이슈 방지
const roundedTime = Math.round(nextCheckTime);
if (roundedTime <= 0) {
// 타이머가 만료됨 - "곧" 또는 다른 적절한 메시지 표시
document.getElementById('next-check').textContent = `동기화중`;
// 선택사항: 여기서 갱신을 트리거할 수 있습니다
// fetchStatus();
} else {
// 정상 카운트다운 - 감소 및 표시
nextCheckTime -= 1;
document.getElementById('next-check').textContent = `${roundedTime}초 후`;
}
}
function init() {
fetchStatus(); // 초기 데이터 로드
// API 데이터는 15초마다 갱신 (백엔드 수집 주기와 동일)
setInterval(fetchStatus, REFRESH_INTERVAL);
// 카운트다운은 1초마다 갱신 (UI 업데이트용)
setInterval(updateNextCheckCountdown, 1000);
}
window.addEventListener('load', init);
</script>
</body>
</html>
두 파일을 경로에 맞게 넣어주자.
/data/
├── healthcheck.py
└── templates/
└── index.html
yum install -y python3 python3-pip python3-devel nginx
pip install flask requests gunicorn
파이썬 굴려야하니까 파이썬도 서버에 설치해주자
실행은
nohup python3 app_hell'scheck.py > "$LOG_FILE" 2>&1 &
명령어로 해주면된다
이거 명색이 디도스라서 무서우니까... 업무 외시간엔 끄자... 어차피 쓰는사람 없다..
#!/bin/bash
# start_monitor.sh - 모니터링 시스템 시작 스크립트
# 스크립트가 실행된 디렉토리를 저장
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 로그 파일 경로 설정
LOG_FILE="$SCRIPT_DIR/monitor.log"
# 작업 디렉토리로 이동
cd "$SCRIPT_DIR"
# 이미 실행 중인 프로세스가 있는지 확인
if pgrep -f "python3 healthcheck.py" > /dev/null; then
echo "모니터링 시스템이 이미 실행 중입니다."
echo "현재 실행 중인 프로세스 PID: $(pgrep -f "python3 healthcheck.py")"
exit 1
fi
# 애플리케이션 시작
echo "모니터링 시스템을 시작합니다..."
nohup python3 healthcheck.py > "$LOG_FILE" 2>&1 &
# 프로세스 ID 저장
PID=$!
# 실행 확인
if ps -p $PID > /dev/null; then
echo "모니터링 시스템이 성공적으로 시작되었습니다. (PID: $PID)"
echo "로그 파일: $LOG_FILE"
# PID 파일 생성
echo $PID > "$SCRIPT_DIR/monitor.pid"
echo "나중에 중지하려면 다음 명령어를 사용하세요: ./stop_monitor.sh"
else
echo "모니터링 시스템 시작에 실패했습니다. 로그 파일을 확인해보세요."
exit 1
fi
#!/bin/bash
# stop_monitor.sh - 모니터링 시스템 중지 스크립트
# 스크립트가 실행된 디렉토리를 저장
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# PID 파일 확인
PID_FILE="$SCRIPT_DIR/monitor.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
echo "저장된 모니터링 시스템 프로세스 PID: $PID"
# 프로세스가 실행 중인지 확인
if ps -p $PID > /dev/null; then
echo "모니터링 시스템을 중지합니다..."
kill $PID
rm "$PID_FILE"
echo "모니터링 시스템이 중지되었습니다."
else
echo "모니터링 시스템이 이미 중지되었습니다."
rm "$PID_FILE"
fi
else
# PID 파일이 없으면 프로세스 이름으로 검색
PID=$(pgrep -f "python3 healthcheck.py")
if [ -n "$PID" ]; then
echo "모니터링 시스템 프로세스 발견: $PID"
echo "모니터링 시스템을 중지합니다..."
kill $PID
echo "모니터링 시스템이 중지되었습니다."
else
echo "실행 중인 모니터링 시스템을 찾을 수 없습니다."
fi
fi
0 07 * * * /data/start_monitor.sh
0 19 * * * /data/stop_monitor.sh
부하안걸리게 크론탭... 슥.. 아 덜걸리게... 아.. 아아... 악
'IT > DevOps,기타 Tools' 카테고리의 다른 글
Fossies(Free and Open Sorce Software Information and Exchange) (0) | 2025.03.25 |
---|---|
Scouter 클라이언트(Swing과 Web) (1) | 2025.03.22 |
md 파일과 Python-VScode를 통해 리눅스 보고서 자동화 (0) | 2025.03.12 |
Eclipse 세팅과 Git 연동 (0) | 2025.03.05 |
Prometheus,Grafana 설치(manifest) (1) | 2025.03.05 |