저번처럼 TCP UDP 포트 설정하기

 

sudo iptables -I INPUT -p tcp --dport 25565 -j ACCEPT
sudo iptables -I INPUT -p udp --dport 25565 -j ACCEPT

오라클 리눅스나 우분투 이미지는 기본적으로 엄격한 방화벽 설정

마인크래프트 포트(25565)를 허용하는 명령어

 

추가하고 확인

 

sudo iptables -I INPUT -p tcp --dport 25565 -j ACCEPT

-I INPUT: Insert의 약자로, INPUT 체인의 가장 맨 위에 추가하는 규칙

-p: protocol의 약자로 통신 방식을 설정

--dport: destionation port의 약자로, 목적지 포트 번호 설정

-j ACCEPT: jump의 약자로, 위 조건이 맞다면 패킷을 허용(ACCEPT)하라는 명령

 

# 규칙 목록 보기
sudo iptables -L -v -n --line-numbers

# 특정 규칙 삭제
sudo iptables -D INPUT [라인번호]

# 모든 규칙 초기화
sudo iptables -F

# 특정 IP 차단
sudo iptables -A INPUT -s [차단할IP] -j DROP

iptables 명령은 입력 즉시 적용이지만, 서버 재부팅 시 사라짐

반드시 netfileter-persistent save 명령을 통해 설정을 파일로 저장해야 재부팅 후에도 방화벽이 유지

 

sudo netfilter-persistent save
sudo netfilter-persistent reload

재부팅 후에도 유지하도록 설정을 저장하는 명령어

save는 현재 메모리상에 적용되어 있는 실시간 방화벽 규칙(iptables)을 파일로 저장

reload는 저장된 설정 파일(/etc/iptables/rules.v4)을 읽어와서 방화벽 시스템에 다시 로드

 

환경 설정 저장 및 로드

 

sudo apt update
sudo apt install openjdk-17-jre-headless -y
java -version

마인크래프트 서버를 실행하기 위해 필요한 엔진 설치 과정

최신 버전(1.20 버전 이상)의 마인크래프트 실행을 위해서는 Java 17 이상이 필요

└ 1.21 버전 이상은 Java 21을 권장

version 명령을 입력했을 때 버전 정보가 나오면 성공

 

중간에 확인 구문, sudo apt update로 oracle 커널이 업데이트 된 것을 적용해달라는 의미로 파악

 

재부팅할 시스템 확인

 

# 설치 자바 패키지 확인
dpkg --list | grep openjdk

# Java 17 삭제(예)
sudo apt purge openjdk-17-jre-headless openjdk-17-jre openjdk-17-jdk -y

# 잔여 설정 및 불필요 패키지 정리
sudo apt autoremove -y

버전 관리를 위한 삭제 명령어

 

# PaperMC
wget https://api.papermc.io/v2/projects/paper/versions/1.20.1/builds/196/downloads/paper-1.20.1-196.jar -O server.jar

# Fabric
wget https://maven.fabricmc.net/net/fabricmc/fabric-installer/1.0.1/fabric-installer-1.0.1.jar
curl -OJ https://meta.fabricmc.net/v2/versions/loader/1.21.11/0.18.4/1.1.1/server/jar

예를 들면 PaperMC의 서버 파일 다운로드

URL은 버전과 버킷 종류에 따라 달라짐

https://fabricmc.net/use/server/ 해당 사이트를 참고해도 가능

 

java -jar server.jar
nano eula.txt

초기 java 실행 시 라이선스 동의 파일이 생성

EULA 수정으로 eula=false를 eula=true로 수정한 뒤 저장

 

# start.bat 혹은 sh로 사용할 jar 실행 명령어
java -Xms4G -Xmx4G -jar server.jar nogui
java -Xms16G -Xmx16G -jar fabric-server-mc.1.21.11-loader.0.18.4-launcher.1.1.1.jar nogui

# 새 가상 화면 만들기
screen -S minecraft

# 가상 화면 접속하기
screen -r minecraft

# 가상 화면 나가기
Ctrl + A D

터미널을 종료해도 서버가 유지되게 하는 서버 백그라운드 구동

스크린을 생성하고 서버 메모리를 할당

 

실행 가능하게 모드 변경

 

정상적으로 eula.txt까지 설정하고 난 뒤 모습

 

# 포트 상태 확인
sudo netstat -tnlp | grep 25565
sudo ss -tunlp | grep 25565

# 리눅스 자체 방화벽(Uncomplicated FireWall) 내리기
# OCI 클라우드 방화벽과 리눅스 자체 방화벽 이중 방화벽 상태
sudo ufw disable

기타 확인 사항

 

pause-when-empty-seconds
접속 중인 플레이어가 없다면 서버를 일시 중지한다.(tick freeze와 같음) 초 단위로 조절한다.
0으로 설정하면 접속 중인 플레이어가 없어도 서버는 계속 돌아간다.

server.properties 변경 사항, 나무위키 링크 참고

 

멍청하게 Source Port Range를 All이 아니라 25565로 해두고 있어서 접속이 안 됐음

 

해결하고 나니 정상적으로 접속 완료

해당 포트 확인 사이트는 https://www.yougetsignal.com/tools/open-ports/ 링크 참고

 

정상 접속 확인

 

모드 다운 받아서 scp로 옮기기

 

mods 폴더로 옮겨주기

0. 정리하고 분류할 개인 깃허브(정리 중)

 

GitHub - miny-genie/data-engineer-roadmap-checklist: 데이터 엔지니어가 되고 싶은 자가 준비하는 커리어 로

데이터 엔지니어가 되고 싶은 자가 준비하는 커리어 로드맵. 무엇을 해야 하는지 어떤 기술이 있는지 정리하고, 이것들을 하나하나 사용해보며 무엇이 나은지 몸으로 배워나간다. 그렇게 실제

github.com

 

1. NLP (Natural Language Processing, 자연어 처리)

  • 개념: 인간의 언어를 컴퓨터가 처리, 분석, 생성할 수 있게 하는 AI의 근간 기술. LLM의 조상격이자 확장된 분야.
  • 기본 동작: 텍스트를 토큰으로 나누고(Tokenization), 각 토큰을 숫자로 변환(Embedding)하여 수학적으로 연산합니다.
  • 활용 방법: 개체명 인식(NER), 감성 분석, 텍스트 분류, 맞춤법 검사.
  • 분야: 번역기, 맞춤법 검사, 검색 엔진, 스팸 필터, 음성 비서(Siri 등), 감정 분석 시스템.
  • 사용 간단 예제: "이 문장에서 사람 이름, 회사 이름, 날짜만 따로 추출해줘."
  • 주요 모델: BERT, RoBERTa, T5, GPT 시리즈
  • 알고리즘: Word2Vec, RNN, LSTM, Attention Mechanism
  • 참고자료: Stanford CS224N: NLP with Deep Learning Coursera NLP Specialization

 

2. LLM (Large Language Model, 대규모 언어 모델)

  • 개념: 수조 개의 단어로 구성된 대규모 텍스트 데이터를 학습하여 문맥을 이해하고 자연스러운 텍스트를 생성하는 모델입니다.
  • 기본 동작: 이전의 단어(Token)을 바탕으로 다음에 올 가장 확률 높은 단어를 예측하는 'Next Token Prediction' 방식
  • 활용 방법: 텍스트 생성, 번역, 요약, 추론, 코딩 도움.
  • 분야: 챗봇, 교육, 콘텐츠 제작, 창의적 글쓰기, 프로그래밍, 데이터 분석.
  • 사용 간단 예제: "복잡한 세법 문제를 사례를 들어서 알기 쉽게 설명해줘."
  • 주요 모델: GPT-4, Llama 3.1, Claude 3.5, Mistral Large, Gemini.
  • 알고리즘: Transformer(Self-Attention), RLHF(인간 피드백 기반 강화학습).
  • 참고자료: Jay Alammar: The Illustrated Transformer

 

3. LangChain (랭체인)

  • 개념: LLM 기반 애플리케이션 개발을 위한 오픈소스 프레임워크. 모델, 데이터 소스, 메모리 등을 연결(Chain)하는 도구 모음.
  • 기본 동작: LLM 호출, 프롬프트 템플릿 관리, 외부 도구 실행(Tool Use), 대화 기록 관리 등을 사슬(Chain)로 연결하는 모듈화.
  • 활용 방법: 여러 단계의 추론이 필요한 앱 구축, 다단계 작업 자동화, RAG 파이프라인 자동화, 챗봇 메모리 관리.
  • 분야: AI 소프트웨어 개발, 프로토타이핑, 자동화 에이전트 구축.
  • 사용 간단 예제: "PDF 파일을 읽고(Loader) -> 작은 조각으로 나누고(Splitter) -> 질문에 답하는(Chain) 프로그램 만들기."
  • 주요 모델: 모든 대형 언어 모델(GPT, Claude, Llama 등)과 호환.
  • 알고리즘: LCEL(LangChain Expression Language)을 통한 선언적 프로그래밍 구조.
  • 참고자료: LangChain 공식 문서

 

4. RAG (Retrieval-Augmented Generation, 검색 증강 생성)

  • 개념: 모델이 가진 지식에만 의존하지 않고, 외부 문서 저장소에서 관련 정보를 검색한 내용을 바탕으로 답변을 생성하는 기술.
  • 기본 동작: 사용자 질문을 벡터화(Vector DB) → Vector DB에서 유사 문서 검색(Retrieval) → 질문과 검색 내용을 LLM에 전달(Generation) → 근거에 기반한 답변 생성.
  • 활용 방법: 최신 뉴스 기반 답변, 기업 내부 보안 문서 QA, 환각(Hallucination) 방지.
  • 분야: 고객센터 챗봇, 법률/의료 문서 전문 검색 시스템, 사내 지식 관리.
  • 사용 간단 예제: "우리 회사 2024년 복지 규정 파일에서 '휴가' 항목만 찾아서 요약해줘."
  • 주요 모델/도구: LangChain, LlamaIndex, Pinecone(Vector DB), FAISS.
  • 알고리즘: Cosine Similarity(유사도 측정), BM25(전통적 검색), Dense Passage Retrieval(DPR).
  • 참고자료: Meta AI: Retrieval-Augmented Generation, Pinecone Learning Center - RAG

 

5. Multimodal (멀티모달)

  • 개념: 텍스트, 이미지, 오디오, 비디오, 센서 데이터 등 다양한 형태(Modality)의 정보를 결합하여 학습하고 추론하는 기술.
  • 기본 동작: 서로 다른 데이터 타입을 각각의 인코더로 처리한 뒤, 통합(Fusion)하여 하나의 신경망에서 종합적인 판단.
  • 활용 방법: 비디오의 소리와 화면을 동시에 분석하여 요약, 텍스트 설명을 음악이나 영상으로 변환.
  • 분야: 미디어 콘텐츠 제작, 지능형 CCTV 분석, 실시간 통번역, 로봇 공학.
  • 사용 간단 예제: 동영상을 업로드하고 "3분 20초쯤에 나오는 사람이 누군지, 무슨 말을 하는지 알려줘."
  • 주요 모델: Gemini(네이티브 멀티모달), Sora(Video), AudioLM(Audio).
  • 알고리즘: Late Fusion(결과값 통합), Early Fusion(입력단계 통합), Joint Embedding.
  • 참고자료: DeepLearning.AI Multimodal Course

 

6. VLM (Vision-Language Model, 비전 언어 모델)

  • 개념: 이미지(Vision)와 텍스트(Language)를 동시에 이해하고 처리하는 인공지능 모델. 사진을 보고 상황을 설명하거나, 이미지 속 텍스트를 읽고 질의응답이 가능.
  • 기본 동작: 이미지 인코더(이미지 특징 추출)와 텍스트 인코더(언어 이해)를 결합하여 두 데이터를 하나의 벡터 공간에 매핑. 이를 통해 "이미지 속의 빨간 차"라는 개념을 시각과 언어 양쪽에서 연결.
  • 활용 방법: 이미지 캡셔닝, 시각적 질의응답(VQA), 이미지 내 객체 탐지 및 설명.
  • 분야: 자율주행, 의료 영상 분석, 전자상거래(상품 검색), 시각 장애인 보조 도구.
  • 사용 간단 예제: "이 영수증 사진에서 총금액과 날짜만 추출해서 표로 만들어줘."
  • 주요 모델: GPT-4o, Claude 3.5 Sonnet, Gemini 1.5 Pro, LLaVA(오픈소스), CLIP(OpenAI).
  • 알고리즘: Contrastive Learning(이미지와 텍스트의 유사성 극대화), Transformer 기반의 Cross-Attention.
  • 참고자료: Hugging Face Multimodal Guide

 

7. AI Agent (AI 에이전트)

  • 개념: 주어진 목표를 달성하기 위해 스스로 계획을 세우고, 필요한 도구를 선택해 실행하며, 결과를 확인하고 다시 시도하는 자율적 AI 시스템.
  • 기본 동작: Loop(루프) 구조. '목표 설정 → 계획 수립(Planning)  → 검색이나 코드 등의 도구 실행(Tools, Action)  → 결과 관찰 → 목표 달성 여부 판단 및 피드백 반영(Observation)'.
  • 활용 방법: 자율적 코딩 에이전트, 시장 조사 이후 보고서 자동 작성, 개인 비서(복잡한 일정 예약 역할).
  • 분야: 업무 자동화, 가상 비서, 자율형 소프트웨어 테스팅.
  • 사용 간단 예제: "이번 주 서울 날씨를 검색하고, 그에 맞는 야외 데이트 코스를 3곳 추천해서 나한테 이메일로 보내줘."
  • 주요 모델/프레임워크: AutoGPT, BabyAGI, LangGraph, CrewAI.
  • 알고리즘: ReAct(Reasoning + Acting), Chain of Thought(CoT), Reflection.
  • 참고자료: Lilian Weng: LLM Powered Autonomous Agents

 

8. MCP (Model Context Protocol)

  • 개념: Anthropic에서 제안한 표준 프로토콜로, AI 모델이 외부 데이터 소스(Google Drive, Slack, 로컬 파일 등)나 도구에 안전하고 일관되게 접근할 수 있도록 하는 규약.
  • 기본 동작: 클라이언트-서버 구조. MCP 서버가 데이터나 기능을 제공하면, AI 모델(클라이언트)이 이 프로토콜에 맞춰 필요한 정보를 요청하고 가져옴.
  • 활용 방법: 복잡한 API 연동 코드 없이 표준화된 커넥터를 통해 AI에게 실시간 데이터 권한 부여.
  • 분야: 엔터프라이즈 AI 워크플로우, 개인용 AI 비서, 개발자 생산성 도구.
  • 사용 간단 예제: "내 로컬 컴퓨터의 'project' 폴더에 있는 모든 파이썬 파일을 읽어서 구조도를 그려줘."
  • 주요 모델/도구: Claude Desktop, Cursor(IDE), 다양한 MCP 오픈소스 서버들.
  • 알고리즘: JSON-RPC 기반의 메시징 규격, 표준화된 Resource/Tool 정의 프레임워크.
  • 참고자료: Model Context Protocol 공식 문서

 

9. n8n (Workflow Automation)

  • 개념: 코딩 없이(No-Code) 시각적으로 노드를 연결하여 AI 워크플로우를 자동화하는 도구.
  • 기본 동작: 트리거(시작 조건) 설정 후, API 연동 노드들을 이어 데이터가 흐름을 연결. 중간에 AI 노드 배치 가능.
  • 활용 방법: 매일 특정 시간에 데이터 수집 및 AI 분석, Slack 메시지에 AI가 자동 답변하기.
  • 분야: 비즈니스 자동화, 마케팅 자동화, 데이터 파이프라인 구축.
  • 사용 간단 예제: "구글 시트에 새 행이 추가되면 → AI가 내용을 요약해서 → 이메일로 발송해."
  • 주요 모델/연동: OpenAI, Anthropic, Google Gemini, Hugging Face 모델들과 연동 가능.
  • 알고리즘: 노드 기반 비순환 그래프(DAG) 구조.
  • 참고자료: n8n 공식 유튜브 채널 (AI 활용법)

 

7. Trouble Shooting

진행하면서 더 나은 환경 설정, 간편한 환경 설정을 위해서 이것저것 시도해보다가...

발생하는 다양한 오류들과 설정들에 대해서 정리하는 섹션.

YAML과 네트워킹, DB 설정 자체에 대한 개념 이해가 조금 더 필요하다고 느꼈다.

 

7-1.  Mongo Express 웹 접속 오류

/docker-entrypoint.sh: line 15: /dev/tcp/mongo/27017: Invalid argument
Fri Dec  5 06:58:33 UTC 2025 retrying to connect to mongo:27017 (10/10)
/docker-entrypoint.sh: line 15: mongo: Try again
/docker-entrypoint.sh: line 15: /dev/tcp/mongo/27017: Invalid argument
No custom config.js found, loading config.default.js
Welcome to mongo-express 1.0.2
------------------------


Mongo Express server listening at http://0.0.0.0:8081
Server is open to allow connections from anyone (0.0.0.0)
basicAuth credentials are "admin:pass", it is recommended you change this in your config.js!

docker logs ui-mongo-express해서 발생하는 오류 확인

 

ME_CONFIG_MONGODB_URL: mongodb://admin:admin@database-mongo:27017/?authSource=admin

ME_CONFIG_MONGODB_SERVER: database-mongo를 명시해줬으나 찾지 못하는 오류

구체적으로 URL을 기제해서 database_mongo라는 컨테이너 의존성 확보

 

Unauthorized

그럼에도 계속해서 로그인이 되지 않고, 로그인 창이 계속해서 뜨는 오류가 발생

만일 로그인을 하지 않고 팝업을 닫으면 그대로 권한이 없음(unauthorized)만이 웹 상에 남게 됨

그러던 중 stackoverflow의 글을 발견함

admin/admin도 내가 처음에 yaml에 작성한 값도 아닌 기본값이 admin/pass라는 사실

 

해결 완료

 

7-2. pgAdmin 웹 접속 오류

ERROR  : Failed to create the directory /var/lib/pgadmin/sessions:
           [Errno 13] Permission denied: '/var/lib/pgadmin/sessions'
HINT   : Create the directory /var/lib/pgadmin/sessions, ensure it is writeable by
         'pgadmin', and try again, or, create a config_local.py file
         and override the SESSION_DB_PATH setting per
         https://www.pgadmin.org/docs/pgadmin4/9.10/config_py.html
[2025-12-05 07:24:29 +0000] [1] [ERROR] Worker (pid:2357) exited with code 1
[2025-12-05 07:24:29 +0000] [1] [ERROR] Worker (pid:2357) exited with code 1.
[2025-12-05 07:24:29 +0000] [2358] [INFO] Booting worker with pid: 2358

docker logs ui-pgadmin해서 발생하는 오류 확인

 

docker compose stop pgadmin
sudo chmod -R 777 ./database/pgadmin
docker compose start pgadmin

pgadmin 폴더를 읽을 때 발생하는 권한 문제

1. Python/Gunicorn 백엔드 초기화

PGAdmin 4는 Python/Flask 기반의 애플리케이션이며, 웹 서버로 Gunicorn을 사용합니다.

  • 컨테이너를 시작한 직후에 첫 번째 접속이 이루어지면, Gunicorn 워커 프로세스가 웹 요청을 처리하기 위해 Python 가상 환경을 로드하고 Flask 애플리케이션을 초기화하는 데 시간이 소요됩니다.

2. 세션 및 내부 DB 로딩

  • PGAdmin은 사용자의 세션 정보, 이전에 등록된 서버 목록, 내부 설정 등을 저장하기 위해 내부 SQLite 데이터베이스를 사용합니다 (볼륨 ./database/pgadmin에 저장됨).
  • 사용자가 5050 포트로 접속하면, PGAdmin은 이 내부 DB를 로드하고 세션을 인증하는 등의 초기 준비 단계를 거쳐야 합니다. 이 과정이 웹 페이지를 즉시 반환하는 다른 서비스보다 느립니다.

3. 첫 접속 이후 속도 개선

일반적으로 PGAdmin은 첫 접속 시에만 지연이 크게 발생합니다. 한 번 접속하고 세션이 활성화된 상태라면, 이후 접속이나 페이지 이동은 훨씬 빠르게 처리될 것입니다.

 

해결 완료

 

7-3. pgAdmin 권한 설정 오류

만약 chmod의 값을 777이 아니라 775로 한다면 위와 같은 문구를 확인할 수 있다.

 

{
  "success": 0,
  "errormsg": "(sqlite3.OperationalError) attempt to write a readonly database\n[SQL: UPDATE user SET locked=? WHERE user.id = ?]\n[parameters: (0, 1)]\n(Background on this error at: https://sqlalche.me/e/20/e3q8)",
  "info": "",
  "result": null,
  "data": null
}

쓰기 권한이 부족하다는 뜻

 

근데 chmod 777로 해버리면 보안 문제가 발생하니

pgadmin UID는 5050

postgres UID는 999를 이용해서 해야 할 듯..?

 

7-4. pgAdmin 폴더가 VM에 필요한 이유?

pgadmin이 depends on이 database-postgres로 되어있다.
내가 이해하기로는 pgadmin은 database-postgres의 데이터를 조회하고 pgadmin은 웹으로 보여주는 기능이다.
database/postgres랑 database/pgadmin 폴더가 따로 필요한가?

 

결론적으로는 database/pgadmin 폴더가 따로 필요하다.

PGAdmin이 database-postgres의 데이터를 조회하고 웹으로 보여주는 것이 주 기능이지만,

 

1. 🗄️ database/postgres 폴더 (PostgreSQL 데이터 보존) 이 폴더는 PostgreSQL 데이터베이스 자체의 모든 데이터를 저장합니다. 용도: database-postgres 컨테이너가 사용하는 데이터 볼륨입니다 (./database/postgres:/var/lib/postgresql/data). 저장 내용: 실제 사용자 테이블, 인덱스, 데이터베이스 구조 등 PostgreSQL 서버가 운영하는 데 필요한 모든 데이터 파일이 저장됩니다. 필요성: 이 볼륨이 없으면 컨테이너가 재시작될 때마다 데이터베이스의 모든 데이터가 사라집니다. 2. ⚙️ database/pgadmin 폴더 (PGAdmin 설정 보존) 이 폴더는 데이터베이스 데이터가 아니라 PGAdmin 애플리케이션 자체의 설정을 저장합니다. 용도: pgadmin 컨테이너가 사용하는 데이터 볼륨입니다 (./database/pgadmin:/var/lib/pgadmin). 저장 내용: PGAdmin 사용자 정보: 로그인 시 사용한 관리자 계정 이메일 및 암호화된 비밀번호. 서버 연결 정보: PGAdmin에 등록한 database-postgres를 포함한 모든 외부 PostgreSQL 서버의 연결 설정 정보 (호스트, 포트, 사용자 이름 등). 세션 데이터, 로그, 구성 파일 등 PGAdmin 웹 애플리케이션의 상태와 설정 데이터. 필요성: 이 볼륨이 없으면 PGAdmin 컨테이너가 재시작될 때마다 매번 최초 접속 시의 설정 화면이 뜨게 되며, 이전에 등록했던 모든 PostgreSQL 서버 연결 정보가 사라집니다. 💡 컨테이너 의존성과 볼륨의 역할 depends_on: database-postgres: PGAdmin이 PostgreSQL 컨테이너가 완전히 실행된 후에 시작되도록 순서를 보장하여 연결 오류를 방지하는 역할을 합니다. 볼륨 (volumes): 각 컨테이너 내부의 데이터를 호스트 머신의 특정 폴더에 연결(마운트)하여 컨테이너가 삭제되거나 재생성되어도 데이터나 설정이 유지되도록 하는 역할을 합니다. 요약하자면, PostgreSQL 서버의 데이터와 PGAdmin 관리 도구의 설정/상태 데이터는 완전히 별개이기 때문에, 영구적으로 보존하기 위해 각각 별도의 볼륨 폴더가 필요합니다.

 

7-5. 그럼 Mongo Express 폴더는 VM에 필요없는 이유?

여기서 드는 궁금증
그렇다면 왜 Mongo Express는 폴더가 따로 필요 없는가?
 
요약하자면 Mongo Express와 pgAdmin은 역할과 데이터 저장 방식이 다르기 때문이다.
 
1. 웹 인터페이스일 뿐입니다. Mongo Express는 PostgreSQL의 PGAdmin이나 Airflow의 웹서버와 마찬가지로, 백엔드 데이터베이스(database-mongo)의 데이터를 웹 브라우저를 통해 보여주고 조작할 수 있게 해주는 단순한 웹 인터페이스/클라이언트 도구입니다. 2. 별도의 설정 데이터가 거의 없습니다. PGAdmin이 서버 연결 목록, 사용자 관리, 세션 상태 등을 자체적으로 데이터베이스 파일에 저장해야 하는 반면, Mongo Express는 보통 다음과 같이 작동합니다. 설정: Docker Compose 파일에 명시된 환경 변수(ME_CONFIG_MONGODB_SERVER, ADMINUSERNAME, ADMINPASSWORD 등)를 시작 시점에 로드하여 사용합니다. 영구 데이터: Mongo Express가 영구적으로 보존해야 할 사용자 설정, 세션, 로그 기록이 거의 없습니다. 컨테이너가 재시작되어도 환경 변수만 있으면 데이터베이스에 다시 연결할 수 있습니다. 따라서 컨테이너가 죽거나 새로 만들어져도, 설정 파일이 담긴 볼륨이 없어도 기능하는 데 문제가 없습니다.
 
상태가 없는(Stateless) 도구에 가까워 영구적인 설정을 저장할 필요가 없어, 별도의 볼륨 폴더가 없다.

 

7-6. Docker를 이용한 PostgreSQL 자동 복원 시 발생하는 DB 이름 문제

admin과 postgres, 그리고 maintaince

정확하게는 YAML에서 기본 DB 이름을 admin과 postgres로 했을 때의 차이

 

7-7. pg_dump에서 발생한 sql 복원 파일 DROP, ALTER 문제

ubuntu@drawbridge-vm:~/my-project/test$ docker compose logs database-postgres
database-postgres  | The files belonging to this database system will be owned by user "postgres".
database-postgres  | This user must also own the server process.
database-postgres  |
database-postgres  | The database cluster will be initialized with locale "en_US.utf8".
database-postgres  | The default database encoding has accordingly been set to "UTF8".
database-postgres  | The default text search configuration will be set to "english".
database-postgres  |
database-postgres  | Data page checksums are disabled.
database-postgres  |
database-postgres  | fixing permissions on existing directory /var/lib/postgresql/data ... ok
database-postgres  | creating subdirectories ... ok
database-postgres  | selecting dynamic shared memory implementation ... posix
database-postgres  | selecting default max_connections ... 100
database-postgres  | selecting default shared_buffers ... 128MB
database-postgres  | selecting default time zone ... UTC
database-postgres  | creating configuration files ... ok
database-postgres  | running bootstrap script ... ok
database-postgres  | sh: locale: not found
database-postgres  | 2025-12-07 10:55:42.207 UTC [36] WARNING:  no usable system locales were found
database-postgres  | performing post-bootstrap initialization ... ok
database-postgres  | syncing data to disk ... ok
database-postgres  | initdb: warning: enabling "trust" authentication for local connections
database-postgres  | You can change this by editing pg_hba.conf or using the option -A, or
database-postgres  | --auth-local and --auth-host, the next time you run initdb.
database-postgres  |
database-postgres  |
database-postgres  | Success. You can now start the database server using:
database-postgres  |
database-postgres  |     pg_ctl -D /var/lib/postgresql/data -l logfile start
database-postgres  |
database-postgres  | waiting for server to start....2025-12-07 10:55:43.584 UTC [42] LOG:  starting PostgreSQL 14.20 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
database-postgres  | 2025-12-07 10:55:43.591 UTC [42] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
database-postgres  | 2025-12-07 10:55:43.608 UTC [43] LOG:  database system was shut down at 2025-12-07 10:55:43 UTC
database-postgres  | 2025-12-07 10:55:43.615 UTC [42] LOG:  database system is ready to accept connections
database-postgres  |  done
database-postgres  | server started
database-postgres  |
database-postgres  | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/azure_db_dump.sql
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  |  set_config
database-postgres  | ------------
database-postgres  |
database-postgres  | (1 row)
database-postgres  |
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | 2025-12-07 10:55:43.678 UTC [54] ERROR:  publication "postgres_fabricpublication" does not exist
database-postgres  | 2025-12-07 10:55:43.678 UTC [54] STATEMENT:  DROP PUBLICATION postgres_fabricpublication;
database-postgres  | psql:/docker-entrypoint-initdb.d/azure_db_dump.sql:19: ERROR:  publication "postgres_fabricpublication" does not exist
database-postgres  |
database-postgres  | PostgreSQL Database directory appears to contain a database; Skipping initialization
database-postgres  |
database-postgres  | 2025-12-07 10:55:44.164 UTC [1] LOG:  starting PostgreSQL 14.20 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
database-postgres  | 2025-12-07 10:55:44.164 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
database-postgres  | 2025-12-07 10:55:44.164 UTC [1] LOG:  listening on IPv6 address "::", port 5432
database-postgres  | 2025-12-07 10:55:44.171 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
database-postgres  | 2025-12-07 10:55:44.178 UTC [27] LOG:  database system was interrupted; last known up at 2025-12-07 10:55:43 UTC
database-postgres  | 2025-12-07 10:55:44.217 UTC [27] LOG:  database system was not properly shut down; automatic recovery in progress
database-postgres  | 2025-12-07 10:55:44.221 UTC [27] LOG:  invalid record length at 0/1708648: wanted 24, got 0
database-postgres  | 2025-12-07 10:55:44.221 UTC [27] LOG:  redo is not required
database-postgres  | 2025-12-07 10:55:44.247 UTC [1] LOG:  database system is ready to accept connections
database-postgres  | 2025-12-07 10:55:49.148 UTC [40] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:55:54.186 UTC [47] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:55:59.228 UTC [54] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:04.271 UTC [61] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:09.312 UTC [68] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:14.354 UTC [75] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:19.399 UTC [82] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:24.440 UTC [89] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:29.477 UTC [96] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:34.514 UTC [103] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 10:56:39.548 UTC [110] FATAL:  database "admin" does not exist

