Django란 무엇인가? 

[출처] https://tutorial.djangogirls.org/ko/django/

 

파이썬으로 만들어진 무료 오픈소스 웹 애플리케이션 프레임워크(web application framework)

 

웹 서버 요청이 오면, 장고 urlresolver가 url를 통해 경로를 파악하고, 요청사항을 view(함수 모음)에 전달하면, view가 함수를 통해 db에서 데이터 정보를 찾아, model 정보를 거쳐, template에 렌더링하는 방식으로 처리하는 프레임워크이다.

 

Django의 프로젝트와 앱 개념 

- 프로젝트 : 내가 만들려고 하는 전체 프로젝트
- 앱 : 그 안에 들어가는 각각의 카테고리 또는 상위 기능들 (ex. 회원인증, 메신저 발송)

 

출처 https://dogfighterkor.tistory.com/4

Django의 MTV(Model, Template, View) 개념 

- 모델 : 앱의 데이터와 관련된 부분 (DTO 또는 Entity의 개념)
- 템블릿 : 사용자에게 보여지는 부분 (html)
- 뷰 : 모델 데이터를 템블릿으로 전달하거나, 템플릿에서 발생하는 이벤트 처리 (요청 및 응답 처리)
   ㄴ 뷰는 다시 1) 클래스형 뷰, 2) 함수형 뷰로 나뉜다.

 

Django 프로젝트를 위한 사전 설치

 

1. 사전 세팅

1) 파이썬 설치 https://www.python.org/downloads/ 
    * <주의> PATH 추가 꼭 체크하기! - 안 그러면 따로 추가해야함
2) VS Code 설치 (또는 pycharm)

 

2. 가상환경 설정

python3 --version           # 파이썬 버전확인 
python3 -m venv myvenv      # 나의 가상환경 설정

#Mac
source myvenv/bin/activate  # 맥은 bin 폴더를 통해 실행 가능 

#Window
.\myvenv\Scripts\activate.bat # 윈도우는 Scripts 폴더를 통해 실행 가능

 

3. Django 설치 

pip install django~={{파이썬 버전}}

 

 

Django 프로젝트 시작하기

1. Django 프로젝트 시작

django-admin startproject myweb .  # myweb이라는 프로젝트로 시작 끝에 . 안 찍으면 안 됨

 

2. Django 프로젝트 내 App 설치 및 서버 실행해보기

python manage.py startapp aidiary # aidiary라는 app을 설치
python manage.py runserver        # 서버 실행

 

3. Django 설정 - 프로젝트 설정에 내가 만든 App 추가하기 

 

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'aidiary'                      # 앱 추가 
]
TIME_ZONE = 'Asia/Seoul'           # 타임존 추가

 

여기에 추가적으로 디버깅 모드나 호스트에 대한 설정도 같이 할 수 있었습니다.

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True           # 요건 디버깅 옵션임. 보안상 배포할 땐 false로 바꿔야함

ALLOWED_HOSTS = []     # 기본 default로 127.0.0.1이 들어가 있음. 
# ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] # 저는 까먹지 않기 위해 넣어놨어요.

 

4. 프로젝트에 App의 url 설정 정보 추가하기 

 

프로젝트의 urls.py 파일은 전체 App들에 대한 url 경로에 대한 설정을 해요.

App에도 urls.py 파일이 있는데 App에서 설정한 url 정보를 프로젝트의 urls.py안에 include 시켜줄 수 있었습니다.

 

> aidiary의 urls.py 파일 정보

from django.urls import path
from . import views

urlpatterns = [
    path('', views.diary_list, name='diary_list'),
    path('diary/<int:pk>/', views.diary_detail, name='diary_detail'),
    path('diary/post/', views.diary_post, name='diary_post'),
    path('diary/<int:pk>/edit', views.diary_edit, name='diary_edit'),
]

 

> myweb의 urls.py 파일 정보

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('aidiary.urls')),
]

 

 

모델 생성하기 - What is migration?

 

1. 마이그레이션 개념

마이그레이션 : 모델을 데이터베이스에 적용시키는 과정 

> makemigrations : 모델의 생성 및 변경 사항에 대한 기록을 파일로 저장하는 것 -- ({{내 앱}}/migrations)에 저장됨
> migrate : makemigrations를 통해 생성된 파일을 실제로 실행하는 것
python manage.py migrate # 마이그레이트를 하겠다 = 모델을 DB에 반영하겠다

 

초반에 runserver를 하고 나면 이 migrate관련 에러가 뜨는데, 이는 모델에 대한 migrations를 안 해줬다는 경고였어요. 그래서 위 코드를 먼저 작성하면 해결할 수 있었습니다. 

 

2. 어드민 페이지 들어가보기 

아래의 코드를 통해서 수퍼 계정을 만들고 121.0.0.1:8000/admin 으로 들어가면 어드민 페이지가 나왔습니다. 거기서 데이터베이스에 대한 관리가 가능해요. 스프링과 다르게 DB 연동을 사전에 안 해도 어드민 페이지를 통해 테스트 과정에서 바로바로 데이터를 입출력할 수 있다는 게 정말 효율적이더군요.

python manage.py createsuperuser  # 최상위 어드민 계정 생성

 

 

3. 모델 생성하기 

 

App 폴더 하위에 보면 models.py가 있어요. 거기에 모델을 작성해주면 되는데 class 형태로 작성하고, django.db의 models를 사용합니다. Django도 스프링처럼 Orm을 사용해요. 하지만 스프링과 달리, django에서는 entity와 dto에 대해서 명시적인 구분을 따로 하지 않더라고요. (ex. @Table이나 @Entity를 통한 분리) 이 부분은 제가 좀 더 공부하면서 명시적인 구분을 하는 방법이 있는지 보려고 합니다. 

 

4. 모델 생성 및 변경 사항 적용하기 

 

모델 생성이 완료되었다면 이제 이걸 마이그레이션 할 차례입니다.

python manage.py makemigrations  
python manage.py migrate

이 기능이 진짜 좋은게, migrations 폴더 하위를 보면 변동사항이 생길 때마다 이렇게 무슨 짓(?)을 했는지가 기록으로 남아 있어요. 전체적인 수정사항을 확인할 수 있어서 좋다는 생각을 했습니다. 

 

5. 모델 어드민 페이지에 적용하기 

 

App 폴더 하위에 보면 admin.py 파일이 있어요. 거기에 위처럼 admin.site.register(Diary) 요렇게 생성한 모델을 적용해두면, 어드민 페이지에 Diary에 대한 CRUD 작업을 할 수가 있습니다.

 

 

본격적인 작업을 시작하기 전에

1. templates 폴더는 App 폴더 하위에 만들어주기

 

App 폴더 바로 아래에 templates 폴더를 만들고 그 안에 html 작업을 해주어야 정상적으로 view에서 경로를 찾을 수 있었어요.

 

2. models.py, urls.py 파일들이 없다면 각각 만들어주기 

 

책에서는 models.py 외에도 forms.py를 따로 만들어주기도 했어요. 저는 처음에 models.py만 사용해보았는데, 상황에 따라 파일을 만들어주시면 될 것 같습니다. 

 

모델 -> 템플릿 -> 뷰 -> Url 순서로 작업하기

책에 나와 있는대로 저는 모델 - 템플릿 - 뷰 - url 순서로 다이어리 프로젝트를 만들어봤어요. 이전에 스프링으로 구현을 해봤어서 따로 설계는 필요 없었기에 여러 사이트에서 디자인 아이디어를 찾아 AI를 활용하여 Html 템플릿을 만들어보았습니다. 현재까지 구현된 바는 아래와 같아요.

 

 

 

[출처 및 참고] 백엔드를 위한 Django Rest Framework with 파이썬 - 권태형 저

* 9월 셋째주에 다시 서버를 킬 예정입니다. 전기세가 너무 많이 나가고 있어요 ㅠ

완성본

사람들에게 무료로 배포할 수 있는 퀴즈 게임을 만들어보고 싶었어요. 가능하다면 공부하는 사람들에게 도움이 되면 좋겠다, 아니면 연인이나 친구들과 넌센스 퀴즈를 만들어 공유하면 좋겠다 싶어 만들었어요. 작은 미니 pc를 사서 도메인을 구입하고 DNS와 IP를 연결해 사이트를 열어두었습니다. 사이트는 simdev1234.site 여기로 들어가면 무료로 가입 후 퀴즈 게임을 사용할 수 있습니다!:)

 

[ 퀴즈 게임 ]

- Service : 퀴즈 게임을 만들어 친구들과 공유할 수 있는 프로그램 

- Link : simdev1234.site

  ㄴ 전기세 이슈로 잠시 서버를 꺼뒀습니다. 9월 셋째주에 다시 킬 때 쓰시면 돼요. 
- Stack : Java/SpringBoot, Thymeleaf, html/css/javascript, Docker, Redis, Mysql 등 

 

 

기획부터 시작해서 설계까지 

기획하는 단계에서는 '어떤 걸 만들 것인가'를 잘 정해야 하는 것 같습니다. 제가 만들고자 했던 것은 편안하게 할 수 있는 퀴즈 게임이었고, 그걸 만들기 위해서는 기존의 퀴즈 서비스들이 뭐가 있는지를 알아야 했습니다. 



Quizzz와 클래스팅

 

그리고 나서는 브레인 스토밍을 할 차례였습니다. MVP 버전으로 뭘 중점으로 만들 것인가. 그게 관건이었어요.


