03/18 ~ 03/22
기능구현이 끝났다고 생각하기 때문에 오늘부터 이번주 할 일은
서버 상태 모니터링
README.md 파일 작성
프로젝트 브랜치 develop → main 변환 고민
이 프로젝트를 진행하면서 사용한 기능 및 진행과정 정리
포트폴리오 페이지 각 프로젝트별 설명 페이지 추가하기
이력서 및 자기소개서 내용 추가 및 정리
내용정리중에 최적화 요소 보이면 바로 진행
- 기능별로 쿼리 사용 개수 및 처리시간 확인 예정
오늘 일정
서버 상태 모니터링
종류
AWS CloudWatch (AWS 네이티브 모니터링)
- 가장 기본적이고 AWS 생태계와 완벽하게 통합되는 방법
- 만약 EC2 인스턴스에 도커를 직접 설치해 띄웠다면 이 방법을 우선 고려 가능
- 기본 모니터링
- EC2 대시보드에서 서버 자체의 CPU, 네트워크 입출력 등은 별도 설정 없이 바로 확인 가능
- 상세 모니터링 (CloudWatch Agent)
- AWS 기본 설정으로는 서버의 '메모리'와 '디스크 남은 용량'을 볼 수 없음
- 이를 확인하려면 EC2 내부에
CloudWatch Agent를 설치 필요
- Container Insights
- 만약 EC2에 직접 띄운 것이 아닌
AWS ECS나 EKS 같은 컨테이너 오케스트레이션 서비스를 사용한 경우
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
- 도커 컨테이너 각각의 상세 지표를 시각적으로 화려하고 세밀하게 보기 위해서
- 이 오픈소스 스택을 선택했음 (실무에서도 표준처럼 쓰인다고 함)
기존 파일에 합치지 말고 모니터링 전용 파일을 따로 분리
이유
- 안정성 확보
- 모니터링 툴(Prometheus, Grafana 등)은 설정을 변경하거나 재시작해야 할 일이 종종 생김
- 만약 하나의 파일로 합쳐져 있다면, 모니터링 툴을 재시작하다가
- 실수로 운영 중인 블로그 서비스(Django, DB 등)까지 내려가는 대참사를 방지 가능
- 유지보수성
- 파일 하나에 너무 많은 서비스(현재 4개 + 모니터링 3개 = 7개)가 들어가면
- 코드가 지나치게 길어져 가독성이 떨어지고 관리하기 어려워짐
- cAdvisor는 호스트의 /var/lib/docker를 직접 읽어서 데이터를 수집하므로
- 블로그 서비스와 같은 app_network에 속하지 않아도 메인 컨테이너들의 상태를 모두 감시 가능
docker-compose.yml 작성
version: '3.8'
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
command:
- '--config.file=/etc/prometheus/prometheus.yml'
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
depends_on:
- prometheus
prometheus.yml 설정 파일 작성
Prometheus가 cAdvisor의 데이터를 어떻게 가져올지 정의하는 파일
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'cadvisor'
static_configs:
- targets: ['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문체 변경
- 썸네일 등록
- 시리즈 등록
- 임시저장(발행/삭제)
- 휴지통 (복구/영구삭제)
- 비공개 글
- 본문 이미지 삽입
- 등급 상승
마이페이지