문제가 발생

 

nano나 vi에서 편집기도 파일 수정

 

ubuntu@drawbridge-vm:~/my-project/test$ docker compose logs database-postgres
database-postgres  | The files belonging to this database system will be owned by user "postgres".
database-postgres  | This user must also own the server process.
database-postgres  |
database-postgres  | The database cluster will be initialized with locale "en_US.utf8".
database-postgres  | The default database encoding has accordingly been set to "UTF8".
database-postgres  | The default text search configuration will be set to "english".
database-postgres  |
database-postgres  | Data page checksums are disabled.
database-postgres  |
database-postgres  | fixing permissions on existing directory /var/lib/postgresql/data ... ok
database-postgres  | creating subdirectories ... ok
database-postgres  | selecting dynamic shared memory implementation ... posix
database-postgres  | selecting default max_connections ... 100
database-postgres  | selecting default shared_buffers ... 128MB
database-postgres  | selecting default time zone ... UTC
database-postgres  | creating configuration files ... ok
database-postgres  | running bootstrap script ... ok
database-postgres  | sh: locale: not found
database-postgres  | 2025-12-07 11:17:44.478 UTC [36] WARNING:  no usable system locales were found
database-postgres  | performing post-bootstrap initialization ... ok
database-postgres  | syncing data to disk ... ok
database-postgres  |
database-postgres  |
database-postgres  | Success. You can now start the database server using:
database-postgres  |
database-postgres  |     pg_ctl -D /var/lib/postgresql/data -l logfile start
database-postgres  |
database-postgres  | initdb: warning: enabling "trust" authentication for local connections
database-postgres  | You can change this by editing pg_hba.conf or using the option -A, or
database-postgres  | --auth-local and --auth-host, the next time you run initdb.
database-postgres  | waiting for server to start....2025-12-07 11:17:45.672 UTC [42] LOG:  starting PostgreSQL 14.20 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
database-postgres  | 2025-12-07 11:17:45.675 UTC [42] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
database-postgres  | 2025-12-07 11:17:45.684 UTC [43] LOG:  database system was shut down at 2025-12-07 11:17:45 UTC
database-postgres  | 2025-12-07 11:17:45.692 UTC [42] LOG:  database system is ready to accept connections
database-postgres  |  done
database-postgres  | server started
database-postgres  |
database-postgres  | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/azure_db_dump.sql
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  |  set_config
database-postgres  | ------------
database-postgres  |
database-postgres  | (1 row)
database-postgres  |
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | SET
database-postgres  | DROP PUBLICATION
database-postgres  | 2025-12-07 11:17:45.766 UTC [54] ERROR:  relation "public.sessions" does not exist
database-postgres  | 2025-12-07 11:17:45.766 UTC [54] STATEMENT:  ALTER TABLE ONLY public.sessions DROP CONSTRAINT sessions_user_id_fkey;
database-postgres  | psql:/docker-entrypoint-initdb.d/azure_db_dump.sql:20: ERROR:  relation "public.sessions" does not exist
database-postgres  |
database-postgres  | PostgreSQL Database directory appears to contain a database; Skipping initialization
database-postgres  |
database-postgres  | 2025-12-07 11:17:46.296 UTC [1] LOG:  starting PostgreSQL 14.20 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
database-postgres  | 2025-12-07 11:17:46.297 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
database-postgres  | 2025-12-07 11:17:46.297 UTC [1] LOG:  listening on IPv6 address "::", port 5432
database-postgres  | 2025-12-07 11:17:46.302 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
database-postgres  | 2025-12-07 11:17:46.309 UTC [27] LOG:  database system was interrupted; last known up at 2025-12-07 11:17:45 UTC
database-postgres  | 2025-12-07 11:17:46.323 UTC [27] LOG:  database system was not properly shut down; automatic recovery in progress
database-postgres  | 2025-12-07 11:17:46.325 UTC [27] LOG:  invalid record length at 0/1708648: wanted 24, got 0
database-postgres  | 2025-12-07 11:17:46.325 UTC [27] LOG:  redo is not required
database-postgres  | 2025-12-07 11:17:46.346 UTC [1] LOG:  database system is ready to accept connections
database-postgres  | 2025-12-07 11:17:51.282 UTC [40] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 11:17:56.320 UTC [47] FATAL:  database "admin" does not exist
database-postgres  | 2025-12-07 11:18:01.367 UTC [54] FATAL:  database "admin" does not exist

다시 발생한 오류

 

하나만 문제가 아니라서 전부 DROP에 예외를 처리

pg_dump로 생성된 덤프 파일은 객체가 존재한다고 가정하고 DROP 명령을 사용한다.

하지만 로컬 환경에서는 객체들이 전부 없기에 스크립트의 모든 DROP 명령이 오류를 유발하고 복원 스크립트 실행이 중단된다.

존재하는 모든 PUBLICATION, TABLE, INDEX, SEQUENCE, EXTENSION, SCHEMA의 DROP과 ALTER에 대해 했다.

85개의 line에 문제가 있어서 전부 추가

 

7-8. pg_dump에서 발생한 sql 복원 파일 DROP, ALTER 문제 2

6-7에서 DROP과 ALTER는 전부 해결했지만 FATAL admin does not exist 문제는 계속해서 발견

그래도 계속 문제 발생...

 

pg_dump -h cloud-postgredb-server.postgres.database.azure.com -U Drawbridge -d postgres -F p -c -f azure_db_dump.sql
pg_dump -h cloud-postgredb-server.postgres.database.azure.com -U Drawbridge -d postgres -F p -c --if-exists -f azure_db_dump.sql

ㅇㅇ

 

7-9. Azure CDC 미설치 오류

database-postgres  | DROP SCHEMA
database-postgres  | psql:/docker-entrypoint-initdb.d/azure_db_dump.sql:109: ERROR:  could not open extension control file "/usr/local/share/postgresql/extension/azure_cdc.control": No such file or directory
database-postgres  | DROP SCHEMA
database-postgres  | DROP SCHEMA
database-postgres  | DROP SCHEMA
database-postgres  | DROP SCHEMA
database-postgres  | DROP EXTENSION
database-postgres  | DROP SCHEMA
database-postgres  | DROP EXTENSION
database-postgres  | DROP EXTENSION
database-postgres  | 2025-12-07 15:10:48.089 UTC [55] ERROR:  could not open extension control file "/usr/local/share/postgresql/extension/azure_cdc.control": No such file or directory
database-postgres  | 2025-12-07 15:10:48.089 UTC [55] STATEMENT:  CREATE EXTENSION IF NOT EXISTS azure_cdc WITH SCHEMA pg_catalog;
database-postgres  |
database-postgres  | PostgreSQL Database directory appears to contain a database; Skipping initialization
database-postgres  |
database-postgres  | 2025-12-07 15:10:48.598 UTC [1] LOG:  starting PostgreSQL 14.20 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
database-postgres  | 2025-12-07 15:10:48.602 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
database-postgres  | 2025-12-07 15:10:48.602 UTC [1] LOG:  listening on IPv6 address "::", port 5432
database-postgres  | 2025-12-07 15:10:48.609 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
database-postgres  | 2025-12-07 15:10:48.620 UTC [26] LOG:  database system was interrupted; last known up at 2025-12-07 15:10:48 UTC
database-postgres  | 2025-12-07 15:10:48.640 UTC [26] LOG:  database system was not properly shut down; automatic recovery in progress
database-postgres  | 2025-12-07 15:10:48.645 UTC [26] LOG:  redo starts at 0/1708D98
database-postgres  | 2025-12-07 15:10:48.646 UTC [26] LOG:  invalid record length at 0/1708E90: wanted 24, got 0
database-postgres  | 2025-12-07 15:10:48.646 UTC [26] LOG:  redo done at 0/1708E48 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
database-postgres  | 2025-12-07 15:10:48.671 UTC [1] LOG:  database system is ready to accept connections

Azure CDC 관한 오류, 개념 설명

 

이거를 없애야 함

 

# OWNER TO "Drawbridge" 구문을 OWNER TO admin 으로 일괄 변경합니다.
# 따옴표(")는 sed 내에서 이스케이프해야 합니다.
sed -i 's/OWNER TO "Drawbridge"/OWNER TO admin/g' $SQL_FILE

ㅇㅇ

 

구성 요소 역할 설명
sed Stream Editor 파일의 내용을 수정하는 리눅스 명령어입니다.
-i In-place 수정 내용을 화면에 출력하는 대신, 원본 파일에 즉시 저장하도록 지시합니다.
's/pattern/replacement/g' Substitution s (substitute) 명령입니다. 파일 내에서 pattern을 찾아 replacement로 바꿉니다.
pattern OWNER TO "Drawbridge" 찾을 문자열 패턴입니다.
replacement OWNER TO admin 패턴을 대체할 문자열입니다.
g Global 찾은 패턴을 각 줄에서 단 한 번만 바꾸는 대신, 모든 발생 횟수를 바꾸도록 지시합니다 (일괄 변경).
$SQL_FILE 파일 변수 수정할 파일의 경로(azure_db_dump.sql 파일 경로를 저장한 변수)입니다.

 

설명

 

#!/bin/bash

# 컨테이너 내부 경로: /docker-entrypoint-initdb.d/
# SQL_FILE="02_azure_db_dump.sql"
SQL_FILE="/docker-entrypoint-initdb.d/02_azure_db_dump.sql" 

echo "[DEBUG] 01_fix_dump.sh Starting dump file modifications..."

# 1. OWNER TO 일괄 수정
sed -i 's/OWNER TO \"Drawbridge\"/OWNER TO admin/g' "$SQL_FILE"
sed -i 's/OWNER TO azure_pg_admin/OWNER TO admin/g' "$SQL_FILE"
echo "[DEBUG] OWNER TO 일괄 수정 완료."

# 2-1. Azure 확장 CREATE 구문 주석 처리
# CREATE EXTENSION IF NOT EXISTS azure_cdc...
# CREATE EXTENSION IF NOT EXISTS azure_storage...
sed -i '/CREATE EXTENSION IF NOT EXISTS azure/s/^/-- /g' "$SQL_FILE"
echo "[DEBUG] CREATE EXTENSION azure 구문 주석 처리 완료."

# 2-2. Azure 확장 COMMENT 구문 주석 처리
# COMMENT ON EXTENSION azure_cdc...
# COMMENT ON EXTENSION azure_storage...
sed -i '/COMMENT ON EXTENSION azure/s/^/-- /g' "$SQL_FILE"
echo "[DEBUG] COMMENT ON EXTENSION azure 구문 주석 처리 완료."

# 3-1. 기타 확장 CREATE 구문 주석 처리
# CREATE EXTENSION IF NOT EXISTS pgaadauth WITH SCHEMA pg_catalog;
# CREATE EXTENSION IF NOT EXISTS pg_cron WITH SCHEMA pg_catalog;
# CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
sed -i '/CREATE EXTENSION IF NOT EXISTS pg/s/^/-- /g' "$SQL_FILE"
echo "[DEBUG] CREATE EXTENSION pg 구문 주석 처리 완료."

# 3-2. 기타 확장 COMMENT 구문 주석 처리
# COMMENT ON EXTENSION pgaadauth IS 'Microsoft Entra ID Authentication';
# COMMENT ON EXTENSION pg_cron IS 'Job Scheduler for PostgreSQL';
# COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
sed -i '/COMMENT ON EXTENSION pg/s/^/-- /g' "$SQL_FILE"
echo "[DEBUG] COMMENT ON EXTENSION pg 구문 주석 처리 완료."

echo "[DEBUG] 01_fix_dump.sh Modifications complete."

전체 명령 코드

 

cron EXTENSION이 없다는 에러가 발생하기는 한다

 

하지만 데이터는 정상 복원 완료.

...인 줄 알았으나 테이블까지만 복원되고 데이터는 없었다.

 

7-10. CosmosDB 데이터 MongoDB로 복원 중 발생한 decode array 에러

database-mongo | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/01_mongo_dump.sh
database-mongo | [INFO] Waiting for MongoDB to fully start using mongosh...
... (MongoDB 연결 성공 로그)
database-mongo | [INFO] MongoDB is up - executing data import.
database-mongo | 2025-12-10T07:16:33.835+0000 Failed: cannot decode array into a primitive.D
database-mongo | 2025-12-10T07:16:33.835+0000 0 document(s) imported

라는 형태의 로그 발생

이 오류 메시지 (cannot decode array into a primitive.D)는 MongoDB 가져오기 도구(mongoimport)가 가져오려는 데이터 파일의 형식과 지정한 데이터 형식이 일치하지 않을 때 주로 발생합니다.

 

mongoimport	--host localhost \
		--db $DB_NAME \
            	--collection skill_info \
            	--file /path/to/skill_info.ndjson \
            	--type json \
            	--upsert

이런 형태로 mongoimport를 sh에 작성했다.

--type json을 지정했고, 파일 확장자는 .ndjson (Newline Delimited JSON)입니다.

mongoimport는 기본적으로 파일 전체를 JSON 배열로 가정하거나, 한 줄에 하나의 JSON 객체가 있는 NDJSON으로 처리한다.

하지만 파일(.ndjson)의 실제 내용이 MongoDB가 기대하는 BSON(Binary JSON) 문서 구조를 따르지 않을 때 발생한다.

 

허허 그래서 찾아보니 분명 ndjson으로 DMT를 실행했으나 실제로 들어간 값은 json array로 값이 존재한다.

아마 DMT에서 NDJSON을 지원하지 않는 문제일 수도 있겠다. 이는 찾아볼 문제다.

 

mongoimport	--host localhost \
		--db $DB_NAME \
            	--collection skill_info \
            	--file /path/to/skill_info.ndjson \
            	--type json \
            	--upsert \
                --jsonArray	# 새롭게 추가한 옵션

옵션 하나를 추가하여 jsonArray 형태 구조임을 명시한다.

 

7-11. MongoDB Connection Refused

ui-mongo-express | Waiting for database-mongo:27017...
ui-mongo-express | /docker-entrypoint.sh: connect: Connection refused
ui-mongo-express | /docker-entrypoint.sh: line 15: /dev/tcp/database-mongo/27017: Connection refused
ui-mongo-express | Wed Dec 10 07:43:46 UTC 2025 retrying to connect to database-mongo:27017 (2/10)
... (반복)

docker compose logs mongo-express로 보니 connection refused가 발생

 

10번의 재시도를 했지만 서버에 접속이 거부됨

 

그래서 docker compose logs mongo로 살펴보니 MongoDB가 아직 완전히 준비되지 않은 상태였음.

쉽게 말하면 restore하려는 용량이 너무 커서, 성급히 mongo express로 접속하니 아직 준비가 덜 된 상태.

어차피 postgres에서 복구하는데도 30분 조금 안 되게 걸리니 기다려봄.

 

약 4분 정도 기다리니 정상적으로 complete 했다는 로그가 뜸

ubuntu@drawbridge-vm:~/my-project/test$ docker compose logs database-mongo | grep "imported successfully"
database-mongo  | 2025-12-10T07:43:48.555+0000  481 document(s) imported successfully. 0 document(s) failed to import.
database-mongo  | 2025-12-10T07:46:18.252+0000  48089 document(s) imported successfully. 0 document(s) failed to import.
database-mongo  | 2025-12-10T07:46:18.336+0000  481 document(s) imported successfully. 0 document(s) failed to import.

세 ndjson 모두 정상적으로 복원했다는 로그 확인

그리고 다시금 docker compose logs mongo-express로 확인

 

ui-mongo-express  | /docker-entrypoint.sh: connect: Connection refused
ui-mongo-express  | /docker-entrypoint.sh: line 15: /dev/tcp/database-mongo/27017: Connection refused
ui-mongo-express  | Wed Dec 10 07:45:57 UTC 2025 retrying to connect to database-mongo:27017 (8/10)
ui-mongo-express  | /docker-entrypoint.sh: connect: Connection refused
ui-mongo-express  | /docker-entrypoint.sh: line 15: /dev/tcp/database-mongo/27017: Connection refused
ui-mongo-express  | Wed Dec 10 07:45:58 UTC 2025 retrying to connect to database-mongo:27017 (9/10)
ui-mongo-express  | /docker-entrypoint.sh: connect: Connection refused
ui-mongo-express  | /docker-entrypoint.sh: line 15: /dev/tcp/database-mongo/27017: Connection refused
ui-mongo-express  | Wed Dec 10 07:45:59 UTC 2025 retrying to connect to database-mongo:27017 (10/10)
ui-mongo-express  | /docker-entrypoint.sh: connect: Connection refused
ui-mongo-express  | /docker-entrypoint.sh: line 15: /dev/tcp/database-mongo/27017: Connection refused
ui-mongo-express  | No custom config.js found, loading config.default.js
ui-mongo-express  | Welcome to mongo-express 1.0.2
ui-mongo-express  | ------------------------
ui-mongo-express  |
ui-mongo-express  |
ui-mongo-express  | Mongo Express server listening at http://0.0.0.0:8081
ui-mongo-express  | Server is open to allow connections from anyone (0.0.0.0)
ui-mongo-express  | basicAuth credentials are "admin:pass", it is recommended you change this in your config.js!

1. 시간차 (Race Condition)Docker Compose는 기본적으로 서비스들을 거의 동시에 시작합니다. 하지만 MongoDB 서버 (database-mongo)가 완전히 부팅되고 27017 포트에서 연결을 받을 준비를 마치는 데는 시간이 걸립니다.mongo-express 컨테이너는 MongoDB보다 빠르게 시작했습니다.mongo-express는 시작하자마자 database-mongo:27017로 연결을 시도했습니다.하지만 그 시점에 database-mongo는 아직 초기화 중이었기 때문에, 연결 요청을 거부했습니다. $\rightarrow$ Connection refused 또는 ECONNREFUSED 발생2. 초기 연결 재시도 메커니즘 (Retry Logic)mongo-express의 로그 상단을 보면 다음과 같은 재시도 로직이 보입니다.ui-mongo-express | Waiting for database-mongo:27017... ui-mongo-express | Wed Dec 10 07:43:46 UTC 2025 retrying to connect to database-mongo:27017 (2/10) ... (10번 반복) mongo-express의 entrypoint.sh 또는 초기화 스크립트에는 MongoDB가 준비될 때까지 일정 횟수 (여기서는 10번) 연결을 시도하는 대기/재시도 로직이 내장되어 있습니다.$\Rightarrow$ 결론: Connection refused가 반복된 시점은 database-mongo 컨테이너가 완전히 준비되기 전에 mongo-express가 먼저 연결을 시도했기 때문입니다.🌟 최종 성공 이유반복적인 연결 실패 메시지에도 불구하고 결국 mongo-express가 성공적으로 접속할 수 있었던 이유는 다음과 같습니다.MongoDB 준비 완료: mongo-express의 재시도 로직이 끝난 후, 결국 database-mongo가 모든 초기화 작업을 완료하고 27017 포트를 열었습니다.연결 재개: 재시도 로직이 끝난 후에도 mongo-express 자체적으로 연결을 시도하거나, 혹은 연결 오류가 발생할 때마다 애플리케이션 레벨에서 연결을 다시 시도하는 과정이 있었고, 결국 MongoDB가 준비된 시점에 연결이 성공했습니다.이러한 문제를 해결하기 위해 Docker Compose에서는 depends_on 외에도 **healthcheck**와 condition: service_healthy 옵션을 사용하여, 종속된 서비스가 단순히 시작된 것뿐만 아니라 '준비 완료' 상태가 될 때까지 기다리도록 설정하는 것이 일반적인 모범 사례입니다.

 

정상적으로 기존의 CosmosDB의 데이터베이스를 복원했다.

 

내부 collection들과 데이터들도 정상적으로 복원했다.

 

7-11. PostgreSQL restore 후 테이블은 있지만 데이터가 없는 에러

이전에 테이블만 보고 데이터가 전부 옮겨졌다고 착각했다.

데이터까지 봤어야 했는데, 무슨 바람이 불었는지 데이터를 확인해보았고... 그 결과 데이터는 없었다.

 

database-postgres  | 2025-12-10T08:17:45.400383444Z COPY 297
database-postgres  | 2025-12-10T08:17:45.414991626Z COPY 1294
database-postgres  | 2025-12-10T08:17:45.415174827Z 2025-12-10 08:17:45.415 UTC [2568] ERROR:  schema "cron" does not exist
database-postgres  | 2025-12-10T08:17:45.415190787Z 2025-12-10 08:17:45.415 UTC [2568] STATEMENT:  COPY cron.job (jobid, schedule, command, nodename, nodeport, database, username, active, jobname) FROM stdin;
database-postgres  | 2025-12-10T08:17:45.415395028Z psql:/docker-entrypoint-initdb.d/02_azure_db_dump.sql:19201095: ERROR:  schema "cron" does not exist
database-postgres exited with code 3 (restarting)
database-postgres  | 2025-12-10T08:17:46.237532882Z
database-postgres  | 2025-12-10T08:17:46.237570443Z PostgreSQL Database directory appears to contain a database; Skipping initialization
database-postgres  | 2025-12-10T08:17:46.237574643Z
database-postgres  | 2025-12-10T08:17:46.260004239Z 2025-12-10 08:17:46.259 UTC [1] LOG:  starting PostgreSQL 14.20 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
database-postgres  | 2025-12-10T08:17:46.260062599Z 2025-12-10 08:17:46.259 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
database-postgres  | 2025-12-10T08:17:46.260359842Z 2025-12-10 08:17:46.260 UTC [1] LOG:  listening on IPv6 address "::", port 5432
database-postgres  | 2025-12-10T08:17:46.266473844Z 2025-12-10 08:17:46.266 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
database-postgres  | 2025-12-10T08:17:46.272875409Z 2025-12-10 08:17:46.272 UTC [27] LOG:  database system was interrupted; last known up at 2025-12-10 08:17:39 UTC
database-postgres  | 2025-12-10T08:17:46.284278328Z 2025-12-10 08:17:46.284 UTC [27] LOG:  database system was not properly shut down; automatic recovery in progress
database-postgres  | 2025-12-10T08:17:46.288706399Z 2025-12-10 08:17:46.288 UTC [27] LOG:  redo starts at 1/AE0999E0
database-postgres  | 2025-12-10T08:17:47.615519133Z 2025-12-10 08:17:47.615 UTC [27] LOG:  invalid record length at 1/D52CB170: wanted 24, got 0
database-postgres  | 2025-12-10T08:17:47.615549333Z 2025-12-10 08:17:47.615 UTC [27] LOG:  redo done at 1/D52CB148 system usage: CPU: user: 0.61 s, system: 0.71 s, elapsed: 1.32 s
database-postgres  | 2025-12-10T08:17:50.749444592Z 2025-12-10 08:17:50.748 UTC [1] LOG:  database system is ready to accept connections

다행히도 docker compose logs -ft 옵션으로 계속 로그를 찍고 있었어서 확인할 수 있었다.

pg_cron을 주석 처리했는데(pgaadauth, pg_cron, pg_crypto extension을 pg 기반 sed로 주석 처리) 여기서 문제가 발생했다.

cron 스키마가 계속해서 없다는 에러가 발생한다.

 

sed -i \
-e 's/OWNER TO \"Drawbridge\"/OWNER TO admin/g' \
-e 's/OWNER TO azure_pg_admin/OWNER TO admin/g' \
-e '/CREATE EXTENSION IF NOT EXISTS azure/s/^/-- /g' \
-e '/COMMENT ON EXTENSION azure/s/^/-- /g' \
-e '/CREATE EXTENSION IF NOT EXISTS pg/s/^/-- /g' \
-e '/COMMENT ON EXTENSION pg/s/^/-- /g' \
-e '/^COPY cron\.job/,/^\.$/ s/^/-- /g' \
-e '/^COPY cron\.job_run_details/,/^\.$/ s/^/-- /g' \
-e '/^\.$/s/^/-- /g' \
"$SQL_FILE"

이런 형태로 sql 주석 처리 스크립트를 짜서 변경했다.

 

database-postgres  | 2025-12-13T18:56:55.868374535Z
database-postgres  | 2025-12-13T18:56:55.868391455Z PostgreSQL init process complete; ready for start up.
database-postgres  | 2025-12-13T18:56:55.868394615Z
database-postgres  | 2025-12-13T18:56:55.890161129Z 2025-12-13 18:56:55.890 UTC [1] LOG:  starting PostgreSQL 14.20 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
database-postgres  | 2025-12-13T18:56:55.890287849Z 2025-12-13 18:56:55.890 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
database-postgres  | 2025-12-13T18:56:55.890294529Z 2025-12-13 18:56:55.890 UTC [1] LOG:  listening on IPv6 address "::", port 5432
database-postgres  | 2025-12-13T18:56:55.894183397Z 2025-12-13 18:56:55.894 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
database-postgres  | 2025-12-13T18:56:55.901092325Z 2025-12-13 18:56:55.900 UTC [2580] LOG:  database system was shut down at 2025-12-13 18:56:55 UTC
database-postgres  | 2025-12-13T18:56:55.908089735Z 2025-12-13 18:56:55.907 UTC [1] LOG:  database system is ready to accept connections

그랬더니 정상적으로 실행한 것처럼 보인다.

 

7-12. WAL_MAX_SIZE로 인한 Shut down 문제 발생?

database-postgres  | 2025-12-13T18:54:00.297553440Z 2025-12-13 18:54:00.297 UTC [43] LOG:  checkpoints are occurring too frequently (25 seconds apart)
database-postgres  | 2025-12-13T18:54:00.297585400Z 2025-12-13 18:54:00.297 UTC [43] HINT:  Consider increasing the configuration parameter "max_wal_size".
database-postgres  | 2025-12-13T18:54:28.916353466Z 2025-12-13 18:54:28.916 UTC [43] LOG:  checkpoints are occurring too frequently (28 seconds apart)
database-postgres  | 2025-12-13T18:54:28.916400187Z 2025-12-13 18:54:28.916 UTC [43] HINT:  Consider increasing the configuration parameter "max_wal_size".
database-postgres  | 2025-12-13T18:55:58.161870593Z 2025-12-13 18:55:58.161 UTC [43] LOG:  checkpoints are occurring too frequently (29 seconds apart)
database-postgres  | 2025-12-13T18:55:58.161910673Z 2025-12-13 18:55:58.161 UTC [43] HINT:  Consider increasing the configuration parameter "max_wal_size".
database-postgres  | 2025-12-13T18:56:24.461408456Z 2025-12-13 18:56:24.461 UTC [43] LOG:  checkpoints are occurring too frequently (26 seconds apart)
database-postgres  | 2025-12-13T18:56:24.461449336Z 2025-12-13 18:56:24.461 UTC [43] HINT:  Consider increasing the configuration parameter "max_wal_size".
database-postgres  | 2025-12-13T18:56:48.886490914Z 2025-12-13 18:56:48.886 UTC [43] LOG:  checkpoints are occurring too frequently (24 seconds apart)
database-postgres  | 2025-12-13T18:56:48.886527194Z 2025-12-13 18:56:48.886 UTC [43] HINT:  Consider increasing the configuration parameter "max_wal_size".
database-postgres  | 2025-12-13T18:56:52.142191989Z COPY 2083155
database-postgres  | 2025-12-13T18:56:52.167559447Z COPY 1186
database-postgres  | 2025-12-13T18:56:54.764739567Z waiting for server to shut down....2025-12-13 18:56:54.764 UTC [41] LOG:  received fast shutdown request
database-postgres  | 2025-12-13T18:56:54.767656388Z 2025-12-13 18:56:54.767 UTC [41] LOG:  aborting any active transactions
database-postgres  | 2025-12-13T18:56:54.773284667Z 2025-12-13 18:56:54.773 UTC [41] LOG:  background worker "logical replication launcher" (PID 48) exited with exit code 1
database-postgres  | 2025-12-13T18:56:55.599597603Z 2025-12-13 18:56:55.599 UTC [43] LOG:  shutting down
database-postgres  | 2025-12-13T18:56:55.776632849Z .2025-12-13 18:56:55.776 UTC [41] LOG:  database system is shut down
database-postgres  | 2025-12-13T18:56:55.867182367Z  done
database-postgres  | 2025-12-13T18:56:55.867209847Z server stopped

7-11에서 혹시 몰라 로그를 조금 더 올려보았다.

그러니 WAL_MAX_SIZE 관련으로 checkpoints가 너무 자주 일어난다는 WARNING이 발생했다.

그리고 잠시 뒤에 aborting이 뜨더니... shutting down으로 database까지 꺼진다???

 

옵션 설명
max_wal_size WAL 디렉토리 안에 WAL 파일들의 총 용량 최대 크기를 지정할 수 있습니다.
soft limit이기 때문에 특별한 상황에 초과할 수 있습니다. 기본값 1GB
min_wal_size 해당 값 이하로는 WAL파일을 정리하지 않습니다.
(최초 생성이나 WAL RESET 상황은 제외합니다.) 기본값 80MB

이런 거라고 한다.

https://postgresqlco.nf/doc/en/param/max_wal_size/ 여기에 설명이 아주 상세하다.

