2026/03/18 Blog - 28

김기훈·2026년 3월 18일

TIL

목록 보기
168/194
post-thumbnail

코딩테스트(11399)


03/18 ~ 03/22

  • 기능구현이 끝났다고 생각하기 때문에 오늘부터 이번주 할 일은

    • 서버 상태 모니터링

    • README.md 파일 작성

    • 프로젝트 브랜치 develop → main 변환 고민

      • 평소대로 develop에 해버려서 고민중
    • 이 프로젝트를 진행하면서 사용한 기능 및 진행과정 정리

    • 포트폴리오 페이지 각 프로젝트별 설명 페이지 추가하기

    • 이력서 및 자기소개서 내용 추가 및 정리

    • 내용정리중에 최적화 요소 보이면 바로 진행

      • 기능별로 쿼리 사용 개수 및 처리시간 확인 예정

오늘 일정

서버 상태 모니터링

  • AWS에 도커(Docker)로 배포한 상태

종류

  • AWS CloudWatch (AWS 네이티브 모니터링)

    • 가장 기본적이고 AWS 생태계와 완벽하게 통합되는 방법
    • 만약 EC2 인스턴스에 도커를 직접 설치해 띄웠다면 이 방법을 우선 고려 가능
      • 기본 모니터링
        • EC2 대시보드에서 서버 자체의 CPU, 네트워크 입출력 등은 별도 설정 없이 바로 확인 가능
      • 상세 모니터링 (CloudWatch Agent)
        • AWS 기본 설정으로는 서버의 '메모리'와 '디스크 남은 용량'을 볼 수 없음
        • 이를 확인하려면 EC2 내부에 CloudWatch Agent를 설치 필요
      • Container Insights
        • 만약 EC2에 직접 띄운 것이 아닌
          • AWS ECSEKS 같은 컨테이너 오케스트레이션 서비스를 사용한 경우
          • Container Insights 기능을 키면 도커 컨테이너별 상세 지표를 볼 수 있음
  • cAdvisor + Prometheus + Grafana

    • 가장 추천하는 오픈소스 조합
    • 도커 생태계에서 사실상 표준(De facto)으로 쓰이는 가장 강력하고 인기 있는 무료 모니터링 스택
      • cAdvisor (Google)
        • 도커 데몬과 통신하여 현재 실행 중인 각 컨테이너의
          • 리소스 사용량(CPU, 메모리, 네트워크 등)을 수집
      • Prometheus
        • cAdvisor가 수집한 데이터를 주기적으로 긁어와(Pull) 저장하는 시계열 데이터베이스
      • Grafana
        • Prometheus에 저장된 데이터를 보기 좋고 직관적인 대시보드 그래프로 시각화해 주는 툴
      • 장점
        • docker-compose.yml 파일 하나로
        • 위의 세 가지 툴을 도커 컨테이너로 한 번에 띄울 수 있어 구축이 비교적 쉽고
          • 대시보드 퀄리티가 매우 뛰어남
  • 터미널 기반 실시간 확인 (Docker Stats)

    • 거창한 모니터링 시스템을 구축하기 전
    • 서버에 SSH로 접속해서 지금 당장 상태를 확인하고 싶을 때 쓰는 기본 명령어
      • 서버 터미널에 docker stats 라고 입력하면 됨
      • 현재 실행 중인 모든 컨테이너의 CPU 사용률, 메모리 사용량/제한, 네트워크 I/O 등을
        • 실시간 표 형태로 보여줌 (리눅스의 top 명령어와 같음)
  • 상용 APM 툴 (Datadog, WhaTap 등)

    • 구축이나 관리에 시간을 쏟고 싶지 않다면 상용 서비스를 이용하는 것도 방법
      • 모니터링용 도커 컨테이너를 서버에 딱 한 개만 띄우면
        • 알아서 서버와 다른 컨테이너들의 모든 상태를 분석해 자사 웹 대시보드로 전송해 줌
      • 매우 편리하지만 트래픽/서버 규모에 따라 비용이 발생함

cAdvisor + Prometheus + Grafana

  • 도커 컨테이너 각각의 상세 지표를 시각적으로 화려하고 세밀하게 보기 위해서
    • 이 오픈소스 스택을 선택했음 (실무에서도 표준처럼 쓰인다고 함)
  • 기존 파일에 합치지 말고 모니터링 전용 파일을 따로 분리

    • docker-compose.prod.yml
  • 이유

    • 안정성 확보
      • 모니터링 툴(Prometheus, Grafana 등)은 설정을 변경하거나 재시작해야 할 일이 종종 생김
      • 만약 하나의 파일로 합쳐져 있다면, 모니터링 툴을 재시작하다가
        • 실수로 운영 중인 블로그 서비스(Django, DB 등)까지 내려가는 대참사를 방지 가능
    • 유지보수성
      • 파일 하나에 너무 많은 서비스(현재 4개 + 모니터링 3개 = 7개)가 들어가면
        • 코드가 지나치게 길어져 가독성이 떨어지고 관리하기 어려워짐
  • cAdvisor는 호스트의 /var/lib/docker를 직접 읽어서 데이터를 수집하므로
    • 블로그 서비스와 같은 app_network에 속하지 않아도 메인 컨테이너들의 상태를 모두 감시 가능

