저번처럼 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를 만들 때 사용하는 실행 단위이다.

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

1. 개념

그래프(Graph)는 원소 사이의 다대다 관계를 표현하는 자료 구조이다.

버스 노선도, 전철 노선도, 인간 관계도, 수도 배관 시스템, 물질의 분자 구조 등 다양한 관계를 표현할 수 있다.

위와 같은 예시들은 선형 자료 구조나 트리 자료 구조로는 표현할 수 없다.

 

그래프는 연결할 객체를 나타내는 정점(Vertex)과 객체를 연결하는 간선(Edge)의 집합으로 정의한다.

어떤 그래프가 있을 때, 얘는 항상 정확하게 이거!라고 부르기보다는, 조합에 가깝다고 생각한다.

그래프를 어떤 분류로 나누냐에 따라 다양하게 정의할 수 있기 때문이다.

따라서, 해당 글에서는 분류에 초점을 맞추어 살펴보려고 한다.

또한, 트리(tree)도 그래프의 일종이지만, 트리 하나로도 종류가 다양하기에 추후 자세히 살펴본다.

 

2. 용어 설명

차수(Degree) : 정점에 부속되어 있는 간선의 수

진입 차수(In-Degree) : 정점을 머리로 하는 간선의 수

진출 차수(Out-Degree) : 정점을 꼬리로 하는 간선의 수

경로(Path) : 기점부터 종점까지 간선으로 연결된 정점을 순서대로 나열한 리스트

경로 길이(Path length) : 경로를 구성하는 간선 수

단순 경로(Simple Tree) : 모두 다른 정점으로 구성된 경로

사이클(Cycle) : 단순 경로 중 기점과 종점이 같은 경

DAG(Directed Acyclic Graph) : 방향 그래프이면서 사이클이 없는 그래프, 트리도 DAG의 일종

 

3. 방향성에 따른 분류

 

무방향 그래프(Undirected Graph) : 간선에 방향이 없는 그래프

방향 그래프(Directed Graph) : 간선에 방향이 있는 그래프

 

4. 연결성에 따른 분류

 

연결 그래프(Connected Graph) : 모든 정점 쌍 사이에 경로가 있는 그래프

비연결 그래프(Disconnected Graph) : 일부 정점 쌍 사이에 경로가 없는 그래프

 

5. 순환성에 따른 분류

 

순환 그래프(Cyclic Graph) : 하나 이상의 순환을 포함하는 그래프

비순환 그래프(Acyclic Graph) : 순환을 포함하지 않는 그래프

 

6. 특별한 속성에 따른 분류

 

가중치 그래프(Weighted Graph) : 간선에 가중치가 있는 그래프

이분 그래프(Bipartite Graph) : 정점을 두 개의 그룹으로 나눌 수 있는 그래프

완전 그래프(Complete Graph) : 모든 정점 쌍이 서로 연결된 그래프

 

 

다중 그래프(Multigraph) : 두 정점 사이에 여러 간선이 있는 그래프

단순 그래프(Simple Graph) : 두 정점 사이에 최대 하나의 간선만 있는 그래프

정규 그래프(Regular Graph) : 모든 정점의 차수가 동일한 그래프

 

7. 트리와 숲

 

트리(Tree) : 연결되어 있으며 순환 없는 비순환 그래프

숲(Forest) : 하나 이상의 트리로 이루어진 비순환 그래프

 

8. 응용

 

위와 같은 그래프는 어떤 그래프일까? 보는 방법에 따라서 달라질 수 있다.

방향성 기준에서 본다면, 무방향 그래프이다.

연결성 기준에서 본다면, 연결 그래프이다.

순환성 기준에서 본다면, 순환 그래프이다.

동시에 최대 하나의 간선만 가지므로 단순 그래프이면서,

모든 정점의 차수가 같아 정규 그래프이기도 하다.

 

 

이 그래프는 방향 그래프면서 비연결 그래프고, 비순환 그래프면서 단순 그래프다.

동시에 AC, DBE는 각각 트리이면서, 전체적으로 보면 숲을 이룬다.

 

 

