-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)을 읽어와서 방화벽 시스템에 다시 로드
# 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으로 설정하면 접속 중인 플레이어가 없어도 서버는 계속 돌아간다.
/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!
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
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 문제는 계속해서 발견
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)가 가져오려는 데이터 파일의 형식과 지정한 데이터 형식이 일치하지 않을 때 주로 발생합니다.
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
결론부터 말씀드리면, 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 로그와 함께 서버가 종료
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
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
# 컨테이너 이름: 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라는 역할이 존재하지 않습니다.
해결: 데이터베이스 객체의 소유자 변경이나 권한 부여는 실패했지만, 테이블과 데이터 자체는 생성되었습니다.
# 호스트 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
# ...
초기화 스크립트 감지: 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 데이터 볼륨을 삭제하고 컨테이너를 다시 실행해야 합니다.
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) 목록 및 인덱스 파일을 최신 정보로 갱신한다.
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 보안 접속
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만 가능
만약 그럼에도 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: 클라우드 빅데이터 플랫폼 자동화할 때 사용