
996에서 007로: 1인 기업을 위한 자가 치유(Self-Healing) Ops 스택
요약
1인 기업(OPC)을 위해 운영 업무를 자동화하여 관리자의 개입을 최소화하는 '자가 치유(Self-Healing) Ops 스택' 구축 방법을 다룹니다. 상태 확인부터 자동 재시작, 알림, 로그 로테이션으로 이어지는 4계층 무인 운영 체계를 제안합니다.
핵심 포인트
- 상태 확인, 자동 재시작, 알림, 로그 로테이션의 4계층 스택 구축
- 단순 프로세스 생존 확인이 아닌 실제 서비스 건강 상태 체크의 중요성
- systemd를 활용한 실패 시 자동 재시작 및 허위 알람 방지 전략
- 최대 자동화가 아닌 신뢰 경계(trust boundary) 설정으로서의 운영
고통 (The Pain): 100개의 작업을 자동화했지만, 시스템 자체에는 여전히 돌봐줄 사람이 필요합니다. 그리고 그 돌보는 사람은 바로 당신입니다. 새벽 3시의 알림, 주말 동안의 로그 모니터링, 가장 바쁜 날 발생하는 디스크 풀(disk-full) 패닉. 1인 기업의 가장 아이러니한 순간은 이것입니다: 운영(Operations)을 제외한 모든 것이 자동화되어 있다는 사실입니다.
배울 내용:
- "프로세스가 살아있다"가 "서비스가 건강하다"와 왜 다른지 — 그리고 실제로 중요한 것을 확인하는 방법
- 4계층 무인 운영(unattended ops) 스택: 상태 확인(health checks) → 자동 재시작(auto-restart) → 알림(alerting) → 로그 로테이션(log rotation)
- 데이터베이스에 실제로 연결되는
/healthz엔드포인트와 30초 프로브(probe) 스크립트 - 작은 실패를 무한 재시작 루프 없이 5초 만에 스스로 해결하는
systemd Restart=on-failure - 3회 연속 실패 시에만 알림을 보내는 알림 스크립트 — 허위 알람(false alarms)을 방지하고, 복구 시에는 복구 통지를 제공
- 무인 운영(unattended ops)이 왜 최대 자동화가 아니라 **신뢰 경계(trust boundary)**를 설정하는 문제인지
1. 서론: 새벽 3:07 — 당신의 휴대폰이 밝아진다
새벽 3:07, WeCom 알림이 당신을 깨웁니다. 고객의 질문도, 새로운 주문도 아닙니다 — 콘텐츠 팩토리 파이프라인(content factory pipeline)이 멈췄습니다. 데이터베이스(Database) 접속 불가, 기사 미발행, A/B 실험 수렴 확인(convergence check) 미실행. 당신은 일어나 노트북을 열고, 로그를 읽고, 서비스를 재시작하고, 복구를 확인한 뒤 다시 눕습니다. 당신이 눈을 감는 순간, 두 번째 알림이 도착합니다.
이것이 1인 기업(OPC)의 가장 아이러니한 순간입니다: 당신은 모든 작업을 자동화했습니다 — 스스로 자동화될 수 없는 운영(operations)을 제외하고 말이죠. 시스템은 당신을 위해 100가지 일을 수행하지만, 시스템 자체에는 관리자가 필요하며, 유일한 관리자는 당신뿐입니다.
이전 기사에서 우리는 3계층 자동 A/B 테스트 시스템(A/B Testing for One Person — an AI-Driven Automated Optimization System)을 구축했습니다. 시스템은 스스로 시도하고, 수렴하며, 전환합니다. 하지만 이 모든 것은 취약한 가정 위에 놓여 있습니다: 서버가 반드시 살아있어야 한다는 것. 서버를 살아있게 유지하는 것은 여전히 인간의 온콜(on-call) 업무였습니다 — 996(오전 9시부터 오후 9시까지 주 6일 근무)은 사치였고, 그것은 007(언제든 대기해야 하는 상황)이었습니다.
오늘의 해결책은 4계층의 무인 운영 (unattended ops) 스택입니다: 상태 확인 (health checks), 자동 재시작 (auto-restart), 알림 (alerting), 로그 로테이션 (log rotation). 이 시스템을 구축하고 나면, 시스템은 스스로를 관리합니다. 작은 질병은 스스로 일어나서 회복하고, 큰 질병이 발생했을 때만 당신을 깨웁니다. 그리고 단순히 "이제 괜찮아요"라고 말하기 위해 새벽 3시에 당신을 깨우는 일은 없을 것입니다.
2. 먼저 명확히 짚고 넘어갑시다: OPC 운영은 엔터프라이즈 운영이 아닙니다
엔터프라이즈 운영의 목표는 99.99%의 가용성 (availability), 멀티 데이터 센터 중복성 (multi-datacenter redundancy), 온콜 당번표 (on-call rosters), 티켓 시스템 (ticket systems), 변경 승인 (change approvals)입니다. OPC는 이러한 자원이 전혀 없으며, 이러한 목표도 필요하지 않습니다. OPC의 현실은 다음과 같습니다: 서버 한 대, 배포 디렉토리 하나, 당번표 없음, 전담 운영 엔지니어 없음.
따라서 OPC의 운영 목표는 다음 세 문장으로 재정의됩니다:
- 시스템이 죽으면, 스스로 다시 일어난다 (자가 치유 (self-healing))
- 스스로 일어날 수 없다면, 나를 깨운다 (알림 (alerting))
- 작은 문제가 큰 문제로 번지지 않게 한다 (로그 로테이션 (log rotation) 및 디스크 워터마크 (disk watermarks))
핵심 통찰은 단 한 문장입니다: 무인 (unattended)이라는 것은 "아무도 지켜보지 않는다"는 뜻이 아니라, "기계가 먼저 처리하고, 해결할 수 없을 때만 인간을 호출한다"는 뜻입니다. 아래의 모든 설계는 이 문장에서 비롯됩니다.
3. 아키텍처: 4계층 무인 운영 스택