브레인스토밍

  • 주제 : 5초 폭탄 스피드 퀴즈 (10문제 이하)
  • 내용 : 주제별 (우선은 영어단어로)
  • 유형 : 객관식 (3지선다)
  • 기능 :
    • 회원가입 및 로그인
    • 제한 시간 내에 퀴즈 풀기
      • 5초 안에 문제를 못 맞추면 폭탄이 터지고 틀리는 게임
      • 중도 이탈 시, 이전 퀴즈에 이어서 문제 풀기
      • 도전 횟수 : 하루에 5번까지 재도전 가능
    • 결과 보기 : 결과 상세보기
    • 실시간 랭킹 :
      • 랭킹 기준 : 맞춘 최대 정답 갯수가 많을 수록, 도전 횟수 적을수록 / 10위까지만 출력
      • 반영 시점 : 퀴즈 끝나마자마자 점수 반영 (유저수 별로 없음. 즉시 갱신)

 

그 다음에 도메인별 상세 기능 및 정책에 대해 대강 작성을 해보았습니다. 물론 최종으로는 이대로 되지는 않았지만요.

 


도메인별 상세 기능 및 정책

회원

  • 회원가입
    • 절차 : 이메일 인증 → 비밀번호 및 닉네임 입력 → 가입 완료
      • 이메일 인증 코드 정책
        • 코드유형 : 6자리의 십진수
        • 유효기간 : 1분
      • 로그인 유지 기능을 통해서 24시간 동안 로그인을 유지할 수 있다.
    • 회원(Users) 데이터는 아래와 같이 구성되어 있다.
      • 이메일(email) : 동일 이메일로 가입 불가
      • 비밀번호(password) : 8자 이상의 알파벳 대문자 + 소문자 + 특수문자 + 숫자
      • 닉네임(nickname) : 중복 가능
  • 로그인
    • 로그인 성공시,
      • JWT 인증 토큰 발급
        • AT (만료 : 1시간)
      • 로그인 히스토리(기기 정보 및 로그인 일시) 저장
    • 로그인 실패시,
      • 알럿 : 아이디 및 비밀번호를 확인 후 재시도 해주세요~
    • 로그아웃
  • 마이페이지
    • (선택) 비밀번호 초기화
    • (선택) 닉네임 수정

퀴즈

  • 퀴즈가 시작되면 5초 안에 각 문항을 풀어야한다.
  • 각각의 보기는 랜덤순으로 노출되도록 한다. (ex. 123 → 321 → 213…)
  • 퀴즈는 최대 5번까지 풀 수 있으며, 다시 풀었을 때 랭킹 순위가 올라갈 수 있다.
  • 중도에 이탈할 경우 풀던 문항부터 풀도록 한다.
    (단, 이 경우 재진입시 문항 시간은 초기화된다.)
  • 랭킹 정산 방법
    • 랭킹 기준 : 맞춘 최대 정답 갯수가 많을 수록, 도전 횟수 적을수록 / 10위까지만 출력
      • 계산방법 : ROUND{(전체 맞춘 문항 갯수 / 도전 횟수) - (도전횟수 -1)}
      • Redis의 SortedSet를 통해서 도전할 때마다 score 갱신
    • 반영 시점 : 퀴즈 끝나마자마자 점수 반영 (유저수 별로 없음. 즉시 갱신)
  • 문항(questions) 데이터는 아래와 같이 구성되어 있다.
    • 주제(subjects) : 우선은 ‘영어’하나만 갖는다.
    • 유형(question_type) : 객관식/주관식.. → 우선은 객관식만 갖는다
    • 질문(question_text) : 문항 질문지(ex. reimburse 의 뜻을 고르시오.)
    • 선지(choices) : 보기내용 및 정답 여부
    • 노출여부
    • 노출순서
    • 생성일, 수정일, 노출일
  • 퀴즈 풀이 이력(quizz_attempt_histories) 데이터는 아래와 같이 구성되어 있다.
    • 사용자 ID
    • 문항 ID / (선택한) 선지 ID / (입력한) 주관식 답변
    • 정답 여부
    • 시도 횟수? (-> 불필요할 것 같은데 우선 차후에 확인)
    • 생성일


그 다음에는 아키텍처 구성도와 DB 설계도를 작성해보았어요. 사실 이렇게 만들어놔도 만들다보면 바뀌기도 해요. 특히 DB 같은 경우에는 기능이 추가될 때마다 정규화를 할지 역정규화를 할지 고민하다 조금씩 달라지게 되더라고요.

 

 

 

복잡한 시퀀스 다이어그램의 일부. 이렇게 작성을 해도 결국에는 복잡한 절차를 줄이게 되더라고요. 이러니 항상 개발을 시작할 때는 욕심은 금물! 최소한으로 기획을 하고 개발을 하는 게 최선이라는 생각을 했습니다. 

 

 

그리고 드디어 대망의 코딩의 시작. 폴더 구조는 모놀로틱 구조에 계층형 구조를 섞어두고, 약간의 어뎁터/포트 방식도 사용해 보았습니다. 처음 구상은 아래와 같았어요.

com.xxx.xxx
├── application
│   ├── UserService.java               # Port: Interface
│   ├── QuizService.java
│   └── impl
│       ├── UserServiceImpl.java       # Adapter: 구현체
│       └── QuizServiceImpl.java

├── domain
│   ├── user
│   │   ├── User.java                  # Domain Entity
│   │   ├── UserRepository.java        # Domain Port
│   │   └── UserCommand.java           # 도메인 서비스 (필요 시)
│   └── quiz
│       ├── Quiz.java
│       ├── QuizRepository.java
│       └── QuizCommand.java

├── interfaces
│   ├── user
│   │   ├── UserController.java
│   │   ├── request
│   │   │   └── CreateUserRequest.java
│   │   └── response
│   │       └── UserResponse.java
│   └── quiz
│       ├── QuizController.java
│       ├── request
│       │   └── CreateQuizRequest.java
│       └── response
│           └── QuizResponse.java

├── infra
│   ├── auth
│   │   ├── JwtTokenProvider.java
│   │   └── SecurityConfig.java
│   ├── persistence                   # JPA 구현체 or MyBatis Mapper 위치
│   │   ├── user
│   │   │   ├── UserEntity.java
│   │   │   └── UserJpaRepository.java
│   │   └── quiz
│   │       ├── QuizEntity.java
│   │       └── QuizJpaRepository.java
│   └── redis
│       └── RedisConfig.java

├── global
│   ├── exception
│   │   ├── GlobalExceptionHandler.java
│   │   ├── CustomException.java
│   │   └── ErrorCode.java
│   └── common
│       └── Constants.java

└── XxxApplication.java

 

그리고 현실은 아래와 같았죠.

 

 

폴더 구조의 경우 초기 구상과 크게 달라지진 않았던 것 같긴 합니다. 

 

다양한 재미 요소

퀴즈 게임을 만들고 테스트를 해보면서 애인과 친구들을 위해 퀴즈를 만들어 공유를 했었어요. 예를 들어 아래와 같이, 궁합 테스트를 하는 게임을 만들어서 애인에게 공유를 했었죠.

 

내가 만든 퀴즈는 다른 사람들과 링크를 공유할 수 있게 만들었기 때문에, 빠른 공유가 가능합니다. 정해진 시간 안에 문제를 풀도록 되어 있기 때문에 타임 압박을 받으며 솔직(?)한 답변을 할 수 있어요.:)

 

가능하면 쉽고 빠르게. 그러면서도 모두가 재밌게 즐길 수 있는 방향으로 만들어보려 노력했습니다. 무료로 배포하는 거니, 편하게 사용해주시면 좋겠어요. :) 이제 또다른 플젝을 시작해봐야겠군요. 이번에는 파이썬과 Django를 써봐야겠어요. 자바랑 스프링은 너무 물리니, 새로운 시도도 좋겠죠. 그 때까지 다들 행쇼하시고, 다시 보아요~

최근 Open AI의 상업용 API를 통해 일기를 분석해주는 AI 성장일기라는 웹 프로그램을 만들었다. 일기에서 볼 수 있는 핵심 가치, 반복된 사고, 감정 상태를 분석하고, 필요로 하는 다음 행동에 대해 추천하는 식으로 결과를 받았었다. 이 때, 나는 Prompt에 몇 가지 감정 상태에 대한 Scale값을 미리 전달했었는데, 같은 일기에 대해 매번 결과값이 상이한 것을 보았다. 몇몇의 지인들에게 프로그램을 사용하게 해보았더니, "답변이 좀 어색하다."라는 대답을 받았다.
 
스스로 AI에 대한 상식이 부족함을 느꼈다. 그래서 머신러닝과 인공지능에 대한 강의들을 속성으로 듣고 관련된 책을 찾았다. 실제 머신러닝 강의에서는 아래보다 더 많은 내용을 다룬다. 딥하게 가면 선형대수와 같은 수학에 대한 사전 지식이 필요하고, 학습기들에 대한 개념들도 심층적으로 다룬다. 모든 걸 다 이해할 수는 없었는데, 개념적인 부분만 간단하게 정리해보았다.
 

인공지능

 
인공지능이란, 인간의 지능을 모방하여 문제해결을 위해 사람처럼 학습하고 이해하는 기계를 만드는 분야를 말한다.
머신러닝은, 풀어서 기계학습을 말하는 것으로, 인공지능의 한 분야이다. 인간의 학습 능력을 기계를 통해 구현하는 분야를 말한다.
딥러닝은, 머신러닝 중에서도 심층 신경망 기반으로 학습을 하는 방법을 말한다.
 