근데 여기서 든 의문이 WARNING에 가깝지 ERROR는 아니지 않나?

저 사이즈가 작다고 DB가 꺼지기까지 하나?

 

해서 LLM에게 물어봤음

🚨 max_wal_size가 서버 셧다운을 직접 유발하는가?

결론부터 말씀드리면, max_wal_size가 너무 작다고 해서 PostgreSQL 서버가 강제로 "셧다운(Shutdown)"되지는 않습니다.

하지만 간접적으로 서버 운영에 심각한 문제를 일으켜 서비스 중단(Outage)이나 비정상 종료(Crash)의 원인이 될 수 있습니다.

1. 잦은 체크포인트의 위험 (성능 저하)

max_wal_size가 너무 작으면(특히 쓰기 작업량이 많은 환경에서) 다음과 같은 성능 문제가 발생합니다.

  • 잦은 체크포인트 요청: PostgreSQL이 max_wal_size에 도달할 때마다 강제로 체크포인트를 시작합니다. 이는 로그에서 본 checkpoints are occurring too frequently 경고의 원인입니다.
  • I/O 폭증 (I/O Spike): 체크포인트는 메모리(Shared Buffers)의 변경된 모든 데이터를 디스크(데이터 파일)에 기록하는 작업입니다. 이 작업이 빈번하게 발생하면 디스크 I/O 부하가 급증하게 됩니다.
  • 성능 저하 및 지연: I/O 부하가 너무 높아지면, 데이터베이스는 다른 쿼리(쓰기 및 읽기)를 처리하는 속도가 느려지거나 일시적으로 멈출 수 있습니다. 이는 사용자가 느끼는 서비스 지연이나 응답 없음 상태를 초래합니다.

 

⚠️ 위험 증가: 만약 데이터베이스가 부하를 견디지 못하고 운영체제(OS) 수준에서 OOM(Out of Memory) Killer에 의해 프로세스가 종료되거나, 디스크 I/O가 멈춰버리는 등 시스템 장애가 발생하면 비정상적인 서버 종료(Crash)가 발생할 수 있습니다.

 

2. 디스크 공간 고갈의 위험 (주요 서버 중단 원인)

max_wal_size 설정 자체가 아니라, WAL 아카이빙(Archiving)이나 복제(Replication) 문제가 함께 발생했을 때 심각한 서버 중단을 일으킬 수 있습니다.

  • max_wal_size는 WAL 세그먼트를 재활용하거나 삭제하는 시점을 결정하는 **소프트 한계(Soft Limit)**입니다.
  • 만약 WAL을 계속 생성하는데 디스크 아카이브 명령이 실패하거나, 복제 슬롯이 오랫동안 활성화되어 오래된 WAL 파일을 지우지 못하게 되면, WAL 파일은 계속해서 증가하여 max_wal_size를 넘어섭니다.
  • 결국 서버가 실행되는 디스크 공간이 **고갈(Disk Full)**되면 PostgreSQL은 더 이상 WAL을 쓸 수 없게 되어 **강제 종료(Crash)**됩니다. 이는 max_wal_size가 작아서 발생하는 문제라기보다는 디스크 공간 관리 문제입니다.

 

로그 타임스탬프 로그 내용
2025-12-13T18:56:54.764739567Z waiting for server to shut down....
2025-12-13T18:56:54.764 UTC
[41] LOG: received fast shutdown request
종료 명령 수신! 외부(Docker, Kubernetes, 스크립트 등)에서 PostgreSQL 서버 프로세스에 종료(Shutdown) 요청을 보냈습
2025-12-13T18:56:54.767 UTC
[41] LOG: aborting any active transactions
진행 중인 트랜잭션 취소.
2025-12-13T18:56:55.599 UTC
[43] LOG: shutting down
백그라운드 프로세스가 종료 절차를 밟고 있음.
2025-12-13T18:56:55.776 UTC
[41] LOG: database system is shut down
데이터베이스 시스템 종료 완료.
2025-12-13T18:56:55.867182367Z
done
 
2025-12-13T18:56:55.867209847Z
server stopped
서버 프로세스 종료 완료.

 

데이터베이스 초기화 스크립트(initdb 또는 사용자 정의 스크립트)의 완료입니다.

초기화 스크립트 실행: 컨테이너가 처음 시작될 때, 데이터베이스 구조 생성(CREATE TABLE)과 대용량 데이터 로딩(COPY)을 수행하는 스크립트(예: Docker Entrypoint 스크립트 내의 SQL 실행)가 실행되었습니다.

스크립트 완료: 마지막 COPY 1294 명령이 18:56:52.588Z에 완료된 후, 스크립트가 성공적으로 끝났습니다.

아마 중단에 끝난 게 아니라 sql 복구 스크립트가 끝나서 재실행한 것 같다는 이야기

 

그래서 sql 맨 아래로 가보니까 딱히 shutting down에 관한 건 없었음

아마 sql 복구 dump 파일이자 명령어니 그건 당연한 건가 싶기도 함

그래서 전체 로그를 다시 읽어보니까

 

 

COPY 완료: 모든 COPY 명령이 18:56:52.588Z에 성공적으로 완료되었습니다.

종료 요청 수신: 2025-12-13T18:56:54.764 UTC에 received fast shutdown request 로그가 기록

정상 종료 완료: 2025-12-13T18:56:55.776 UTC에 database system is shut down 로그와 함께 서버가 종료

 

이 상태라 sql로 복구해서 정상적으로 종료한 게 맞았음, 정확하게는 리부팅

 

7-13. WAL MAX SIZE가 아니라 fix_dump.sh을 잘못 짠 문제

ubuntu@drawbridge-vm:~/my-project/test/database/init-scripts/postgres_scripts$ grep -n "gld_gonggopit_posting_company_tech" 02_azure_db_dump.sql
73:DROP TABLE IF EXISTS gold.gld_gonggopit_posting_company_tech;
1796:-- Name: gld_gonggopit_posting_company_tech; Type: TABLE; Schema: gold; Owner: Drawbridge
1799:CREATE TABLE gold.gld_gonggopit_posting_company_tech (
1821:ALTER TABLE gold.gld_gonggopit_posting_company_tech OWNER TO admin;
19293059:-- -- Data for Name: gld_gonggopit_posting_company_tech; Type: TABLE DATA; Schema: gold; Owner: Drawbridge
19293062:-- COPY gold.gld_gonggopit_posting_company_tech (company_name_s, posting_id, posting_title_j, job_category_kor, posting_tech_stack, company_type, exp_min, exp_max, education_s, edu_category, start_datetime, end_datetime, posting_views_total, applicants_total, hiring_process_clean, posting_tech_stack_cnt, posting_tech_stack_clean, job_category_kor_clean) FROM stdin;

fix_dump.sh을 실행하고 나서 azure_db_dump.sql 파일

 

ubuntu@drawbridge-vm:~/my-project/test/copy_database/init-scripts/postgres_scripts$ grep -n "gld_gonggopit_posting_company_tech" 02_azure_db_dump.sql
73:DROP TABLE IF EXISTS gold.gld_gonggopit_posting_company_tech;
1796:-- Name: gld_gonggopit_posting_company_tech; Type: TABLE; Schema: gold; Owner: Drawbridge
1799:CREATE TABLE gold.gld_gonggopit_posting_company_tech (
1821:ALTER TABLE gold.gld_gonggopit_posting_company_tech OWNER TO "Drawbridge";
19293059:-- Data for Name: gld_gonggopit_posting_company_tech; Type: TABLE DATA; Schema: gold; Owner: Drawbridge
19293062:COPY gold.gld_gonggopit_posting_company_tech (company_name_s, posting_id, posting_title_j, job_category_kor, posting_tech_stack, company_type, exp_min, exp_max, education_s, edu_category, start_datetime, end_datetime, posting_views_total, applicants_total, hiring_process_clean, posting_tech_stack_cnt, posting_tech_stack_clean, job_category_kor_clean) FROM stdin;
ubuntu@drawbridge-vm:~/my-project/test/copy_database/init-scripts/postgres_scripts$

fix_dump.sh을 실행하기 전의 azure_db_dump.sql 파일

잘 보면 COPY 데이터에서 문제가 발생했다.

 

sed -i \
-e 's/OWNER TO \"Drawbridge\"/OWNER TO admin/g' \
-e 's/OWNER TO azure_pg_admin/OWNER TO admin/g' \
-e '/CREATE EXTENSION IF NOT EXISTS azure/s/^/-- /g' \
-e '/COMMENT ON EXTENSION azure/s/^/-- /g' \
-e '/CREATE EXTENSION IF NOT EXISTS pg/s/^/-- /g' \
-e '/COMMENT ON EXTENSION pg/s/^/-- /g' \
# -e '/^COPY cron\.job/,/^\.$/ s/^/-- /g' \			< 제거
# -e '/^COPY cron\.job_run_details/,/^\.$/ s/^/-- /g' \		< 제거
-e '/^\.$/s/^/-- /g' \
"$SQL_FILE"

문제가 되는 부분이 cron 부분이라고 생각했다.

왜냐하면 저 제거한 2줄짜리 주석은 한 줄 주석이 아니라 블록 단위 주석 처리이기 때문이다.

내가 의도한 부분은 A (다음 줄) A 이렇게 되어있는, 다음 줄까지 걸쳐있는 한두 줄을 주석 처리하고 싶었던 건데,

그게 아니라 A ~~ BCDEF ~~ A 이런 느낌으로 sed 표현식이 받아들여서 전체 블록이 주석이 된 것 같다는 느낌?

 

여기가 문제

흠... 뭐가 문제지...

 

 

다시 한 번 처음부터 확인

 

보니까 확실히 숫자가 큰 부분의 COPY 데이터에 주석이 붙었다.

그래서 한 번 19951545 라인으로 가서 보기로 했다.

 

허허 그랬더니 전부 주석이 붙어있다.

위로 올라가서 어디까지 이어져있나 한 번 확인해본다.

 

아니나 다를까 cron.job이 문제였다.

혹시나 해서 해당 단락의 끝이 어디인지 이동해보았다.

 

이유를 알았다.

그동안 Ctrl + b (위로 한 페이지 이동), Ctrl + f (아래로 한 페이지 이동)로만 이동하다가

{ (해당 블록의 처음으로 이동)과 } (해당 블록의 마지막으로 이동) 기능을 알아내서 이동해보니 확실해졌다.

cron.job부터 문서의 마지막까지 '하나의 블록'으로 취급 받고 있다.

그리고 모종의 이유로 sed 구문이 잘못 들어가서 전체가 주석이 되었다.

 

sed -i \
-e 's/OWNER TO \"Drawbridge\"/OWNER TO admin/g' \
-e 's/OWNER TO azure_pg_admin/OWNER TO admin/g' \
-e '/CREATE EXTENSION IF NOT EXISTS azure/s/^/-- /g' \
-e '/COMMENT ON EXTENSION azure/s/^/-- /g' \
-e '/CREATE EXTENSION IF NOT EXISTS pg/s/^/-- /g' \
-e '/COMMENT ON EXTENSION pg/s/^/-- /g' \
-e '/^COPY cron\.job/,/^\.$/ s/^/-- /g' \
-e '/^COPY cron\.job_run_details/,/^\.$/ s/^/-- /g' \
"$SQL_FILE"

사용한 sed 명령어

 

-e '/^COPY cron\.job/,/^\.$/ s/^/-- /g' \

그중에서 이걸 살펴본다.

-e는 여러 개의 sed 명령을 순차적으로 실행할 때 사용한다.

 

 

-e '/^COPY cron\.job/,/^\.$/ s/^/-- /g'

 

내용 설명
^COPY cron\.job 정규 표현식으로 해석하는 패턴
^ 라인의 시작을 의미
COPY cron 리터럴(literal) 텍스트로 COPY cron 구문을 찾으라는 뜻
\. 점(.) 문자는 정규 표현식에서 모든 문자를 의미한다. 앞에 \를 붙여 점 문자 자체로 인식하게 한다.
job 리터럴(literal) 텍스트로 job 구문을 찾으라는 뜻

시작 패턴 내용

 

다행히도 silver, gold, public 스키마에 대해서 데이터는 전부 들어왔다.

 

database-postgres  | 2025-12-15T09:01:29.407472323Z 2025-12-15 09:01:29.407 UTC [2140] ERROR:  schema "cron" does not exist at character 26
database-postgres  | 2025-12-15T09:01:29.407483483Z 2025-12-15 09:01:29.407 UTC [2140] STATEMENT:  SELECT pg_catalog.setval('cron.jobid_seq', 1, false);
database-postgres  | 2025-12-15T09:01:29.407562203Z psql:/docker-entrypoint-initdb.d/02_azure_db_dump.sql:19996720: ERROR:  schema "cron" does not exist

하지만 그 와중에도 cron 관련 데이터 로그가 남아있었다. 

 

cron이 없는데 cron 관련 setval을 해서 오류가 발생한 것

 

-e '/^SELECT pg_catalog\.setval(\'cron\./ s/^/-- /g' \

해당 sed 구문을 추가

 

database-postgres  | 2025-12-15T10:34:22.134563093Z 2025-12-15 10:34:22.134 UTC [2391] ERROR:  canceling autovacuum task
database-postgres  | 2025-12-15T10:34:22.134602933Z 2025-12-15 10:34:22.134 UTC [2391] CONTEXT:  while scanning block 132806 of relation "bronze.brz_hf_meta"
database-postgres  | 2025-12-15T10:34:22.134605973Z     automatic vacuum of table "admin.bronze.brz_hf_meta"
database-postgres  | 2025-12-15T10:34:27.933539677Z 2025-12-15 10:34:27.933 UTC [2006] WARNING:  wal_level is insufficient to publish logical changes
database-postgres  | 2025-12-15T10:34:27.933560517Z 2025-12-15 10:34:27.933 UTC [2006] HINT:  Set wal_level to logical before creating subscriptions.
database-postgres  | 2025-12-15T10:34:27.934066641Z psql:/docker-entrypoint-initdb.d/02_azure_db_dump.sql:19996950: WARNING:  wal_level is insufficient to publish logical changes
database-postgres  | 2025-12-15T10:34:27.934088801Z HINT:  Set wal_level to logical before creating subscriptions.
database-postgres  | 2025-12-15T10:34:27.934541244Z CREATE PUBLICATION
database-postgres  | 2025-12-15T10:34:27.934610405Z ALTER PUBLICATION
database-postgres  | 2025-12-15T10:34:27.934965687Z 2025-12-15 10:34:27.934 UTC [2006] ERROR:  schema "cron" does not exist
database-postgres  | 2025-12-15T10:34:27.934981527Z 2025-12-15 10:34:27.934 UTC [2006] STATEMENT:  GRANT USAGE ON SCHEMA cron TO azure_pg_admin WITH GRANT OPTION;
database-postgres  | 2025-12-15T10:34:27.935049488Z psql:/docker-entrypoint-initdb.d/02_azure_db_dump.sql:19996959: ERROR:  schema "cron" does not exist

또 다른 에러 로그 발생

 

해당 구문 grep으로 검색

 

 

 

 

 

 

 

참고한 웹 사이트 : tistory-inpa-yaml

참고한 웹 사이트 : IBM-topics-yaml

참고한 웹 사이트 : https://bentist.tistory.com/75

참고한 웹 사이트 : https://cozy-dev-area.tistory.com/75

참고한 웹 사이트 : tistory-docker-daemon

참고한 웹 사이트 : forum-docker-pgadmin-connect

참고한 웹 사이트 : reddit-mongoexpress-connect-missing

참고한 웹 사이트 : stackoverflow-mongo-express-service-browser-require-user

참고한 문서 자료 : https://docs.docker.com/compose/

참고한 문서 자료 : https://www.pgadmin.org/docs/

참고한 문서 자료 : https://hub.docker.com/r/dpage/pgadmin4

참고한 문서 자료 : https://airflow.apache.org/docs/docker-stack/build-arg-ref.html

참고한 문서 자료 : https://hub.docker.com/_/mongo-express

참고한 문서 자료 : pgadmin-docs-container-deployment-chown

참고한 웹 사이트 : https://dba.stackexchange.com/questions/68077/pgadmin-what-is-the-maintenance-db

참고한 웹 사이트 : stackoverflow-how-change-maintenance-database-postgres

참고한 문서 자료 : medium-docker-compose-db-세팅-자동화

참고한 웹 사이트 : https://github.com/AzureCosmosDB/data-migration-desktop-tool/releases

참고한 웹 사이트 : https://tmaxtibero.blog/4592-2/

참고한 문서 자료 : https://postgresql.kr/docs/10/wal-configuration.html

참고한 문서 자료 : https://postgresqlco.nf/doc/en/param/max_wal_size/

마지막 줄

1. YAML

YAML ain't markup language(YAML은 마크업 언어가 아닙니다)라고 하면서

Yet Another Markup Language(하지만 또다른 마크업 언어이지요)라고 소개하는 YAML

자기소개부터 유별난 이 친구를 알기 위해서는 데이터를 알아야 한다.

 

Format은 양식, 체제, 서식, 형식, 틀 등등 많은 뜻으로 읽힌다.

파이썬에도 format 함수가 있고, 날짜를 바꿀 때도 정해진 format을 맞추라 하고, formatting한다고도 표현한다.

(혹은 serialization, 직렬화라고도 한다.)

누구나 자신만의 스타일이 있고, 편한 방식이 있다.

하지만 모두의 스타일을 존중하면 중구난방으로 형식이 퍼져버린다.

다양한 형식은 호환과 연동이 어려울 뿐더러 그냥 단순하게 읽기 어렵다.

그렇기에 우리는 서로 데이터를 주고 받을 때 하나의 공통 규칙이 필요하다.

그것이 format이다.

 

CSV, XML, JSON, Properties처럼 데이터를 정의하는 하나의 규칙.

YAML 또한 그중 하나다.

 

출처 : https://bentist.tistory.com/75

ㅇㅇ

 

2. Docker Daemon

Docker 데몬(Docker Daemon)은 Docker 시스템에서 중추적인 역할을 하는 백그라운드 프로세스

Docker 데몬은 컨테이너, 이미지, 네트워크, 볼륨 등을 관리하며, 사용자 요청에 따라 Docker 엔진의 핵심 작업을 수행

기본적으로 데몬은 클라이언트의 명령을 수신하고 이를 처리하여 컨테이너의 생성을 포함한 다양한 작업을 수행하는 역할

Docker 데몬은 클라이언트의 명령을 받고 이를 실행하는 서버 역할을 하기 때문에, 사용자 인터페이스와는 별개의 존재

데몬은 시스템이 부팅될 때 자동으로 시작되며, 지속적으로 백그라운드에서 동작하면서 Docker 관련 명령을 처리

 

3. Docker Compose

 

 

# default value
restart: no

restart 정책은 컨테이너가 종료(Stop)된 이유와 Docker 데몬 상태에 따라 재시작 여부를 결정한다.

 

정책 설명 Docker Daemon
재시작 시
컨테이너가 비정상 종료 시 사용 목적
no 재시작 안 함 재시작 X 재시작 X 일회성 작업, 테스트,
수동 관리가 필요한 컨테이너
always 항상 재시작 재시작 O 재시작 O 서비스 중지가 없는 서비스
(DB, 캐시, 웹 서버 등)
on-failure[:max-retires] 실패 시 재시작 재시작 X 재시작 O 불필요한 재시작 루프 방지
(디버깅이나 일회성 컨테이너)
unless-stopped 수동 중지 외 재시작 재시작 O 재시작 O 가장 일반적으로 사용하는 설정

 

restart 설정에 대한 옵션과 설명

on-failure는 컨테이너가 정상 종료하지 않은 경우(exit code가 0이 아닌 경우)에만 재시작한다.

그리고 설정한 max-retries로 재시작 최대 시도 횟수를 지정한다.

 

# Airflow Component
command:  werbserver

Airflow는 단일 프로세스로 동작하는 것이 아니라, 여러 개의 독립적인 프로세스가 협력하여 작동한다.

command 옵션은 컨테이너가 시작될 때 어떤 Airflow 컴포넌트를 실행할지 지정한다.

 

 

command 실행되는 컴포넌트 역할
webserver 웹 서버(Web Server) 사용자가 DAG 관리, 실행 상태 확인, 로그 접근 등을 할 수 있는 웹 UI 제공
scheduler 스케줄러 (Scheduler) DAG 정의 파일을 모니터링하고, 스케줄에 따라 Task Instance를 생성하는 핵심 요소
worker 워커 (Worker) 스케줄러가 생성한 실제 작업을 할당받아 실행하는 프로세스
init (비공식) 초기화 (Initialize) DB Migration 등 Airflow 환경을 최초 설정하는 명령어

 

webserver 명령을 가진 컨테이너는 오직 HTTP 요청 처리 및 UI 렌더링 역할만 수행한다.

webserver는 따로 DAG를 실행하거나 스케줄링하는 기능은 없다.

따라서 이 컨테이너가 멈추더라도 스케줄러와 워커가 살아있다면, 확인만 못 할 뿐 작업은 계속 실행된다.

 

Dockerfile이나 Docker Compose에서 컨테이너의 실행을 지정하는 방법은 command 옵션과 entrypoint 옵션이 있다.

command는 실행할 주요 명령어나 인수를 지정하고, entrypoint는 컨테이너가 시작 시 가장 먼저 실행하는 프로그램을 지정한다.

command는 실행할 airflow 컴포넌트를 지정하고, entrypoint는 airflow 명령어 자체를 실행한다.

 

4. Postgres Database Migration

ㅇㅇ

 

C:\Program Files\PostgreSQL\version\bin\pg_dump.exe

일반적인 경로를 따라갔다면 위와 같은 경로에 pg_dump.exe 파일이 있다.

제어판 > 사용자 계정 > 사용자 계정 > 환경 변수 변경에서 PATH 등록을 한다.

 

# pg_dump -h cloud-postgredb-server.postgres.database.azure.com -U Drawbridge -d postgres -F p -c -f azure_db_dump.sql
pg_dump -h <Azure-DB-Host> -U <Azure-DB-User> -d <Database-Name> -F p -c -f azure_db_dump.sql

CMD에서 위와 같은 명령으로 pg_dump를 진행한다.

 

pg_dump: PostgreSQL 백업 도구.

-h <Azure-DB-Host>: Azure PostgreSQL 서버의 호스트 이름.

-U <Azure-DB-User>: Azure DB 사용자 이름 (예: user@server-name).

-d <Database-Name>: 내보낼 데이터베이스 이름.

-F p: Plain Text (SQL 스크립트) 형식으로 출력 (-F c는 Custom Format으로, 더 효율적일 수 있으나 복원 시 pg_restore가 필요함).

-c: CLEAN 옵션. 복원 시 기존 테이블을 삭제(DROP)한 후 다시 생성하도록 스크립트에 포함합니다.

-f azure_db_dump.sql: 출력 파일 이름입니다.

 

본인의 host를 모르겠다면 여기를 참고할 수 있다.

 

정상적으로 쉘 명령어를 입력했다면 암호를 입력하라고 나온다.

명령어에 적는 -h(호스트 이름)과 비밀번호는, cloud db instance를 만들 때 입력한 계정과 비밀번호를 입력해야 한다.

 

쉽게 말하면 이 화면의 최좌하단에 보이는 관리자 로그인과 암호를 써야 한다는 뜻이다.

5분이 조금 안 걸렸다. 10,378,528KB로 약 10GB에 해당하는 사이즈라 조금 걸린 듯하다.

 

tar -czvf azure_db_dump.tar.gz azure_db_dump.sql

압축하는 쉘 명령어

tarTape ARchiver의 약자로, 여러 파일을 하나의 아카이브(묶음) 파일로 만들거나 해제할 때 사용하는 기본 유틸리티

-c Create. 새로운 아카이브 파일을 생성하라는 명령어

-z Gzip을 사용하여 압축/해제하라는 명령어 / 이 옵션 때문에 최종 파일의 확장자가 .gz
(.tar 파일을 .tar.gz로 만드는 핵심 옵션입니다.)

-v Verbose. 명령을 수행하는 동안 터미널에 처리 중인 파일 목록을 자세하게 출력하라는 옵션

-f File. 아카이브 파일의 이름을 지정하는 옵션 이 옵션 뒤에는 반드시 아카이브 파일 이름이 와야 한다.

azure_db_dump.tar.gz생성될 아카이브(압축) 파일의 이름

azure_db_dump.sql아카이브 파일 안에 묶어서 압축할) 원본 파일의 이름

 

약 3분이 걸려 2,327,428KB(약 2.2GB)로 압축 성공했다.

 

scp azure_db_dump.tar.gz ubuntu@<Oracle-VM-IP>:/home/ubuntu/data/

scp로 옮기는 데 약 13분이 걸렸다.

 

tar -xzvf azure_db_dump.tar.gz

압축 해제하는 데 약 1분이 걸렸다.

 

# Docker 컨테이너에 접속 (postgres 유저 사용)
docker exec -it database-postgres psql -U admin

# psql 프롬프트에서 새 DB 생성
CREATE DATABASE postgres; 

# psql 종료
\q

본인이 복구하고 싶은 데이터베이스를 만들어야 한다.

정확하게는 복원하려는 데이터베이스의 이름이 동일해야 하는 건 아니지만, 아무 데이터베이스 자체는 존재해야 한다.

pg_dump 명령 중 -c 옵션을 사용했기 때문에, 기존 객체(테이블이나 함수 등)을 삭제한다.

그렇기에 비어있는 데이터베이스 하나를 새롭게 준비하는 게 깔끔하다.

 

docker cp azure_db_dump.sql database-postgres:/tmp/azure_db_dump.sql

덤프 파일을 컨테이너 내부로 복사한다. 약 1분이 걸렸다.

 

# 컨테이너 이름: database-postgres
# 사용자 이름: admin
# 데이터베이스 이름: admin 

docker exec -i database-postgres psql -U admin -d admin < azure_db_dump.sql

복원 실행: docker exec을 사용하여 컨테이너 내부에서 psql 명령을 실행하고 SQL 덤프 파일을 파이프(|)로 연결합니다.

 

docker exec -i: 컨테이너 내부에서 명령을 실행하고 표준 입력(-i)을 허용한다.

psql -U admin -d admin: admin 사용자 및 admin 데이터베이스로 접속한다.

< azure_db_dump.sql: 로컬 VM에 있는 덤프 파일을 읽어 psql의 표준 입력으로 전달한다.

 

ERROR:  schema "gold" does not exist
ERROR:  relation "public.users" does not exist
ERROR:  relation "public.users" does not exist
ERROR:  relation "public.user_skill_scores" does not exist
ERROR:  relation "public.sessions" does not exist
ERROR:  relation "public.job_seekers" does not exist
ERROR:  relation "public.company_members" does not exist
ERROR:  relation "public.companies" does not exist
ERROR:  relation "public.companies" does not exist
ERROR:  schema "meta" does not exist
ERROR:  schema "bronze" does not exist

1. Azure/클라우드 전용 객체 오류

이 오류는 Docker 환경의 PostgreSQL에는 존재하지 않는 Azure 클라우드 환경의 특수 기능 때문에 발생합니다. 이는 기능상 무시해도 됩니다.

  • 오류 유형: ERROR: extension "pgaadauth" does not exist, ERROR: extension "azure" does not exist, ERROR: schema "cron" does not exist, ERROR: publication "postgres_fabricpublication" does not exist, ERROR: role "azure_pg_admin" does not exist 등
  • 원인: Azure PostgreSQL은 관리형 서비스이므로 인증(pgaadauth), 로깅(pg_cron), Azure 연동(azure), 논리 복제(publication) 등을 위한 **전용 확장(Extension)**과 **내장 역할(Role)**을 사용합니다. 도커 컨테이너의 표준 PostgreSQL 이미지에는 이들이 포함되어 있지 않아 생성/삭제 명령에서 실패합니다.
  • 해결: 이는 무시해도 됩니다. 애플리케이션의 핵심 데이터(테이블, 데이터) 복원에는 영향을 미치지 않습니다.

2. 객체 존재/부재 오류 (순서 문제)

이 오류는 덤프 파일 실행 순서와 관련된 충돌이며, 덤프에 -c (CLEAN) 옵션을 사용했을 때 자주 발생합니다.

  • 오류 유형: ERROR: relation "public.sessions" does not exist, ERROR: index "ux_user_skill" does not exist
  • 원인: 덤프 파일은 객체를 생성하기 전에 DROP 명령을 넣습니다. 하지만 만약 이전에 복원 시도가 실패했거나, 대상 객체가 애초에 없는 상태라면 DROP 명령이 실패합니다. (예: "야, 세션 테이블 삭제해!" -> "세션 테이블이 없는데요?")
  • 해결: 이 오류 역시 무시해도 됩니다. DROP 명령은 실패했지만, 다음 단계의 CREATE 명령은 성공적으로 실행됩니다.

3. 역할(Role) 및 권한 오류

데이터 복원 및 객체 생성에는 성공했으나, 권한 설정에서 실패한 오류입니다.

  • 오류 유형: ERROR: role "Drawbridge" does not exist, ERROR: role "azure_pg_admin" does not exist
  • 원인: Azure DB는 덤프된 객체(테이블, 스키마 등)에 대해 Drawbridge 같은 특정 역할을 소유자(Owner)로 지정했거나 해당 역할에게 GRANT 권한을 부여하도록 덤프 파일에 기록되어 있습니다. 하지만 Docker 컨테이너의 PostgreSQL에는 Drawbridge라는 역할이 존재하지 않습니다.
  • 해결: 데이터베이스 객체의 소유자 변경이나 권한 부여는 실패했지만, 테이블과 데이터 자체는 생성되었습니다.

 

참고로 등록을 해야 DB가 보인다.

Host name/address database-postgres

Port 5432

Maintenance database admin

Username admin

Password Docker Compose 파일에 설정한 비밀번호

 

docker exec -it database-postgres psql -U admin -d admin -c "CREATE SCHEMA bronze;"
docker exec -it database-postgres psql -U admin -d admin -c "CREATE SCHEMA silver;"
docker exec -it database-postgres psql -U admin -d admin -c "CREATE SCHEMA gold;"

흠 오류가 발생

 

아니네

 

ㅇㅇ

 

4-1. Migration with Docker Compose

# 호스트 VM에 새로운 초기화 디렉터리 생성 (예: my-init-scripts)
mkdir -p ./database/my-init-scripts

# 덤프 파일을 이 디렉터리로 이동
mv azure_db_dump.sql ./database/my-init-scripts/

아니면 시작 시 복원하는 방법도 존재한다.

 

# docker-compose.yml 파일 수정 예시

services:
  database-postgres:
    image: postgres:latest
    # ... (기존 환경 변수 생략)
    volumes:
      # 1. 실제 데이터가 저장되는 볼륨 (기존 설정)
      - ./database/postgres:/var/lib/postgresql/data
      
      # 2. 초기화 스크립트 볼륨 추가 (새로운 설정)
      #    컨테이너가 처음 시작될 때, 이 디렉터리 내부의 .sql 파일을 자동으로 실행합니다.
      - ./database/my-init-scripts:/docker-entrypoint-initdb.d
    # ...

 

컨테이너 시작: Docker Compose가 database-postgres 컨테이너를 시작합니다.

초기화 스크립트 감지: PostgreSQL 공식 이미지의 시작 스크립트는 /docker-entrypoint-initdb.d 디렉터리(호스트의 ./database/my-init-scripts에 연결됨)를 확인합니다.

덤프 파일 실행: 이 디렉터리 안에 있는 azure_db_dump.sql 파일이 감지되면, PostgreSQL 서버가 초기화된 직후 자동으로 해당 SQL 스크립트가 실행됩니다.

이 때, 덤프 파일에 포함된 모든 CREATE TABLE, INSERT 명령이 실행되어 데이터가 복원됩니다.

서비스 정상 실행: 복원이 완료된 후 PostgreSQL 서비스가 완전히 실행됩니다.

 

# 1. 컨테이너 종료 및 삭제
docker compose down
# 2. 기존 데이터 볼륨 삭제 (기존 데이터가 있다면 이 단계에서 삭제됩니다.)
sudo rm -rf ./database/postgres 
# 3. 새로운 설정으로 컨테이너 다시 실행 (자동 복원 스크립트 실행)
docker compose up -d

복원 시 주의사항 (재실행 문제)

이 자동 복원 방식은 **컨테이너가 처음 실행될 때(즉, /var/lib/postgresql/data가 비어있을 때)**만 작동합니다.

만약 이미 PostgreSQL 컨테이너를 실행하여 데이터(./database/postgres 폴더)가 생성된 상태라면, 자동 복원 스크립트는 다시 실행되지 않습니다.

해결책: 자동 복원 기능을 사용하려면, 반드시 PostgreSQL 데이터 볼륨을 삭제하고 컨테이너를 다시 실행해야 합니다.

 

5. CosmosDB Migration

 

Releases · AzureCosmosDB/data-migration-desktop-tool

Contribute to AzureCosmosDB/data-migration-desktop-tool development by creating an account on GitHub.

github.com

ㅇㅇ

 

{
  "Source": "Cosmos-nosql",
  "Sink": "JSON",

  "SourceSettings": {
    "ConnectionString": "AccountEndpoint=https://jobskill-cosmosdb.documents.azure.com:443/;AccountKey=<PRIMARY_KEY>;",
    "Database": "jumpit_skills",
    "EnableCrossPartition": true,
    "DegreeOfParallelism": 8,
    "PageSize": 1000
  },

  "SinkSettings": {
    "JsonFormat": "NewLineDelimited",
    "Compression": "None",
    "Append": false
  },

  "Operations": [
    {
      "SourceSettings": { "Container": "skill_info", "Query": "SELECT * FROM c" },
      "SinkSettings":   { "FilePath": "<YOUR_PATH>\\skill_info.ndjson" }
    },
    {
      "SourceSettings": { "Container": "skill_answers", "Query": "SELECT * FROM c" },
      "SinkSettings":   { "FilePath": "<YOUR_PATH>\\skill_answers.ndjson" }
    },
    {
      "SourceSettings": { "Container": "skill_questions", "Query": "SELECT * FROM c" },
      "SinkSettings":   { "FilePath": "<YOUR_PATH>\\skill_questions.ndjson" }
    }
  ]
}

ㅇㅇ

 

ㅇㅇ

 

  json ndjson
설명 JavaScript Object Notation Newline Delimited JSON
구조 전체 파일을 하나의 JSON 배열([ ])로 감싸고,
그 안에 여러 객체를 쉼표로 구분하여 저장한다.
각 줄이 하나의 완전하고 독립적인 JSON 객체다.
객체 사이를 줄 바꿈 문자(Newline \n)으로 구분한다.
쉼표나 대괄호가 없다.
예시 json [
    {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}
]
json {"id": 1, "name": "Alice"} {"id": 2, "name:" Bob"}
유효성 파일 전체가 하나의 유효한 JSON 구문 각 줄이 유효한 JSON 객체
처리 방식 ㆍ 파일을 열어 유효하다고 판단하기 위해서는 파일의 시작과 끝을 모두 읽어야 한다.
ㆍ 파일이 크면 전체를 메모리에 로드해야 하므로 메모리 부하가 크다.
ㆍ 줄 단위로 읽고 처리할 수 있어, 파일 전체를 메모리에 로드할 필요가 없다.
ㆍ 스트리밍(실시간 처리) 데이터 처리에 매우 효율적이다.
용도 웹 API 응답, 설정 파일 등 비교적 소중규모 데이터 전송 대규모 로그 파일, 데이터 덤프(Cosmos DB DMT), 대규모 데이터 파이프라인 등 용량이 매우 큰 데이터 처리

ㅇㅇ

 

#!/bin/bash

# 1. MongoDB 서버가 완전히 준비될 때까지 대기
# 기본적으로 MongoDB의 포트는 27017입니다.
until nc -z localhost 27017; do
  echo "MongoDB is unavailable - sleeping"
  sleep 1
done

echo "MongoDB is up - executing data import"

# 데이터베이스 이름 정의
DB_NAME="jumpit_skills"

# 2. 각 NDJSON 파일을 MongoDB에 가져오기 (Import)

# skill_info 컬렉션 복구
mongoimport --host localhost --db $DB_NAME --collection skill_info --file /docker-entrypoint-initdb.d/init-mongo-data/skill_info.ndjson --type json --upsert

# skill_answers 컬렉션 복구
mongoimport --host localhost --db $DB_NAME --collection skill_answers --file /docker-entrypoint-initdb.d/init-mongo-data/skill_answers.ndjson --type json --upsert

# skill_questions 컬렉션 복구
mongoimport --host localhost --db $DB_NAME --collection skill_questions --file /docker-entrypoint-initdb.d/init-mongo-data/skill_questions.ndjson --type json --upsert

echo "MongoDB data import completed successfully."

PostgreSQL을 Docker 환경에서 사용하실 때, init-scripts 폴더에 .sql 파일을 넣어두면 별도의 대기 명령어 없이도 데이터가 자동으로 로드되는 것은 PostgreSQL 공식 Docker 이미지의 엔트리포인트(Entrypoint) 설계 덕분입니다.

하지만 MongoDB의 mongoimport 방식을 쉘 스크립트로 구현할 때는 서버 대기 로직이 필요하거나 최소한 강력히 권장됩니다.

 

1. PostgreSQL이 대기가 필요 없는 이유

PostgreSQL 공식 Docker 이미지는 컨테이너의 Entrypoint 스크립트가 다음과 같이 설계되어 있습니다.

  • 내부 루프 대기: Entrypoint 스크립트 자체에 PostgreSQL 서버가 완전히 시작되고 클라이언트 연결을 받을 준비가 될 때까지 기다리는 내부 로직이 포함되어 있습니다.
  • 직접 실행: 서버가 준비되면, Entrypoint 스크립트는 /docker-entrypoint-initdb.d/ 폴더에 있는 모든 .sql, .sh 파일을 직접 실행합니다. 이 파일들은 서버 프로세스와 동일한 환경에서 실행되므로, 파일 내용이 안전하게 실행됩니다.
  • 결론: 사용자가 별도의 sleep이나 until 명령어를 스크립트에 넣지 않아도 이미 Docker 이미지 레벨에서 안전장치가 되어 있습니다.

2. MongoDB가 대기가 필요한 이유

MongoDB의 경우는 PostgreSQL과 상황이 다릅니다.

  1. 동시성 문제 (Race Condition):
    • docker-compose up 명령은 MongoDB 서버 프로세스(mongod)를 시작시킵니다.
    • 이 서버 프로세스가 완전히 메모리를 초기화하고 포트를 열어 클라이언트(mongoimport)의 요청을 받을 준비가 되는 데는 약간의 시간이 걸립니다.
    • 쉘 스크립트에서 서버 시작 직후 바로 mongoimport 명령을 실행하면, 서버가 아직 준비되지 않은 상태일 수 있습니다. 이 경우 mongoimport는 "Connection refused" 오류를 내고 실패합니다.
  2. mongoimport의 본질:
    • mongoimport는 외부 클라이언트 프로그램입니다. 이는 PostgreSQL의 Entrypoint가 내부적으로 SQL 파일을 실행하는 것과 달리, 네트워크 연결을 통해 서버에 접근해야 하는 클라이언트 명령어입니다.
    • 네트워크 연결을 시도하므로, 연결 대상인 MongoDB 서버가 준비될 때까지 기다리는 코드가 필요합니다.

 

scp -i <SSH-KEY> <SEND FILE1> <SEND FILE2> <SEND FILE3> ubuntu@<VM-IP>:<VM-PATH>

보내야 하는 ndjson 파일이 총 3개라서 위와 같은 명령으로 dump 파일을 보낸다.

skill_answers.ndjson(약 1.5GB), skill_info.ndjson(58KB), skill_questions.ndjson(581KB)의 파일이다.

첫 번째가 용량이 커서 그런지 전부 합쳐서 약 12분정도 걸렸다.

 

6. Dockefile

ㅇㅇ

 

 

 

 

참고한 웹 사이트 : tistory-inpa-yaml

참고한 웹 사이트 : IBM-topics-yaml

참고한 웹 사이트 : https://bentist.tistory.com/75

참고한 웹 사이트 : https://cozy-dev-area.tistory.com/75

참고한 웹 사이트 : tistory-docker-daemon

참고한 웹 사이트 : forum-docker-pgadmin-connect

참고한 웹 사이트 : reddit-mongoexpress-connect-missing

참고한 웹 사이트 : stackoverflow-mongo-express-service-browser-require-user

참고한 문서 자료 : https://docs.docker.com/compose/

참고한 문서 자료 : https://www.pgadmin.org/docs/

참고한 문서 자료 : https://hub.docker.com/r/dpage/pgadmin4

참고한 문서 자료 : https://airflow.apache.org/docs/docker-stack/build-arg-ref.html

참고한 문서 자료 : https://hub.docker.com/_/mongo-express

참고한 문서 자료 : pgadmin-docs-container-deployment-chown

참고한 웹 사이트 : https://dba.stackexchange.com/questions/68077/pgadmin-what-is-the-maintenance-db

참고한 웹 사이트 : stackoverflow-how-change-maintenance-database-postgres

참고한 문서 자료 : medium-docker-compose-db-세팅-자동화

참고한 웹 사이트 : https://github.com/AzureCosmosDB/data-migration-desktop-tool/releases

마지막 줄

저번에 PAYG로 생성한 OCI VM이 있다.

여기에 접속해서 Docker도 설치하고, Web도 돌리는 환경으로 설정해본다.

 

1. PuTTY 접속 (1, 2번 중 택 1)

우선 VM을 만들었으니 여기에 접속해야 한다.

접속하는 방법은 PuTTY가 대표적이다.

put(전송하다) + tty(teletype, 터미널을 뜻하는 유닉스 용어) 합성어

 

 



ssh-key-YYYY-MM-DD.key의 개인키와  ssh-key-YYYY-MM-DD.key.pub의 공용키를 받았을 것이다.

 

Key comment (키 설명)

역할: 키를 식별하기 위한 주석(Comment) 또는 이름표 역할

설정: 보통 사용자 이름과 키 생성 날짜(rsa-key-2025-11-27 등)가 자동으로 입력됩니다.

사용: 여러 개의 SSH 키를 사용할 때, 이 키가 어떤 서버(예: oracle-arm-vm-tokyo)에 사용되는 키인지 한눈에 알 수 있다.

 

Key passphrase (키 암호/비밀번호)

역할: 개인 키(Private Key) 자체를 암호화하여 2차 보안 장치를 추가하는 역할입니다.

설정: 사용자가 설정하는 비밀번호입니다. (일반 계정 비밀번호와는 별개)

사용: PuTTY로 VM에 접속할 때, 개인 키 파일을 등록한 후 추가로 이 암호를 입력해야만 키가 해독되어 접속이 가능합니다.

 

생성한 키 등록

 

서버 저장 후 접속

 

정보 입력 후 접속하기

 

OS 이미지 PuTTY login as 값
Oracle Linux opc
Ubuntu ubuntu
Red Hat (RHEL) cloud-user 혹은 ec2-user
CentOS centos
AlmaLinux cloud-user
Rocky Linux cloud-user
Windows opc 또는 Administrator
Marketplace 제공하는 업체마다 상이, 문서 확인 필요
My Images 이미지 생성 시 지정한 이름

이미지 별 login as 값은 위와 같으니 참고하면 된다.

 

Linux 명령어로 확인 가능

exit이나 logout으로 종료 가능

 

2. Window CMD로 접속 (1, 2번 중 택 1)

ssh -i PEM_ABS_PATH IMAGE@VM_PUBLIC_IP

위와 같은 명령어로 접속 가능

 

The authenticity of host 'YOUR_ORACLE_PUBLIC_IP (YOUR_ORACLE_PUBLIC_IP)' can't be established.
ED25519 key fingerprint is SHA256:CcQL2uRBFTkw7YOGdZWQzgwBrceltb56iqcm8rv9i20.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

ㅇㅇ

 

Bad permissions. Try removing permissions for user: BUILTIN\\Users (S-1-5-32-545) on file YOUR_PRIVATE_KEY.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions for 'YOUR_PRIVATE_KEY' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "YOUR_PRIVATE_KEY": bad permissions
IMAGE@VM_PUBLIC_IP: Permission denied (publickey).

ㅇㅇ

 

# 상속을 비활성화하고, 파일에 정의된 모든 기존 사용자 권한을 제거하는 명령어
icacls "<YOUR_PRIVATE_KEY_ABS_PATH>" /inheritance:r

# 파일을 소유한 현재 사용자에게만 읽기(R) 권한을 명시적으로 부여하는 명령어
icacls "<YOUR_PRIVATE_KEY_ABS_PATH>" /grant:r "%USERNAME%":R

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

3. Docker

# 패키지 업데이트
sudo apt update

# 필수 패키지 설치
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y

# Docker 공식 GPG 키 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# Docker Stable 저장소 설정
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Docker 엔진 설치
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io -y

# Docker Compose V2는 Docker CLI에 통합되어 docker compose 명령으로 사용
# Docker Compose 설치
sudo apt install docker-compose-plugin -y

# 설치 버전 정보 확인
docker compose version

실행해야 하는 전체 명령어

 

# 패키지 업데이트
sudo apt update

로컬 시스템에 저장된 패키지 저장소(repository) 목록 및 인덱스 파일을 최신 정보로 갱신한다.

apt install 전에 필수로 실행해야 하는 명령어

 

# 필수 패키지 설치
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y

sudo api intall ... -y는 필요한 도구를 설치하는 명령어

Docker 저장소를 추가하고 https 통신을 수행하는 데 필요한 기본 패키지들을 설치한다.

-y 옵션은 설치 시 나오는 확인 질문에 자동으로 Yes라고 답하여 설치 과정을 자동화한다.

 

apt-transport-https는 HTTPS를 지원하는 옵션

apt가 HTTPS 프로토콜을 사용하여 저장소에 접근할 수 있게 한다.

 

ca-certifiactes는 인증서 검증을 하는 옵션

서버의 SSL/TLS 인증서의 유효성을 검증하는 데 필요한 CA 인증서를 제공하여 보안 통신을 진행한다.

 

curl은 파일 전송 도구다.

지정된 URL에서 데이터를 다운로드/업로드한다. 여기서는 Docker의 GPG 키를 다운로드할 때 사용한다.

 

software-properties-common는 저장소 관리 도구다.

리눅스 저장소(PPA 등)을 추가하거나 제거할 때 필요한 유틸리티를 제공한다.

 

혹시라도 sudo apt install 명령을 실행하고 위와 같은 화면이 나올 수 있다.

이는 Ubuntu 시스템에서 패키지 업데이트(설치)를 수행한 후, 커널 재시작(업그레이드)이 필요하다는 알림이다.

 

networkd-dispatcher.service(네트워크 설정 변경 감시 및 이벤트 발생 시 스크립트를 실행하는 서비스)와

unattended-upgrades.service(백그라운드에서 보안 패치 등을 자동으로 처리하는 서비스)가 재시작이 필요하다.

서비스를 선택하고 재시작을 하면 이후 install 할 때 같은 알림이 안 뜬다.

 

# Docker 공식 GPG 키 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

curl -fsSL https://...gpg는 GPG 키 다운로드하려는 명령어다.

└ GnuPG(GNU Privacy Guard)에서 사용되는 암호화 및 디지털 서명 도구

curl 명령을 사용하여 Docker 공식 다운로드 링크에서 공개 GPG 키를 다운로드한다.

이 키는 다운로드할 패키지가 docker에 의해 서명되었음을 확인하는 데 사용한다.

 

-f는 fail 옵션으로 HTTP 오류 발생 시 자동 종료한다.

-s는 silent 옵션으로 진행 표시줄이나 오류를 출력하지 않는다.

-S는 show error 옵션으로 -s와 함께 사용하며 오류는 표시한다.

-L는 location 옵션으로 서버가 redirect할 경우, 해당 위치로 따라간다.

 

| 는 Linux에서 사용하는 파이프 명령이다.

A | B의 경우 A의 결과를 B로 건네거나, A 이후 B를 이어서 실행하라는 명령이다.

 

sudo는 최고 관리자 권한 명령어를 실행한다.

시스템의 보안 관련 키(GPG) 파일을 저장해야 하므로 관리자(root) 권한으로 명령을 실행한다.

 

gpg는 암호화 및 서명을 담당하는 프로그램이다.

여기서는 키 포맷을 다루는 데 사용한다.

 

--dearmor는 키 변환 옵션이다.

다운로드한 GPG 키는 보통 텍스트 기반의 ASCII Armor 포맷이다.

이 옵션으로 해당 키를 Linux 시스템이 사용하는 바이너리 형태의 키링 포맷으로 변환한다.

 

-o /usr/shar/keyrings/ ... 로 출력 파일 위치를 지정한다.

docker-archive-keyring.gpg라는 이름으로 변환한 GPG 키를 지정한 디렉터리에 저장한다.

이 디렉터리는 시스템이 신뢰하는 저장소 키를 보관하는 표준 위치다.

 

# Docker Stable 저장소 설정
# 다운로드할 Docker 패키지의 무결성과 신뢰성을 보장하고, 시스템에 Docker 공식 다운로드 위치를 지정
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

echo "deb ... stable"은 저장소 경로를 지정하는 명령어다.

Docker 패키지가 위치한 공식 repository의 경로를 설정하는 문자열이다.

 

deb는 Debian 패키지 약어다. 즉 .deb 형식의 패지키 저장소임을 나타낸다.

[arch=...]는 아키텍처를 지정한다. 시스템의 CPU 아키텍쳐(amd64, arm64 등)에 맞는 Docker 버전을 다운로드한다.

$(dpkg --print-architecture)는 현재 아키텍쳐를 자동으로 출력한다.

signed-by=...로 저장한 GPG 서명 키를 지정하여 보안을 강화한다.

https://...stable은 저장소 URL로 Docker 공식 안정(stable) 버전 저장소 주소다.

$(lsb_release -cs)는 현재 Ubuntu/Debian 시스템의 코드명(jammy, focal 등)을 자동으로 출력한다.

 

tee는 표준 입력 내용을 받아서

1) 지정된 파일에 내용을 기록하고, 2) 그 내용을 표준 출력으로 동시에 전달해주는 명령어다.