조금 특이한 정점이 하나 뿐인 이것은 그래프라고 부를 수 있을까?

처음에 그래프는 '정점과 간선의 집합'이라고 정의했다.

정점이 1개, 간선은 0개인 집합이다. 따라서 하나 뿐인 정점도 그래프가 맞다.

공집합도 집합이지 않은가?

 

간선이 없기 때문에 방향, 무방향 판별은 무의미하다.

그렇지만 비연결 그래프이고, 비순환 그래프면서

간선이 0개이기에 '최대 하나의 간선'을 가져야 하는 단순 그래프도 만족한다.

또한, 모든 정점의 차수가 0인 정규 그래프도 마찬가지로 만족한다.

무엇보다 이분 그래프도 될 수 있다.

 

9. 결론

백준이라는 알고리즘 트레이닝 사이트에서 열심히 문제를 풀어보고 있다.

그러다보니 어느덧 단계별로 풀어보기는 31단계까지 왔다.

그렇게 '31단계 그래프와 순회'의 마지막 문제를 풀다가 의문이 들었다.

'내가 그래프의 개념을 잘못 알고 있었나?'

 

그래프를 '사이클이 존재하는 간선과 정점의 모임'으로 알고 있었다.

문제를 해석하다가 '모든 정점이 다 이어진 입력만 주어지나?'

'어라? 그래프가 입력으로 주어진다고 했는데, 정점이 끊어진 것도 그래프가 될 수 있나?'

하는 의문에 휩싸였고... 전공책을 다시 꺼내들었다.

그리고 놀랍게도 개념을 잘못 알고 있었다는 사실을 깨달았다.

 

문제를 풀수록 '정확한 개념'을 바탕으로, '유연한 사고'를 생각하는 게 중요하다.

특히 그래프 같은 경우, 어떻게 바라보는냐에 따라서 달라질 수 있다.

이를 바탕으로, 비연결(단절)인 경우, 정점이 하나인 경우 등 다양하게 고려해야 한다.

문제를 다양하게 바라보고 해석할 줄 알아야 한다.

'Computer Science > 자료 구조(Data Structure)' 카테고리의 다른 글

비트마스크(Bitmask)  (0) 2023.08.13

Convolution
ㆍ [3B1B: But what is a convolution?]
https://www.youtube.com/watch?v=KuXjwB4LzSA
ㆍ [3B1B: Convolutions | Why X+Y in probability is a beautiful mess]
https://www.youtube.com/watch?v=IaSGqQa5O-M
ㆍ [Blog: [ Math ] Convolution(합성곱)의 원리와 목적]
https://supermemi.tistory.com/entry/Convolution%ED%95%A9%EC%84%B1%EA%B3%B1%EC%9D%98-%EC%9B%90%EB%A6%AC%EC%99%80-%EB%AA%A9%EC%A0%81#google_vignette
ㆍ [The Julia Programming Language: Convolutions in image processing]
https://www.youtube.com/watch?v=8rrHTtUzyZA&t=0s

1. Prologue

"100-200*300-500+20"

프로그래머스 67257번 문제이자, 2020 카카오 인턴십 2번 문제다.

위와 같은 문자열 수식이 주어진다고 할 때, 저 수식을 활용하여 정답을 도출하는 문제였다.

문제는 저 입력이 '문자열'이라는 것이다.

어떻게 저 연산 기호와 숫자를 잘 분리하느냐가 핵심이었다.

 

string = "100-200*300-500+20"
nums = string.replace("*", "_").replace("+", "_").replace("-", "_").split("_")
for num in set(nums): string = string.replace(num, "_")
operator = string.split("_")[1:-1]

# ['100', '200', '300', '500', '20']
print(nums)

# ['-', '*', '-', '+']
print(operator)

해당 문제에서 숫자는 0에서 999까지의 숫자 범위라고 명시해놓았다.

하지만 숫자를 기준으로 나누기보다는, 3개뿐인 연산 기호를 기준으로 나누는 것이 빠르다고 생각했다.

그래서 우선 연산 기호 3종류를 전부 '_' 문자로 바꿔주었다.

 └ 익숙하면서도 무시하라는 의미가 담겨있도록 underscore를 사용하였다.