docker-compose.yml 작성

version: '3.8' # Docker Compose 파일의 버전을 3.8로 정의합니다.
services: # 실행할 컨테이너 서비스들의 목록을 정의하기 시작합니다.

  cadvisor: # 도커 컨테이너의 리소스를 수집하는 cAdvisor 서비스입니다.
    image: gcr.io/cadvisor/cadvisor:latest # Google에서 제공하는 cAdvisor의 최신 공식 이미지를 사용합니다.
    container_name: cadvisor # 생성될 컨테이너의 이름을 'cadvisor'로 명시합니다.
    ports: # 포트 포워딩 설정입니다.
      - "8080:8080" # EC2(호스트)의 8080 포트로 들어오는 요청을 컨테이너의 8080 포트로 연결합니다.
    volumes: # 컨테이너가 호스트의 도커 정보를 읽을 수 있도록 디렉토리를 연결(마운트)합니다.
      - /:/rootfs:ro # 호스트의 최상위 루트 디렉토리를 읽기 전용(ro)으로 연결합니다.
      - /var/run:/var/run:ro # 실행 중인 프로세스 정보를 읽기 전용으로 연결합니다.
      - /sys:/sys:ro # 시스템 하드웨어 정보를 읽기 전용으로 연결합니다.
      - /var/lib/docker/:/var/lib/docker:ro # 도커 데몬의 상태와 컨테이너 데이터를 읽기 전용으로 연결합니다.
      - /dev/disk/:/dev/disk:ro # 디스크 용량 및 I/O 정보를 읽기 전용으로 연결합니다.

  prometheus: # cAdvisor가 수집한 데이터를 가져와 저장하는 시계열 DB 서비스입니다.
    image: prom/prometheus:latest # Prometheus 최신 공식 이미지를 사용합니다.
    container_name: prometheus # 컨테이너 이름을 'prometheus'로 지정합니다.
    ports: # 포트 포워딩 설정입니다.
      - "9090:9090" # EC2의 9090 포트를 컨테이너의 9090 포트로 연결합니다.
    volumes: # Prometheus 설정 파일을 컨테이너에 주입하기 위한 마운트입니다.
      - ./prometheus.yml:/etc/prometheus/prometheus.yml # 현재 폴더의 설정 파일을 컨테이너 내부 경로로 덮어씌웁니다.
    command: # 컨테이너가 실행될 때 적용할 실행 옵션입니다.
      - '--config.file=/etc/prometheus/prometheus.yml' # 우리가 마운트한 설정 파일을 읽어서 실행하도록 강제합니다.

  grafana: # 수집된 데이터를 예쁜 그래프로 보여주는 시각화 툴입니다.
    image: grafana/grafana:latest # Grafana 최신 공식 이미지를 사용합니다.
    container_name: grafana # 컨테이너 이름을 'grafana'로 지정합니다.
    ports: # 포트 포워딩 설정입니다.
      - "3000:3000" # EC2의 3000 포트를 컨테이너의 3000 포트(Grafana 웹 화면)로 연결합니다.
    depends_on: # 컨테이너 실행 순서를 보장합니다.
      - prometheus # prometheus 컨테이너가 먼저 실행된 후에 grafana가 실행되도록 합니다.

prometheus.yml 설정 파일 작성

  • Prometheus가 cAdvisor의 데이터를 어떻게 가져올지 정의하는 파일

global: # Prometheus의 전역(Global) 설정을 시작합니다.
  scrape_interval: 15s # 15초마다 한 번씩 대상(cAdvisor 등)으로부터 지표 데이터를 긁어옵니다(Scrape).

scrape_configs: # 데이터를 수집할 대상(Target)들의 목록을 설정합니다.
  - job_name: 'cadvisor' # 이 수집 작업의 이름을 'cadvisor'라고 붙입니다.
    static_configs: # 동적이 아닌 고정된 IP나 도메인(여기서는 컨테이너 이름)을 대상으로 설정합니다.
      - targets: ['cadvisor:8080'] # 도커 내부 네트워크를 통해 'cadvisor' 컨테이너의 8080 포트로 접속해 데이터를 가져옵니다.