echo의 출력을 바로 redirection(>)으로 쓰려고 하면 sudo 권한이 적용되지 않는다.

tee는 sudo 권한으로 실행하면서 파일 쓰기를 안전하게 수행할 수 있다.

 

/etc/apt/sources.list.d 폴더는 APT 패키지 관리자가 추가 저장소 목록을 보관하는 표준 위치다.

/docker.list는 새로 추가할 Docker 저장소 정보가 기록될 파일이다.

>를 통해 tee 명령의 표준 출력을 다른 곳으로 보낸다.

/dev/null는 Linux에서 일종의 블랙홀 역할을 한다. tee의 표준 출력으로 내보내는 불필요 메시지가 화면에 출력하지 않게 한다.

여기서 없애는 메시지는 저장소 설정 문자열이다.

 

# Docker 엔진 설치
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io -y

Docker 저장소 패키지 목록을 시스템이 인식하도록 새롭게 update한다.

sudo apt install로 Docker를 설치한다.

docker-ce는 Docker의 기본 엔진(데몬)이다. ce는 Community Edition의 약어다.

docker-ce-cli는 Docker 데몬과 통신하는 명령어다.

containerd.io는 컨테이너의 생명주기를 관리하는 CNCF의 핵심 컨테이너 런타임이다.

 

# Docker Compose V2는 Docker CLI에 통합되어 docker compose 명령으로 사용
# Docker Compose 설치
sudo apt install docker-compose-plugin -y

Docker compose 명령어를 사용할 수 있게 하는 compose 플러그인을 설치한다.

 

# 설치 버전 정보 확인
docker compose version

이후 정상 설치 되었는지 버전을 확인한다.

 

정상적으로 VM에 Docker를 설치했다.

 

4. Docker Compose Yaml Copy

# 로컬 컴퓨터의 CLI (터미널/PowerShell)에서 실행
# 이전에 안내된 스크립트를 사용하여 파일을 /home/opc/my-service/ 로 전송합니다.
scp -i /path/to/key.pem docker-compose.yml .env opc@<VM_Public_IP>:/home/opc/my-service/
scp -r -i /path/to/key.pem webapp airflow opc@<VM_Public_IP>:/home/opc/my-service/

ㅇㅇ

 

# 상속을 비활성화하고, 파일에 정의된 모든 기존 사용자 권한을 제거하는 명령어
icacls "<YOUR_PRIVATE_KEY_ABS_PATH>" /inheritance:r

ㅇㅇ

 

# 파일을 소유한 현재 사용자에게만 읽기(R) 권한을 명시적으로 부여하는 명령어
icacls "<YOUR_PRIVATE_KEY_ABS_PATH>" /grant:r "%USERNAME%":R

ㅇㅇ

 

ㅇㅇ

 

# docker-compose.yml 파일이 위치한 디렉터리에서 실행
docker compose up --build -d

ㅇㅇ

 

혹시라도 이런 오류가 발생한다면, 권한 설정을 해줘야 한다.

이는 Docker 데몬과 통신할 수 있는 권한이 없기 때문에 발생하는 에러 사항이다.

 

# 사용자를 docker 그룹에 추가
sudo usermod -aG docker $USER

# 권한 변경 시, 세션을 종료 후 재시작 필요
exit

관리자 권한(sudo)으로 명령어를 실행한다.

usermod는 사용자 정보를 수정하는 명령어고, -aG docker는 사용자를 docker 그룹(G)에 추가(a)하는 옵션이다.

권한이 바뀌었다면 반드시 세션을 재시작해줘야 적용된다.

 

# Docker 권한이 있는 테스트
docker run hello-world

# 사용자를 시스템의 특정 그룹에서 제거하는 명령어
sudo gpasswd -d $USER docker

참고 사항, 테스트 코드와 사용자 제거하는 명령어

 

블로그ㅇ를 참고해서 

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

Stateless (상태 비저장) 설명 보안 규칙에서 Stateless를 켜고 끌 수 있게 되어 있는데, 이는 해당 규칙이 **트래픽의 상태(State)**를 추적할지 여부를 결정하는 중요한 옵션입니다. Stateful (상태 저장) 규칙 (기본값, 권장) 대부분의 방화벽 규칙은 Stateful입니다. 동작: 방화벽이 특정 세션의 상태를 기억합니다. 인그레스(들어오는) 규칙 설정 시: 만약 외부에서 VM으로 들어오는(Ingress) 트래픽을 허용하면, 그 트래픽에 대한 **응답(Reply)으로 나가는 트래픽(Egress)**은 자동으로 허용됩니다. 장점: 관리자가 나가는 규칙(Egress Rule)을 따로 설정할 필요가 없어 규칙 관리가 단순하고 효율적입니다. Stateless (상태 비저장) 규칙 (Stateless) 이미지에서 토글을 켜지 않은 상태가 기본적으로 Stateful입니다. 만약 Stateless를 활성화(토글 켬)하면 다음과 같이 작동합니다. 동작: 방화벽이 트래픽의 세션 상태를 기억하지 않고, 패킷 하나하나를 규칙에 따라 검사합니다. 양방향 규칙 필요: 인그레스 규칙을 허용했더라도, VM이 외부로 응답(Egress)을 보내려면 반드시 별도의 Egress 규칙을 설정하여 응답 트래픽을 허용해야 합니다. 장점: 세션 추적에 필요한 리소스를 절약하므로 매우 높은 성능이 요구되거나 단방향 통신만 필요한 특정 고급 환경에서 사용됩니다.

 

Source Port Range (소스 포트 범위) 상세 설명 Source Port Range는 클라우드 방화벽 규칙(특히 OCI의 보안 목록)에서 트래픽을 시작하는(보내는) 장치가 사용하는 포트 번호를 지정하는 항목입니다. 이 항목은 트래픽이 들어오는 포트를 지정하는 Destination Port Range와 역할이 정반대이며, 특히 Stateless(상태 비저장) 규칙을 다룰 때 그 중요성이 커집니다. 1. Source Port Range의 역할 Source Port는 통신을 시작하는 클라이언트 장치 (사용자의 웹 브라우저, Airflow 스케줄러, 다른 서버 등)가 임의로 할당하는 포트입니다. 클라이언트 측 포트: 웹 브라우저나 애플리케이션이 외부 서버(OCI VM)와 통신을 시작할 때, 자신의 로컬 포트 중에서 임시로 할당받는 포트 번호입니다. 범위: 이 포트 번호는 일반적으로 $\text{1024}$ 이상의 포트 중에서 무작위로 할당됩니다 (에페머럴 포트, Ephemeral Port). 2. 왜 Source Port를 따로 지정하지 않는가? 일반적인 Stateful (상태 저장) 인그레스(Ingress, 들어오는) 규칙을 설정할 때, Source Port Range는 대부분 비워두거나 All로 설정합니다. 그 이유는 다음과 같습니다. 임의성: 클라이언트가 어떤 포트를 사용할지 미리 알 수 없으며, 요청이 들어올 때마다 포트 번호가 달라집니다. 따라서 특정 Source Port를 지정하면 대부분의 합법적인 트래픽이 차단됩니다. 보안: 방화벽의 주 목적은 **Destination Port (도착지 포트)**를 막아 서버의 특정 서비스 접근을 통제하는 것이지, 클라이언트가 사용하는 임시 포트를 제한하는 것이 아닙니다. 예시: 당신의 PC에서 VM의 $\text{8080}$ 포트(Destination Port)로 Airflow 접속 요청을 보낼 때, 당신의 PC는 $\text{45678}$번 포트(Source Port)를 사용할 수도 있고, 다음 번에는 $\text{59123}$번 포트를 사용할 수도 있습니다. 3. Source Port Range를 지정해야 하는 특별한 경우 Source Port Range를 특정 값으로 제한해야 하는 경우는 매우 드물며, 주로 다음과 같은 상황에서 발생합니다. 특정 서버만 허용: 만약 당신의 특정 서버 A만 OCI VM의 $\text{8080}$ 포트에 접속하도록 허용하고 싶다면, Source CIDR에 서버 A의 IP를 지정하고, Source Port Range는 비워둡니다. (Source Port는 대부분의 경우 중요하지 않습니다.) Stateless 규칙 사용 시 응답 트래픽 제어: Stateful 방화벽은 인그레스 규칙만 설정하면 응답(Egress)을 자동으로 허용합니다. 하지만 Stateless 방화벽을 사용한다면, 응답 트래픽(VM $\to$ 외부)을 위한 Egress 규칙을 설정해야 합니다. 이때 Egress 규칙의 Destination Port는 외부 클라이언트의 Source Port가 되므로, 이 값이 $\text{1024-65535}$와 같은 넓은 범위로 설정될 수 있습니다.

 

이러고 새로고침하면 정상적으로 접속 가능

 

Services Port Number Purpose Container Image
Nginx Proxy Manager 80
81
443
일반 HTTP 접속 및 Let's Encrypt 인증
NPM 관리자 GUI 접속
HTTPS 보안 접속
 