아키텍처 다이어그램은 이 글의 로드맵입니다.
┌────────────────────────────────────────────────────────┐
│ Layer 1: Health Checks │
│ → service exposes /healthz, probe every 30s │
...
각 계층은 한 종류의 문제를 해결하며, 하위 계층은 상위 계층의 대비책 (fallback) 역할을 합니다. 하나씩 자세히 살펴보겠습니다.
4. Layer 1: Health Checks — 시스템이 "살아있음"을 보고하게 하라
먼저, 직관에 어긋나는 함정이 하나 있습니다: 프로세스가 살아있는 것과 서비스가 건강한 것은 동일하지 않습니다.
제가 처음으로 라이브니스 (liveness) 스크립트를 작성했을 때, 저는 단지 "프로세스가 존재하는가"만을 확인했습니다: pgrep -f content_factory. 프로세스가 존재하기만 하면 모든 것이 괜찮다고 생각했습니다. 그러던 어느 날, 프로세스는 떠 있었지만 서비스는 이미 죽어 있는 상태를 발견했습니다. 데이터베이스 연결 풀 (connection pool)이 고갈되어 모든 요청이 타임아웃 (timeout)이 될 때까지 멈춰 있었습니다. 프로세스는 죽지 않았지만, 비즈니스는 중단된 상태였습니다.
따라서 헬스 체크 (health check)는 세 가지 수준이 필요합니다:
- 프로세스 수준 (Process level): 프로세스가 존재하는가 (가장 약함)
- 인터페이스 수준 (Interface level): HTTP 엔드포인트 (endpoint)가 응답하는가 (중간)
- 비즈니스 수준 (Business level): 비즈니스 상태가 올바른가 (가장 강력함 — 그리고 OPC에 실제로 필요한 것)
첫 번째 단계, 서비스에 헬스 체크 엔드포인트를 추가합니다 (FastAPI 예시):
# app/main.py - health check endpoint
import fastapi
import psycopg2
...
이 엔드포인트는 "프로세스가 살아있다"를 넘어 한 가지 일을 더 수행합니다 — 실제로 데이터베이스에 한 번 연결해 보는 것입니다. 인터페이스에 접근할 수 있다고 해서 의존성 (dependency)까지 접근할 수 있다는 뜻은 아닙니다. 프로브 (probe)는 가장 취약한 의존성을 조사해야 합니다. 데이터베이스는 OPC 시스템에서 가장 취약한 의존성이므로, /healthz는 반드시 3초의 연결 타임아웃 (connect timeout)을 설정하여 실제로 한 번 연결을 시도해야 하며, 프로브 스크립트는 최대 5초 동안 대기해야 합니다.
두 번째 단계, healthcheck.sh 프로브 스크립트를 생성합니다 (30초마다 cron 실행):
#!/bin/bash
# healthcheck.sh - probe script
HEALTH_URL="http://127.0.0.1:8000/healthz"
...
curl의 종료 코드 (exit code)와 HTTP 상태 코드 (status code)를 모두 확인해야 하며, 성능 저하 (degraded) 상태도 로그에 기록해야 합니다. 이 계층은 아무것도 처리하지 않습니다 — 오직 **탐지 (detect)**만 수행합니다. 탐지된 내용은 다음 계층으로 넘겨집니다.
5. Layer 2: Auto-Restart — 작은 질병은 스스로 치유된다
헬스 체크가 문제를 발견했을 때, 누가 이를 처리할까요? 만약 한밤중이라면, 정답은 사람이 아닙니다 — 시스템 (systemd)이 처리하게 두십시오.
systemd의 재시작 정책 (Restart policy)은 정확히 이 용도로 설계되었습니다. 첫 번째 단계, /etc/systemd/system/ 아래에 content-factory.service를 생성합니다:
# /etc/systemd/system/content-factory.service
[Unit]
Description=OPC Content Factory
...
가장 중요한 두 가지 파라미터는 다음과 같습니다:
Restart=on-failure: 프로세스가 비정상적으로 종료될 때 자동으로 재시작합니다.Restart=always를 사용하지 마세요 — 만약 설정 오류로 인해 프로세스가 계속 충돌한다면,always는 무한히 재시작하며 로그를 범람시킬 것입니다. 반면on-failure를StartLimitBurst=5와 결합하면 5분 동안 최대 5번까지만 재시작을 시도한 뒤, 시도를 멈추고failed상태로 진입합니다. 바로 이때 알림(Alerting)이 개입할 차례입니다.RestartSec=5: 재시작하기 전 5초 동안 대기하여, 데이터베이스가 회복할 수 있는 약간의 시간을 제공합니다.
서비스를 활성화하고 상태를 확인하려면 다음 명령어를 실행하세요:
systemctl daemon-reload
systemctl enable content-factory
systemctl start content-factory
...
전체적인 자가 치유(Self-healing) 결정 흐름:
프로브(Probe) 실패 → systemd 자동 재시작 → 재프로브 → 성공 시 "자가 치유(self-healed)" 기록; 연속된 실패가 누적되어 임계값을 넘어서면 알림 계층(alert layer)으로 진입합니다. 자동 재시작이 "행동(action)"이라면, 상태 확인(health checks)은 "눈(eyes)"입니다 — 이 둘은 반드시 쌍으로 존재해야 합니다. 프로브 없는 재시작은 눈을 감고 운전하는 것과 같습니다.
6. 레이어 3: 알림(Alerting) — 스스로 일어날 수 없다면, 사람을 불러라
systemd가 몇 번 재시작을 시도했음에도 여전히 복구되지 않는다면, 그것은 단순한 일시적 충돌이 아니라 사람이 개입해야 하는 문제입니다. 바로 그때 알림(Alerting)이 역할을 수행할 차례이며, 알림 자체도 절제되어야 합니다.
알림의 가장 큰 적은 오탐(false positives)입니다. 단 한 번의 잘못된 알림이 초래하는 비용은 단순한 메시지 한 통이 아닙니다. 그것은 당신이 알림을 신뢰하지 않게 만들고, 알림을 무음 처리하게 만들며, 결국 진짜 중요한 문제를 놓치게 만드는 것입니다. 따라서 알림은 두 가지를 수행해야 합니다: 연속적인 확인(consecutive confirmation) + 복구 통지(recovery notice).
systemd와 독립적으로 작동하는 monitor.py를 생성합니다 (1분마다 cron 실행):
# monitor.py - 알림 스크립트, 3회 연속 실패 시에만 푸시
import urllib.request
...
이 방식은 표준 라이브러리인 urllib만을 사용하므로, 별도로 requests를 설치할 필요가 없습니다. 상태(State)는 /var/run 하위의 파일에 저장되므로, 머신을 재부팅하면 상태가 초기화되고 카운트도 다시 시작됩니다. 동작 방식은 다음과 같습니다:
- 1회 또는 2회 실패 시: 카운트만 증가하며 푸시 알림은 보내지 않음 — 일시적인 지터(jitter) 현상은 신경 쓰지 않습니다.
- 3회 실패 시: WeCom 알림을 푸시하며, 단 한 번만 실행됩니다.
- 복구(Recovery): 알림 상태였다가 정상화되면, "self-healed" 메시지를 푸시하고 카운트를 0으로 초기화합니다.
WeCom 봇 웹후크(webhook)는 한 줄의 설정만으로 가능하며, WeCom이 설치된 휴대폰으로 알림을 받을 수 있습니다. cron 설정은 다음과 같습니다:
# crontab -e 에 다음 줄을 추가하세요
* * * * * cd /home/opc && /usr/bin/python3 monitor.py >> /var/log/opc-monitor.log 2>&1
전체 알림 체인:
이 시점에서 "시스템이 스스로를 돌본다"는 말이 성립됩니다. 작은 질병은 스스로 치유(self-heal)하고, 큰 문제는 사람을 호출하며, 상태가 좋아지면 당신에게 알려줍니다.
7. 레이어 4: 로그 로테이션 (Log Rotation) — 로그가 디스크를 채우게 두지 마세요
마지막 레이어는 가장 눈에 띄지 않지만, OPC 서버에서 발생하는 가장 흔한 사고의 근본 원인입니다: 로그가 디스크를 가득 채우고, 모든 서비스가 중단되는 것입니다.
서비스가 실행되자마자 로그는 눈에 띄는 속도로 증가합니다. nginx 액세스 로그, 애플리케이션 로그, 프로브(probe) 로그, 모니터 로그 등 여러 파일이 차곡차곡 쌓입니다. 디스크가 가득 차면 데이터베이스는 쓰기를 할 수 없고, 서비스는 시작할 수 없으며, 심지어 SSH조차 멈출 수 있습니다. 그리고 이는 대개 가장 바쁜 날에 발생합니다.
해결책은 Linux의 내장 로그 로테이션 도구인 logrotate입니다:
# /etc/logrotate.d/opc
/var/log/content-factory/*.log {
daily
...
이 설정의 의미는 다음과 같습니다: 매일 로테이션하거나, 단일 파일이 50M를 초과하면 즉시 로테이션합니다. 7개의 복사본을 유지하며, 오래된 로그는 .gz로 압축합니다. copytruncate는 활발하게 쓰기 작업 중인 프로세스에 친화적입니다. 먼저 복사한 다음 내용을 잘라내기(truncate)하므로, 애플리케이션을 재시작할 필요가 없습니다.
순환(Rotation)만으로는 충분하지 않습니다. 디스크 워터마크(disk watermark) 자체도 모니터링해야 합니다. disk_watch.sh를 생성하세요:
# disk_watch.sh - 디스크 워터마크 체크, 매시간 cron 실행
usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$usage" -gt 85 ]; then
...
디스크 사용량이 85%를 초과하면 알림을 전송합니다. 로그 순환(Log rotation)이 "치료"라면, 디스크 워터마크는 "예방"입니다. 이 둘이 결합되어 가장 흔한 함정을 차단합니다.
8. 전후 비교: 무엇을 절약하는가
| 영역 | 사람이 직접 대응 (Human on-call) | 무인 스택 (Unattended stack) |
|---|---|---|
| 탐지 (Probing) | 직감에 의존한 "괜찮겠지" | /healthz가 30초마다 실제로 데이터베이스에 연결 확인 |
| ... |
9. 심층 분석: 무인(Unattended)의 핵심은 자동화가 아니라 — 신뢰 경계(Trust Boundary)이다
많은 사람이 "무인(unattended)"을 "완전 자동(fully automatic)"으로 오해하여, 모든 단계에 자동화를 쑤셔 넣습니다. 그 결과 아무도 이해할 수 없는 시스템을 만들게 되며, 시스템이 고장 나면 모든 것이 무너집니다. 이는 방향이 잘못되었습니다.
무인 운영(unattended ops)의 본질은 장애 복구(failure recovery) 주위에 신뢰 경계(trust boundary)를 설정하는 것입니다. 즉, 어떤 것은 기계가 스스로 처리하고, 어떤 것은 반드시 사람을 불러야 하는지를 결정하는 것입니다. 만약 경계가 너무 넓게 설정되면, 기계는 잘못된 방향으로 계속해서 재시작을 시도하며 상황을 악화시킵니다. 반대로 경계가 너무 좁게 설정되면, 기계는 아무것도 하지 못하고 결국 당신이 여전히 온콜(on-call) 업무를 맡게 됩니다.
이 스택에서 경계는 세 가지 선으로 그려집니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