머신러닝

 
머신러닝에서 중요한 것은, "학습"이다. 실생활에서 발생하는 문제를 해결하기 위해 기계를 사용하는데, MNIST(미국 우체국)과 같은 곳에서 제공하는 대량의 Data Set을 통해 충분한 데이터를 사전에 확보하는 것이 중요하다. 그러고 나면 우리는 데이터에 대한 1) 전처리를 하거나, 불필요한 데이터를 축소하는 2) 특징추출을 할 수 있다. 이후로는 어떠한 학습기를 사용해 문제를 해결할 것인가에 대한 고민이 남는다. 어떤 학습기 = 모델을 쓸 거냐에 대해 3) 모델 수립 및 분석을 하고, 4) 모델 평가를 한다. 학습기에 대한 모든 학습이 정상적으로 이루어져 새로운 문제들이 오차 없이 해결 된다면, 5) 배포의 단계를 거쳐 머신러닝 개발을 완료한다.
 

머신러닝의 기본 요소

 
머신러닝에서는, 데이터를 "Vector" 단위로 표현한다. Vector는 열벡터라고도 부르는데, 말그대로 N차원의 행렬을 말한다. 데이터의 특성이 무엇이냐에 따라 Vector의 차원은 여러개가 될 수 있다. 
 
x = [1, 1, 1, 0, 0]  ==> 이것을 세로로 둔 것을, "열 벡터"라고 한다. 열이 여러 개가 되면 그것을 차원이라고 부른다.
 

머신러닝의 주제 

 
무엇을 어떻게 학습시킬 것이냐에 따라, 머신러닝의 주제는 달라질 수 있다. 대량의 데이터를 이산적으로 분류하고 싶다면 "분류"를, 시간적인 흐름에 따라 어떤 연관성을 파악하고 싶다면 "회귀"를, 그룹을 나누는 클러스터링을 하고 싶다면 "군집화"를, 원데이터에 불필요한 데이터를 축소하고 싶다면 "특징 추출"을 할 수 있다. 여기서 만약 복잡한 신경망 기술을 활용하게 되면 그건 "딥러닝"에 해당이 된다. 다른 머신러닝 방식과 다르게, 딥러닝을 활용하면 비정형 데이터를 효과적으로 처리할 수 있다.

 

학습 시스템 관련 개념

 
목표하고 하는 결과값을 미리 알고 있다면 그것은 "지도 학습"에 해당된다. 그렇지 않다면, 임의의 군집을 나누어 끊임 없이 오차를 줄여가는 방식과 같은 "비지도 학습"을 하게 된다. 두 가지를 섞은 게 "준지도 학습"이다. 이 외에도 파블로그의 개 실험과 같이 강화학습을 통해 학습을 하는 경우도 있다.  

 

딥러닝

심층신경망을 통해 학습을 하는 기술을 말한다. 이때 사용되는 것으로 RNN과 LSTM이 있는데 관련된 내용은 다음과 같다.
 

기본 순환 신경망(RNN)

RNN은 Recurrent Neural Network의 약자로, Recurrent는 재귀, 반복을 의미한다. 쉽게 말해 이전의 기억을 활용하는 신경망을 말한다. 주식의 시세와 같이 순서 정보를 가지고 있으며 출현 순서가 중요한 데이터를 처리할 때 유용하다. 입력층 - 은닉층 - 출력층으로 구성된 구조가 기존의 MLP(다층신경망)와 비슷하나, 은닉 노드에 가중치를 갖는 순환 엣지가 존재하여 직전에 발생한 정보를 현재의 입력으로 제공한다는 점이 다르다.

입력 → [기억] → 출력
       ↑   ↓
       [기억]

 
RNN이 가진 단점은, 기울기 소실 문제로 긴 시퀀스에서 초기 정보가 점점 사라진다는 점이다. 그래서 LSTM이 등장했다.
 

LSTM와 GRU

LSTM은 Long Short-Term Memory의 약자로, 말그대로 장기기억과 단기 기억을 구분하는 신경망을 말한다. 중요한 기억은 오래 기억하며, 불필요한 정보는 삭제하는 방식으로 동작한다. 이것을 단순화한 것이 GRU이다.
 
LSTM의 경우,

입력 → [망각 게이트] → [입력 게이트] → [출력 게이트] → 출력
        ↑                                 ↓
     [장기 기억 저장소]

 
GRU의 경우,

입력 → [초기화 게이트] → [업데이트 게이트] → 출력

 
RNN은 간단한 시계열 예측에 사용되며, LSTM의 경우 긴 문장을 번역하거나 음성을 인식할 때 사용되고, GRU는 상대적으로 짧은 텍스트를 분류하거나 감정을 분석할 때 유용하다고 한다.
 

LLM(Large Langauge Model)

 
LLM은 딥러닝에 기반을 둔 것으로, 자연어 처리 시 언어를 이해하고 생성할 수 있는 언어 모델이다. LLM은 크게 1) 상업용 API와 2) 오픈소스 LLM으로 나뉜다. 
 

상업용 API LLM

 
Open AI에서 제공하는 상업용 API가 위에 해당된다. 토큰 단위로 비용을 지불하면서 Prompt 명령어를 통해 Data로부터 분석 결과를 받을 수 있다. 이러한 상업용 API는 범용적 언어 생성 능력이 뛰어하다. 그렇기에 단순 검색용으로 적합하지만, 특정 도메인에 특화된 결과값을 받기에는 적절하지 못하다.
 

오픈 소스 LLM

 
오픈 소스 LLM은 추가학습을 통해 특정 도메인에 특화된 방식으로 학습을 할 수 있다. 이를 sLLM이라고 부른다. 
 
LLM은 말그대로 대량의 언어 데이터를 활용해 그럴듯한 대답을 만들어내는 데에 특화되어 있으나, 그 정보의 사실 유무에 대해 판단할 능력은 없다. 실제 Open AI의 API를 통해 일기를 분석했을 때, 내 일기를 엉뚱하게 분석하는 것을 종종 보았다. 이에 관해 평가해주었던 교수님들은 AI가 민감한 일기 정보를 부정적으로 분석할 걸 우려하셨다. 예를 들어 우울을 조장하는 듯한 멘트가 나오는 것처럼. 상업용 AI에서 겉만 멀쩡하지만 결과가 온전하지 않게 나타나는 문제를 "환각 현상"이라 부른다. 이를 해결하기 위해 RAG(검색 증강 생성)이라는 기술이 발전했다. 
 

검색 증강 생성 RAG

 
검색 증강 생성 기술을 쉽게 보면, 마치 도서관 사서와도 같다. 질문을 받으면 관련된 문서나 데이터를 찾아보고, 그 데이터를 토대로 답변을 한다. 일반 LLM이 2년 전에 읽은 책들로만 답변을 한 다면, RAG 시스템은 도서관에서 최신 책을 찾아보고 답변을 해준다. 이런 방식은 최신 제품 정보를 제공하는 고객서비스나, 최신 의학 논문을 참고하는 의료 분야, 현재 법률 정보를 활용하는 법률 자문 서비스 등에 활용될 수 있다. 
 
RAG 시스템에서 중요한 것은 "검색"이다. 검색을 하기 위해서는 검색 데이터를 벡터 DB에 저장하는 과정이 필요하다. 1) 사용자가 검색을 하면 검색 내용을 벡터 DB에 저장한다. 이후 2) 벡터 DB에서 검색을 한 후 요청값에 맞게 프롬프트를 완성한다. 이러한 과정을 겪으면 사실이 아닌 결과값을 사실에 가깝게 변경할 수 있다. 
 

이제 내가 해야할 건 무엇일까?

 
나는 스프링을 통해서 일기 분석 앱을 만들었었다. 기존의 LLM 관련 강의들을 보면 주로 파이썬으로 이루어진 강의들이 많았는데, 당장 파이썬을 공부하는 것 보다는 기존 앱을 변경하는 방식이 나을 것 같았다. 위대한 Claude에게 한 번 너라면 어떻게 할 거냐 물어보았다. 이 친구가 내게 조언한 건 다음과 같았다.
 
[1] 프로젝트 개선 방안 
- RAG 시스템 구현하기 : 일기 데이터를 벡터화하고, 유사한 감정/상황 패턴을 검색하게 한 뒤, 더 정확한 분석 결과를 제공하라.
- 사용자 피드백 시스템을 도입하라
- 감정 분석 결과의 일관성을 확보하라
 
[2] 추천 학습 경로
- Prompt Engineering 심화 학습
- 벡터 DB 구축 방버 학습
- Spring 에서 AI 통합 방법 학습 
 
[3] 구성할 수 있는 아키텍처

[Client Layer]
    ↓ HTTP/REST
[API Layer]
    ↓
[Service Layer] → [AI Service Layer]
    ↓               ↓
[Repository Layer]  [Vector DB Layer]
    ↓               ↓
[DB]          [Vector Storage]

 
[4] 데이터 흐름

사용자 요청
  ↓
1. 일기 텍스트 접수
  ↓
2. 임베딩 생성 (OpenAI)
  ↓
3. 벡터 DB 검색
  ↓
4. 유사 일기 컨텍스트 구성
  ↓
5. GPT 분석 요청
  ↓
6. 결과 반환

 
벡터 DB는 PostgreSQL을 사용할 수 있을 것 같다. 우선, 코틀린을 먼저 공부하려 하고 있었기 때문에 이 부분은 잠시 보류하면서 차후 진행을 해봐야 겠다. 어쩌면 GRU를 사용해 감정 분석만 하는 프로그램을 만들 수도 있겠다.
 
[참고]
LLM을 활용한 실전 AI 애플리케이션 개발, 허정준 저.
방송통신대학교 머신러닝/빅데이터 강의 
 
 
 
 
 