Airflow Webserver 8080 Airflow 웹 GUI 관리 도구  
pgAdmin 5050 PostgreSQL 웹 GUI 관리 도구  
Mongo Express 8081 MongoDB 웹 GUI 관리 도구  
웹 애플리케이션 (Node.js) 3000 [내부 전용] NPM 접근 실제 웹앱 포트  
PostgreSQL 5432 [내부 전용] DB 포트  
MongoDB 27017 [내부 전용] DB 포트  
Redis 6379 캐싱, 세션 관리, Airflow Broker (Celery) redis
RabbitMQ 5672 메시지 브로커 (Airflow Celery Backend) rabbitmq
MinIO 9000 S3 호환 오브젝트 스토리지 (Airflow XCom 백엔드) minio/minio
Grafana 3000 시각화 대시보드 및 모니터링 grafana/grafana
Prometheus 9090 시계열 데이터(Metrics) 수집 prom/prometheus
Jupyter/Notebook 8888 데이터 분석 및 실험 환경 jupyter/notebook
Jenkins 8080 CI/CD 자동화 서버 jenkins/jenkins
MySQL 3306 관계형 데이터베이스 mysql

그밖에도 다양한 서비스 포트들 공부

 

pgAdmin 웹 접속을 위한 방화벽

 

 

 

참고한 웹 사이트 : https://monkeybusiness.tistory.com/649

참고한 문서 자료 : https://www.ssh.com/academy/ssh/scp

참고한 문서 자료 : https://www.ssh.com/academy/ssh/public-key-authentication

마지막 줄

 

GitHub - miny-genie/MS-DT-SCHOOL-3rd-project-DrawBridge: Microsoft Data School 3차 프로젝트, Drawbridge 깃허브입니다.

Microsoft Data School 3차 프로젝트, Drawbridge 깃허브입니다. Contribute to miny-genie/MS-DT-SCHOOL-3rd-project-DrawBridge development by creating an account on GitHub.

github.com

오늘 해볼 것은 service migration이다.

 

 

클라우드 서비스 무료 이용

Oracle Cloud Free Tier는 기업에게 무제한으로 사용할 수 있는 상시 무료 클라우드 서비스를 제공합니다.

www.oracle.com

그중에 오라클의 프리 티어 클라우드 서비스를 사용해본다.

Oracle Cloud Infrastructure

 

ㅇㅇ

 

ㅇㅇ

 

  Oracle AWS GCP
무료 계층      
컴퓨팅      
스토리지      
데이터베이스      

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

1. Basic Information

ㅇㅇ

 

Name (인스턴스 이름): instance-20251123-1128 → Oracle이 자동으로 임시 이름을 넣어준 것.
원하면 마음대로 바꿀 수 있어요

2) Create in compartment
drawbridge (root)
→ 계정 안에서 VM이 속할 “폴더 같은 공간”입니다.
대부분 사람은 root compartment 하나만 씁니다.
→ 그냥 저 상태로 두면 돼요.
(특별히 여러 프로젝트 구분할 게 아니면 변경할 필요 없음)

 

Availability Domain(가용 도메인) = 같은 리전 안의 데이터센터 구역

Oracle Cloud 리전(region)은 보통 여러 개의 “AD(Availability Domain)”로 나뉘어 있고,
각 AD는 서로 분리된 데이터센터 구역이에요.

  • 예: 서울 리전 → AD1, AD2, AD3
  • 토론토 리전 → AD1만 있을 수도 있음(지역마다 AD 개수가 다름)

 

VM을 어떤 방식의 Capacity(자원 형태)로 생성할지 선택하는 단계

On-demand capacity 일반 VM. 안정적. 언제든 사용 가능
Preemptible capacity 값싼 대신 Oracle이 언제든 VM 뺏어갈 수 있음
Capacity reservation 특정 자원을 미리 예약(유료)
Dedicated host 전용 서버(비쌈, Free Tier 불가)
Compute cluster HPC(고성능 컴퓨팅)용

 

Cluster placement group(클러스터 배치 그룹) 설정

Cluster placement group은 고성능 컴퓨팅(HPC) 용도예요.

 

여러 VM을 아주 가까운 물리적 위치에 묶어서

RDMA 같은 초고속 네트워크로 연결하고 싶을 때 사용하는 기능입니다.

머신러닝 대규모 클러스터

계산 과학/시뮬레이션

HPC 노드

→ 일반적인 Web/DB/Airflow/DevOps 환경에서는 완전 불필요.

 

Fault Domain(장애 도메인) 을 선택하는 단계

✔ Fault Domain이 뭐냐?

하나의 Availability Domain(AD) 안에 있는
서버 랙 그룹(전원/네트워크 분리된 구역)

AD = 큰 데이터센터

FD(FAULT-DOMAIN) = 그 안의 작은 물리적 구역

 

즉,
동일 AD 안에서 완전히 분리된 하드웨어 그룹들이에요

✔ 개발자/개인 사용자에게 중요한가?

거의 전혀 중요하지 않습니다.
DB/웹 서버 1대만 돌리는 상황에서는 FD는 아무 의미 없음.

다만 이런 경우에는 신경 써야 함:

 

여러 VM을 띄워서 고가용성을 구성할 때

VM 여러 대를 서로 다른 Fault Domain에 배치하고 싶을 때

→ 하지만 지금은 VM 한 대 만들고 옮겨서 돌리는 상황이므로 그냥 기본값 OK.

 

VM에 어떤 OS 이미지를 설치할지 선택하는 단계(Image)

 

다양한 서버 이미지 제공

 

Canonical Ubuntu 22.04
Docker, PostgreSQL, Airflow, npmplus 전부 가장 안정적으로 돌아가는 OS

  • LTS(Long Term Support)라 2027년까지 안정 지원
  • Docker, Node.js, Postgres 설치 문서가 전부 Ubuntu 기준
  • Oracle ARM(A1)에서도 완벽 호환
  • Free Tier에서 성능 가장 잘 나옴
  • Airflow 공식 문서도 Ubuntu 기반 추천
    Canonical Ubuntu 22.04 기본 패키지 포함한 정상 Ubuntu 이거 추천 (지금 선택한 것)
    Canonical Ubuntu 22.04 Minimal 매우 최소 패키지만 포함 나중에 패키지 직접 더 설치해야 해서 귀찮음
    aarch64 ARM 전용 빌드 Free Tier A1은 ARM이지만 Full 버전은 자동으로 ARM 지원

 

Shape(인스턴스 타입) 선택 화면

 

🔹 1) Virtual machine (기본 선택된 것)

  • 우리가 원하는 옵션
  • 일반적인 VM
  • OS 설치하고 PostgreSQL/Docker/Airflow/Web 등을 돌릴 수 있음
  • Ampere A1 Flex 역시 이 카테고리 안에 있음

🔹 2) Bare metal machine (베어메탈)

  • 물리 서버를 통째로 빌리는 방식
  • Free Tier 불가
  • 너무 비싸고 필요 없음

Shape series (CPU 종류 선택 영역)

여기 4가지가 나와:

① AMD

  • x86(AMD) CPU
  • Free Tier에는 초라한 1GB RAM짜리 VM.Standard.E2.1.Micro만 가능
  • 너가 지금 보던 그 약한 인스턴스
  • Docker + PostgreSQL + Airflow 돌리기엔 불가능에 가까움

② Intel

  • x86(Intel) CPU
  • Free Tier 없음
  • 유료

③ Ampere(우리가 선택해야 하는 것)

  • ARM 기반 CPU
  • Oracle Cloud의 최고 가성비 Free Tier = Ampere A1.Flex
  • 최대 4 OCPU / 24GB RAM 항상 무료
  • PostgreSQL + Docker + Airflow + npmplus 올리려면 이게 유일하게 충분함

④ Specialty and previous generation

  • 오래된 또는 특수 용도(예: GPU, HPC)
  • Free Tier 없음
  • 고성능 머신 필요할 때만 씀

 

A1.Flex VM의 CPU(OCPU)와 RAM(메모리) 크기를 설정하는 단계

1) Shape name: VM.Standard.A1.Flex (Always Free-eligible)

  • Free Tier로 사용할 수 있는 ARM 기반 인스턴스
  • 네가 선택한 OS(우분투 22.04)와 완벽 호환
  • CPU/RAM을 자유롭게 조절할 수 있는 "Flex" 타입

2) Network bandwidth

  • 네트워크 속도 (Gbps)
  • OCPU 수에 비례해서 증가함
  • 기본 1 OCPU = 1Gbps
    → CPU 늘리면 더 빨라짐

3) Maximum VNICs

  • 네트워크 인터페이스 최대 개수 (VM에 붙일 수 있는 NIC 수)
  • 일반적인 서버 운영에서는 1개만 사용하면 충분함
    → 신경 안 써도 됨

4) Number of OCPUs

지금은 1개로 되어 있음.
하지만 Free Tier에서 최대 4 OCPU까지 무료로 사용할 수 있음.

 

5) Amount of memory (GB)

지금 6GB로 되어 있음.
OCPU 1개당 RAM 6GB 사용 가능.

즉:

  • 1 OCPU → 6GB RAM
  • 2 OCPU → 12GB RAM
  • 4 OCPU → 24GB RAM

Oracle Free Tier에서 완전 무료로 제공되는 최대 스펙

 

ㅇㅇ

 

2. Security

Oracle Cloud에서는 VM을 만들 때:

  • Shielded Instance(보안 강화 인스턴스)
  • Confidential Computing(기밀 컴퓨팅)

이 두 기능을 동시에 켤 수 없고,
또한 기밀 컴퓨팅은 특정 하드웨어에서만 지원

 

1. Shielded Instance (보안 강화 인스턴스)

Shielded Instance
“서버가 부팅될 때 해킹되거나 변조되는 것을 막아주는 보안 기능”입니다.

보호 항목

  • 서버 시작 과정(boot)이 악의적으로 바뀌지 않도록 보호
  • 변경된 커널, 악성 부트로더 등을 감지
  • TPM(Trusted Platform Module)을 사용한 부팅 무결성 검증

2. Confidential Computing (기밀 컴퓨팅)

Confidential Computing
“서버가 실행 중일 때 메모리 속 데이터를 암호화하여 외부에서 볼 수 없도록 하는 첨단 기술”입니다.

보호 항목

  • CPU가 연산 중인 데이터까지 암호화
  • 클라우드 제공자(Oracle, AWS 등)도 볼 수 없음
  • 하이퍼바이저나 OS가 해킹되어도 메모리 내용 보호

기술 구현 방법

AMD SEV(-SNP)

  • Intel TDX
  • Arm CCA

같은 하드웨어 기반 메모리 암호화 기술을 사용합니다.

 

1. Secure Boot (보안 부팅)

Secure Boot는 UEFI 기능으로,
신뢰할 수 있는 서명된 부트로더와 운영체제만 부팅되도록 막는 보안 기술입니다.

  • 악성 부트로더
  • 변조된 커널
  • 서명되지 않은 OS
    이 부팅되는 것을 차단합니다.

"이 PC는 정품 서명된 OS만 실행하겠다" 라고 체크하는 장치.

 

 

2. Measured Boot (측정된 부팅)

Measured Boot는 부팅 시 다음과 같은 부트 구성 요소들을 측정(hash)하여
TPM에 기록하는 기술입니다:

  • 부트로더
  • 드라이버
  • 운영체제 핵심 구성요소
  • 부팅 과정이 변조되었는지,
  • 해킹된 파일이 있는지

사후 검증이 가능

Bare metal 서버(물리 서버)는 Measured Boot를 지원하지 않는 경우가 많다(이미지에도 적혀 있음).

 

3. TPM (Trusted Platform Module)

TPM은
암호화 키와 부팅 측정값을 안전하게 저장하는 보안 칩입니다.

Measured Boot에서 기록된 값도 TPM에 저장됩니다.

TPM 기능을 통해 부팅·커널 변조 여부를 나중에 감지할 수 있

 

3. Networking

  • VNIC (Virtual Network Interface Card): 가상 랜카드입니다.
  • VCN (Virtual Cloud Network): 클라우드 내에 존재하는 '나만의 가상 사설망'입니다. 집으로 치면 인터넷 공유기와 내부 네트워크 전체를 의미합니다.
  • Subnet (서브넷): VCN이라는 큰 네트워크를 더 작게 쪼갠 단위입니다.

 

A. Primary VNIC (기본 VNIC)

VNIC name: 이 랜카드의 이름을 정하는 곳입니다.

설정 팁: 비워두셔도 자동으로 생성되므로 굳이 입력하지 않으셔도 됩니다.

 

B. Primary network (기본 네트워크 - VCN 설정)

이 VM이 소속될 거대한 네트워크 울타리(VCN)를 선택하는 단계입니다.

  • Select existing virtual cloud network (기존 VCN 선택): 이미 만들어둔 VCN이 있다면 선택합니다.
  • Create new virtual cloud network (새 VCN 생성): 새로운 네트워크 환경을 만듭니다.
  • Specify OCID: 특정 ID로 직접 지정하는 고급 옵션입니다.
  • Compartment: 이 네트워크 자원이 저장될 폴더(구획)입니다. 보통 (root)로 된 본인 계정 이름이 기본값입니다.

 

💡 중요: 처음 만드시는 경우라면 드롭박스에 선택할 VCN이 없을 수 있습니다. 이 경우 **Create new virtual cloud network**를 선택하세요. 오라클이 알아서 세팅해 줍니다.

 

 

C. Subnet (서브넷 설정)

VCN 내부의 세부 네트워크 구역을 설정합니다. 외부 접속 여부를 결정하는 가장 중요한 단계입니다.

  • Select existing subnet: 이미 만들어둔 서브넷을 선택합니다.
  • Create new public subnet (새 공용 서브넷 생성): 외부 인터넷에서 접속 가능한 서브넷을 만듭니다.
  • Create new private subnet (새 사설 서브넷 생성): (옵션에 따라 보일 수 있음) 외부와 단절된 내부 전용 서브넷을 만듭니다.

 

CIDR block

값: 10.0.0.0/24

설명: "이 서브넷 안에서 IP 주소를 몇 개나 사용할 것인가?"를 정하는 주소 범위

/24는 약 256개의 IP 주소를 쓸 수 있다는 뜻

 

경고 메시지 (Warning 박스)

"There are additional options available when you use the Networking pages..."

의미: "지금 여기서 만드는 건 '간편 모드'라서 복잡한 고급 설정은 못 합니다. 혹시 아주 정교한 네트워크 설계가 필요하면 여기서 만들지 말고 네트워크 메뉴 가서 따로 만드세요."라는 뜻입니다.

대처: 일반적인 웹 서버나 테스트 용도라면 무시하셔도 됩니다. 이 간편 모드로도 충분

 

개념 : 사설 IP (Private IP)

서버에는 보통 두 가지 IP 주소가 붙습니다.

1. 공인 IP (Public IP): 외부(우리 집, 카페, 회사)에서 이 서버로 찾아갈 때 쓰는 주소입니다. (예: 150.230.x.x)

2. 사설 IP (Private IP): 지금 설정하는 것입니다. 클라우드 내부에서 자기들끼리(예: 웹서버와 DB서버 간) 통신할 때 쓰는 '내부용' 주소. (예: 10.0.0.x)

우리가 SSH로 접속하거나 웹사이트를 띄울 때는 '공인 IP'를 쓰기 때문에, 이 '사설 IP'는 오라클이 정해주는 대로 써도 아무런 문제가 없다.

 

1. 화면 분석 및 해석

A. Public IPv4 address assignment (공인 IP 할당)

  • 현재 상태: 스위치가 회색(OFF)으로 꺼져 있고, 아래에 경고가 뜹니다.
  • 경고 내용: "You must select a public subnet to assign a public IPv4 address."
    • 해석: "공인 IP를 받으려면 '공용 서브넷(Public Subnet)'을 선택해야 합니다."
  • 의미: 현재 위쪽 설정(Networking)에서 선택된 서브넷이 **'사설(Private) 서브넷'**이거나, 혹은 설정이 꼬여서 시스템이 공용 서브넷으로 인식하지 못하고 있다는 뜻입니다.

B. IPv6 address assignment

  • 현재 상태: 꺼져 있음.
  • 설명: 차세대 인터넷 주소 체계입니다.
  • 추천: 초보자 단계에서는 복잡하기만 하므로 꺼진 상태(기본값) 그대로 두세요.

2. 해결 방법

이 경고를 없애고 정상적으로 접속하기 위해, 마우스 스크롤을 조금 위로 올려서 Subnet 항목을 다시 확인해야 합니다.

상황별 대처법:

  1. 위쪽에서 Create new public subnet을 선택한 상태라면:
    • 이 경우, 이 화면의 경고는 무시하셔도 됩니다.
    • "새로 만들겠다"고 설정했기 때문에, 실제 생성이 될 때 자동으로 공인 IP가 부여됩니다. (UI가 아직 생성이 안 된 상태라 경고를 띄우는 경우가 종종 있습니다.)
  2. 위쪽에서 Select existing subnet을 선택한 상태라면:
    • 선택한 서브넷 이름 옆에 **(Private)**라고 적혀 있는지 확인하세요.
    • 만약 Private이라면 잘못 선택한 것입니다. **(Public)**이라고 적힌 서브넷으로 바꿔주세요.
    • 바꾸면 이 화면의 스위치가 켜지거나, Automatically assign public IPv4 address 옵션을 켤 수 있게 바뀝니다. 반드시 켜져야 합니다.

 

1. DNS record (DNS 레코드)

이 서버에 내부 네트워크에서만 사용할 수 있는 **이름 주소(Private DNS)**를 할당할지 묻는 항목입니다.

  • Assign a private DNS record (기본값): 서버에 호스트 이름(Hostname)을 이용한 내부 주소를 할당합니다.
    • 예: 내부 다른 서버에서 이 VM에 접속할 때 IP 주소 대신 my-web-server와 같은 이름으로 접속할 수 있게 됩니다.
  • Do not assign a private DNS record: 내부 주소를 사용하지 않습니다.
항목 추천 설정 설명
Assign a private DNS record 선택 유지 (기본값) 나중에 내부에서 다른 서버와 연결할 가능성이 있다면 유용합니다.
Hostname 간단한 이름 입력 free-vm, test-server 등 원하는 이름을 영어 소문자로 입력하세요. (필수 입력 항목이 될 수 있습니다.)

 

 

2. Launch options (네트워킹 유형)

VM이 네트워크 트래픽을 처리하는 방식(성능 vs. 유연성)을 선택하는 항목입니다.

유형 설명 추천
Let Oracle... choose the best networking type VM 사양과 운영체제에 따라 OCI가 가장 적합한 타입을 자동으로 선택합니다. (기본값) 선택 유지
Paravirtualized networking 범용적인 네트워킹 방식입니다. 대부분의 애플리케이션에 적합합니다.  
Hardware-assisted (SR-IOV) networking 비디오 스트리밍, 클러스터링 등 초저지연이 필요한 경우 사용합니다. 유연성이 떨어집니다.

 

3. VCN tags / Subnet tags (태그)

리소스 관리 및 분류를 위한 메타데이터(꼬리표)를 붙이는 기능

 

ㅇㅇ

 

4. Storage

A. Specify a custom boot volume size and performance setting (사용자 지정 부트 볼륨 크기 및 성능)

  • 기본값 (Default): 기본 부트 볼륨 크기는 46.6 GB입니다.
  • 목적: 프리티어는 총 200GB의 블록 볼륨(하드 드라이브 공간)을 제공합니다. 이 200GB를 모두 사용하려면 이 옵션을 켜서 부트 볼륨의 크기를 최대치로 늘려야 합니다.
  • 💡 추천 설정: 스위치를 켜고 (On), 크기를 200 GB로 입력하세요.

참고: 오라클 프리티어 정책상 부트 볼륨과 블록 볼륨을 합쳐 총 200GB가 무료입니다. 부트 볼륨에 200GB를 모두 할당하면 별도의 블록 볼륨을 추가할 필요 없이 넉넉하게 사용할 수 있습니다.

 

B. Use in-transit encryption (전송 중 암호화 사용)

  • 현재 상태: 스위치가 켜져 있습니다 (On).
  • 설명: VM 인스턴스와 볼륨(하드 드라이브) 사이에서 데이터가 이동할 때 암호화하는 기능입니다. 보안을 강화합니다.
  • 💡 추천 설정: 켜진 상태 그대로 두세요. (보안을 위한 좋은 설정입니다.)

 

C. Encrypt this volume with a key that you manage (사용자가 관리하는 키로 볼륨 암호화)

  • 현재 상태: 스위치가 꺼져 있습니다.
  • 설명: 볼륨 암호화에 사용되는 키를 오라클이 아닌 사용자가 직접 관리하는 옵션입니다.
  • 💡 추천 설정: 꺼진 상태 그대로 두세요. (일반 사용자에게는 불필요하고, 키 관리의 복잡성만 높아집니다.)

 

부트 볼륨 크기 및 성능 설정 항목: 이 항목들은 VM의 하드 드라이브 크기와 성능을 결정합니다.

A. Boot volume size (GB)

  • 설명: VM의 주 저장 공간 크기입니다. 앞서 말씀드린 것처럼 프리티어는 총 200GB까지 무료로 사용할 수 있습니다.
  • 💡 추천 설정: 제공된 이미지에는 50으로 되어 있지만, 프리티어의 최대 용량을 사용하려면 200으로 변경하세요.

 

B. Boot volume performance (VPU)

  • VPU (Volume Performance Units): 볼륨의 성능을 나타내는 단위입니다. VPU가 높을수록 IOPS (Input/Output Operations Per Second), 즉 초당 입출력 횟수가 높아져 디스크 처리 속도가 빨라집니다.
  • 설명: 이미지에는 기본값인 10으로 설정되어 있습니다. VPU는 볼륨 크기에 따라 자동 설정되거나 (Balanced 기준) 수동으로 조절할 수 있습니다.
  • 추천 설정: 기본값인 10 VPU를 그대로 두세요. 프리티어 환경에서는 기본 성능 설정(Balanced)으로도 충분합니다.

 

지표 현재 값 (이미지 기준) 의미
IOPS 3000 IOPS 초당 디스크 입출력 횟수. (높을수록 빠름)
Throughput 24 MB/s 초당 데이터 전송량. (높을수록 빠름)
Target volume performance Balanced (균형) 대부분의 워크로드에 적합한 표준 성능 레벨입니다.

성능 지표 (Performance Metrics)

아래에 표시되는 IOPS 및 Throughput은 위의 설정값에 따라 자동으로 계산하는 지표

 

Boot volume size (GB) IOPS Throughput (MB/s)
50 3000 24
51 3060 24.48
52 3120 24.96
53 3180 25.44
54 3240 25.92
55 3300 26.40

 

Boot 사이즈에 따른 IOPS와 Throughput 증가율 비교표

 

Boot volume performance (VPU) IOPS Throughput (MB/s) Target volume performance
10 3000 24 Balanced
20 3750 30 Higher performance
30 4500 36 Ultra high performance
40 5250 42 Ultra high performance
50 6000 48 Ultra high performance
60 6750 54 Ultra high performance

VPU에 따른 IOPS와 Throughput 증가율 비교표

Select VPU between 10 - 120 VPU

Volume performance units (VPUs) indicate the amount of performance related resources,

such as IOPS/GB and throughput/GB, allocated to a volume

 

화끈하게 최대치로 지른다.

 

Block volumes (블록 볼륨)

부트 볼륨(Boot volume, C드라이브 역할) 외에 추가적인 보조 하드 드라이브를 VM에 연결할지 여부를 묻는 부분

  • Attach block volume (블록 볼륨 연결): 이 버튼을 누르면 VM에 새로운 저장 공간을 추가할 수 있습니다.
  • "No items to display" (표시할 항목 없음): 현재 이 VM에 연결된 추가 블록 볼륨이 없다는 뜻입니다.

부트 볼륨 = 200 GB로 설정한 경우

추가 조치 필요 없음. 이미 프리티어에서 제공하는 총 200GB의 저장 공간을 모두 사용하고 있음

부트 볼륨을 200 GB 미만(예: 50 GB)으로 설정한 경우:

남은 공간(예: 150GB)을 활용하려면 이 섹션에서 **Attach block volume**을 눌러 별도의 보조 드라이브(예: 150GB 크기)를 생성하고 연결해야 합니다. (하지만 부트 볼륨에 통합하는 것이 가장 간단합니다.)

이 섹션은 보조 저장 공간이 필요하지 않다면 비워두는 것이 일반적

 

ㅇㅇ

 

dd

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

아 드디어 나에게도 올 것이 왔구나 싶었지만, 곧바로 카드사에서 문자가 날라왔다.

'ORACLE 해외이용거절 출금가능 잔액부족'

 

흠 달러보다 비싸네 원래 비쌌나?

 

 

오... 원래 유로가 조금 더 비쌌군...

 

근데 거래통화 138.19 SGD (승인금액 106.81 USD)

한화로 약 16만원의 입출금

 

약 3시간을 기다리니 승인이 났다.

 

그러고 나니 드디어 VM을 생성했다.

 

생성 완료

 

이렇게 VM을 생성

 

Free trial credit 한도 내에서 사용하면 과금은 없음, 확인 필요

 

 

참고한 웹 사이트 : https://coadingabc.tistory.com/8

참고한 웹 사이트 : https://softone.tistory.com/88

참고한 웹 사이트 : https://svrforum.com/svr/1239345

참고한 웹 사이트 : reddit/oracle_always_free_service_have_boot_volume_cost

참고한 웹 사이트 : docs-orcale/en/cloud-architecture/goverence/naming-convention

참고한 웹 사이트 : github/rssnyder/oracle-cloud-free-tier-guide-md

참고한 웹 사이트 : https://docs.oracle.com/en-us/iaas/Content/Block/Concepts/blockvolumeperformance.htm#vpus

참고한 웹 사이트 : https://www.oracle.com/cloud/storage/pricing/

참고한 웹 사이트 : https://lowendtalk.com/discussion/185957/are-you-getting-full-disk-speed-from-oracle-free-tier

참고한 웹 사이트 : reddit/resolving_oracle_cloud_out_of_capacity_issue

참고한 웹 사이트 : https://forums.oracle.com/ords/apexds/post/out-of-capacity-for-shape-vm-standard-a1-flex-0940

참고한 웹 사이트 : reddit/how_to_delete_terminated_oracle_cloud_vm_instances

airflow-docker
├── dags                     # airflow DAG 코드 저장 폴더
└── docker-compose.yml       # docker 실행 환경 설정 yml 파일

이 형태로 연습용 폴더 만들기

 

services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_USER: airflow
      POSTGRES_PASSWORD: airflow
      POSTGRES_DB: airflow
    volumes:
      - postgres_data:/var/lib/postgresql/data

  airflow-webserver:
    image: apache/airflow:2.9.0
    depends_on:
      - postgres
    environment:
      AIRFLOW__CORE__EXECUTOR: LocalExecutor
      AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
      AIRFLOW__CORE__FERNET_KEY: "something_random"
      AIRFLOW__CORE__LOAD_EXAMPLES: "false"
    volumes:
      - ./dags:/opt/airflow/dags
    ports:
      - "8080:8080"
    command: webserver

  airflow-scheduler:
    image: apache/airflow:2.9.0
    depends_on:
      - postgres
    environment:
      AIRFLOW__CORE__EXECUTOR: LocalExecutor
      AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    volumes:
      - ./dags:/opt/airflow/dags
    command: scheduler

volumes:
  postgres_data:

docker-compose.yml 파일 내용

 

# Airflow 2.6 버전까지
docker compose run --rm airflow-webserver airflow db init

# Airflow 2.7 버전부터
docker compose run --rm airflow-webserver airflow db migrate

docker desktop 실행 후 bash(cmd)에서 명령어 실행

DB를 초기화하는 명령어

 

docker compose run --rm airflow-webserver \
  airflow users create \
    --username admin \
    --password admin \
    --firstname Admin \
    --lastname User \
    --role Admin \
    --email admin@example.com
    
# One line
docker compose run --rm airflow-webserver airflow users create --username admin --password admin --firstname Admin --lastname User --role Admin --email admin@example.com

계정 생성 명령어

 

# 혹시라도 문제가 생겼을 때 내리는 명령어
docker compose down --volumes

# Airflow 2.9.0 이미지랑 postgres 이미지를 가져오는 명령어
docker compose pull

기타 참고 명령어(에러 발생 시 쓰는 명령어)

 

docker compose up -d

docker로, postgres, airflow 웹서버, airflow 스케줄러를 백그라운드에 띄우는 명령어

 

이후 8080번 포트로 localhost 접속하기

아이디와 비밀번호는 명령어로 설정한 대로 admin

 

성공적으로 로그인 시 뜨는 초기 화면

그런데 위에 주의 구문을 보면 "The scheduler does not appear to be running. The DAGs list may not update, and new tasks will not be scheduled."라고 쓰여있다.

이는 스케줄러가 없어서 뜨는 주의사항이다.

 

실제로 docker ps로 현재 돌아가고 있는 컨테이너를 보면 airflow webserver 뿐이다.

(npmplus는 서버를 돌리기 위해서 따로 돌아가고 있는 컨테이너라 여기서는 무시한다.)

 

Airflow가 정상 동작하기 위해서는 최소한 2개의 컨테이너가 필요하다.

1. Websever - UI를 띄워주는 친구

2. Scheduler - DAG 스케줄을 보고 Task를 실제로 큐에 넣어주는 친구

 

docker compose up -d airflow-scheduler

만약 스케줄러가 없다면 백그라운드로 실행하자.

 

docker compose logs airflow-webserver --tail=50
docker compose logs airflow-scheduler --tail=50

만약 그럼에도 airflow-webserver, airflow-scheduler, postgres 중에 자꾸 꺼지는 게 있다면