그런 다음 split() 내장 함수를 사용하여 분리하면 nums에 숫자들만 담긴다.

 

그런 다음 nums를 set 자료 구조로 바꾼다.

기존 string 문자열에서 해당 숫자 문자를 전부 '_'로 바꾸고, 다시 split()으로 분리하면, 연산 기호만 뽑을 수 있다.

이때 set을 사용하는 이유는, 숫자가 중복되는 경우가 있을 수 있기 때문이다.

예를 들어 nums에 ["10", "20", "20", "10"]이 담겼다고 해보자.

replace(bef, aft) 함수를 사용하면 해당 문자열 내에 있는 모든 bef를 aft로 바꿔준다.

즉 처음에 있는 "10"을 이미 바꿔주었기 때문에, 뒤에 있는 "10"은 바꿀 이유가 없다.

시간을 줄이기 위해 set을 사용하였다. 

 

import re

string = "100-200*300-500+20"
expression = re.split('([*+-])', string)

# ['100', '-', '200', '*', '300', '-', '500', '+', '20']
print(expression)

나름 깔끔하게 작성해서 괜찮다고 생각하던 찰나에, 정규 표현식을 접했다.

단 한 줄의 코드로 내가 위에서 작성한 과정을 보기 좋게 줄여버렸다.

이에 정규 표현식이라는 '문자열 처리'에 대한 필요성을 느꼈다.

 

과장을 조금 보태자면, 정규 표현식을 사용하면 어떤 문자열이든 단 한 줄 코딩으로 처리가 가능하다.

 

2. Regular Expression

    2.1. 개념

정규 표현식(Regular Expression)은 텍스트에서 문자열 패턴을 찾기 위한 형식이다.

특정 규칙을 가진 문자열을 표현하기 위해 사용하며, 검색ㆍ대체 ㆍ추출 같은 작업에 용이하다.

정규 표현식을 사용할 때 '메타 문자'라는 것을 사용한다.

 

    2.2. 메타 문자

메타 문자
종류 설명 예시
. 어떤 문자 하나와 일치하는지 확인 (줄바꿈 제외) a.b는 acb, aab, a3b와 일치, ab, a\nb와 불일치
^ 문자열의 시작과 일치하는지 확인 ^a는 abc, apk, a1a와 일치, ba, meta와 불일치
$ 문자열의 끝과 일치하는지 확인 a$는 aaa, sea와 일치, apple, naan과 불일치 
* 바로 앞의 문자가 0번 이상 반복하는지 확인 bo*는 b, bo, booo와 일치, bq, base와 불일치 
+ 바로 앞의 문자가 1번 이상 반복하는지 확인 b+c는 bc, bbc, bbbbc와 일치, bb, c, bac와 불일치
{ } 정확한 반복 횟수나 범위를 지정 a{2}는 aa와 일치, a{2, 3}은 aa, aaa와 일치
[ ] 대괄호 안에 문자 중 하나와 일치하는지 확인 [abc]는 a, b, c와 일치, ab, bc, ac와 불일치
\ 다음에 오는 메타 문자를 문자 그대로 취급 \^는 ^ 문자와 일치(메타 문자 취급을 하지 않음)
| 두 조건 중 하나와 일치하는지 확인 (OR) a|b는 a, b와 일치, ab, ba, aa, bb와 불일치
() 정규식을 그룹으로 묶어 여러 문자를 조합하여 사용 b(an)+a는 banana와 일치, ba, ban, bnaa과 불일치
? 바로 앞의 문자가 0번 혹은 1번 있는지 확인 ab?c는 ac와 일치, abc와 일치, abbc와 불일치

메타 문자란, 원래 문자가 가진 뜻이 아닌 다른 의미로 사용하는 문자들을 말한다.

예를 들어 마침표에 해당하는 온점(.)은 '문장의 맺음'을 표현하거나, 객체 메소드를 호출하는 용도(~의)로 사용한다.

하지만 메타 문자로서 온점은 '정확하게 이 문자와 대응하는 문자'라는 의미로서 사용한다. 