실행

  • 파일추가 후 재빌드는 필요없음

    • 이전에 사용하던 docker-compose.yml에서는
      • 백엔드(Django) 쪽 코드를 보면 build: . 이라는 옵션이 존재했음
      • 지금 작성한 파일에는 build: . 옵션이 없고, 전부 image: grafana/grafana:latest
      • cAdvisor, Prometheus, Grafana는
        • 구글이나 각 회사에서 이미 완벽하게 빌드해서 도커 허브(Docker Hub)에 올려둔 완성품임

배포서버 실행 순서

# 1. 기존 프로젝트 폴더(블로그 서비스가 있는 곳)로 이동합니다. (경로는 본인 환경에 맞게 수정)
cd ~/indi_Blog_Project

# 2. 모니터링 전용 폴더를 새로 만듭니다.
mkdir monitoring

# 3. 새로 만든 모니터링 폴더로 들어갑니다.
cd monitoring

# 4. nano(또는 vim) 에디터로 모니터링 전용 docker-compose 파일을 만듭니다.
nano docker-compose.yml

# 5. 마찬가지로 prometheus 설정 파일을 만듭니다.
nano prometheus.yml

# 6. 대망의 실행! 빌드(build) 과정 없이, 이미지를 다운받아 바로 백그라운드(-d)로 띄웁니다.
docker compose up -d

# 7. 컨테이너 3개가 잘 켜졌는지 상태를 확인합니다. (Status가 Up이면 성공!)
docker compose ps
NAME         IMAGE                             COMMAND                  SERVICE      CREATED         STATUS                            PORTS
cadvisor     gcr.io/cadvisor/cadvisor:latest   "/usr/bin/cadvisor -…"   cadvisor     9 seconds ago   Up 7 seconds (health: starting)   0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp
grafana      grafana/grafana:latest            "/run.sh"                grafana      9 seconds ago   Up 7 seconds                      0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp
prometheus   prom/prometheus:latest            "/bin/prometheus --c…"   prometheus   9 seconds ago   Up 7 seconds                      0.0.0.0:9090->9090/tcp, [::]:9090->9090/tcp

AWS 설정

AWS EC2 방화벽(포트) 열기

  • Grafana는 기본적으로 3000번 포트를 사용
    • 외부(내 컴퓨터 브라우저)에서 EC2의 3000번 포트로 접속할 수 있도록 길을 뚫어줘야 함
      • AWS 관리 콘솔에 로그인하고 EC2 대시보드로 이동
      • 현재 실행 중인 인스턴스를 클릭하고, 하단의 [보안] 탭을 누름
      • 보안 그룹 링크(sg-xxxxxx)를 클릭
      • 우측 하단의 [인바운드 규칙 편집] 버튼을 누름
      • [규칙 추가]를 누르고 다음과 같이 설정
        • 유형: 사용자 지정 TCP
        • 포트 범위: 3000
        • 소스: Anywhere-IPv4 (0.0.0.0/0 선택)
      • [규칙 저장]을 클릭

Grafana 접속 및 로그인

  • 웹 브라우저 주소창에 http://[EC2의_탄력적IP_또는_퍼블릭IP]:3000을 입력하고 접속합니다.
  • Grafana 로그인 화면이 나오면, 초기 계정 정보를 입력합니다.
    • ID: admin
    • Password: admin
  • 로그인 직후 비밀번호 변경 화면이 나옴
    • 원하는 새 비밀번호로 변경해 줍니다. (나중에 하려면 Skip을 눌러도 됩니다.)

Prometheus 데이터 소스 연결

  • Grafana는 도화지일 뿐이고, 물감(데이터)은 Prometheus가 가지고 있음(둘을 연결해 줘야 함)
  • Grafana 메인 화면 좌측 메뉴에서 [Connections] -> [Data sources]를 클릭
  • 파란색 [Add data source] 버튼을 누르고, 맨 위에 있는 [Prometheus]를 선택
  • 설정 화면이 나오면 HTTP 섹션의 URL 칸에 다음과 같이 적어줌
    • http://prometheus:9090
    • 해설: 같은 docker-compose 파일로 실행된 컨테이너들은 자동으로 같은 네트워크로 묶임
      • 따라서 컨테이너 이름인 prometheus를 도메인 주소처럼 사용할 수 있어 편리함
  • 스크롤을 맨 밑으로 내려서 [Save & test]를 클릭(초록색 체크 표시가 나오면 연결 성공)

Import

  • 남이 만든 멋진 대시보드 템플릿 가져오기

    • 그래프를 하나하나 그릴 필요 없이
      • 전 세계의 대단한 개발자들이 만들어둔 템플릿을 클릭 한 번에 가져올 수 있음
    • 좌측 메뉴에서 [Dashboards]를 클릭하고, 우측 상단의 [New] -> [Import]를 클릭
  • Import via grafana.com 항목의 빈칸에
    • 숫자 14282 (가장 유명한 cAdvisor 도커 모니터링 템플릿 ID)를 입력하고
    • 우측의 [Load] 버튼을 누름
  • Options 화면의 맨 아래 Prometheus 드롭다운 메뉴를 클릭해서
    • 방금 연결한 Prometheus를 선택
  • [Import] 버튼을 클릭합니다.