위와 같은 명령어를 통해서 가장 최근 50자의 로그를 읽어서 꺼지는 에러를 해결해보자.

 

webserver, scheduler가 모두 돌아간다면 8080 포트 localhost에 정상적으로 작성한 DAG가 뜬다.

 

특정 DAG를 눌러서 Trigger DAG로 직접 실행할 수 있다.

 

이런 식으로 DAG 내의 task들과 실행 결과를 볼 수 있다.

 

# 스케줄러를 재시작하는 cmd 명령어
docker compose restart airflow-scheduler

# airflow가 인식하는 DAG 목록 확인하는 cmd 명령어(webserver 기준)
docker compose exec airflow-webserver ls -l /opt/airflow/dags

# airflow가 인식하는 DAG 목록 확인하는 cmd 명령어(scheduler 기준)
docker compose exec airflow-scheduler ls -l /opt/airflow/dags

# webserver가 DAG를 인식했는지 확인하는 cmd 명령어
docker compose exec airflow-webserver airflow dags list

그밖에 새롭게 DAG를 추가하고 webserver에 안 뜰 때 쓰는 명령어들

 

# 파이썬 함수를 실행하는 작업
# 데이터 처리 로직, 파일 읽기/쓰기, Pandas, API 호출 등 대부분의 로직
PythonOperator(
	task_id="str",
    python_callable=func_name
)

# 기본 Bash/쉘 명령 실행
# 셸 명령 실행, Python 스크립트를 CLI로 실행, 간단한 파일 작업
BashOperator(
	task_id="list_files",
    bash_command="ls -al /opt/airflow"
)

# if문을 통해 어떤 task를 실행할지 결정
# 파일 존재 여부에 따라 작업 경로 변경, 비즈니스 로직에 따른 작업 선택
BranchPythonOperator(
	task_id="decide",
    python_callable=func_name
)

# 실제로는 아무것도 안 하는 DAG 구조(Flow) 조정에 사용하는 빈 작업
# 시작점(start), 종료점(end), 병렬 작업 합류점(fan-in), branch 후 merge, ETL Flow 기본 틀 잡기
EmptyOperator(
	task_id="start"
)

# 메일 발송, 성공/실패 알림, 리포트 결과 전달 등에 사용
EmailOperator(
	task_id="send_email",
    to="user@example.com",
    subject="Done!",
    html_content="Pipeline completed."
)

# 어떤 조건이 충족될 때까지 기다리는 작업
# FileSensor: 특정 파일이 생길 때까지 기다림
# S3KeySensor: S3에 키가 생길 때까지 기다림
# ExternalTaskSensor: 다른 DAG의 Task가 끝날 때까지 기다림
# HttpSensor: HTTP 응답 코드 확인
# SqlSensor: DB 쿼리 결과 조건 충족될 때까지 기다림
FileSensor(
	task_id="wait_file",
    filepath="/opt/airflow/data/data.csv",
    poke_interval=30,
)

# DB에 직접 쿼리 실행하는 작업
# PostgresOperator
# MySqlOperator
# BigQueryInsertJobOperator
PostgresOperator(
	task_id="run_sql",
    postgres_conn_id="my_db",
    sql="SELECT COUNT(*) FROM users;"
)

# 실무에서 쓸 법한 k8s, docker, spark 연동
# DockerOperator: 컨테이너 하나 실행
# KubernetesPodOperator: K8s Pod을 실행(가장 강력한 방법)
# SparkSubmitOperator: Spark job 제출
# Dataproc / Glue / EMR Operators: 클라우드 빅데이터 플랫폼 자동화할 때 사용

Operator는 airflow에서 task를 만들 때 사용하는 실행 단위이다.

즉 무엇을 어떻게 실행할지 정하는 템플릿이자 클래스다.

https://github.com/miny-genie/MS-DT-SCHOOL-3rd-project-DrawBridge?tab=readme-ov-file

 

GitHub - miny-genie/MS-DT-SCHOOL-3rd-project-DrawBridge: Microsoft Data School 3차 프로젝트, Drawbridge 깃허브입니다.

Microsoft Data School 3차 프로젝트, Drawbridge 깃허브입니다. Contribute to miny-genie/MS-DT-SCHOOL-3rd-project-DrawBridge development by creating an account on GitHub.

github.com

Microsoft Data School 1기 최종 프로젝트가 끝이 났다.

감사하게도 1등을 할 수 있었고, 운영국에서 인상 깊게 봐주셔서 좋은 제의를 주셨다.

바로 해당 프로젝트를 일부 보완해서, 페르소나로 설정한 점핏 사이트에 제안서를 내보는 것이 어떻냐는 것이다.

그리하여 localhost:3000이나 cloudflare를 활용한 임시 DNS로 접속하는 게 아니라, 고정 DNS가 필요하다고 판단했다.

이건 내가 서버를 설정하기까지의 이야기다.

 

고정 도메인으로 사람을 불러들이기 위해서는 당연하게도 도메인이 필요하다.

가비아, 호스팅케이알, 닷네임 등의 국내 사이트와 네임칩, 고대디, 포크번 등의 해외 사이트가 있다.

가격은 전체적으로 해외 사이트가 싸고, xyz, fit, chat, is처럼 잘 안 쓰는 도메인들도 많다.

잘 쓰지 않는다는 건 싸다는 뜻이기에 해외 사이트를 찾아보았고, 그중 가장 가격이 싼 네임칩에서 도메인을 구매했다.

 

팀명이자 서비스명인 drawbridge로 검색해보니 별 도메인이 다 나온다.

그중 fit이라는 도메인은 처음으로 보는 단어지만, 프로젝트 취지(핏한 정보를 제공)와는 딱 맞는다.

이벤트로 1년 이용 가격이 $3, 한화로 약 4,500원정도이기에 해당 도메인으로 골랐다.

(참고로 위에서 Add to cart가 아니라 Make offer인 이유는 이미 구매했기 때문이다.)

 

 

How to set up DNS records for your domain in a Cloudflare account - Hosting - Namecheap.com

There are two ways to enable Cloudflare for your domain name: 1. By means of a CNAME record for www.domain.com. NOTE: CNAME setup is available for paid Business or Enterprise Cloudflare plans. 2. By pointing a domain name to Cloudflare nameservers which is

www.namecheap.com

도메인을 구매한 다음 cloudflare를 통해 서버를 연결하는 방법이다.

밑에서 설명하는 cloudflare와 namecheap 연결 방법은 위 링크에서 상세히 볼 수 있다.

 

도메인을 구매한 다음 본인 account의 Dashboard로 들어가서 Domain List를 확인한다.

여기서 본인이 구매한 도메인 목록 중에 연결한 도메인의 Manage로 들어간다.

 

구매한 도메인에서 NAMESERVERS 탭에서 Custom DNS로 바꿔준다.

그럼 이미 2개에 도메인이 존재할 텐데, 해당 값을 Cloudflare 값으로 바꿔줘야 한다.

 

Cloudflare에 로그인하여 우상단의 Connect a domain으로 들어간다.

그리고 본인이 구매한 도메인 주소를 입력하고 간단한 설정 입력을 해준다.

 

그렇게 도메인 등록을 하고 빠르면 10분 늦어도 2~3시간 안에, 도메인 사용이 가능해진다.

사용 등록이 끝나면 메일로 안내가 날아오고, cloudflare 본인의 도메인 Overview에 위와 같이 화면이 뜬다.

 

좌측 탭에서 DNS > Records로 들어가 가장 아래에 있는 Cloudflare Nameservers를

아까 위에서 언급했던 namecheap의 NAMESERVERS에 넣어주면, namecheap과 cloudflare 연동은 끝이 난다.

 

그 다음 구매한 도메인이 내 컴퓨터로 들어올 수 있게 추가해줘야 한다.

Add record 버튼을 눌러 2개의 값을 추가해야 한다.

하나는 Type(A), Name(@), IPv4 Address(내 외부 IP)에 해당하고

다른 하나는 Type(A), Name(내가 구매한 도메인), IPv4 Address(내 외부 IP)에 해당한다.

이 과정을 거치지 않으면 도메인에 접속해도 내 컴퓨터로 들어올 수 없다.

 

내 컴퓨터의 외부 IP 주소는 네이버에서 쉽게 알 수 있다.

이제 내 컴퓨터로 들어오는 길을 명시했으니, 문을 열어줄 차례다.

즉 포트포워딩을 해야 한다.

 

현재 상황은 게이트웨이를 거치고 LG 공유기를 거쳐서, 집안 기기들에 내부 IP를 할당해서 사용하고 있다.

핸드폰은 와이파이로 공유기에 물려서 사용하지만, 데스크탑 같은 경우는 게이트웨이에 직접 물려서 사용하고 있다.

최종 목적 PC가 내 데스크탑이기에 게이트웨이 포트포워딩을 하면 되지만, 사람마다 다르니 잘 확인하고 포트포워딩하자.

 

참고로 iptime 공유기를 사용한다면 192.168.0.1 주소로, LG 공유기라면 192.168.219.1로 들어가면 된다.

다른 통신사와 공유기 모델들의 공유기 관리자 URL과 포트포워딩은 모른다. 쉬우니 포트포워딩 정도는 알아서 하자.

 

여담이지만 게이트웨이를 추가한지 얼마 안 됐는데, 추가하기 전에는 PC를 무선랜카드로 공유기에 물려서 사용하고 있었다.

심지어 공유기도 통신사 기본 제공 공유기도 아니라 더 나은 성능의 iptime 공유기를 구매해서 사용하고 있었다.

그러다보니 습관적으로 LG 공유기 설정에 들어가서 NAT로 모드를 바꾸고보니 PC로 접속이 안 되는 기현상이 일어났다.

생각해보니 게이트웨이에 랜선으로 직접 연결해서, 무선으로 공유기에 안 들어가지고 암튼 좀 이상해졌다.

핵심은 본인의 상황과 목적 PC 환경을 잘 알고 포트포워딩하자는 거다.

 

돌아와서 본인 컴퓨터로 들어오는 라우터에 NAT 설정을 해주자.

포트는 80(HTTP)와 HTTPS(443) 2개만 하면 된다.

 

상세 화면은 위와 같다.

들어오는 포트는 80~80, 그리고 443~443으로 하고 내부 포트는 서비스 포트와 동일하게 설정한다.

그리고 해당 포트로 들어올 때, 어느 내부 IP 주소로 이어줄지를 적어야 한다.

 

들여보낼 컴퓨터에서 cmd > ipconfig로 내부 IPv4 주소를 확인해서 넣어주자.

 

라우터에서 포트포워딩 설정을 마무리한다.

 

netstat -ano | findstr ":80 "
netstat -ano | findstr ":443 "

이후 powershell이나 cmd에서 두 명령어를 통해 80과 443 포트가 LISTENING 상태인지 확인한다.

 

 

GitHub - ZoeyVid/NPMplus: improved fork of nginx-proxy-manager

improved fork of nginx-proxy-manager. Contribute to ZoeyVid/NPMplus development by creating an account on GitHub.

github.com

이제 마지막 단계다.

Namecheap에서 도메인을 등록(구매)했고, Cloudflare를 통해서 구매한 도메인(drawbridge.fit)을 외부 IP로 연결했고,

외부 IP 중 80 혹은 443 포트로 들어온다면, 내 컴퓨터의 포트로 들어오게 연결했다.

그럼 내 컴퓨터의 80 혹은 443 포트로 들어온다면, 이제 실행 중인 웹을 보여줘야 한다.

이렇게 이어주는 것을 리버스 프록시(reverse proxy)라고 한다.

 

프록시(Proxy), 리다이렉션(Redirection) 등의 설정을 GUI 기반으로 쉽게 할 수 있는 Github 자료가 있다.

NPMPlus라고 Nginx Proxy Manager의 역할을 쉽게 할 수 있다.

이를 소개해준 수강생 박우재님께 감사드린다.

 

흐름을 정리하면 아래와 같다.

1. 사용자가 도메인(https://drawbridge.fit)에 접속

2. Cloudflare DNS가 외부IP를 알려줌

3. 패킷이 게이트웨이/공유기 포트포워딩 통해 내 PC의 80/443 → NPMplus로 전달

4. NPMplus가 192.168.219.xx:3000(Next.js 서버)로 프록시

5. React/Next.js 페이지 보여짐

 

NPMPlus는 docker 기반으로 동작하는 linux 기반 코드다.

즉 docker를 설치하고 compose.yaml 파일을 다운받아 내게 맞는 설정으로 바꿔준다.

그리고 docker compose up -d 명령어로 docker에서 실행하면 된다.

 

Github 설명서에도 나와있지만 우선 크게 2개를 설정해야 한다.

TZ(timezone)을 본인이 속한 나라의 TZ로 설정하고, 추후 인증서 완료나 변경 고지를 받은 이메일을 적어준다.

 

직접 돌려보니 이외에도 2개를 더 손봐야 한다. 하나는 volumes고 다른 하나는 ports다.

docker에 올리면 data 폴더가 필요한데 이게 주석처리 되어 data 폴더가 생기지 않았다.

그래서 volumes:를 추가하여 "이전 경로 : /data"를 추가해줘야 한다.

그리고 NPMPlus를 실행하기 위해서는 81번 포트가 필요하고, 외부에서 접속하기 위해서는 80과 443 포트가 필요하다.

그렇기에 하단에 ports:를 추가하여 포트들을 명시해줘야 한다.

 

services:
  npmplus:
    container_name: npmplus
    image: docker.io/zoeyvid/npmplus:latest # or ghcr.io/zoeyvid/npmplus:latest
    restart: always
    volumes:
      - "./data:/data"
    environment:
      - "TZ=Asia/Seoul"
      - "ACME_EMAIL=<YOUR_EMAIL>"
    ports:
        - "81:81"                # Admin UI
        - "80:80"                # (옵션) HTTP
        - "443:443"              # (옵션) HTTPS
        - "443:443/udp"          # (옵션) HTTP/3

필요한 부분만 정리하자면 compose.yaml 파일 코드는 위처럼 된다.

 

yaml 파일을 "./data : /data"로 설정해뒀기 때문에 compose.yaml이 있는 같은 위치에 빈 data 폴더를 하나 만들어둔다.

그게 아니라면 앞의 "./data"를 바꾸고 본인이 편할대로 설정해도 되고,

docker의 기본 경로에 data 폴더가 이미 있거나 만들었다면 /opt 경로를 사용해도 무방하다.

 

docker desktop에서 docker compose up -d를 통해 컨테이너를 실행하면 된다.

docker에서 별다른 에러 사항이 없거나, 에러를 해결하고 난 다음에 localhost:81으로 접속하면 된다.

초기 81번 사이트의 아이디는 admin@example.org고 비밀번호는 로그에 찍혀나온다.

 

docker-compose up -d && docker logs <컨테이너이름> | findstr password

docker Terminal에서 findstr(윈도우 기준) 명령어로 패스워드를 찾아야만 로그인 할 수 있다.

 

로그인해서 초기 아이디와 비밀번호까지 설정을 마치고 나면 위와 같은 화면을 볼 수 있다.

여기서 가장 왼쪽의 메뉴에서 proxy를 설정한다.

 

Add Proxy Host를 하여 본인의 리버스 프록시 설정을 하나 추가한다.

좌측의 메뉴에서 Domain/IP + optional path에는 내 컴퓨터의 내부 IPv4 주소를 적어준다.

Port에는 현재 3000이 들어가있는데, react + next.js 기반의 웹이기 때문에 3000으로 포트를 적어주었다.

만약 다른 쪽으로 이어주고 싶다면 거기에 맞게 포트 번호를 적어줘야 한다.

 └ Vue.js(8080), Vite(5173), Flask(5000), FastAPI(8000), Django(8000), PostgreSQL(5432), MongoDB(27017) 등

그래야 내가 구매한 도메인으로 들어오면, 내가 실행 중인 localhost:port_number로 매핑된다.

 

추가하고 STATUS가 Online이라면 성공이고, 아니라면 실패다.

포트가 열려있는지, 인증서 설정은 제대로 했는지, scheme가 http는 맞는지, 포트 번호를 맞게 썼는지 확인해보자.

 

이제 내가 구매한 도메인으로 접속해보자. NPMPlus의 기본 성공 화면이 뜰 것이다.

웹을 열어서 페이지를 보여주면 끝이다.

 

react + next.js에서 실행하는 환경 코드인 package.json에서 start를 변경한다.

127.0.0.1이 아니라 모든 host가 들어올 수 있게 0.0.0.0으로 잡고 start 할 수 있게끔 한다.

개발 환경을 테스트할 거라면 npm run dev를, 실제 서비스를 한다면 npm run start로 웹을 가동한다.

 

드디어 누구나 접속할 수 있는 고정 도메인의 웹 페이지를 여는데 성공했다.

Database Migration 개요

클라우드 환경에서 보안 부분을 검토할 사항들이다.

이처럼 고려할 부분이 많은 대규모 이주가 migration이다.

이때 무엇을 할 것인지, 무엇을 고려할지 고민해야 한다.

 

Database를 migration한다고 할 때 생각할 수 있는 과정의 preview다.

 

 

Database Migration 실습

DMS를 이용한다면 동일한 데이터베이스끼리밖에 못 한다.

이 점을 유의하여 DMS(Database Migration Service)를 하나 만들어준다.

 

DMS를 사용하면 DB를 migration을 지원하는 방식이라 보통 on-premise에서 cloud로 가는 상황이다.

즉 대부분 민감한 데이터가 많고, 방화벽 허용이나, 보안 툴을 기반으로 데이터에 접근하는 형태로 짜여있다.

Cloud에서는 이러한 민감한 데이터에 직접 접근할 수 없기 때문에 SHIR을 사용한다.

 

SHIR(Self-Hosted Integration Runtime)은 크게 다음과 같은 기능을 지원한다.

ㆍ on-premise DB 연결: SQL Server, Oracle 등 내부망 DB에 직접 연결한다.

ㆍ 보안 게이트웨이: 외부에서 직접 들어오지 않고, SHIR이 나가서 데이터를 전송한다.

ㆍ migration 처리: DMS가 migration 작업을 요청하면 SHIR이 데이터를 옮긴다.

ㆍ 지속적 복제 지원: 온라인 migration 시 변경 데이터도 지속 반영이 가능하다.

 

SHIR로 사용할 윈도우 서버용 VM을 하나 배포한다.

 

VM을 IR 서버로 사용하기 위해 프로그램 링크를 DMS에서 가져온다.

IR 서버(VM)에서 연결 인증을 해야 하니 Key도 저장해두자.

 

이런 화면이 뜨는 링크다.

 

VM에서 접속하여 설치해주자.

 

설치 이후 key 등록으로 연결해준다.

 

이동할 목적지(destination) DB를 하나 만든다.

 

SQL Server에서 모든 네트워크에서 접속할 수 있게 방화벽 규칙을 설정한다.

 

이후 Migration Service에서 새롭게 시나리오를 선택하고 설정을 적으면 migration 할 수 있다.

 

Azure Automation

Azure Automation은 클라우드에서 반복 작업을 스크립트화 하여 자동으로 실행할 수 있는 서비스다.

예를 들어, 데이터베이스 백업이나 성능 점검 같은 작업을 자동으로 처리할 수 있다.

 

주요 구성 요소들은 다음과 같다.

ㆍ Automation Account: 자동화 자산을 저장하고 관리하는 컨테이너

ㆍ Runbook: PowerShell 또는 Python으로 작성한 자동화 스크립트

ㆍ Credential/Connection Asset: 자동화 스크립트에서 사용할 인증 정보

ㆍ Schedule: Runbook을 자동으로 실행하는 시간표

ㆍ Webhook: 외부 시스템에서 Runbook을 호출하는 URL

 

Azure Automation에서 수행하는 Runbook은 다른 리소스 접근 시 권한이 필요하다.

Azure Managed Identity는 Azure 리소스가 인증정보를 직접 관리하지 않고, Azure AD를 통해 자동으로 인증하는 서비스다.

쉽게 말해, 알아서 Azure 서비스가 자신임을 증명할 수 있는 ID를 만들고, 다른 Azure 서비스와 통신한다고 이해하면 된다. Managed Identity는 사용자 이름이나 비밀번호를 코드나 설정파일에 직접 넣지 않아도 동작하게 만들어 보안 문제가 감소한다.

또한 해당 ID에 필요한 권한을 부여하는 방법으로 권한 관리를 쉽게 사용할 수 있다.

특히 Azure Automation, Azure SQL Azure 서비스끼리 연동할 때 아주 유용하다.

 

Azure 리소스에서 Automation Account를 검색해서 생성한다.

 

Resource Group의 IAM(엑세스 제어)으로 가서, 사용자 지정 역할 만들기를 한다.

 

IAM을 위처럼 만들어준다.

 

Automation Account에서 역할(권한) 할당을 한다.

 

방금 IAM에서 만든 역할에 기반하여, 리소스 그룹과 스토리지 계정의 권한을 설정한다.

 

위처럼 권한을 부여하면 된다.

 

이제 Automation Account에서 Runbook 탭으로 이동해서 자동화할 스크립트를 작성할 수 있다.

 

이런 식으로 필요한 부분을 설정하여 스크립트를 작성하면 된다.

Nonclustered Index

저번에 Putty에 저장했던 VM에 접속한다.

 

sudo su

dd

 

ㅇㅇ

 

USE AdventureWorks;
GO

CREATE NONCLUSTERED COLUMNSTORE INDEX [IX_SalesOrderDetail_ColumnStore]
ON Sales.SalesOrderDetail
(UnitPrice, OrderQty, ProductID)
GO

사용한 쿼리

 

2가지 방법으로 생성한 index를 확인할 수 있다.

 

SELECT * FROM sys.indexes WHERE name = 'IX_SalesOrderDetail_ColumnStore';
GO

사용한 쿼리

 

In - memory OLTP

In-memory database(왼쪽), On-disk database(오른쪽)

 

데이터베이스와 테이블은 어디에 저장하느냐(RAM, SSD, HDD)에 따라 특징이 달라진다.

그리고 해당 저장소에 어떤 DBMS를 쓰는지에 따라서도 고유한 접근 시간, 속도, 가격 차이가 생긴다.

단순히 Memory와 Disk의 저장 차이를 비교해보자면 위 그림과 같다.

 

Memory 기반은 데이터를 읽을 때, disk에서 메모리 버퍼 영역으로 데이터를 로드할 필요가 없다.

때문에 빠른 속도를 지원한다. 그밖에도 아래와 같은 특성 때문에 높은 트랜잭션 처리량을 요구하는 금융, 게임, 웹에서 애용한다.

ㆍ 메인 메모리 저장: 데이터와 인덱스가 서버의 메인 메모리(RAM)에 상주한다.

ㆍ 디스크 I/O 최소화: 데이터를 읽을 때 디스크에 접근할 필요가 없으므로, 디스크 I/O로 인한 병목 현상이 사라진다.

ㆍ 성능 극대화: 디스크  기반보다 트랜잭션 처리(OLTP) 성능이 수십 배까지 높아진다.

 

이번 실습에서는 메모리 최적화 테이블인 dbo.ShoppingCart를 만든다.

메모리 최적화 테이블 생성절(MEMORY_OPTIMIZED=ON)로 생성해본다.

 

In-memory OLTP를 사용하기 위해서는 최소 130의 호환성 수준이 필요하다.

이를 위해 호환성 수준을 탐색한다.

 

SELECT sdb.compatibility_level
FROM sys.databases as sdb
WHERE sdb.name = Db_Name();

사용한 쿼리

 

ALTER DATABASE CURRENT
SET COMPATIBILITY_LEVEL = 130;

이 쿼리로 사용 수준을 130으로 올려야 한다.

 

ALTER DATABASE CURRENT
SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT=ON

트랜잭션이 디스크 기반 테이블과 메모리 기반 테이블에 둘 다 상관이 있다면,

트랜잭션 중 메모리 최적화된 부분은 트랜잭션 격리 수준을 SNAPSHOT 레벨로 설정하여 동작해야 한다.

위 쿼리를 통해 메모리 최적화 테이블의 수준을 확실히 강제화 시킨다.

 

데이터베이스에 여럿이 동시 접근을 할 경우, 문제가 생기는데

SNAPSHOT으로 설정하면 한 시점에서만 값을 보여줘서 일관성을 제공한다??

동시성 사용이 높아진다. 그리고 빨라진다?

이걸 설정을 안 하면 오류가 발생?

 

ALTER DATABASE AdventureWorks
ADD FILEGROUP AdventureWorks_mod
CONTAINS memory_optimized_data

메모리 최적화 테이블을 만들기 위해서는 사전에 메모리 최적화 파일 그룹을 먼저 만들어 줘야 한다.

참고로 메모리 최적화 파일 그룹은 DB당 하나만 생성할 수 있다.

 

ALTER DATABASE AdventureWorks
ADD FILE (NAME='AdventureWorks_mod', FILENAME='/var/opt/mssql/data/AdventureWorks_mod')
TO FILEGROUP AdventureWorks_mod

만든 파일 그룹에 메모리 최적화 테이블 전용 파일을 만든다.

 

Putty로 Linux 가상 머신에 접속하여 확인하면 파란색 글자의 '메모리 최적화 전용 파일'을 볼 수 있다.

참고로 파일 그룹은 하나만 가능하지만, 파일은 여러 개가 가능하다.

 

CREATE TABLE dbo.ShoppingCart (
	ShoppingCartId INT IDENTITY(1, 1) PRIMARY KEY NONCLUSTERED,
	UserId INT NOT NULL INDEX ix_UserId NONCLUSTERED HASH WITH (BUCKET_COUNT=1000000),
	CreatedDate DATETIME2 NOT NULL,
	TotalPrice MONEY
) WITH (MEMORY_OPTIMIZED=ON)
GO

이제 in-memory table을 만든다.

In-memory table을 만들 때는 PK 값을 필수로 요구한다.

 

Query Store

Query Store는 쿼리, 실행계획, 런타임 통계에 대해 자세한 정보를 수집할 수 있게 해주는 기능이다.

Query Store는 이러한 문제를 해결하기 위해, 데이터베이스의 '블랙박스' 역할을 한다.

ㆍ 실행 계획 변화 추적: 쿼리의 성능이 변했을 때, 쿼리 스토어에 기록된 과거 실행 계획과 현재 실행 계획을 비교하여 원인을 파악할 수 있습니다.

ㆍ 성능 문제 식별: 실행 시간이 오래 걸리거나 CPU를 많이 사용하는 쿼리를 쉽게 식별하여 튜닝 대상을 빠르게 찾을 수 있습니다.

ㆍ 성능 회귀 분석: 특정 패치나 인덱스 변경 후 성능이 저하되었을 경우, 변경 전후의 성능 데이터를  비교하여  문제를  진단하고  이전의  좋은  실행  계획을  강제로  적용할  수 있습니다.

 

Query Store는 성능 분석에 막대한 양의 정보를 제공하지만, 시스템 성능에 영향을 줄 수도 있기 때문에 기본적으로는 비활성화되어 있습니다.

따라서 이 기능을 사용하려면 데이터베이스 단위로 직접 활성화해주어야 합니다.

 

ALTER DATABASE AdventureWorks
SET QUERY_STORE = ON (
	OPERATION_MODE = READ_WRITE,
	QUERY_CAPTURE_MODE = ALL,
	MAX_STORAGE_SIZE_MB = 100,
	CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS=30)
);

위 쿼리로 query store를 켜준다.

 

SELECT TOP 20 * FROm [Person].[Person];
SELECT COUNT(*) FROM [Production].[Product];
SELECT * FROM [Sales].[SalesOrderHeader] WHERE TotalDue > 100;

이후 위와 같은 예제 쿼리를 여러 번 수행해본다.

 

ㅇㅇ

 

이런 정보가 나온다.

 

SELECT Txt.query_text_id, Txt.query_sql_text, Pl.plan_id, Qry.*
FROM sys.query_store_plan AS Pl
JOIN sys.query_store_query AS Qry ON Pl.query_id = Qry.query_id
JOIN sys.query_store_query_text AS Txt ON Qry.query_text_id = Txt.query_text_id;

사용한 쿼리는 위와 같다.

 

DMV(Dynamic Managment View)

동적  관리뷰는  서버의  상태  정보를  반환해  줘서  서버  인스턴스의  건강  상태와  문제를 진단하고 성능을 튜닝하는 목적으로 제공됩니다.

DMV는 마치 SQL Server 인스턴스에 부착된 '실시간 모니터'와 같습니다.

DMV는 데이터베이스 엔진의 현재 상태 정보를 반환해주는 뷰(View)입니다.

이를 통해 서버의 건강 상태를 진단하고, 성능 문제를 파악하며, 쿼리 튜닝의 실마리를 찾을 수 있습니다.

 

DMV는 왜 필요한가요?

성능 문제가 발생했을 때, 관리자들은 '지금 이 순간 SQL Server에서 무슨 일이 벌어지고 있는지'를 파악해야 합니다.

DMV는 이러한 질문에 대한 해답을 제공합니다.

ㆍ 실시간 진단: DMV는 버퍼 캐시, 메모리 사용량, 잠금(Lock), 대기(Wait) 상태 등 서버의 다양한 내부 상태를 실시간으로 보여줍니다.

ㆍ 문제점  식별: 특정  쿼리가  리소스를  과도하게  사용하거나, 특정  프로세스가  잠금을 유발하는 등의 문제를 DMV를 통해 정확하게 식별할 수 있습니다.

ㆍ 성능 튜닝의 시작: DMV가 제공하는 정보는 성능 저하의 원인을 파악하고, 어떤 쿼리를 먼저 튜닝해야 할지 결정하는 데 중요한 기준이 됩니다.

 