나는 조립식 PC를 쓰고 있다. 조립식 PC를 구매하면 좋은 점은, 내가 원하는 맞춤형 사양을 비교적 저렴하게 구매할 수 있다는 것. 다만 단점은 PC가 맛이 갔을 때 해결은 내가 해야한다는 점이다.

 

* 내돈내산으로 구매했던, 다나와 PC 견적 사이트 -- [바로가기]

 

내 PC도 어쩌다 한 번씩 부팅이 잘 안 될 때가 있다. 대체로 원인은 RAM이 더러워져서 발생하는 경우가 많았다.

 

그럴 때 해결하는 법.

 

[방법1] 일단 RAM을 꺼냈다가 다시 끼워본다. 

 

- RAM을 반대로 끼울 때도 있다. 그래서 안 될 수도 있음

- RAM이 완전히 안 껴졌을 때가 있다. 그러면 안 됨

 

[출처] 동아일보

 

[방법2] RAM 끝을 확인해본다. 드럽다면 손으로 한 번 닦아준다. 그래도 안 되면 "깨끗한" 지우개로 한 번 지운다.

 

* 대신, 지우개 찌꺼기는 절대 부품 안에 들어가지 않도록 잘 제거해준다. 지우개 찌꺼기가 부품 안에 들어가는 경우가 있다. 그러면 정말 망한다고 한다.

 

 

[출처] 동아일보

 

이렇게 해서 한 번도 부팅이 안 된 적이 없었다. 

 

기본적인 상식일 수 있는데 나처럼 뻘짓하는 사람들을 위해 재미삼아 적어본다.

 

 

'Infra > Device' 카테고리의 다른 글

윈도우와 맥 모두 호환되는 키보드  (1) 2024.12.11

 

요즘 취미로 글쓰기 모임을 시작했다. 가볍게 릴레이로 글을 쓰는데 손맛이 좋은 키보드를 갖고 싶었다.

 

와중에 코딩도 해야해서 윈도우랑 맥이 호환되는 키보드를 찾았다.

 

엑토 레트로2 블루투스 키보드 B307 

 

옛날 타자기에 대한 로망을 갖고 있었는데 일단 비주얼에서 만족했다.

 

누를 때 소음이 좀 나는 편이라서 회사에서 쓰기는 그렇고, 집에서 쓰기 좋다.

 

PC뿐 아니라 블루투스로 핸드폰이나 태블릿을 연결할 수 있어서 유용하게 쓰고 있다.  

 

'Infra > Device' 카테고리의 다른 글

조립식 PC 부팅이 안 될 때, RAM 상태를 확인하기  (0) 2024.12.11

시작하기 전에 - 가상화 기술의 종류 돌아보기

 
가상화 기술은 크게 세 가지로 나뉜다. 1) 호스트 가상화, 2) 하이퍼바이저 가상화, 3) 컨테이너 가상화.
호스트 가상화의 경우, 호스트 OS에 설치 프로그램을 받아 실행하는 것으로 게스트 OS를 쓰는 방식을 말한다.
하이퍼 바이저는, Windows에 딸려있는 Hyper-V 같이 하드웨어에 붙어 있는 가상화 기술을 말한다. 
컨테이너는, Docker 같이 각각의 컨테이너로 격리된 공간을 나누고, 호스트 OS의 자원을 공유할 수 있게 한 기술을 말한다.  
이번에 설치하는 Oracle Virtualbox는 이 중에서도 호스트 가상화 기술을 사용한 것이다.
 

[출처] 한빛출판네트워크, 원리부터 이해하는 도커 - 컨테이너, 가상화, 구성요소

 

 호스트 가상화하이퍼바이저 가상화컨테이너 가상화
내용HW에 설치된 호스트 OS 상에 가상화 SW가 설치되고 그 위에 SW 실행을 위한 게스트 OS가 구동되는 방식특정 OS에 의존하지 않고 하드웨어에 직접 설치되는 구조의 가상화 기술

- 전가상화 : H/W를 완전히 가상화 하는 방식(관리용 DOMO통해 게스트OS들의 커널 요청을 번역하여 하드웨어로 전달)

- 반가상화 : 게스트 OS가 하이퍼콜을 요청
애플리케이션을 동작시키는데 필요한 라이브러리 및 종속 리소스를 함께 패키지로 묶어 생성한 호스트 OS상의 논리적인 구역
장점게스트 OS가 HW 리소스에 접근하는 것을 제어하고 동기화하기 때문에 호스트 OS에 제약이 없음오버헤드 비용이 적음,
하드웨어를 직접 관리하여 리소스 관리가 유연함
게스트 OS의 실행에 소요되는 오버헤드가 없어 상대적으로 고속으로 작동

애플리케이션에서 사용하는 미들웨어나 라이브러리의 버전이 상이하여 발생하는 문제를 컨테이너를 통한 격리로 해결
단점호스트 OS와 게스트 OS의 공존으로 필요이상의 CPU, 디스크, 메모리 사용의 오버헤드 발생--
프로그램VMWare Workstation,
Microsoft Virtual Server,
Oracle Virtual Box
VMware ESXi
Microsoft Hyper-V
Citrix XenServe
Docker

 
 

준비 

Oracle Virtualbox 및 Mac OS 이미지를 다운로드한다.
* 가급적 Mac OS 버전을 13 이상으로 까는 걸 추천한다. Redcat 같은 앱은 버전 13 이상이어야 가능..
 
[1] Oracle Virtualbox 프로그램 설치 [바로가기]
[2] Oracle Virtualbox 프로그램 확장팩 설치 [바로가기]
[3] Mac OS 버전 이미지 다운로드
[4] 현재 내 컴퓨터가 가상화 기술을 허용하고 있는지 확인 - 부팅할 때 ESC를 눌러서 세팅화면으로 넘어가서 확인하기 
 
 

가상머신 생성하기

 
[1] [새로만들기] 를 눌러 가상 머신을 만든다.

 
[2] 가상머신의 이름을 작성하고 종류는 Mac OS X, 버전은 Mac OS X (64-bit)
 
* 버전의 경우, 사용하려는 Mac OS 이미지 버전과 맞는 것을 고르는 게 좋다. 그렇지 않으면 제대로 설치가 안 된다. 
* 만약, 처음 [새로 만들기]를 누르는 게 아니라면, ISO 이미지를 여기서 추가해줘도 좋다. 

 
[3] 하드웨어의 기본 메모리를 9000 MB 이상, 프로세서를 2개 이상으로 설정한다.
 
* 실제로 해보면 프로세서 2개도 느렸다. 나는 가상머신 쓸 때 호스트OS로 작업을 안 할 거라, 그냥 4개로 넣어줬다. 

 
[4] 새 하드디스크를 만들어 준다. 파일 형식은 VMDK로 하고 Split into 2GB Parts에 체크를 해준다.

 
 

설정 세팅

 
[1] [시스템] - [마더보드]의 설정을 체크한다.
- 플로피 체크해제
- 칩셋 : ICH9
- 포인팅 장치 : USB 태블릿
- EFI 활성화

 
[2] [시스템]-[프로세서] 에서 PAE/NX 가 활성화 되어있는지 확인

 
[3] [디스플레이]-[화면] 에서 "비디오 메모리"를 128MB로 수정, 그래픽 컨트롤러도 VBoxVGA를 사용한다.

 
[4] [저장소]에서 컨트롤러 SATA의 "호스트 I/O 캐시 사용"에 체크 

 
[5] 아래와 같이 ISO 가 먼저 올 수 있게 SATA 포트를 0으로 설정해 준다.

 
 

CLI에서 스크립트 실행하기

Virtualbox에서 MacOS를 가상화하기 위해서는 CPU ID, DMI 정보, SMC 설정, CPU 프로필 설정을 해야한다. 
 
[1] Windows에서 CMD창을 관리자 권한으로 실행하고, 아래의 코드를 입력해 설정을 시도한다.

cd "C:\Program Files\Oracle\VirtualBox"

VBoxManage.exe modifyvm "macOS" --cpuidset 00000001 000106e5 00100800 0098e3fd bfebfbff
VBoxManage setextradata "macOS" "VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "imacOS19,1"
VBoxManage setextradata "macOS" "VBoxInternal/Devices/efi/0/Config/DmiSystemVersion" "1.0"
VBoxManage setextradata "macOS" "VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "macOS-AA95B1DDAB278B95"
VBoxManage setextradata "macOS" "VBoxInternal/Devices/smc/0/Config/DeviceKey" "ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc"
VBoxManage setextradata "macOS" "VBoxInternal/Devices/smc/0/Config/GetKeyFromRealSMC" 1

 
[2] 만약, 부팅 중에 "bdsdxe failed to load boot0001 uefi vbox cd-rom vb1-1a2b3c4d" 에러가 나타난다면 아래를 추가적으로 입력한다. 미리 에러를 막고 싶다면 그냥 [1]에서 같이 입력하는 방법 추천.

VBoxManage.exe modifyvm "macOS" --cpu-profile "Intel Core i7-6700K"

 
 

시작하기

Apple 화면이 뜨면 이제 설치를 해주면 된다. 
 
[1] [시작하기]를 눌러서 실행한다. 

 
Apple 화면이 잘 뜬다면 당신은 이제 80% 성공.

 
 
[2] Mac OS 설치 전에, [유틸리티] - [디스크 유틸리티]에서 [보기] 버튼을 클릭, "모든 장치 보기" 또는 "Show All Devices" 선택
 

 
[3] 왼쪽 사이드 바에서 VBOX HARDDISK Media를 선택 (최상위 디스크 선택), 상단의 "지우기" 버튼 클릭
 