이런 메타 문자의 종류와 설명, 예시를 표로 정리하였다.

 

*, +, ?는 각각 {0, }, {1, }, {0, 1}로 표현하는 것이 가능하다.

하지만 가독성 때문에 메타문자로 적는 것을 권장한다고 한다.

적재적소에 필요에 따라 사용하면 된다.

 

    2.3. 자주 사용하는 표현 표기법

자주 사용하는 정규식은 별도의 표기법으로 표현할 수 있다
문자 클래스 설명
\d 숫자와 매치된다. [0-9]와 동일한 표현식이다.
\D 숫자가 아닌 것과 매치된다. [^0-9]와 동일한 표현식이다.
\s 공백(whitespace) 문자와 매치된다.
\S 공백 문자가 아닌 것과 매치된다.
\w 문자+숫자(alphanumeric)와 매치된다. [a-zA-Z0-9_]와 동일한 표현식이다.
\W 문자+숫자(alphanumeric)가 아닌 문자와 매치된다. [^a-zA-Z0-9_]와 동일한 표현식이다.

대괄호[ ]를 사용한 메타 문자는 다양하게 활용할 수 있다.

[a-z]는 영소문자, [A-Z]는 영대문자, [a-zA-Z]는 모든 영문자, [0-9]는 모든 숫자처럼 말이다.

그래서 이런 정규 표현식은 약어로 존재한다. 그것을 정리해 둔 표이다.

소문자와 대문자의 관계가 서로 상반됨을 알 수 있다.

 

    2.4. Regular Expression Method