ㅇㅇ

 

SELECT wait_type, wait_time_ms
FROM sys.dm_os_wait_stats;

ㅇㅇ

 

ㅇㅇ

 

HA(High Availability)

주로 서비스를 하는 서버와 백업으로 준비된 서비스 서버의 데이터가 항상 동기화 되어야 업무 관련 어플리케이션 동작의 일관성이 유지 될 수 있습니다. 주 서비스 서버에서 보조 서버로 시스템이 전환될 때 서비스가 지속되게 하기 위한 데이터 동기화 방식은 여러가지가 있을 수 있습니다.
데이터베이스와 같이 데이터 자체의 일관성이 중요한 어플리케이션에서 무난하게 데이터 일관성을 보장하기 Corosync, Pacemaker에 기반한 클러스터링 기법을 주로 리눅스 용 클러스터링 방법으로 사용합니다.

 

window는 failover cluster manager로 고가용성을 관리했다.

 

고가용성 관리를 위해 VM 2대를 만든다.

 

각 VM마다 외부 접속을 허용하는 인바운드 규칙으로 1433 포트 번호를 추가한다.

정확하게는 SQL Database 클라이언트 애플리케이션을 호스팅하는 데스크톱 컴퓨터에서 열어야 하는 유일한 포트를 의미한다.

 

각각의 Putty를 통해 접속한다.

 

각 VM의 IP를 확인한다.

여기서는 10.208.0.5/24와 10.208.0.6/24에 해당한다.

 

상호 간에 통신이 가능한지 확인한다.

vi /etc/hosts로 도메인 기반 리다이렉션이 가능하다.

 

# advanced package tools 및 기본 패키지 설치
sudo apt update -y
sudo apt install curl apt-transport-https software-properties-common -y

# MS SQL Server를 설치하기 위해 MS Ubuntu 서버 repository 경로 등록
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg 
curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list | sudo tee /etc/apt/sources.list.d/mssql-server-2022.list

# 저장소 갱신
sudo apt-get update

# MS SQL Server 설치
sudo apt-get install -y mssql-server

# MS SQL Server Setup 진행
sudo /opt/mssql/bin/mssql-conf setup

상호 통신을 확인했다면 SQLserver를 각각 설치하고 Setup을 진행한다.

비밀번호는 동일하게 SQLserver123으로 진행했다.

 

# 가용성 그룹 설정 ON
sudo /opt/mssql/bin/mssql-conf set hadr.hadrenabled 1

# MS SQL Server 재시작
sudo systemctl restart mssql-server

ㅇㅇ

 

이후 SSMS에서 각각의 VM에 접속한다.

 

CREATE LOGIN dbm_login WITH PASSWORD = 'Password123%'
CREATE USER dbm_user FOR LOGIN dbm_login;

서버 1, 서버 2 모두 진행

 

-- 마스터 키와 인증서 생성
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'Password123%';
CREATE CERTIFICATE dbm_certificate WITH SUBJECT = 'dbm';

-- 인증서 백업(linux 파일 경로)
BACKUP CERTIFICATE dbm_certificate
TO FILE = '/var/opt/mssql/data/dbm_certificate.cer'
WITH PRIVATE KEY (
	FILE = '/var/opt/mssql/data/dbm_certificate.pvk',
	ENCRYPTION BY PASSWORD = 'Password123%'
)

서버 1에서만 진행

각각 진행하지 않는 이유는 같은 인증서와 개인키를 써야 하는데, 각각 생성하면 다른 인증서와 개인키를 생성한다.

즉 한 쪽에서만 생성하고 이후에 다른 쪽 서버로 옮겨줘야 한다.

 

1에서만 cer(인증서)와 pvk(개인키)가 있어야 한다.

 

passwd root
vi /etc/ssh/sshd_config
service sshd restart

서버 2에서만 진행

 

scp dbm_certificate.* root@HA-VM-3:/var/opt/mssql/data/

서버 1에서 진행

 

password 입력 창에서 passwd root로 입력한 패스워드를 입력하면 된다.

그럼 정상적으로 왼쪽(서버1)에서 오른쪽(서버2)로 cer(인증서)와 pvk(개인키) 파일을 옮겼다.

 

chown mssql:mssql dbm_certificate.*

서버 2에서 진행

오른쪽 부분을 잘 보면 dbm_cer 파일 2개의 소유권이 root, root임을 알 수 있다.

SSMS에서 관리하고 사용하기 위해서는 mssql로 소유권을 변경해야 한다.

 

서버 2에 해당하는 SSMS 쿼리 편집기에서 쿼리를 수행한다.

이를 통해 앞서 받은 pvk(개인키)로 마스터키를 만든다.

 

CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'Password123%'
CREATE CERTIFICATE dbm_certificate
	AUTHORIZATION dbm_user
	FROM FILE = '/var/opt/mssql/data/dbm_certificate.cer'
	WITH PRIVATE KEY (
		FILE = '/var/opt/mssql/data/dbm_certificate.pvk',
		DECRYPTION BY PASSWORD = 'Password123%'
	);

사용한 쿼리

 

# 택1: 선택지 1
ufw disable

# 택1: 선택지 2
sudo ufw allow 2224/tcp
sudo ufw allow 3121/tcp
sudo ufw allow 21064/tcp
sudo ufw allow 5405/udp
sudo ufw allow 1433/tcp
sudo ufw allow 5022/tcp

데이터베이스 미러링 endpoint는 TCP를 사용하여 DB 미러링 세션에 참여한다.

또는 가용성 복제본들을 호스팅 하는 서버들 간의 메시지를 주고 받는다.

DB미러링 endpoint는 TCP 유니크한 끝점 번호를 사용한다.

Endpoint 작업을 수행하기 전에 먼저 간단하게 방화벽을 양쪽 서버 모두 내려주는 작업을 한다.

실제로는 특정 포트만 허용하는 것이 바람직하기에 선택지 2번으로 포트를 허용하는 것을 추천한다.

 

-- ENDPOINT를 생성하는 쿼리
CREATE ENDPOINT [Hadr_endpoint]
	AS TCP (LISTENER_IP = (YOUR_VM_INTERNAL_IP), LISTENER_PORT = 5022)
	FOR DATA_MIRRORING (
		ROLE = ALL,
		AUTHENTICATION = CERTIFICATE dbm_certificate,
		ENCRYPTION = REQUIRED ALGORITHM AES
	)
ALTER ENDPOINT [Hadr_endpiont] STATE = STARTED
GRANT CONNECT ON ENDPOINT::[Hadr_endpoint] TO [dbm_login]

-- ENDPOINT를 확인하는 쿼리
SELECT * FROM sys.endpoints

위 쿼리는 가용성 그룹을 위한 listener endpoint를 생성하는 쿼리다.

YOUR_VM_INTERNAL_IP에 위에서 확인한 ip 주소를 적어넣는다.

 

각 VM에 대해서 SSMS에서 전부 진행해준다.

위처럼 endpoint를 확인할 수 있으면 성공이다.

 

ALTER ENDPOINT Hadr_endpoint STATE = STARTED;

만약 다양한 사유로 생성한 endpoint가 STOPPED 상태라면 위 쿼리로 활성화한다.

 

서버 1에서만 실행

좌측에서 가용성 부분에서 확인 가능하다.

 

CREATE AVAILABILITY GROUP [Server1]
	WITH (DB_FAILOVER = ON, CLUSTER_TYPE = NONE)
	FOR REPLICA ON
		N'HA-VM-2' WITH (
			ENDPOINT_URL = N'tcp://HA-VM-2:5022',
			AVAILABILITY_MODE = SYNCHRONOUS_COMMIT,
			FAILOVER_MODE = MANUAL,
			SEEDING_MODE = AUTOMATIC,
			SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL)
		),
		N'HA-VM-3' WITH (
			ENDPOINT_URL = N'tcp://HA-VM-3:5022',
			AVAILABILITY_MODE = SYNCHRONOUS_COMMIT,
			FAILOVER_MODE = MANUAL,
			SEEDING_MODE = AUTOMATIC,
			SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL)
		)
ALTER AVAILABILITY GROUP [Server1] GRANT CREATE ANY DATABASE

사용한 쿼리

 

실제 DB를 만들 때 가용성 그룹에 추가하여 관리한다.

가용성 그룹에 추가하려는 DB가 완전 복구 모드인지, 유효한 로그 백업이 있는지 확인해야 한다.

테스트할 DB를 만들었다면 데이터베이스 백업을 수행해야 한다.

 

-- DB 생성, 완전 복구 모드로 변환, disk에 백업
CREATE DATABASE [test_database]
ALTER DATABASE [test_database] SET RECOVERY FULL
BACKUP DATABASE [test_database] TO DISK = N'NUL'

-- 가용성 그룹에 추가
ALTER AVAILABILITY GROUP [Server1] ADD DATABASE [test_database]

서버 1에서만 실행, 사용한 쿼리

 

해당 DB가 동기화 상태인지 확인한다.

이후에 'ALTER AVAILABILITY GROUP [Server1] FORCE_FAILOVER_ALLOW_DATA_LOSS;'를 통해

서버 2번에서 강제 failover로 Primary로 변경하는 것도 가능하다.

 

SQL Server in Container and k8s

SQL Server 컨테이너화의 주요 특징 및 장점

ㆍ 빠른 배포 및 경량화: OS 설치 및 설정 과정이 생략되어 몇 초 만에 SQL Server 인스턴스를 시작할 수 있다.

ㆍ 개발/테스트 환경의 일관성: 개발, QA, 운영 환경이 동일한 컨테이너 이미지로 구성되어 로컬 문제를 방지합니다.

ㆍ 운영 효율성 향상: Dockerfile 을 통해 환경 설정을 코드화(IaC: Infrastructure as Code)할 수 있어, 반복 수동 작업을 줄인다.

ㆍ 효율적인 리소스 사용: 호스트 OS 의 커널을 공유하므로 VM보다 훨씬 가벼우며, 필요한 리소스만 할당하여 사용 가능하다.

ㆍ 고가용성 및 확장성: 쿠버네티스로 SQL Server 인스턴스를 손쉽게 복제, 관리하여 장애 발생 시 자동 복구 구현이 가능하다.

 

SQL Server 컨테이너화의 단점 및 고려사항

ㆍ 성능 오버헤드: OS 위에 컨테이너 런타임이 추가되므로, 성능 오버헤드가 발생할 수 있다.

ㆍ 운영 복잡성 증가: Docker를 넘어 쿠버네티스까지 도입할 경우, 오케스트레이션에 대한 학습과 운영 노하우가 필요하다.

ㆍ 영구 데이터 관리: 컨테이너의 본질은 임시다. 데이터 영속성(Persistence)을 위해 스토리지 개념을 명확히 이해해야 한다.

     └ 볼륨(Volume), PersistentVolumeClaim(PVC) 등 

ㆍ 보안 및 모니터링: 컨테이너 환경에 최적화된 보안 설정 및 모니터링 체계를 별도로 구축해야 힌다.

 

가상 머신(VM)과 컨테이너의 차이점

ㆍ 가상 머신(VM): 하드웨어 위에 하이퍼바이저를 통해 Guest OS를 통째로 올리는 방식, 무겁고 부팅 시간이 오래 걸린다.

ㆍ 컨테이너: 호스트 OS의 커널을 공유하며, 애플리케이션과 필요한 라이브러리만 패키징, 훨씬 가볍고, 리소스 효율성이 좋다.

 

실습용 VM을 하나 만든다.

 

외부에서 접속이 가능하게끔 1433 인바운드 포트 규칙을 허용한다.

 

설정한 아이디와 비밀번호를 기반으로 putty로 접속한다.

 

# 시스템 업데이트 및 필수 패키지 설치
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release

# Docker 공식 GPG 키 추가
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Docker Repository 추가
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 시스템 업데이트 및 Docker Engine 설치
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

# Docker 설치 확인 및 사용자 권한 설정
docker -v			# sudo docker run hello-world
sudo usermod -aG docker $USER	# sudo 없이 docker 명령어를 사용하기 위해 현재 사용자를 docker 그룹에 추가

# SQL Server 컨테이너 실행
docker run -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=Contoso!0000' -p 1433:1433 --name mssql-test -d mcr.microsoft.com/mssql/server:2022-latest

# 컨테이너 확인
docker ps

위 명령어를 따라 linux 기반으로 VM에 docker를 설치하고 실행할 수 있다.

 

로컬 SSMS로 접속하면 이제 컨테이너에서 생성한 MS SQL에 접속할 수 있다.

(주소는 Azure VM의 공용 IP 주소, 계정은 SA, 비밀번호는 명령어에 따라 Contoso!0000다.)

 

SQL Server Docker Volume

Container는 기본적으로 임시적이다.

프로덕션 환경에서 데이터베이스 컨테이너를 사용할 때, 데이터를 컨테이너 외부의 영구적인 저장소에 보관해야 한다.

 └ Production Environment: 서비스나 제품이 실제로 사용자에게 제공되고 운영되는 실제 환경

Docker에서 가장 일반적인 방법은 Docker Volume을 사용한다.

 

Docker Volume은 컨테이너가 생성되고 삭제되는 것과 무관하게 데이터를 영구적으로 저장할 수 있는 디렉터리다.

Volume은 호스트 OS의 특정 디렉터리를 컨테이너의 디렉터리에 연결(마운트)하는 방식으로 동작한다.

Volume의 특징들은 다음과 같다.

ㆍ 컨테이너의 수명 주기와 독립적이다.

ㆍ Docker가 관리하는 별도의 영역에 생성한다.

ㆍ 컨테이너 간에 데이터를 공유할 때 유용하다.

ㆍ 호스트 머신의 파일 시스템에 직접 접근하는 bind mount보다 성능이 좋고, 보안성이 뛰어나다.

 

# 기존 컨테이너 및 이미지 정리
docker stop mssql-test
docker rm mssql-test

# Docker Volume 생성 및 확인
docker volume create mssql_data
docker volume ls

# Volume 기반 SQL Server Container 실행
docker run -e "ACCEPT_EULA=Y" \
-e 'MSSQL_SA_PASSWORD=Contoso!0000' \
-p 1433:1433 \
--name mssql-with-volume \
-v mssql_data:/var/opt/mssql \
-d mcr.microsoft.com/mssql/server:2022-latest

# 컨테이너 확인
docker ps		# STATUS가 Up인지 확인

Volume 기반으로 컨테이너를 생성한다.

 

Volume을 위해 VM MS SQL에 재연결 후 DB, 테이블, 데이터를 생성해본다.

 

CREATE DATABASE testDB;
GO

USE testDB;
GO

CREATE TABLE testTable (ID INT, Name NVARCHAR(50));

INSERT INTO testTable VALUES (1, 'test_sentence');
GO

SELECT * FROM testTable;
GO

 

사용한 쿼리

 

# 검증을 위해 컨테이너 정지 후 삭제
docker stop mssql-with-volume
docker rm mssql-with-volume

# 새로운 컨테이너 생성
docker run -e "ACCEPT_EULA=Y" -e 'MSSQL_SA_PASSWORD=Contoso!0000' -p 1433:1433 --name mssql-validation -v mssql_data:/var/opt/mssql -d mcr.microsoft.com/mssql/server:2022-latest

Volume에 데이터를 보존하고 있나 확인하기 위해, 삭제 후 재생성한다.

 

로컬 SSMS에서 재연결하고 확인해보면 데이터가 살아있다.

 

k8s

# kubectl 설치
curl -LO "https://dl.k8s.io/release/v1.30.1/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
kubectl version

# minikube 설치
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube version

# minikube 클러스터 시작
minikube start --driver=docker --force

ㅇㅇ

 

apiVersion: apps/v1
kind: Deployment
metadata:
 name: mssql-deployment
spec:
 replicas: 1
 selector:
   matchLabels:
     app: mssql
 template:
   metadata:
     labels:
       app: mssql
   spec:
     containers:
     - name: mssql-container
       image: mcr.microsoft.com/mssql/server:2022-latest
       ports:
       - containerPort: 1433
       env:
       - name: ACCEPT_EULA
         value: "Y"
       - name: MSSQL_SA_PASSWORD
         value: "Contoso!0000"
---
apiVersion: v1
kind: Service
metadata:
 name: mssql-service
spec:
 selector:
   app: mssql
 ports:
   - protocol: TCP
     port: 1433
     targetPort: 1433
 type: NodePort

vi mssql-deployment.yaml로 파일을 생성한다.

 

# k8s에 배포 및 상태 확인
kubectl apply -f mssql-deployment.yaml
kubectl get pods
kubectl get services
kubectl get pods

# SQL Server 접속
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /etc/apt/keyrings/microsoft.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/ubuntu/22.04/prod jammy main" | sudo tee /etc/apt/sources.list.d/mssql-release.list
sudo apt update
sudo ACCEPT_EULA=Y apt install -y mssql-tools18 unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.bashrc
source ~/.bashrc
minikube ip    # 192.168.49.2
kubectl get svc mssql-service    # 1433:30603
sqlcmd -S <minikube-ip>,<nodeport> -C -U sa -P 'Contoso!0000

위 방식으로 putty를 통해 SQL server에 접속할 수 있다.

 

 

Query시 발생하는 성능 문제 해결

호환성 수준(Compatibility Level)을 변경하는 쿼리다.

호환성 수준은 DB가 어떤 SQL Server 버전의 쿼리 동작 방식과 기능 집합을 따를지를 정하는 설정이다.

 

호환성 수준 값 해당 SQL Server 버전
80 SQL Server 2000
90 SQL Server 2005
100 SQL Server 2008
110 SQL Server 2012
120 SQL Server 2014
130 SQL Server 2016
140 SQL Server 2017
150 SQL Server 2019
160 SQL Server 2022

호환성 수준에 따른 버전은 위와 같다.

 

USE [master];
GO

ALTER DATABASE [AdventureWorks2017] SET QUERY_STORE = ON;
GO

ALTER DATABASE [AdventureWorks2017] SET QUERY_STORE (OPERATION_MODE = READ_WRITE);
GO

ALTER DATABASE [AdventureWorks2017] SET COMPATIBILITY_LEVEL = 100;
GO

사용한 쿼리는 위와 같다.

 

USE [master];
GO

ALTER DATABASE [AdventureWorks2017] SET COMPATIBILITY_LEVEL = 160;
GO

Github의 CreateRandomWorkloadGenerator에 해당하는 SQL 쿼리를 가져온다.

Github의 ExecuteRandomWorkload에 해당하는 SQL 쿼리를 가져온다.

 

두 버전에서 모두 실행을 해보고, 비교를 진행하기 위해 보고서를 찾아 들어온다.

 

설정과 강제 옵션

 

ARM Deploy

Azure Resource Manager(ARM) template

Azure 리소스를 선언적으로 정의하는 JSON 파일.

인프라를 코드로(Infrastructure as Code, IaC) 관리하고, 리소스 배포를 자동화하고, 반복 가능하게 만든다.

 

SKU(Stock Keeping Unit)

Azure에서는 리소스의 가격 등급(Pricing Tier)과 성능, 용량 등을 정의하는 단위를 의미한다.
예를 들어, SQL Database의 SKU는 Basic, Standard, Premium  등으로 나누며, 각기 다른 성능과 비용을 가진다.


Firewall rule(방화벽 규칙)

특정 IP 주소나 IP 주소 범위에서 Azure SQL Server로 들어오는 네트워크 트래픽을 허용하거나 차단하는 보안 규칙.

데이터베이스를 무단 액세스로부터 보호하는 첫 번째 방어선이다.

 

SQL DB 리소스 배포를 위한 ARM 탬플릿 Github 링크에서 json 코드를 가져온다. 

 

Azure 사이트에서 리소스를 배포한다.

 

azuredeploy.json을 가져와서 데이터베이스 SKU를 변경한다.

Basic 등급을 5 DTU(Database Transaction Unit)와 2GB의 저장 공간을 제공하는 가장 기본 서비스 계층이다.

여기서 capacity는 DTU를 의미한다.

 

,
{
    "type": "Microsoft.Sql/servers/firewallRules",
    "apiVersion": "2022-05-01-preview",
    "name": "[format('{0}/{1}', parameters('serverName'), 'AllowAllWindowsAzureIps')]",
    "dependsOn": [
    	"[resourceId('Microsoft.Sql/servers', parameters('serverName'))]"
    ],
    "properties": {
    	"startIpAddress": "0.0.0.0",
        "endIpAddress": "0.0.0.0"
    }
}

서버 방화벽 규칙(Firewall Rule) 추가하는 코드다.

"type": "Microsoft.Sql/servers" 리소스 정의 내부에 추가한다.

 

 

새롭게 추가한 firewall rule에서 preperties에 해당하는 IP는 구체적으로 지정해주면 좋다.

이런 Custom Json 형태로 리소스를 deploy하는 것이 가능하다.

 

index 개요

ㅇㅇ

 

index 주요 종류에 따른 방향성

 

클러스터형 index

 

비클러스터형 index

테이블 데이터에 영향을 주지 않고 검색 성능을 올릴 수 있어 유연하게 활용할 수 있다.

하지만 너무 많은 비클러스터형 index는 데이터 수정(INSERT, UPDATE, DELETE) 성능을 저하시켜 적절한 수 유지가 중요하다.

 

복합과 커버링 index 설명

 

Unique index 설명

 

힙 테이블에 대한 설명

Production 환경에서 주요 테이블은 적절한 clustered indexes를 가지는 것을 권장한다.

힙 테이블은 특수항 사용 사례에만 제한적으로 사용하는 것이 좋다.

 

Filtered Index 설명

 

그 외 특수한 index들은 B Tree index로 처리하기 어려운 특정 데이터에 대해 최적화한 index다.

특히 columnstore index는 SQL Server 2012부터 도입하여 데이터 웨어하우스 성능을 크게 향상했다.

 

생성

 

제거

 

index 재구성 개요

 

페이지 수가 적은 소형 index(1000 페이지 미만)는 조각화가 심해도 성능에 미치는 영향이 적을 수 있다.

리소스 효율성을 고려하여 대용량 index에 우선순위를 둬야 한다.

 

index 조각화 설명

 

조각화 측정 방법

 

조각화 자동화 관리

 

관리 대응 전략 1 - index 재구성

 

관리 대응 전략 2 - index rebuild

 

index 통계

 

index 모니터링

 

index 유지보수 작업 중에는 서버 리소스(CPU, Memory, Disk I/O)를 모니터링해야 한다.

리소스 사용량이 과도하게 높아 다른 작업에 영향을 미칠 경우, MAXDOP 옵션을 조정하여 작업 일정 변경을 고려해야 한다.

 

다양한 에러가 발생할 수 있다.

 

데이터베이스 설계 문제 식별

ㅇㅇ

 

경고 메시지는 쿼리에 암시적 변환(implicit conversion)이 있음을 나타낸다.

이는 SQL Server 쿼리 최적화 프로그램이 쿼리의 데이터 형식을 다른 데이터 형식으로 변환해야 했음을 의미한다.

암시적 변환이란, SQL Server가 서로 다른 데이터 타입을 비교할 때 자동으로 수행하는 데이터 타입 변환이다

 

암시적 변환은 다음과 같은 문제점이 있다.
 └ 성능 저하: 추가적인 CPU 오버헤드 발생
 └ 인덱스 비활용: 변환으로 인해 인덱스 사용 불가능
 └ 통계 부정확성: 쿼리 최적화기의 예상치와 실제 결과의 차이

 

WHERE 절에서 NationalIDNumber열과 비교되는 값이 따옴표로 묶이지 않은 숫자(14417807)로 적혀있다.
테이블 구조를 검토하면 NATIONALIDNUMBER 열이 INT(정수) 데이터 형식이 아닌 NVARCHAR(문자열)를 사용해야 한다.

이러한 불일치로 인해 DB 최적화 프로그램은 숫자를 NVARCHAR 값으로 암시적으로 변환한다.

이는 최적이 아닌 계획을 생성하여 쿼리 성능에 추가적인 오버헤드를 유발한다.

 

 

Azure SQL Server 기반 DB 유지 관리 개요

Microsoft 지능형 데이터 플랫폼

구성은 온프레미스-하이브리드-클라우드의 단계로 이루어져있다.

 └ on-premise: 기존 SQL Server 인프라를 통한 데이터 관리 및 처리

 └ 하이브리드: on-premise와 클라우드 환경 간의 원활한 데이터 통합

 └ 클라우드: 온전한 클라우드 네이티브 데이터 서비스

 

Azure DB 관리자는 클라우드 환경에서 DB 설계, 구현, 유지 관리를 담당한다.

관리자는 다음과 같은 역할 책임을 가진다.

ㆍ DB 아키텍쳐 설계 및 최적화

ㆍ 성능 모니터링 및 튜닝

ㆍ 보안 및 규정 준수 관리

ㆍ 고가용성 및 재해 복구 구현

ㆍ on-premise와 클라우드 환경의 통합 관리

ㆍ 데이터 접근 권한 제어

 

Azure SQL Server 기반 제품들이다.

 

Migration 시나리오 SQL Server on VM Azure SQL Database SQL Managed Instance
on-premise 직접 이전 Lift and Shift로 가장 쉬움 애플리케이션 변경 필요 최소한의 변경만 필요
제어 수준 높음(OS, SQL 완전 제어) 낮음(PaaS) 중간(Instance 제어)
관리 부담 높음 낮음 낮음
확장성 수동 확장 자동 확장 수동 확장
비용 모델 VM 비용 + license 사용량 기반 Instance 기반

Azure SQL Server 기반 제품들의 migration 시나리오에 따른 비교 표다.

 

SQL Server on VM의 주요 기능들이다.

배포는 '리소스 계획 > Azure portal에서 VM 생성 > 네트워크 구성 > 스토리지 최적화 > SQL Server' 구성 순이다.

그밖의 특징들은 다음과 같다.

ㆍ 완전한 SQL Server 환경 제공

ㆍ 기존 on-premise 애플리케이션의 쉬운 migration

ㆍ OS 및 DB 엔진에 대한 완전 제어

ㆍ License 이동성 지원 (Azure 하이브리드 혜택)

ㆍ 특수 워크로드를 위한 맞춤형 구성 가능

 

주요 사항들은 다음과 같다.

 

Azure SQL Database의 주요 기능들이다.

그 외 핵심 기능으로 자동 백업, 자동 패치, 성능 모니터링 등이 있다.

 

Azure SQL Database의 배포 오션 비교다.

 

Azure SQL Database의 개발 패턴이다.

 

비교 항목 DTU 모델 vCore 모델
리소스 측정 추상화 단위(DTU) CPU, 메모리, IO 직접 제어
적합 워크로드 예측 가능한 소규모 워크로드 복잡하고 가변적인 워크로드
하이브리드 혜택 지원 X 지원 O
serverless 옵션 지원 X 지원 O

Azure SQL Database의 운영 효율화 전략이다.

 

Azure SQL Managed Instance의 아키텍처 및 배포 모델에 대한 설명이다.

주요 기능으로는 자동 백업, 자동 패치, 엔터프라이즈 기능 등이 있다.

 

Azure SQL Managed Instance의 Migration 옵션에 대한 설명이다.

 

Azure SQL Managed Instance의 주요 사항들은 다음과 같다.

 

Microsoft SQL Server 인덱스 관리 개요

index 필요 이유

 

Index는 DB 테이블의 검색 속도 향상하기 위해 설계한 특별한 자료구조다.

Index는 DB 성능의 핵심 요소로, 대량 데이터를 처리하는 환경에서는 필수 요소다.

적절한 index를 설정하지 않으면 시스템이 모든 데이터를 순차적으로 검색해야 하기에 성능이 크게 하락한다.

이러한 index는 테이블의 특정 열을 기반으로 생성한다.

 

SQL Server에서 index가 있는 경우 동작 과정이다.

 

SQL Server 대부분의 index는 B+ tree 구조로 동작한다.

B+ tree 구조가 신이어도 index는 생성하고 방치해서는 안 되는 하나의 객체다.

시간이 지남에 따라 데이터 삽입, 수정, 변경, 삭제가 발생하며 index가 조각화되며 성능은 떨어진다.

이는 디스크 공간을 낭비하며, 검색 속도(전체 시스템의 응답 시간 지연)도 저하하는 요인이다.

특히 대용량 DB에서는 index 유지 보수가 무척 중요해진다.

 

리소스 모니터링

Azure SQL에서 작업 리소스 모니터링 및 최적화 - Training | Microsoft Learn

 

Azure SQL에서 작업 리소스 모니터링 및 최적화 - Training

Azure SQL에서 작업 리소스 모니터링 및 최적화

learn.microsoft.com

참고사항

 

SQL Server 만들기

 

허용 및 구성

 

설정 확인

 

SQL Database 만들기

 

네트워크 추가

 

index 실습

VM에 접속하여 깃허브 데이터베이스 백업 파일 링크에서 백업 파일 다운로드

SSMS 접속 이후 쿼리를 편집하여 실행하여 데이터베이스 복구 진행

 

RESTORE DATABASE AdventureWorks2017
FROM DISK = 'C:\LabFiles\Monitor and optimize\AdventureWorks2017.bak'
WITH RECOVERY,
      MOVE 'AdventureWorks2017' 
        TO 'C:\LabFiles\Monitor and optimize\AdventureWorks2017.mdf',
      MOVE 'AdventureWorks2017_log'
        TO 'C:\LabFiles\Monitor and optimize\AdventureWorks2017_log.ldf';

사용한 쿼리다. bak를 저장한 위치에 따라 경로를 바꿔서 실행하면 된다.

 

조각화 상태가 있는지 확인하는 쿼리

 