결과


no data 해결

  • Grafana가 Prometheus로부터 가져올 데이터(물감)를 아예 찾지 못하고 있다
    • AWS EC2 관리 콘솔
      • [보안 그룹]으로 이동하여 9090번 포트를 열기
    • 웹 브라우저 주소창에 http://[EC2_탄력적IP]:9090/targets 라고 입력하여 접속
    • 화면에 cadvisor라는 이름의 블록이 보이고
      • 그 안의 State가 초록색 UP으로 표시되어 있는지 확인

기능

시리즈

  • 시리즈 제목에 책 이모지 지우기
  • 시리즈에 포함된 포스트의 제목 밑에 내용이 있음에도 없다고 뜸
  • 시리즈에 포함된 포스트를 수정하면 시리즈에 속함이 없어짐

수정

  • 시리즈 제목에 책 이모지를 지우기

titleElement.textContent = `📚 ${seriesName}`;

——————————————————————————————————————[비교]—————————————————————————————————————————
titleElement.textContent = seriesName;
  • 시리즈에 포함된 내용이 존재함에도 "내용이 없습니다."가 뜨는 문제

    class Meta:
        model = Post
        fields = [
            "id",
            "title",
            "thumbnail",
            "author_nickname",
            "author_profile_image",
            "author_grade_image",
            "created_at",
            "visibility",
            "likes_count",
            "tags",
            "series_id",
            "series_name",
        ]
——————————————————————————————————————[비교]—————————————————————————————————————————
    class Meta:
        model = Post
        fields = [
            "id",
            "title",
            "summary",
            "thumbnail",
            "author_nickname",
            "author_profile_image",
            "author_grade_image",
            "created_at",
            "visibility",
            "likes_count",
            "tags",
            "series_id",
            "series_name",
        ]
  • 시리즈에 속한 포스트를 수정하면 시리즈가 비워짐

    • PostDetailSerializer는 시리즈의 고유번호를 series_id라는 이름으로 내려주고 있음
    • 하지만 프론트엔드 자바스크립트에서는 존재하지 않는 data.series라는 값을 찾으려 시도
      • 결과적으로 초기화 과정에서 선택 박스가 "선택 안 함"으로 맞춰지게 되고
      • 이 상태에서 "수정 완료"를 누르면 시리즈 정보가 없는 것(null)으로 서버에 전송되어
      • 시리즈에서 빠지게 되는 현상이었음
            if (res.ok) {
                document.getElementById('title').value = data.title;
                document.getElementById('visibility').value = data.visibility;

                if (data.series) {
                    document.getElementById('series').value = data.series;
                }
                if (data.tags && data.tags.length > 0) {
                    document.getElementById('tags').value = data.tags.join(', ');
                }
——————————————————————————————————————[비교]—————————————————————————————————————————
            if (res.ok) {
                // 제목 입력 칸에 백엔드에서 받아온 기존 제목을 채워 넣습니다.
                document.getElementById('title').value = data.title;
                // 공개 설정 드롭다운을 기존 상태(공개/비공개)로 맞춥니다.
                document.getElementById('visibility').value = data.visibility;

                // 백엔드에서 내려주는 시리즈 ID의 키값은 'series_id' 이므로 이에 맞춰 검사
                if (data.series_id) {
                    // 글이 속해있던 시리즈가 존재한다면, 시리즈 선택 드롭다운의 값을 해당 ID로 변경하여 선택 상태로 만듬
                    document.getElementById('series').value = data.series_id;
                }

                // 태그 배열이 존재하고 1개 이상이라면 쉼표 단위 문자열로 변환하여 입력 칸에 채웁니다.
                if (data.tags && data.tags.length > 0) {
                    document.getElementById('tags').value = data.tags.join(', ');
                }

기능 정리

  • 회원가입

    • 이메일인증
    • 비밀번호 찾기 이메일 인증
  • 로그인

    • 소셜로그인 (github / discord)
    • 일반 로그인 (nike57793254@gmail.com)
  • 내용

    • 태그 클릭 필터링 (홈페이지에서 클릭 / 본문에서 클릭)
    • 좋아요 기능
    • 검색
    • ai문체 변경
    • 썸네일 등록
    • 시리즈 등록
    • 임시저장(발행/삭제)
    • 휴지통 (복구/영구삭제)
    • 비공개 글
    • 본문 이미지 삽입
    • 등급 상승
  • 마이페이지

    • 닉네임 중복 검증
    • 프로필 이미지 변경

profile
안녕하세요.

0개의 댓글