Method 목적
match() 문자열의 맨 시작부터 정규식 패턴과 일치하는 내용을 찾는다.
예시 re.match('hi', 'hello hi') >>> None
re.match('hi', 'hi greeting') >>> <re.Match object; span=(0, 2), match='hi'>
search() 문자열 전체에서 정규식 패턴과 일치하는 첫 번째 내용을 찾는다.
예시 re.search('hi', 'hello hi') >>> <re.Match object; span=(6, 8), match='hi'>
re.match('hi', 'hi, hi?') >>> <re.Match object; span=(0, 2), match='hi'>
findall() 문자열 전체에서 정규식 패턴과 일치하는 모든 내용을 찾는다.
예시 re.findall('hi', 'hi hi hello') >>> ['hi', 'hi']
re.findall('a+b', 'aab, aba, aab, b') >>> ['aab', 'ab, 'aab']
finditer() 문자열 전체에서 정규식 패턴과 일치하는 모든 내용을 찾아 iterator로 반환한다.
예시 re.finditer('ab', 'abracadabra') >>> <callable_iterator object at 0x00001CB7AE06E3>
fullmatch() 문자열 전체가 정규식 패턴과 정확히 일치하는 경우에만 객체를 반환한다.
예시 re.fullmatch('Pomeranian', 'Pomegranate') >>> None
re.fullmatch('b(an)*a', 'banana') >>> <re.Match object; span=(0, 6), match='banana'>
split()  정규식 패턴을 구분자로 사용하여 문자열을 분할한다. 이때 ()를 사용하면 구분자를 포함한다.
예시 re.split('-', 'What-The-Hell') >>> ['What', 'The', 'Hell']
re.split('(-)', "What-The-Hell') >>> ['What', '-', 'The', '-', 'Hell']
sub() 문자열 전체에서 정규식 패턴과 일치하는 부분을 다른 문자열로 대체한다.
예시 re.sub(' ', '!OH!', 'friends like me') >>> friends!OH!like!OH!me
re.sub('o|g', '-', 'algorithmprogramming') >>> al--rithmpr--ammin-
subn() sub() 결과에 대체한 횟수도 함께 반환한다.
예시 re.subn(' ', '!OH!', 'friends like me') >>> (friends!OH!like!OH!me, 2)
re.subn('o|g', '-', 'algorithmprogramming') >>> (al--rithmpr--ammin-, 5)

위에서 설명한 메타 문자를 이용하여 원하는 문자열을 추출할 차례이다.

기본적으로 re(regular expression) 라이브러리를 호출해야 사용이 가능하다.

예를 들어서 'ab*'라는 정규식 패턴과 일치하는지 찾고 싶다고 해보자.

 

우선 pattern = re.compile('ab*')로 컴파일 객체 만들어야 한다.

그 다음 result = pattern.match('abbb')의 형태로 코드를 작성하면 result에 결과가 담기게 된다.

이 과정을 축약하여 result = re.match('ab*', 'abbb') 형태로 작성하는 것도 지원한다.

위의 표에서는 가독성을 위하여 축약 형태로 예시를 설명했음을 알린다.

 

    2.5. Match Method

Match 객체의 method
Method 용도 예시
group 문자열 반환 re.search('isThere', 'dvDNJisThereKMdcOnklm').group()
>>> isThere
start 문자열의 시작 위치를 반환 re.search('isThere', 'dvDNJisThereKMdcOnklm').start()
>>> 5
end 문자열의 끝 위치를 반환 re.search('isThere', 'dvDNJisThereKMdcOnklm').end()
>>> 12
span 문자열의 (시작, 끝)을 tuple로 반환 re.search('isThere', 'dvDNJisThereKMdcOnklm').span()
>>> (5, 12)

match, search, finditer 등의 메소드로 객체를 탐색할 경우, Match Object 형태로 결과를 반환한다.

하지만 일치하는 문자가 있는지? 시작은 어디인지? 끝은 어디인지?가 궁금한 것이다.

위의 4가지 메소드를 사용하면 match object 결과에서 원하는 부분만 return 할 수 있다.

 

    2.6. Compile Option

Compile Option
종류 설명 예시
IGNORECASE 또는 I 대소문자를 구분하지 않음 re.search('abc', 'ABC', re.IGNORECASE)
>>> <re.Match object; span=(0, 3), match='ABC'>
DOTALL 또는 S 줄바꿈 문자를 포함한 모든 문자와 매칭 re.search('a.b', 'a\nb', re.DOTALL)
>>> <re.Match object; span=(0, 3), match='a\nb'>
MULTILINE 또는 M 메타 문자 ^, $를 각 줄에 대해서 적용 re.search('^a', 'ba\na', re.MULTILINE)
>>> <re.Match object; span=(3, 4), match='a'>

re.search('k$', 'asd\n'apk', re/MULTILINE)
>>> <re.Match object; span=(5, 6), match='k'>
VERBOSE 또는 X 문자열에 사용한 공백과 주석 무시 re.compile(r'''
    \d+    # 숫자
    \.       # 소수점
    \d+    # 소수 부분
    ''', '3.141592', re.VERBOSE)
>>> <re.Match object; span=(0, 8), match='3.141592'>

컴파일 옵션(혹은 플래그)라고 부르는 동작 방식이 있다.

re 라이브러리에서 제공하는 패턴 객체 생성 시의 컴파일 옵션이다.

 

마지막 VERBISE 옵션에서 사용한 r'' 문법은 raw string 문법이다.

메타 문자를 활용하여 [\d+]라고 사용하지 않고 단 한 글자만 필요하여 \d라고 쓰고 싶다고 해보자.

이러면 역슬래시(\)를 메타 문자로 인식해버린다. 따라서 \d가 아닌 \\d라고 작성해주어야 한다.

이것을 r'\d'라고 작성할 수 있다.

예시에서 r''' '''로 3개씩 사용한 이유는 여러 줄에 걸쳐있는 경우이기 때문이다.

 

 

고민을 시작한 문제 출처 : https://school.programmers.co.kr/learn/courses/30/lessons/67257

참고한 문서 자료 : https://wikidocs.net/4308

참고한 문서 자료 : https://wikidocs.net/21703

참고한 웹 사이트 : https://nachwon.github.io/regular-expressions/

참고한 웹 사이트 : https://spidyweb.tistory.com/373

'Computer Science > 파이썬(Python)' 카테고리의 다른 글

Python Decorator  (0) 2023.11.12
Python Iterator  (0) 2023.11.11
Python list.pop(0) vs deque.popleft()  (0) 2023.10.03
Python float OverflowError  (0) 2023.09.19
Python Namespace(Scope)  (0) 2023.08.18

+ Recent posts