USE AdventureWorks2017
GO

SELECT
    i.name AS Index_Name,						-- 인덱스 이름
    avg_fragmentation_in_percent,               -- 평균 조각화율
    db_name(database_id) AS Database_Name,      -- 데이터베이스 이름
    i.object_id,                                -- 인덱스가 속한 객체의 ID (테이블 등)
    i.index_id,                                 -- 인덱스 ID
    index_type_desc								-- 인덱스 유형 설명 (예: CLUSTERED, NONCLUSTERED)

FROM
	sys.dm_db_index_physical_stats(
		db_id('AdventureWorks2017'),            -- 대상 데이터베이스 ID
		object_id('person.address'),            -- 대상 테이블의 객체 ID
		NULL, NULL,								-- 인덱스,파티션 ID는 NULL(모든 인덱스 포함)
		'DETAILED'								-- 'DETAILED' 모드로 상세 조각화 정보 수집
	) ps
INNER JOIN sys.indexes i
	ON ps.object_id = i.object_id
	AND ps.index_id = i.index_id                -- 인덱스 메타데이터와 조각화 정보 조인
WHERE avg_fragmentation_in_percent > 50         -- 조각화율이 50% 초과인 인덱스만 선택

사용한 쿼리

 

USE AdventureWorks2017
GO
    
INSERT INTO [Person].[Address]
([AddressLine1], [AddressLine2], [City], [StateProvinceID], [PostalCode], [SpatialLocation], [rowguid], [ModifiedDate])
   
SELECT
	AddressLine1,
    AddressLine2, 
    'Amsterdam',
    StateProvinceID, 
    PostalCode, 
    SpatialLocation, 
    newid(), 
    getdate()
FROM
	Person.Address;
GO

쿼리로 데이터 추가하여 index 일부러 파편화하기

 

다시 확인해보면 조각난 index를 볼 수 있다.

 

상단의 쿼리는 조각 모음 쿼리다.

하단은 조각난 상태의 index에서 쿼리 실행 결과다.

 

USE AdventureWorks2017
GO

ALTER INDEX [IX_Address_StateProvinceID] ON [Person].[Address] REBUILD PARTITION = ALL 
WITH (
	PAD_INDEX = OFF, 
    STATISTICS_NORECOMPUTE = OFF, 
    SORT_IN_TEMPDB = OFF, 
    IGNORE_DUP_KEY = OFF, 
    ONLINE = OFF, 
    ALLOW_ROW_LOCKS = ON, 
    ALLOW_PAGE_LOCKS = ON
)

조각 모음 쿼리는 위와 같다.

 

With 옵션 의미
PAD_INDEX = OFF 리프(leaf) 페이지에 추가 여유 공간을 두지 않음 (기본값)
STATISTICS_NORECOMPUTE = OFF 인덱스 통계를 자동으로 재계산하게 설정
SORT_IN_TEMPDB = OFF 정렬 작업을 tempdb가 아닌 사용자 DB에서 수행
IGNORE_DUP_KEY = OFF 중복 키 입력 시 에러 발생 (기본값)
ONLINE = OFF 인덱스 재구성 중 테이블이 잠김, 사용자가 접근 불가
ALLOW_ROW_LOCKS = ON 인덱스에 행 수준 잠금 허용
ALLOW_PAGE_LOCKS = ON 인덱스에 페이지 수준 잠 허용

IX_Address_StateProvinceID 인덱스를 전체 파티션 대상으로 재구성한다 (PARTITION = ALL)

REBUILD 인덱스를 완전히 다시 생성하는 작업이다.

조각화 제거를 하고, 통계도 기본적으로 다시 계산한다. (STATISTICS_NORECOMPUTE = OFF)

 

SET STATISTICS IO, TIME ON
GO
    
USE AdventureWorks2017
GO
    
SELECT
	DISTINCT (StateProvinceID),
	count(StateProvinceID) AS CustomerCount
FROM person.Address
GROUP BY StateProvinceID
ORDER BY count(StateProvinceID) DESC;
GO

선택 사항으로 쿼리 실행 시간을 보여주는 쿼리 (하단에 해당하는 쿼리)

 

조각 모음을 하고 다시 결과 확인

 

설정한 컬럼에 대해서 index 파편화 해결

 

다시 실행 시간 확인

 

94 페이지 로드에서 70 페이지 로드로 감축

 

개요

어제 3 - Task 8까지 진행

3 - Task 9의 검증 진행은 install 계정(MASTER 계정)으로 사용

 

SQL Server on Linux

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

ㅇㅇ

 

sudo su
cd /etc/skel
vi .vimrc
vi .bashrc	# PS1="\h[\u \w]$ "
cd /root
cp /etc/skel/.vimrc /root
vi .bashrc	# PS1="\h[\u \w]$ " (다음라인) alias vi='vim'

ㅇㅇ

 

필수는 아니고 글자 크기나 이런 걸 정하는 파일

 

# advanced package tools 및 기본 패키지 설치
sudo apt update -y
sudo apt install curl apt-transport-https software-properties-common -y

# MS SQL Server를 설치하기 위해 MS Ubuntu 서버 repository 경로 등록
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg 
curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list | sudo tee /etc/apt/sources.list.d/mssql-server-2022.list

# 저장소 갱신
sudo apt-get update

# MS SQL Server 설치
sudo apt-get install -y mssql-server

# MS SQL Server 실행
sudo /opt/mssql/bin/mssql-conf setup

ㅇㅇ

 

비번 SQLserver123

 

ㅇㅇ

 

# 패키지 설치
curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
sudo apt-get update
sudo apt-get install mssql-tools18 unixodbc-dev

# 환경 변수 추가
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.bash_profile
source ~/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.bashrc
source ~/.bashrc

# SQLCMD로 SQL Server에 접속
sqlcmd -S localhost -U SA -P '비밀번호' -C

ㅇㅇ

 

ㅇㅇ

 

SQL Server on Linux는 보통 /var/opt/mssql/data 경로의 하위에 mdf 파일 및 ldf 파일을 보관한다.

MDF(Primary Data File) 파일은 데이터베이스의 핵심 데이터를 저장한다.

(테이블, 뷰, 저장 프로시저 등) 하나의 데이터베이스당 기본적으로 1개의 MDF 파일이 반드시 존재해야 한다.
LDF(Transaction Log File) 파일은 트랜잭션 로그를 저장한다.
데이터 변경 내역, 트랜잭션 복구/롤백에 사용한다. SQL Server는 LDF 파일을 사용해서 복원, 롤백, 복구 등의 작업을 수행한다.

 

ㅇㅇ

 

ㅇㅇ

 

chown mssql:mssql FabrikamFiber*으로 소유권자를 변경해야 한다.

 

cd /var/opt/mssql
mkdir backup
cd backup
wget https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2014.bak
chown  mssql:mssql  AdventureWorks2014.bak
cd ..
chown  mssql:mssql  backup

ㅇㅇ

 

ㅇㅇ

 

혹시라도 접속이 안 되면 포트 확인하기

 

ㅇㅇ

 

ㅇㅇ

 

또는 SQL 명령으로 백업

 

USE master;
GO

RESTORE DATABASE AdventureWorks
FROM DISK = '/var/opt/mssql/backup/AdventureWorks2014.bak'
WITH 
    MOVE 'AdventureWorks2014_Data' TO '/var/opt/mssql/data/AdventureWorks2014_Data.mdf',
    MOVE 'AdventureWorks2014_Log' TO '/var/opt/mssql/data/AdventureWorks2014_Log.ldf';
GO

ㅇㅇ

 

ㅇㅇ

 

/var/opt/mssql 경로에 Samples 폴더를 mkdir로 만들고 vi로 예제 txt 파일을 하나 만든다.

이렇게 적은 내용은 cat 명령으로 확인할 수 있다.

 

Linux와 Windows는 newline을 다르게 관리한다.

Preview 단계에서는 SQL Server on Linux는 오직 Windows 포맷으로만 newline을 인식할 수 있다.

만약 Windows 에서 작성한 txt 파일을 복사하여 넣은 것이 아니라, txt를 직접 리눅스에서 만들어 줬다면 변환 작업이 필요하다.

 

apt-get install dos2unix
unix2dos -n samples.txt WinSampleLocations.txt

이를 통해 변경을 수행한다.

 

SSMS에서 로드 가능

 

CREATE TABLE Locations (LocationID INT, Location varchar(255));

BULK INSERT Locations
FROM '/var/opt/mssql/Samples/WinSampleLocations.txt'
WITH (FIELDTERMINATOR = ',');

SELECT * FROM Locations;

ㅇㅇ

 

보안 설정

ㅇㅇ

 

CREATE LOGIN DIO WITH PASSWORD = 'WRYYYYYYYYYY8%'

사용한 쿼리.

 

지금 접속한 건 SA 관리자 계정이라 Full Control이기에 할 수 있는 작업이다.

 

USE AdventureWorks;
GO

CREATE USER DIO;
GO

사용한 쿼리. 각 사용자에 대한 역할 부여와 권한 관리도 가능하다.

그밖에도 SQL Server 감사 기능(DB 엔진에 발생한 이벤트에 대한 추적과 기록을 남길 수 있게 해주는 기능)을 설정할 수 있다.

 

Dynamic Data Masking(동적 데이터 마스킹) 기능도 있다.

이를 행 수준 보안 기법과 연동할 수 있다.

SQL Server on Windows 개요

해당 실습은 클라우드 서비스가 아닌 on-premises 구축이다.

클라우드를 이해하기 위해 on-premises의 물리 서버, 네트워크 스위치, 스토리지, OS 등의 핵심 인프라가 존재한다.

즉 이런 전체 인프라 시스템의 핵심을 파악하는 실습이다.

 

나중에 내가 일할 곳이 반드시 클라우드 환경이라는 보장도 없고, on-premises 환경이라는 보장도 없다.

클라우드 환경이라고 해도 내가 공부한 부분이, 내 담당 업무일지 아닐지는 전혀 알 수 없다.

이번 실습은 이를 대비하기 위해 전반적인 흐름이 어떻게 이루어지는지 알기 위함이다.

 

그밖에도 금융과 보안 산업의 기업은 on-premises를 선호한다.

데이터의 통제, 법적 규제 준수, 맞춤 보안, 내부 감사, 정보 유출 감소, 예측 가능한 성능, 독립 인프라 관리, 장기 비용 효율 등의

다양한 이유로 인해서 해당 산업군은 on-premises를 아직 선호하고 있다.

 

SQL Server on Windows 실습

위 사진과 같은 형태로 on-premises를 구축한다.

SQL Server(SQL VM)를 2개를 설치하는 이유는 고가용성(High Availability) 때문이다.

단순 흐름 이해 실습을 위해서라면 하나의 SQL Server만 생성하여 실습해도 상관없어 보인다.

 

실습에 사용하기 위한 리소스 그룹을 생성한다.

 

사용할 모든 VM들이 서로 통신할 수 있도록 만들어진 '가상 네트워크' 공간을 생성한다.

 

네트워크 공간을 생성할 때 IP주소 설정탭에서 한 가지만 변경한다.

2번째 octet(옥텟) 값만 변경하면 된다. 변경할 값은 겹치지 않게 임의로 원하는 숫자를 사용하면 된다.

사실 이것도... 사용하려는 상황에 따라 다르게 설정하지만, 여기서는 내가 5번째 수강생이라, 10.5.0.0/16을 사용했다.

이게 무슨 의미인지 가볍게 설명하자면 아래와 같다.

(참고로 octet이란 ■. ■. ■. ■와 같은 IP주소에서 ■ 한 부분을 말한다.)

 

IPv4 인터넷 프로토콜은 32bit 주소 체계를 사용한다.

IPv4 주소는 '서브넷 마스크(Subnet Mask)'라는 32bit 값과 함께 사용한다.

가령 192.168.100.1/24라는 IP가 있을 때, /(슬래시)를 기준으로 좌측은 IP주소, 우측은 'subnet mask'를 말한다. 

 

IPv4 주소는 네트워크 부분(Network Part)과 호스트 부분(Host Part)으로 나눈다.

이때 서브넷 마스크에서 1은 네트워크 부분을, 0은 호스트 부분을 표시한다.

즉 '10진수. 10진수. 10진수. 10진수'라는 주소가 있을 때, 어디까지가 네트워크인지 구분하는 표기법이 서브넷 마스크다.

 └ 네트워크 부분은 '어떤 IPv4 네트워크에 있는가'를 나타내는 값

 └ 호스트 부분은 '어떤 단말인가'를 나타내는 값

 └ 구분을 하기 위한 값으로 0과 1이 번갈아 나오지 않고, 한 번 0이 나오면 그 뒤의 값은 전부 0이다.

 

서브넷 마스크 표기에는 2가지 방법이 있다.

10진수 표기법으로 32bit를 쪼개서 옥텟으로 구분하여 표기하는 방식(EX. 255.255.255.0)이 있고,

CIDR 표기법으로 10진수 표기에서 있는 1의 수를 IPv4 주소 뒤에 붙이는 방식(Ex. 192.168.100.1/24)이 있다.

 

정리하자면 10.5.0.0이라는 가상 네트워크 주소를 만들 거고,

Subnet Mask가 16이니 65,536(2^(32-16))개의 단말이 접속 가능하게 만들겠다는 의미다.

 

생성한 '가상 네트워크' 자원에서 '설정 > 주소 공간'으로 들어간다.

이후 주소 공간 CIDR의 Subnet mask가 16인지, 주소 범위는 3번째와 4번째 octet이 0.0 ~ 255.255인지 확인한다.

아니라면 주소 공간을 16으로 바꿔준다.

 

2개의 SQL Subnet을 생성한다고 했을 때, 1개의 DC(Domain Controller) Subnet까지 총 3개의 Subnet을 사용한다.

이를 위해서 Subnet mask를 16으로 할당하여 여러 개의 Subnet을 사용할 수 있게 하는 거다.

 

이후 서브넷 탭으로 이동해서 DC-subnet, SQL-subnet-1, SQL-subnet-2를 생성한다.

이때 이름은 본인이 구분할 수 있는 이름이면 상관없다.

또한 서브넷의 순서대로 3번째 4번째 octet과 subnet mask를 0.0/24, 1.0/24, 2.0/24로 설정한다.

3번째 octet은 본인이 구분할 수 있는 다른 숫자라면 상관없다. (가령 10.5.19.0/24 같은 형태)

 

이는 10.5.0.0라는 커다란 전체 가상 네트워크에서 10.5.n.0이라는 주소를 사용한다는 의미이다.

그리고 10.5.n.0의 subnet mask는 24로 설정했다.

고로 각 10.5.n.0에서 사용 가능한 단말의 개수는 256개가 된다. (2^(32-24))

 

이렇게 3개의 vnet을 설정하면 된다.

그런데 잠시, 분명 사용 가능한 단말의 개수는 256개라고 했는데, 여기서 사용 가능한 IP는 251개다.

이는 네트워크 주소(10.5.n.0), 브로드캐스트 주소(10.5.n.255)는 항상 제외하고 254개만 사용한다.

여기에 추가로 Azure는 게이트웨이 주소(10.5.n.1), DNS 서버용(10.5.n.2), 예비 대비용(10.5.n.3)으로 3개를 더 제외한다.

따라서 251개만 사용할 수 있는 상태다.

 

이러면 현재 위의 구조에서 빨간색으로 네모친 Subnet 3개를 만든 상황이다.

이제는 VM(가상 머신)을 만들 차례다.

 

가장 먼저 DC VM을 만들어준다.

DC(도메인 컨트롤러; Domain Controller)는 윈도우 서버 시스템에서 네트워크의 핵심적인 역할을 수행하는 서버다.

'도메인'이라는 중앙 집중식 관리 영역 내에서 사용자, 보안 정책 등 모든 자원을 관리하고 인증하는 역할을 담당한다.

 

Azure Resource에서 '가상 머신'을 검색해서 여러 설정을 진행한 뒤 생성한다.

여기서는 크게 '가상머신 이름', '이미지', '크기', '관리자 계정'을 신경써서 만들어야 한다.

 

가상머신 이름은, 본인이 이게 DC VM이라는 걸 구분할 수 있는 고유한 이름이면 상관없다.

이미지는, 본인이 어떤 운영체제의 가상머신을 만들지 고른다. 여기서는 Windows Server 2019를 사용한다.

크기는, 클라우드 가상 머신의 성능을 말한다. 여기서는 Stndard D2s_v3를 사용한다.

관리자 계정은, Domain Controller에 접속할 계정으로 구분할 수 있는 어떤 ID, PW든 상관없다.

여기서는 DomainAdmin이라는 이름으로 만들었고, 이하 'DC계정'으로 명명한다.

 

디스크 탭에서도 간단한 설정을 한다.

상황과 자금 상황에 맞게 'OS 디스크 유형'을 변경하면 된다.

수강생이 많아 자원이 많이 들어간다는 이유로 여기서는 '표준 HDD'로 진행한다.

(사진에서는 잘못 눌러서 표준 SSD인데, 읽기/쓰기 속도에서 큰 차이는 없어 그냥 진행했다.)

 

네트워킹 탭에서 미리 만든 DC(도메인 컨트롤러)의 서브넷으로 설정하고 생성해야 한다.

이후 검토 + 만들기로 VM을 생성한다. 

 

VM을 생성하고 나면, DC(Domain Controller)에서 사용할 VM의 네트워킹 인바운드 규칙을 설정한다.

인바운드 대상 포트 번호는 1433(SQL Port 용도), 5022(Mirroring Port 용도)다.

 

인바운드 규칙까지 설정하고 나면 Domain Controller 서버 설정을 하러 갈 차례다. 

RDP(Remote Desktop Protocol) 프로그램으로 DC VM에 접속한다.

이때 접속하는 ID, PW(사용자 자격 증명)은 DC 계정으로 접속한다.

 

잠시 기다리면 Server Manager 창이 뜨는데 여기서 Add roles and features를 선택한다.

이후 Server Roles 탭에서 Acitve Directory Domain Service와 DNS Server를 활성화한다.

그리고 Next를 끝까지 눌러 설치를 진행한다.

 

Active Directory Domain Services(AD DS)는 사용자, 컴퓨터, 그룹 등 네트워크 자원을 중앙에서 관리하는 시스템이다.

쉽게 말하면 '회사 컴퓨터 로그인 시스템'이라고 볼 수 있다.

애초에 해당 VM을 DC의 용도로 사용할 목적이니 당연히 활성화해야 하는 항목이다.

이를 활성화하면 '도메인 기반 로그인(SSO)', '정책 배포(GPO)', '디렉터리 서비스(LDAP 기반)' 등이 가능하다.

 

DNS Server는 도메인 이름을 IP 주소로 변환해주는 시스템이다.

Active Directory Domain Services는 DNS 없이 제대로 동작할 수 없는 항목이다.

따라서 AD DS를 사용하고, 손쉽게 domain으로 접속하기 위해서 활성화하는 항목이다.

 

위에서 Active Directory Domain Services(AD DS)를 활성화했기 때문에 좌측의 탭에서 설정할 수 있다.

이제 이 VM 서버를 DC(Domain Controller)로 승격하여 사용한다.

 

세부 설정이다.

위 사진처럼 설정을 진행하고 domain은 본인이 어떤 도메인을 root로 사용할지 적으면 된다.

Domian Controller Options에서 입력하는 password는 DC계정의 password를 입력한다.

 

이렇게 DC 서버로 승격하는 설정을 하고 나면, DC 설정을 적용하기 위해 재부팅을 시작한다.

재부팅에 꽤 오랜 시간이 걸리니 유의하자.

 

재부팅을 기다리는 사이에 아까 생성한 DC VM의 IP와 DNS를 이어준다.

'네트워킹 > 네트워크 설정' 탭에 들어가면 '개인 IP 주소'가 있다.

이 IP 주소는 DC-VM-1dt008-1의 주소를 가리킨다. 이 주소를 복사한다.

 

복사한 주소를 '설정 > DNS 서버' 탭에 들어가서 사용자 지정으로 바꾼 다음, IP 주소를 추가한다.

이제 DNS를 사용하여 접속하면 자동으로 IP 매핑을 하여 접속할 수 있다.

 

이제 3개의 Subnet과 DC VM 1개의 설정이 끝났다.

환경에 대한 설정이 끝났으니, 이제 프로그램을 설치하고 관리할 사람이 필요하다. 이를 위해 2개의 계정을 만든다.

하나는 감독 계정으로, 모든 VM에 로그인해서 SQL Server를 설치하고, 클러스터와 가용성 그룹을 설정하는 계정이다.

다른 하나는 일꾼 계정으로, SQL Server 서비스를 실행하고 데이터를 처리하는 계정이다.

 

Pythonic하게 비유를 하자면 이제까지 파이썬 설치와 환경변수 설정을 했다면

Master(감독 계정)는 라이브러리나 extension을 설치하고 코드를 짜는 사람인 거고,

Slave(일꾼 계정)는 작성한 코드를 계속해서 실행해주는 사람이다.

이때 Master는 설치를 주로 하기에 install이라는 이름의 계정으로 생성했다.

 

DC VM의 Server Manager 창을 위처럼 다시 띄운다.

우상단의 'Tools > Active Directory Administrative Center' 탭으로 들어간다.

 

'좌측의_내가_작성한_최상위_도메인 > Users > New > User'로 계정을 만들기 위해 들어간다.

 

감독 계정을 만든다.

Full name과 User AccountName에 설치를 주로 할 계정이기에 install이라는 이름을 지어줬다. 

비밀번호는 admin123%으로 했지만, 본인이 기억할 수 있는 거라면 뭐든 상관없다.

이 계정을 이제 MASTER 계정이라고 명명한다.

일꾼 계정을 만든다.

Full name과 User AccountName에 코드를 주로 돌리기에 SQLslave라는 이름을 지어줬다. 

비밀번호는 slave123%으로 했지만, 본인이 기억할 수 있는 거라면 뭐든 상관없다.

이 계정을 이제 SLAVE 계정이라고 명명한다.

 

MASTER 계정은 모든 VM에 접속해서 설치, 설정을 해야 하는 계정이다.

그렇기에 모든 곳에 들어가서 작업할 수 있는 권한을 설정해야 한다.

'Builtin > Properties' 탭으로 들어간다.

 

MASTER 계정에 대한 권한 설정 세부 사항이다.

위와 같은 순서대로 접속하여 Permissions에 Full Control을 골라준다.

 

계정 생성까지 끝났으니 이제 실제 SQL Server를 실을 VM을 만들 차례다.

이렇게 생성하는 VM은 SQL VM이라고 명명하겠다.

 

단순 참고 사항으로 on-premises 같은 경우 공용 IP 주소를 사용하지 않는다.

보안을 위해 대부분 개인 IP 주소만 사용한다.

외부에서 직접 접속할 수 없게 만들어 해킹과 같은 위협으로부터 VM을 보호한다고 한다.

 

Azure Resource에서 '가상 머신'을 검색해서 여러 설정을 진행한 뒤 생성한다.

DC VM을 만들 때와 같이 '가상머신 이름', '이미지', '크기', '관리자 계정'을 신경써서 만들어야 한다.

 

DC VM 때와 마찬가지로 자원을 절약해야 한다면, 디스크 설정을 변경하자.

 

네트워킹 탭에서 아까 만들어둔 10.5.n.0에 해당하는 서브넷을 이제 사용할 때가 됐다.

서브넷 탭에서 1번과 2번 중에 원하는 서브넷으로 이어준다.

이후 검토 + 만들기로 SQL VM을 생성한다. 

 

같은 방식으로 2번째 SQL VM도 생성한다.

다만 이때 '기본 사항 > 가용성 영역'에서 Zone 2으로 변경하고 만들어도 된다. (굳이 안 해도 된다는 말과 같다.)

이 가용성 영역은 VM을 만드는 데이터 센터의 위치를 말한다.

그러니까 1번 Azure 데이터 센터에 만들래? 2번 Azure 데이터 센터에 만들래?라고 물어보는 것이다.

당연히 어느 데이터 센터에 만들든 상관없지만, 가용성 영역을 나눈다면 이 자체로 HA(고가용성)을 추구하는 것이다.

어찌됐든 편한 방법으로 2개의 SQL VM을 생성하자.

 

아까 DC-subnet, SQL-subnet-1, SQL-subnet-2를 만들었고,

각각의 subnet에는 subnet mask를 24로 설정하여 256개의 단말(네트워크)을 등록할 수 있었다.

물론 5개의 예약 IP 때문에 실질적으로 사용 가능한 주소는 251개 뿐이었다.

이제 생성한 2개의 SQL VM을 SQL-subnet의 단말로서 등록해야 한다.

마치 공유기(subnet)에서 핸드폰(SQL VM)과 연결하여 네트워킹하는 것처럼 말이다.

 

이를 위해 '네트워크 > 네트워크 설정 > 네트워크 인터페이스/IP 구성'으로 들어간다.

 

IP 설정을 추가하여 어떤 주소로 사용할지 정해야 한다.

0, 1, 2, 3, 255는 이미 예약 IP로 사용하고 있기에 다섯 개를 제외한 그 어떤 숫자를 써도 상관없다.

여기서는 임의로 10.5.1.7로 설정했다.

3번째 octet이 1인 이유는 첫 번째 subnet에 등록한 SQL VM이기 때문이다.

마찬가지로 2번째 SQL VM에 대해서도 10.5.2.n으로 설정한다.

 

이제 두 개의 SQL VM을 '도메인'에 가입(join)하는 과정을 진행한다.
앞서 만든 SQL VM들을 root.domain.com이라는 거대한 '네트워크 마을(도메인)'의 정식 주민으로 등록하는 과정이다.

 

위 사진처럼 SQL VM의 RDP로 각각 접속하여 Server Manager 창이 뜰 때까지 기다린다.

이후 좌측의 Local Server 탭으로 들어가서 상단의 Workgroup을 눌러준다.

Change로 들어가 Member of Domain을 눌러 DC VM에서 설정한 Domain 이름으로 연결한다.

관리자 계정과 암호에는 DC VM의 도메인에 가입하는 과정이기 때문에 DC 계정을 입력한다.

 

만약 모든 VM의 ID와 PW를 동일하게 설정했다면, ROOT\DomainAdmin 이런 식으로 명시해줘야 인식한다.

SQL-VM-1과 SQL -VM-2 모두 RDP로 접속하여 Workgroup을 바꿔준다.


도메인에 가입해야 이유는 다음과 같다.

 └ 중앙 관리: Domain Controller가 모든 VM을 한꺼번에 관리할 수 있다. 보안 정책, 사용자 권한 설정을 한 번에 적용할 수 있다.
 └ 보안 강화: 도메인에 속한 모든 컴퓨터끼리는 서로 안전하게 통신할 수 있게 된다.
 └ 쉬운 접근: 특정 VM의 이름을 직접 외울 필요 없이, 도메인 이름으로 쉽게 찾아서 접근할 수 있다.

 

DC subnet, DC VM, SQL Sub net, SQL VM을 전부 만들고 설정했다.

이제 MASTER 계정을 2개의 SQL VM에 등록하여 설치, 관리하게 만들어야 한다.

 

각 SQL VM에 RDP로 접속하여 Server Manager 창으로 들어간다.

우상단의 'Tools > Computer Management' 탭으로 들어간다.

 

'Local Users and Groups > Groups > Administrators > Add'로 들어간다.

그리고 MASTER 계정의 아이디(install)을 추가한다.

그러기 위해서는 도메인 네트워크의 permissions이 필요하기에 DC 계정으로 허락한다.

이 과정을 2개의 SQL VM에 대해 전부 설정한다.

 

그리고 모든 VM에 로그인하기 위해서 로그인 설정을 진행한다.

각 SQL VM에 RDP로 접속하여 Server Manager 창으로 들어간다.

우상단의 'Tools > Active Directory Users and Computers' 탭으로 들어간다.

 

'자신이_만든_도메인 > Users > 자신의_MASTER_계정(여기서는 install)'으로 들어간다.

Account 탭으로 들어가서 첫 번째 설정을 끄고, 세 번째 설정을 켠다.

User must change password at next logon은 처음 로그인하기 전 반드시 비밀번호를 바꿔야 하는 설정이고,

Password never express는 비밀번호를 이후 바꾸지 않아도 되는 설정이다.

이 과정을 2개의 SQL VM에 대해 전부 설정한다.

 

이제 본격적으로 MASTER 계정으로 접속해 프로그램 설치를 진행할 차례다.

SQL VM 1과 2 모두 MASTER 계정(install)을 통해 접속한다.

 

이제 각 SQL VM에 MSSQL과 SSMS를 설치한다.

 

Custom으로 설치한다.

 

설치 진행을 하고 Installation에서 첫 번째 항목으로 진행한다.

 

Feature Selection 탭까지 와서 위의 6개 항목을 활성화하고 설치를 진행한다.

 

Server Configuration에서 SQL Server Agent를 자동으로 선택한다.

 

Database Engine Configuration에서 위 사진과 같이 설정해준다.

여기서 Enter password는 SQL Server를 위한 새로운 비밀번호다. 여기서는 sqlserver123%로 설정했지만 편할대로 하면 된다.

 

이후 SSMS 링크에 접속하여 각 SQL VM에 설치한다.

 

각 SQL VM에서 cmd를 열어 wf.msc를 입력하거나, 직접 방화벽 설정으로 들어간다.

Properties로 들어가 방화벽 설정을 꺼준다.

 

SQL VM에서 SSMS를 열어 접속이 잘 되는지 확인해보자.

Login은 SA고, Password는 SQL Server를 설정할 때 입력한 비밀번호다. 여기서는 sqlserver123%에 해당한다.

 

 

 

 

+ Recent posts