- 이름: Macintosh HD (원하는 이름)
- 포맷: APFS
- 구성: GUID 파티션 맵
- "지우기" 클릭
 

 
[4] 아래와 같이 내가 설치하고자 하는 디스크 상세 정보가 잘 나오면 됨

 
[5] 다시 앞으로 돌아가서 Mac OS 설치를 시작한다. 

MSA

- 어플리케이션을 목적에 따라 여러 개의 독립적인 서비스로 분할하여 개발하고 배포하는 방식  

 

MSA의 특징

- 독립된 각 서비스 마다 고유의 ip와 port를 가진다.

- 각각의 서비스가 분산되어 있기 때문에, 배포와 테스트가 어렵다

 

MSA를 왜 쓸까?

느슨하게 결합된 서비스들의 모임으로 프로젝트를 구성함으로써, 전체 시스템 구조를 논리적으로 구분해 이해하고, 

고가용성(부분 장애가 전체 장애로의 확장을 막음)을 높이며, 분산 처리를 가능하도록 하기 위함 

 

MSA를 편리하게 하기 위한 아키텍처 컴포넌트

 

 

스프링 클라우드(Spring Cloud)

분산 시스템의 몇가지 공통된 패턴들을 빠르게 빌드할 수 있도록 다양한 툴을 제공해 준다.

예를 들어, Configuration management(설정 관리), Service Discovery(서비스 검색), circuit breakers(회로 차단), intelligent routing(라우팅), micro-proxy, control bus, short lived microservices and contract testing.

- 공식 도큐먼트 : https://spring.io/projects/spring-cloud

 

스프링 클라우드 피처

  • Distributed/versioned configuration
  • Service registration and discovery
  • Routing
  • Service-to-service calls
  • Load balancing
  • Circuit Breakers
  • Distributed messaging
  • Short lived microservices (tasks)
  • Consumer-driven and producer-driven contract testing

Service Discory란? 왜 쓸까?

클라언트에게 필요한 서비스가 어디에 있는지 빠르게 찾으려고.

서비스를 등록/해제/조회할 수 있는 API를 제공하며, 고가용성이 보장되어야 한다.

Service Registry를 통해 서비스를 관리하며, 지속적인 서비스 상태 모니터링이 가능하다.

- Netflix Eureka, Apache Zookepper 등이 있음 

 

Eureka 

Spring Cloud Netflix는 Netflix OSS와 Spring 어플리케이션을 통합해주는 것으로, autoconfiguration 및 Spring 환경과 다른 Spring 프로그래밍 모델과의 binding을 제공합니다. 몇 가지 간단한 Annotation을 사용하면 애플리케이션 내부의 공통 패턴을 신속하게 활성화 및 구성하고, 검증된 Netflix 구성 요소를 사용하여 대규모 분산 시스템을 구축할 수 있습니다. 제공되는 패턴에는 Service Discovery(Eureka)가 포함됩니다. (출처:spring cloud netflix 공식 document)

-> 다시 말해, Netflix OSS를 Spring에서 사용할 수 있도록 해주는 것으로,

-> Netflix는 2007년 DB 문제로 서비스가 중단된 이래 신뢰성이 높고 scale-out한 시스템을 구축하기 위해 Netflix OSS를 만들었다.

-> Netflix OSS는 1) 고가용성(하나가 망해도 다른 하나가 산다), 2) scale-out, 3) easy deploy의 특징을 갖는다. 

-> Eureka 또한 Netlfix OSS의 하나로, Service Discovery를 담당하는 모듈을 말한다.

* 자세한 내용은 다음 링크에 들어가 있다. https://p-bear.tistory.com/77

 

Eurkea의 피처

  • Service Discovery: Eureka instances can be registered and clients can discover the instances using Spring-managed beans
  • Service Discovery: an embedded Eureka server can be created with declarative Java configuration

Eureka Service Discovery의 용어

- Discovery : 검색

- Registry : 등록 목록

- Eureka Client : 각 서비스에 해당되는 모듈 

  ㄴ 자기 자신을 Eureka Server에 등록한다.

  ㄴ Eureka Server를 통해 다른 Client의 정보를 검색할 수 있다.

- Eureka Server : Eureka Client를 관리하는 서버

  ㄴ Eureka Client 정보를 Registry에 등록

  ㄴ Heartbeat를 통해 Client가 수행 중임을 확인

 

Eurkea Service Discovery 흐름의 예시 

1. Eureka Client 서비스가 시작될 때, Eureka Server에 자신의 정보를 등록

2. Eureka Client는 Eureka Server로부터 다른 Client의 연결 정보가 등록되어 있는 Registry를 받고 Local에 저장한다.

3. N초마다 Eureka Server로부터 변경 사항을 갱신받는다.

4. N초마다 ping를 통해 자신이 동작하고 있다는 신호를 보낸다. 신호를 보내지 못하면 Eureka Server가 보내지 못한 Client를 Registry에서 제외시킨다

 

Eureka Service Discovery 사용하기 

- Eureka Server

# eureka server port
server:
  port: 8761

# MSA module id (eureka)
spring:
  application:
    name: mindchat

# Eureka server does not need to be included
eureka:
  client:
    register-with-eureka: false  # 레지스트리에 자신을 등록할 것인가 (default : true)
    fetch-registry: false        # 레지스트리에 있는 정보를 가져올 것인가
    service-url: #server의 위치 지정
      defaultZone: http://localhost:8761/eureka

- Eureka Client 

  ㄴ eureka.client.register-with-eureka : 레지스트리에 자신을 등록할 것인가 여부 

  ㄴ eureka.client.fetch-registry : 레지스트리에 있는 정보를 검색할 것인가 여부

server:
  port: 0 #랜덤 포트 사용

spring:
  application:
    name: user-service

eureka:
  instance:
    instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

 

port를 0으로 설정하면 랜덤 포트가 지정된다. 이렇게 되면 동일 서비스를 여러 개의 랜덤 포트로 빌드 후 실행이 가능하다.

(ex. user-service 앱이 port:53000과 port:533333에서 실행되고 있다)

그러나 유레카는 각각의 서비스를 프로젝트 내에서 지정한 ip/port를 통해 구분하기 때문에, http://localhost:8761에 접속하여 실행중인 application을 보면 1개의 app만 실행 중인 것으로 나타난다.

(Eureka에서 0번 포트의 앱이 두개 있더라고 하나로 치부) 

이때, eureka.instance.instance-id를 사용하면 eureka에서 app을 구분하는 값을 지정된 값으로 변경할 수 있다.

 

Spring Gateway

Spring MVC는 동기적이며 논블로킹 처리를 하는 Servlet 기반의 프레임워크이다.

출처:https://velog.io/@tritny6516/Spring-Thread-Pool

server:
  tomcat:
    threads:
      max: 200        # 생성할 수 있는 thread의 총 개수
      min-spare: 10   # 항상 활성화 되어있는(idle) thread의 개수
    accept-count: 100 # 작업 큐의 사이즈

다수의 요청이 존재할 때 Spring MVC는 Queue안에 요청 사항을 받아두고, 미리 지정해둔 Thread수에 따라, Thread Pool의 Thread를 사용하여 멀티태스킹을 하게 된다. 이 때, 각각의 Thread는 동기적인 처리를 하게 되는데, 하나의 요청을 받게 되면 응답까지 한 번에 받을 수 있도록 처리하며, 하나의 함수에 대해 return을 받을 때까지 블로킹 처리를 한다. 

 - 동기적이다 : 호출과 응답이 동시에 이루어진다. (ex. 공을 던지고 받는다)
 - 비동기적이다 : 호출과 응답이 동시에 이루어지지 않는다. (ex. 기차에 공을 올려 도착할 때까지 기다린다)
 - 블로킹 : 함수를 콜했을 때 응답을 받기 위해 멈춰있는 상태
 - 논블로킹 : 함수를 콜했을 때 응답을 받기 위해 멈춰있기 않고 다음 줄을 실행하는 상태  

 

이 방식의 경우, 정해진 time-slice에 따라 Queue로 이동하며 멀티 태스킹을 하게 되는데, 다수의 Client의 요청에 대하여 DB데이터 응답 시간이 지연될 경우, 해당 시간 동안 손가락을 빨고 기다리는 상황이 발생할 수 있게 된다.

이를 해결하기 위한 방법이 Spring Webflux이다. Spring Webflux는 Node.js와 같이 Event Loop가 돈다. JavaScript는 비동기로 동작을 하는데, 함수를 Stack에 두어 LIFO 방식으로 실행하는 것은 Java와 동일하지만, 함수를 Callback Queue에 또한 넣어둠으로써 Stack이 다 비워졌을 때 FIFO으로 Queue의 함수를 실행하도록 Event Loop를 돈다. 

Javascript Event Loop

Webflux 또한 이러한 Event Loop를 사용한다. 10초의 지연 시간이 있는 요청이 있다면 해당 요청에 대한 핸들러(=함수)에게 처리를 위임하고, 처리가 완료되는 즉시 Callback Queue에 Callback을 추가한뒤 Stack을 통해 실행하게 된다. 10초의 지연 시간 동안 처리할 다른 함수가 있다면 해당 함수가 Stack에 두어 실행된다. 이러한 처리 방식을 비동기/논블로킹 처리 방식이라 하며, 이벤트 발생에 대해 반응을 한다하여, Reactive Programming이라고 한다.

 

Spring MVC Webflux
동기 비동기
블로킹 논블로킹
명령형 프로그래밍 반응형 프로그래밍
tomcat netty
JDBC,JPA 네트워킹 지원 반응형 라이브러리(Reactor, RxJava) 지원

출처 : https://pearlluck.tistory.com/726

 

Gateway는 왜 사용해야 하나?

기본적으로 작은 서비스들당 50~100개의 app들이 돈다고 할 때,

이 많은 app들의 endpoint(ip+host+port+path)를 일일히 관리하려면 엄청난 수작업이 필요하고, 

각각의 서비스들마다 공통적으로 들어가는 인증/인가, 로깅 등의 기능을 중복으로 작업하는 것에 어려움이 생긴다.

 

[1] 공통 로직 처리

 

Gateway를 일종의 Proxy(대리자)로 두게 되면, 각각의 요청사항에 대한 공통 기능(인증/인가 및 로깅)을 편리하게 작업할 수 있다.

 

[2] Api Routing

라우팅은 네트워크 상에서 경로를 선택하는 프로세스를 의미한다. API Routing이란 동일한 API 요청들에 대해, 

각각의 클라이언트나 서비스에 따라 다른 엔드포인트를 사용할 수 있도록 해주는 것을 말한다.

(1) 로드밸런싱

API Gateway를 사용하면 여러 개의 서비스 서버로 API를 분산시켜 주기 때문에 로드밸런서 역할을 한다.

(2) 클라이언트 및 서비스별 엔드포인트 제공

API Gateway를 사용하면 요청을 공통 API로 보내더라도 각 클라이언/서비스별로 엔드포인트를 분산할 수 있다. 

(3) 메세지/헤더 기반 라우팅 

메세지나 헤더 기반으로 API 라우팅을 할 수 있다. 라우팅에서 요청 API에 대한 메세지를 파싱할 때 많은 파워를 소모할 수 있기 때문에, Restful API를 제공할 경우, Header에 Routing 정보를 두어 Header정보를 파싱하도록하고, Body는 포워딩 할 경우 API Gateway의 부하를 줄여줄 수 있다. 

 

[3] Mediation 기능 

(1) 메세지 포맷 변환

클라이언트와 서버가 서로 다른 메세지 포맷을 사용할 때, 이를 적절히 변환시켜 주는 역할을 의미

(2) 프로토콜 변환

내부 API는 gRPC를 사용하고 외부 API는 Restful API를 사용함으로써 내부 API의 성능을 높이면서 범용성을 높일 수 있도록 프로토콜 변환이 가능하다.

(3) Aggregation

서로 다른 API를 묶어서 하나의 API로 제공하는 것을 의미한다. 어떤 요청에 대해 A/B/C모듈 각각의 API를 호출해야할 경우, API Gateway를 사용하면 A/B/C API를 한번에 처리가 가능하다.

 

Gradle에서 의존성 추가

** 주의 사항1

spring-cloud-starter-gateway-mvc : Spring Mvc 기반 / Servlet API / Blocking I/O 모델 사용 / Tomcat 

spring-cloud-starter-gateway : Spring Webflux 기반 / Reative API / Non-blocking I/O 모델 사용 / Netty 

** 주의 사항2

spring-cloud-starter-gateway는 Webflux 기반이기 때문에 SpringMVC기반의 라이브러리와 함께 쓸 경우,

"Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway." 메세지가 발생한다.

따라서 spring-boot-starter-web 등의 MVC 기반 라이브러리는 같이 사용하면 안된다.

dependencies {
   implementation 'org.springframework.cloud:spring-cloud-starter-gateway' #추가
   implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
server:
  port: 8000

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      default-zone: http://localhost:8761/eureka

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-service #서비스id
          uri: http://localhost:8081/
          predicates: # 조건 
            - Path=/first-service/**
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**

Netty가 작동했다.

위 상태로만 gateway를 넣게 되면 /**까지 같이 엔드포인트에 포함되기 때문에 필터를 통한 작업이 추가적으로 필요하다.

 

Spring Cloud Gateway에 Filter 적용하기

출처:인프런_springcloud로 개발하는 마이크로서비스애플리케이션

[1] Java 코드 

@Configuration
public class FilterConfig {

    @Bean
    public RouteLocator gateRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                .route(r -> r.path("/first-service/**")
                        .filters(f -> f.addRequestHeader("first-request", "first-request-header")
                                .addResponseHeader("first-response", "first-request-response"))
                        .uri("http://localhost:8081/")
                )
                .route(r -> r.path("/second-service/**")
                        .filters(f -> f.addRequestHeader("second-request", "second-request-header")
                                .addResponseHeader("second-response", "second-request-response"))
                        .uri("http://localhost:8082/")
                )
                .build();
    }

}

[2] property(yml)

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
            - AddRequestHeader=first-request, first-request-header2 # key, value
            - AddResponseHeader=first-response, first-response-header2
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - AddRequestHeader=second-request, second-request-header2 # key, value
            - AddResponseHeader=second-response, second-response-header2

 

Custom Filter 사용하기

@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {

    public CustomFilter(){
        super(Config.class);
    }

    public static class Config{
        // configuration properties
    }

    @Override
    public GatewayFilter apply(Config config) {
        // Custom Pre Filter
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            log.info("Custom PRE filter : request id -> {}", request.getId());

            // Custom Post Filter
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {  // Mono : 비동기 방식에서의 단일 응답값 의미
                log.info("Custom POST filter : response code -> {}", response.getStatusCode());
            }));
        };
    }
}

 

GlobalFilter 사용하기

- 모든 라우터에서 전역적으로 필터를 사용할 수 있다.

- GlobalFilter를 만들고, application-yml에 default-filters로 해당 필터를 입력해주면 전역 필터로 동작한다.

@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {

    public GlobalFilter(){
        super(Config.class);
    }

    @Data
    public static class Config{
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            log.info("Global Filter BaseMessage : {}", config.getBaseMessage());

            if (config.isPreLogger()) {
                log.info("Global Filter Start : request id -> {}", request.getId());
            }

            // Post Filter
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                if (config.isPreLogger()) {
                    log.info("Global Filter End : response code -> {}", response.getStatusCode());
                }
            }));
        };
    }
}
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true

 

Load Balancer

기존의 경우 uri에 직접 ip:port를 전달함으로써 gateway에서 해당 ip:port로 이동했다면

gateway에 아래와 같은 형식으로 uri에 넣어줌으로써, Eureka를 거쳐 ip:port로의 이동을 할 수 있다.

- lb://{서비스 어플리케이션 name의 upper}

routes:
  - id: user-service
    #uri: http://localhost:8081/
    uri: lb://USER-SERVICE
    predicates:
      - Path=/user/**
    filters:
      - name: CustomFilter
      - name: UserLoggingFilter
        args:
          baseMessage: Spring Cloud Gateway User Logger Filter
          preLogger: true
          postLogger: true

 

[ 참고/출처 ] 

https://joon2974.tistory.com/26

https://kinchi22.github.io/2019/09/22/api-gateway/

https://velog.io/@tritny6516/Spring-Thread-Pool

https://velog.io/@seokkitdo/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EB%9E%80

https://pearlluck.tistory.com/726

https://p-bear.tistory.com/77

https://adjh54.tistory.com/207

https://djlife.tistory.com/10#google_vignette
https://velog.io/@ksh9409255/Service-Discovery

https://velog.io/@jkijki12/Eureka%EB%9E%80

 

 

1. Spring Cloud Netflix Eureka 

- 유레카는 Service Discovery 중 하나이다.

- Service Discovery는 말 그대로 서비스를 찾아주는 것으로써, 어느 위치에 어떤 서버가 있는지를 찾아준다.

  rf. key / value 형태로 서비스를 등록하고 검색할 수 있도록 해준다.

- 넷플릭스 자사의 기술들을 Java Spring 재단에 기부를 하면서 만들어진 것이 Eureka이다. 

- Eureka를 쓰려면 먼저 서비스를 등록해야 한다. 

- Client에서 요청이 Load Banlancer로 들어오면 Service Discovery가 서비스의 위치를 찾아준다.

 

2. 프로젝트 환경 구성

[1] 프로젝트 생성

- 마인드챗이라고, 내 마음 속 이야기를 채팅창에 적을 수 있는 플젝을 한 번 만들어보고 싶어서 아래처럼 만들어봤다.

 

[2] 의존성에서 유레카 서버 하나만 먼저 넣어준다. 

- build.gradle의 모습

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2023.0.0")
}

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

 

[3] 메인 클래스에 유레카 서버를 활성화하는 Annotation 추가

@SpringBootApplication
@EnableEurekaServer
public class MindchatApplication {

    public static void main(String[] args) {
        SpringApplication.run(MindchatApplication.class, args);
    }

}

 

[4] application.yml 작성

# service port (Eureka 서버 포트)
server:
  port: 8761

# MSA 각 서비스의 고유 id 부여
spring:
  application:
    name: discoveryservice

# 마이크로 서비스를 등록하는 역할을 한다.
# Eureka는 서버로서 구동만 하면 되기 때문에 아래 사항을 false로 입력
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

- 실행 시, 로그에 8761 포트 뜸

main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8761 (http)

- http://localhost:8761 로 들어오면 Eureka Dashboard 화면 보임 

- 아래가 등록된 마이크로 서비스 목록을 볼 수 있는 곳임

 

3. 마이크로 서비스 모듈 만들어보기 

- [File] - [new project] 로 모듈을 만든다.

- 중요한 건 이번에는 Eureka Discovery Client 를 추가한다.

- build.gradle 

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.1'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('springCloudVersion', "2023.0.0")
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

 

- Main 클래스에 아래와 같이 어노테이션 추가

@SpringBootApplication
@EnableDiscoveryClient
public class UserApplication {
	public static void main(String[] args) {
		SpringApplication.run(UserApplication.class, args);
	}

}

- yml 설정

server:
  port: 9001

spring:
  application:
    name: userservice

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

- 유레카 확인

 

[ 출처 ] 인프런 - Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)

[ 참고 ]

https://bcho.tistory.com/1252

 

시간이 지날 수록 소프트웨어는 Antifragile한 아키텍처를 구성하고자 하였으며, Cloud Native한 아키텍처를 구성하고자 했다. 아래는 그러한 소프트웨어 아키텍처의 특징을 정리한 내용이다.

 

1. Antifragile 아키텍처

1) AutoScaling (자동확장성)

   : 상황에 따라 인스턴스의 갯수를 자동으로 늘리는 등 자동 확장이 가능한 것

2) Microservices

    : 세밀한 단위로 모듈과 기능을 세분화한 서비스. 넷플릭스와 아마존에서 가장 잘 구축했음 

3) chaos engineering

   : chaos, 급격하고 예측 불가한 상황에서도 견딜 수 있을 만큼 안정적인 서비스 구축

4) continuous deployments

   : 지속적인 통합/배포를 의미 

 

2. Cloud Native 아키텍처

1) 확장 가능한 아키텍처

    : 확장/수평된 형태로 시스템의 부하를 분산해 가용성을 높인다. (Scale-out)

    : 컨테이너 기반의 어플리케이션 단위 패키지를 구성하고, 모니터링을 한다.

2) 탄력적 아키텍처

    : 각 기능을 분리된 서비스로 구성하고, 자동화 파이프라인을 통해 배포해 배포 시간을 단축시킨다.

3) 장애 격리 

    : 특정 서비스에 오류가 발생해도 다른 서비스에 영향을 주지 않는다.

 

3. Cloud Native Application 특징

1) MSA로 개발된다

2) CI/CD : 지속적인 통합과 배포 

    -  지속적인 통합을 위해 jenkins, Team CI, Travis CI 를 사용할 수 있다. 

    - 지속적인 배포에는 continuous delivery(수동반영)/continuous deployment(자동반영)가 있다

    - 카나리 배포 또는 블루그린 배포 사용 가능 

3) Devops : 구현-테스트-배포를 무한 반복 

4) Containers : 컨테이너 가상화 기술 

 

아래부터는 MSA 서비스를 위한 12가지 요인들과 MSA의 특징을 정리했다.

 

4. 12 Factors 

https://12factor.net/ 

MSA 서비스를 위해 필요로 하는 12가지 요인들이다.

피보탈에서 아래에 +3 (Api First, Telemetry(수치화/시각화), Authentication and authorization) 을 더 추가했다.

I. Codebase
One codebase tracked in revision control, many deploys
> 리포지토리에 저장한 각 마이크로 서비스에 대한 단일 코드베이스 / 버전 제어 위함 / 코드는 한 곳에서 배포

II. Dependencies
Explicitly declare and isolate dependencies
> 종속성. 각 마이크로 서비스는 각각의 모듈에 종속적이라 전체 서비스에 영향을 주지 않음

III. Config
Store config in the environment
> 설정 

IV. Backing services
Treat backing services as attached resources
> 서비스 지원. 데이터/캐싱/메시징 등을 지원

V. Build, release, run
Strictly separate build and run stages
> 빌드/릴리즈/실행환경을 분리하라. 각 태그 있고 분리되며 자동화된 배포가 이뤄져야한다.

VI. Processes
Execute the app as one or more stateless processes
> 하나의 프로세스는 다른 프로세스와 독립되어야 한다.

VII. Port binding
Export services via port binding
> 포트 바인딩. 각 서비스별로 포트가 달라야한다.

VIII. Concurrency
Scale out via the process model
> 동시성. 하나의 서비스가 여러 인스턴스에 나뉘며 부하 분산 -> 동시성을 가져야 한다.

IX. Disposability
Maximize robustness with fast startup and graceful shutdown
> 확장성. 서비스 인스턴스 등록 및 삭제 / 실행이 쉬워야

X. Dev/prod parity
Keep development, staging, and production as similar as possible
> 개발/운영 분리

XI. Logs
Treat logs as event streams
> 로그를 출력하는 로직을 어플리케이션과 분리해야 한다. (별도의 모니터링)
> Azure 또는 ELK를 사용할 수 있음 

XII. Admin processes
Run admin/management tasks as one-off processes
> 적절한 관리. 마이크로 서비스가 어떠한 상태로 사용되며 리소스가 어떻게 쓰이는지 
  데이터를 정리하고 분석하는 기능을 통해 관리가 가능하다.

 

5. 모놀리틱 vs Microservice

모놀리틱은 하나에 모든 서비스를 다 넣는다면, MSA는 서비스를 분리해서 개발하고 운영하는 걸 말한다.

유지보수가 쉽다. 필요한 서비스만 독립적으로 배포가 가능하다. 

https://hahahoho5915.tistory.com/71#google_vignette

 

6. MSA 표준 구성 요소

[1] MSA 표준 구성 전반 

  • 전반적인 프로세스
    • 클라이언트 => API gateway => Service Router -> Service Discovery (A서비스 찾음) => Load Banlancing통해 A서비스 중 1번 인스턴스로 접근 
  • CI/CD Automation
  • Backing Service 
  • Telemetry (Monitoring, 진단)

[2] MSA 기반 기술

- CNCF(Cloud Native Computing Foundation) https://landscape.cncf.io/

 

Cloud Native Landscape

The Cloud Native Landscape organizes all cloud native open source projects and proprietary products into categories, providing an overview of the current ecosystem

landscape.cncf.io

 

스프링부트의 Spring Cloud를 사용하면 MSA 환경을 구성할 수 있다. 

 

7. Spring Cloud

https://spring.io/projects/spring-cloud 

Spring Cloud는 개발자가 분산 시스템에서 일부 공통 패턴
(예: 구성 관리, 서비스 검색, 회로 차단기, 지능형 라우팅, 마이크로 
프록시, 제어 버스, 단기 마이크로서비스 및 계약 테스트)을 신속하게 구축할 수 있는 도구를 제공합니다.

 

스프링 클라우드의 특징들은 아래와 같으며, 다양한 내부 프로젝트들을 통해 각 특징을 구현할 수 있다.

 

  • 분산/버전 관리 구성  
  • 서비스 등록 및 검색   
  • 라우팅
  • 서비스 간 통신
  • 로드 밸런싱
  • 회로 차단기
  • 분산 메시징
  • 단기 마이크로서비스(작업)
  • 소비자 중심 및 생산자 중심 계약 테스트

수업에서는 이들중에서도, 아래의 내부 프로젝트들을 소개했다. 

- 중앙 설정 관리 : Spring Cloud Config Server 

- Location Transparency : Naming Server(Eureka) = 서비스의 위치 확인 및 검색

- Load Distribution : Ribbon 대신 Spring Cloud Gateway 사용 (최신 버전에서 권장됨)

- Easier REST clients : Feign Client

- Visibility and monitoring : Zipkin Distributed Tracing, Netflix API gateway 

- 장애 복구 : Hystrix

 

[출처]

인프런 - Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)

[참고]

https://wonit.tistory.com/490

https://hahahoho5915.tistory.com/71#google_vignette

1. Prometheus란?

- 관련된 신화 : 신으로부터 불을 훔친 자로써, 형벌로 동생에게 판도라라는 여성을 보내 판도라의 상자 사건이 일어남

- 메트릭 수집, 시각화, 알림, 서비스 디스커버리 기능을 모두 제공하는 오픈 소스 모니터링 시스템이다.

- 프로메테우스로 모니터링을 하려면 모니터링 항목 수집을 위해 Exporter라고 하는 Agent가 수행되어야 한다.

   ex. node_exporter, mysql_exporter, postgre_exporter, redis_exporter, kafka_exporter.....

 

2. Dockerfile로 mysqld_exporter와 node_exporter가 포함된 MySQL 컨테이너 생성하기

- Prometheus에서 모니터링을 하려면 MySQL 컨테이너에 node_exporter와 mysqld_exporter를 같이 설치해줘야한다. 

- 기존의 percona에서는 두 exporter가 없었기 때문에 커스텀 image를 생성해줘야한다.

 

[1] custom_image 디렉토리 안에 dockerfile로 커스텀 image 생성 

FROM centos:7
COPY ["Percona-Server-client-57-5.7.31-34.1.el7.x86_64.rpm",\
      "Percona-Server-server-57-5.7.31-34.1.el7.x86_64.rpm", \
      "Percona-Server-shared-57-5.7.31-34.1.el7.x86_64.rpm", \
      "Percona-Server-shared-compat-57-5.7.31-34.1.el7.x86_64.rpm", \
      "node_exporter-1.7.0.linux-amd64.tar.gz", \
      "mysqld_exporter-0.15.1.linux-amd64.tar.gz", \
      "start_node_exporter.sh", \
      "start_mysqld_exporter.sh", \
      ".my.cnf","/home/opc/tmp/"]
USER root
RUN groupadd -g 1001 mysql
RUN useradd -u 1001 -r -g 1001 mysql
RUN yum install -y perl.x86_64 \
    libaio.x86_64 \
    numactl-libs.x86_64 \
    net-tools.x86_64 \
    sudo.x86_64 \
    openssl.x86_64
WORKDIR /home/opc/tmp/
RUN rpm -ivh Percona-Server-shared-57-5.7.31-34.1.el7.x86_64.rpm \
    Percona-Server-shared-compat-57-5.7.31-34.1.el7.x86_64.rpm \
    Percona-Server-client-57-5.7.31-34.1.el7.x86_64.rpm \
    Percona-Server-server-57-5.7.31-34.1.el7.x86_64.rpm
RUN mkdir -p /home/opc/opt/exporters/ && \
    tar -xzvf ./node_exporter-1.7.0.linux-amd64.tar.gz -C /home/opc/opt/exporters && \
    tar -xzvf ./mysqld_exporter-0.15.1.linux-amd64.tar.gz -C /home/opc/opt/exporters
WORKDIR /home/opc/opt/exporters/
RUN mkdir -p node_exporter && \
    mkdir -p mysqld_exporter && \
    mv node_exporter-1.7.0.linux-amd64 node_exporter && \
    mv mysqld_exporter-0.15.1.linux-amd64 mysqld_exporter && \
    mv /home/opc/tmp/start_node_exporter.sh /home/opc/opt/exporters/node_exporter/ && \
    mv /home/opc/tmp/start_mysqld_exporter.sh /home/opc/opt/exporters/mysqld_exporter/ && \
    mv /home/opc/tmp/.my.cnf /home/opc/opt/exporters/mysqld_exporter/ && \
    chmod o+x node_exporter/start_node_exporter.sh && \
    chmod o+x mysqld_exporter/start_mysqld_exporter.sh && \
    rm -rf /home/opc/tmp/*.rpm && \
    /usr/bin/install -m 0775 -o mysql -g mysql -d /var/lib/mysql \
    /var/run/mysqld /docker-entrypoint-initdb.d
VOLUME ["/var/lib/mysql", "/var/log/mysql","/etc/percona-server.conf.d"]
COPY ps-entry.sh /home/opc/tmp/docker-entrypoint.sh
RUN chmod +x /home/opc/tmp/docker-entrypoint.sh
ENTRYPOINT ["/home/opc/tmp/docker-entrypoint.sh"]
USER mysql
EXPOSE 3306
CMD ["mysqld"]

 

[2] 링크를 통해 설치 파일 다운로드
https://docs.percona.com/percona-server/5.7/installation/yum_repo.html#installing-from-the-percona-yum-repository
https://www.percona.com/downloads/Percona-Server-5.7/Percona-Server-5.7.31-34/binary/redhat/7/x86_64/Percona-Server-5.7.31-34-r2e68637-el7-x86_64-bundle.tar
https://prometheus.io/download/#node_exporter

 

[3] mysqld_exporter 설정 파일 추가 및 mysqld/node_exporter 실행 쉘스크립트 작성

- mysqld_exporter 설정 파일

vi .my.cnf
[client]
host=localhost
user=exporter
password=exporter123

- node_exporter와 mysqld_exporter 실행 쉘스크립트

#vi start node_exporter.sh
nohup /opt/exporters/node_exporter/node_exporter &

#vi start_mysqld_exporter.sh
nohup /opt/exporters/mysqld_exporter/mysqld_exporter
--config.my-cnf="/opt/exporters/mysqld_exporter/.my.cnf" &


[4] 모두 하나의 디렉토리 안에 넣었는지 확인

tree -a


[5] 이미지 생성

docker build -t mysql57:0.0 ./
docker images


[6] 컨테이너 실행 

docker run -it --name mydb \
-e MYSQL_ROOT_PASSWORD="root" -d mysql57:0.0

 

3. Customer Image로 컨테이너 생성 및 Prometheus 컨테이너 구성

[1] 기존의 컨테이너를 모두 정지 및 삭제

# rm -f 를 쓰면 stop과 동시에 삭제
[root@dev opc]# docker rm -f db001 db002 db003
db001
db002
db003

 

[2] 호스트에서 사용자 그룹(GID)을 생성하고, 사용자(UID)를 생성한다.

- groupadd를 통해 mysql이라는 사용자 그룹을 생성한다(이때, GID는 1001)

- useradd를 통해 mysql이라는 사용자를 생성한다.(이때, UID도 1001)

- chown 에서 -R은 하위 디렉토리까지 권한을 준다는 의미이며, mysql:mysql은 소유자:그룹형식을 의미한다. 

groupadd -g 1001 mysql
useradd -u 1001 -r -g 1001 mysql
chown -R mysql:mysql /db/db001 /db/db002 /db/db003

 

[3] Mysql 컨테이너 세대를 다시 생성 

docker run -it --name db001 -h db001 -p 3306:3306 \
--net mybridge --net-alias=db001 \
-v /db/db001/data:/var/lib/mysql \
-v /db/db001/log:/var/log/mysql \
-v /db/db001/conf:/etc/percona-server.conf.d \
-e MYSQL_ROOT_PASSWORD="root" -d mysql57:0.0

docker run -it --name db002 -h db002 -p 3307:3306 \
--net mybridge --net-alias=db002 \
-v /db/db002/data:/var/lib/mysql \
-v /db/db002/log:/var/log/mysql \
-v /db/db002/conf:/etc/percona-server.conf.d \
-e MYSQL_ROOT_PASSWORD="root" -d mysql57:0.0

docker run -it --name db003 -h db003 -p 3308:3306 \
--net mybridge --net-alias=db003 \
-v /db/db003/data:/var/lib/mysql \
-v /db/db003/log:/var/log/mysql \
-v /db/db003/conf:/etc/percona-server.conf.d \
-e MYSQL_ROOT_PASSWORD="root" -d mysql57:0.0

 

[4] Prometheus가 사용할 디렉토리와 Computer 디렉토리 생성

mkdir -p /db/prom001 /db/prom001/data /db/prom001/conf
chmod 777 /db/prom001 /db/prom001/data /db/prom001/conf

 

[5] 프로메테우스 컨테이너 실행

(1) 설정 파일 구성

cd /db/prom001/conf
vi prometheus.yml

- 각 컨테이너별로 node_exporter와 mysqld_exporter 잡과 연결 정보를 등록한다.

- node_exporter는 주로 9100을 디폴트로 사용하고, mysqld_exporter는 주로 9104을 디폴트로 사용한다.

global:
  scrape_interval:     5s
  evaluation_interval: 5s

scrape_configs:
- job_name: linux_db001
  static_configs:
    - targets: ['db001:9100']
      labels:
        alias: db001
- job_name: mysql_db001
  static_configs:
    - targets: ['db001:9104']
      labels:
        alias: db001
- job_name: linux_db002
  static_configs:
    - targets: ['db002:9100']
      labels:
        alias: db002
- job_name: mysql_db002
  static_configs:
    - targets: ['db002:9104']
      labels:
        alias: db002
- job_name: linux_db003
  static_configs:
    - targets: ['db003:9100']
      labels:
        alias: db003
- job_name: mysql_db003
  static_configs:
    - targets: ['db003:9104']
      labels:
        alias: db003

 

(2) Docker로 Prometheus Container 실행 

docker run -it --name prom001 -h prom001 -p 9090:9090 \
--net mybridge --net-alias=prom001 \
-v /db/prom001/data:/data \
-v /db/prom001/conf:/etc/prometheus \
-d prom/prometheus-linux-amd64

 

(3) Prometheus 설정

- Master MySQL 컨테이너에서 아래와 같이 프로메테우스용 user 생성

docker exec -it -uroot db001 /bin/bash
mysql -uroot -p

mysql > create user 'exporter'@'localhost' identified by 'exporter123' with MAX_USER_CONNECTIONS 3;
mysql > grant PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';

- 호스트에서 각각의 exporter 시작 쉘스크립트 실행

docker exec db001 sh /opt/exporters/node_exporter/start_node_exporter.sh

docker exec db001 sh /opt/exporters/mysqld_exporter/start_mysqld_exporter.sh

docker exec db002 sh /opt/exporters/node_exporter/start_node_exporter.sh

docker exec db002 sh /opt/exporters/mysqld_exporter/start_mysqld_exporter.sh

docker exec db003 sh /opt/exporters/node_exporter/start_node_exporter.sh

docker exec db003 sh /opt/exporters/mysqld_exporter/start_mysqld_exporter.sh

 

[6] Prometheus 웹 콘솔 접속

- 아래 주소로 접속을 하면 된다.

http://{{호스트id}}:9090/graph

- "up"이라는 쿼리를 쓰면 각 컨테이너별 exporter를 확인할 수 있다.

 

3. Grafana Container 실행 및 Dashboard만들기

[1] Docker로 Granfana Container 실행

docker run -it --name grafana -h grafana -p 13000:3000 \
--net mybridge --net-alias=grafana \
-d grafana/grafana

 

[2] http://{{host ip}}:13000/ 으로 그라파나 접속

- default 접속 : admin/admin

 

[3] Grafana 구성

- Connections > Data Sources > [Add Data Source] 에서 Prometheus를 선택한다.

- 접속 URL에 프로메테우스 컨테이너 정보 http://prom001:9090 입력 후 저장 및 테스트

 

[4] Dashboard 추가

- Dashboard 유형에는 여러가지가 있는데, 그 중 

- 아래 링크에서 MYSQL_Overview.json의 json 정보를 복사한 후

https://github.com/percona/grafana-dashboards/tree/master/dashboards 

- 프로메테우스의 [+] > Import dashboard 를 통해 로그 후 임포트 

 

[참고]
https://www.lesstif.com/lpt/linux-chown-93127453.html
https://yjkim97.tistory.com/53

https://gurumee92.tistory.com/220

 

+ Recent posts