본문으로 건너뛰기

"SSH" 태그로 연결된 3개 게시물개의 게시물이 있습니다.

모든 태그 보기

Barman rsync 모드

· 약 6분

streaming은 알겠고, rsync는 언제 쓰나

2편에서 Barman의 streaming-only 모델을 셋업했어요. 이 글에서는 같은 lab을 rsync 모델로 바꾸거나 처음부터 rsync로 셋업하고, 왜 rsync를 골랐는지 정리해 보려고 해요.

처음 보는 독자도 따라올 수 있게 환경을 짧게 정리합니다.

호스트역할Barman 라벨
demo-pg01PostgreSQL 17 primary해당 없음
demo-barman01Barman 3.18 서버pg01

pg01은 Barman 서버 라벨이고, demo-pg01은 실제 호스트네임입니다. 환경 셋업이 처음이라면 2편을 먼저 보고 오는 편이 빠릅니다.

rsync 모델 vs streaming 모델 한눈에

항목rsync 모델 (이 글)streaming 모델 (2편)
도입 시기Barman 1.x (2012-)Barman 2.0 (2016-)
전송 채널SSH + rsyncPostgreSQL streaming replication
WAL 수집archive_command (폴링)pg_receivewal (실시간 stream)
의존양방향 SSH 키replication slot, replication user
증분 dedup하드링크 (reuse_backup = link)(PG 17+ 블록 레벨 점진 이행)
병렬 복사parallel_jobs = N(단일 stream)
적합 환경베어메탈/VM, SSH 통제 가능컨테이너/K8s, SSH 미허용

이 글의 주제는 다섯/여섯 번째 행에 있는 하드링크 dedup과 parallel_jobs입니다. 둘 다 rsync 모델 고유의 강점입니다.

양방향 SSH 셋업

rsync 모델은 두 방향의 SSH가 필요합니다.

방향용도
barman@demo-barman01postgres@demo-pg01base backup 시 rsync로 데이터 디렉토리 풀링
postgres@demo-pg01barman@demo-barman01archive_command로 WAL을 Barman에 푸시

비밀번호 없이 통과해야 cron이 자동으로 돌므로 SSH 키 기반입니다.

첫 번째 방향은 demo-barman01barman 사용자에서 설정합니다.

sudo -u barman ssh-keygen -t ed25519 -N "" -f ~barman/.ssh/id_ed25519 \
-C "barman@demo-barman01"

# 공개키를 demo-pg01의 ~postgres/.ssh/authorized_keys에 등록
sudo -u barman ssh-copy-id postgres@demo-pg01

# 검증 — 비밀번호 없이 통과해야 함
sudo -u barman ssh postgres@demo-pg01 'echo ok'

두 번째 방향은 demo-pg01postgres 사용자에서 설정합니다.

sudo -u postgres ssh-keygen -t ed25519 -N "" -f ~postgres/.ssh/id_ed25519 \
-C "postgres@demo-pg01"
sudo -u postgres ssh-copy-id barman@demo-barman01
sudo -u postgres ssh barman@demo-barman01 'echo ok'

운영 환경에서는 authorized_keysfrom="..." 호스트 제약이나 command="..." 락다운을 거는 편이 안전합니다. 이 글은 lab이라 단순화했습니다.

PostgreSQL 측의 archive_command 활성화

/var/lib/pgsql/17/data/postgresql.conf 핵심 항목:

listen_addresses = '*'
wal_level = replica
archive_mode = on
archive_command = 'barman-wal-archive -U barman demo-barman01 pg01 %p'

barman-wal-archive는 Barman 패키지에 같이 설치됩니다. 내부적으로 SSH로 barman@demo-barman01에 접속해 WAL 파일을 /var/lib/barman/pg01/incoming/에 정확히 전달합니다 — cp나 직접 scp하는 것보다 안전합니다.

pg_hba.conf에는 Barman의 conninfo 접속을 열어 둡니다. replication slot은 rsync 모델에서 필수가 아니지만, barman check가 PostgreSQL 메타데이터를 읽으려면 이 접속이 필요합니다.

host postgres barman demo-barman01 scram-sha-256

PostgreSQL 재시작 후 사용자 생성:

sudo systemctl restart postgresql-17
sudo -u postgres psql <<'SQL'
CREATE USER barman WITH ENCRYPTED PASSWORD 'changeme';
GRANT pg_read_all_settings, pg_read_all_stats TO barman;
SQL

streaming 모델과 달리 REPLICATION 속성/replication slot은 생성하지 않습니다.

Barman 측의 backup_method = rsync 설정

/etc/barman.d/pg01.conf를 다음과 같이 설정합니다.

[pg01]
description = "Production PostgreSQL primary (rsync mode)"
ssh_command = ssh postgres@demo-pg01
conninfo = host=demo-pg01 user=barman dbname=postgres
backup_method = rsync
parallel_jobs = 2
reuse_backup = link
archiver = on
retention_policy = RECOVERY WINDOW OF 4 WEEKS

핵심 다섯 줄의 의미는 이렇습니다.

의미
backup_method = rsyncbase backup 때 SSH+rsync로 데이터 디렉토리를 가져옵니다
ssh_command = ssh postgres@demo-pg01rsync가 사용할 SSH 명령입니다. barman 사용자에서 postgres@demo-pg01로 접속합니다
parallel_jobs = 2병렬 rsync worker 수입니다. 디스크/네트워크 여유에 맞춰 조정합니다
reuse_backup = link이전 backup에서 변경되지 않은 파일을 하드링크로 재사용합니다. 핵심 dedup 옵션입니다
archiver = onarchive_command로 들어오는 WAL을 자동으로 처리합니다

streaming 모델의 streaming_conninfo, streaming_archiver, slot_name 키는 모두 사용하지 않습니다.

설정 검증:

sudo -u barman barman check pg01

receive-wal running은 streaming 모델 전용이므로 rsync 모드에서는 항목이 빠지거나 disabled로 나옵니다. 그 외 항목이 모두 OK면 셋업이 끝난 것입니다.

첫 backup + 하드링크 dedup 실측

# 첫 base backup
sudo -u barman barman backup pg01

기대 출력 (요약):

Starting backup using rsync-over-ssh method for server pg01 ...
Copy done (time: 12 seconds)
Backup size: 41.5 MiB

이번에는 의도적으로 작은 변경만 만든 뒤 두 번째 backup을 수행합니다.

# 데이터 일부 갱신
sudo -u postgres psql -c "UPDATE notes SET body = body || '!' WHERE id < 100;"

# 두 번째 base backup — reuse_backup = link 가 동작
sudo -u barman barman backup pg01
sudo -u barman barman list-backup pg01

이제 디스크 사용량을 두 가지 방식으로 잽니다.

# 실제 디스크 사용량 (하드링크는 한 번만 카운트)
sudo du -sh /var/lib/barman/pg01/base/
# 예: 42.0 MiB

# 논리 사용량 (하드링크가 중복 카운트되어 backup별로 따로 잡힘)
sudo du -sh --apparent-size /var/lib/barman/pg01/base/
# 예: 83 MiB

두 측정값의 차이인 41 MiB ≈ 첫 backup 크기가 dedup으로 절약된 디스크 공간입니다. 두 번째 backup은 변경된 페이지만 새로 차지하고 나머지는 첫 backup 파일에 hardlink로 연결되므로, backup 횟수가 늘수록 누적 절약 효과가 커집니다.

rsync를 선택하는 기준 4가지

기준rsync 유리streaming 유리
환경베어메탈/VM, SSH 통제 가능컨테이너/K8s, SSH 미허용
디스크 절약하드링크 dedup → 동일 backup 다수 보관 시 절약 큼블록 레벨 dedup은 PG 17+로 점진 이행 중
병렬화parallel_jobs로 N개 worker단일 stream
운영 부담양방향 SSH 키 관리replication slot 관리

베어메탈/VM에서 디스크 효율과 병렬 복사가 중요하면 rsync가 잘 맞습니다. 컨테이너/K8s에서 SSH를 피하고 운영을 단순화하려면 streaming이 유리합니다. 둘 다 가능한 환경이라면 운영자의 SSH 키 관리 부담이 적은 streaming이 무난합니다.

streaming과 rsync 사이의 전환

같은 라벨로 두 모델을 동시에 운영할 수는 없습니다. backup_method는 하나만 지정할 수 있기 때문입니다. 전환 절차는 다음과 같습니다.

# 1단계: 기존 라벨 비활성화
[pg01]
active = false
...
# 2단계: 새 conf 파일로 다른 라벨 만들기 (예: pg01-rsync)
[pg01-rsync]
backup_method = rsync
...

새 라벨에 backup이 안정적으로 들어오는 것이 확인되면, 보통 1주일+ 경과/복구 리허설 1회 이상을 기준으로 기존 라벨을 폐기합니다.

운영 환경에서는 기존 backup을 즉시 버리지 않습니다. 보존 정책에 따라 자연 만료될 때까지 두 라벨을 함께 보관하면 이전 시점 PITR이 필요할 때 안전망이 됩니다.

정리

항목내용
백업 모델backup_method = rsync, SSH+rsync 기반
WAL 수집archive_command = 'barman-wal-archive ...'
SSH 방향양방향 (rsync용 + archive_command용)
핵심 dedupreuse_backup = link, 하드링크 기반이며 백업 횟수가 누적될수록 절약 큼
병렬 복사parallel_jobs = N
streaming과의 관계동시 사용 불가 (라벨당 한 모델), 전환 시 새 라벨로 병행 운영 후 폐기

streaming은 운영 단순화에 강하고 rsync는 디스크 효율과 병렬화에 강해요. 환경 때문에 한쪽을 골라야 하는 상황이 아니라면 streaming부터 시도해 보는 편이 무난해요.

참고 자료

SSH ProxyJump 접속

· 약 4분

문제 상황

클라우드 환경에서 흔히 볼 수 있는 구성이 있어요. 보안을 위해 내부 서버(DB, 앱 서버 등)는 외부에서 직접 접속할 수 없고, Bastion 서버(점프 서버)를 통해서만 접근할 수 있는 구조예요.

[내 PC] → [Bastion 서버] → [내부 서버]
(공인 IP) (사설 IP)

이런 구조에서 내부 서버에 접속하려면 보통 두 단계를 거칩니다.

# 1단계: Bastion 서버에 접속
ssh user@bastion-host

# 2단계: Bastion에서 내부 서버에 접속
ssh user@internal-host

매번 두 번 SSH를 치는 건 번거롭고, 파일 전송(scp)은 더 복잡해집니다. SSH ProxyJump를 사용하면 이 과정을 한 줄로 줄일 수 있습니다.

ProxyJump란?

OpenSSH 7.3(2016년)부터 추가된 기능으로, SSH 접속 시 중간 서버를 점프 호스트로 지정할 수 있습니다. 중간 서버에 셸을 열지 않고, TCP 포워딩만 수행합니다.

주요 특징은 다음과 같습니다.

  • Bastion 서버에 로그인하지 않고 통과만 합니다.
  • 최종 목적지까지 암호화가 유지됩니다(Bastion에서 트래픽을 볼 수 없습니다).
  • 파일 전송(scp, rsync)도 동일하게 동작합니다.

기본 사용법: -J 플래그

ssh -J user@bastion-host user@internal-host

이 한 줄로 Bastion을 거쳐 내부 서버에 바로 접속합니다.

예시

# Bastion(203.0.113.10)을 거쳐 내부 DB 서버(10.0.1.50)에 접속
ssh -J admin@203.0.113.10 dbadmin@10.0.1.50

포트가 다른 경우

# Bastion이 2222 포트를 사용하는 경우
ssh -J admin@203.0.113.10:2222 dbadmin@10.0.1.50

SSH Config로 영구 설정

매번 -J 플래그를 타이핑하는 건 여전히 번거롭습니다. ~/.ssh/config에 설정해두면 ssh internal-db만으로 접속할 수 있습니다.

# Bastion 서버
Host bastion
HostName 203.0.113.10
User admin
IdentityFile ~/.ssh/id_ed25519

# 내부 DB 서버 (Bastion 경유)
Host internal-db
HostName 10.0.1.50
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519

이제 다음과 같이 접속합니다.

ssh internal-db

파일 전송도 자연스럽게 동작합니다.

# 내부 서버로 파일 복사
scp backup.sql internal-db:/tmp/

# 내부 서버에서 파일 가져오기
scp internal-db:/var/log/app.log ./

# rsync도 동일
rsync -avz ./deploy/ internal-db:/var/www/app/

다중 점프 (Multi-hop)

Bastion이 여러 단계인 경우도 지원합니다.

[내 PC] → [Bastion 1] → [Bastion 2] → [최종 서버]

-J 플래그 방식

ssh -J user@bastion1,user@bastion2 user@final-host

쉼표로 구분하면 순서대로 점프합니다.

SSH Config 방식

Host bastion1
HostName 203.0.113.10
User admin

Host bastion2
HostName 10.0.1.1
User admin
ProxyJump bastion1

Host final-server
HostName 10.0.2.50
User deploy
ProxyJump bastion2

final-serverbastion2bastion1 → 인터넷 순서의 체인으로 연결됩니다.

ProxyJump vs ProxyCommand

SSH Config에는 비슷한 역할을 하는 ProxyCommand가 있습니다. 차이점은 다음과 같습니다.

# ProxyJump (간단)
Host internal
ProxyJump bastion

# ProxyCommand (구버전 호환)
Host internal
ProxyCommand ssh -W %h:%p bastion
ProxyJumpProxyCommand
도입OpenSSH 7.3+OpenSSH 5.4+
문법간단복잡
다중 점프쉼표로 구분중첩 필요
커맨드라인-J 플래그-o ProxyCommand=...

OpenSSH 7.3 이상이면 ProxyJump를 쓰는 편이 낫습니다. 더 간단하고 다중 점프도 깔끔합니다.

OpenSSH 버전은 ssh -V로 확인합니다.

실전 팁

1. Bastion 서버에 에이전트 포워딩

내부 서버 접속 시 로컬의 SSH 키를 사용하려면 에이전트 포워딩을 활성화합니다.

Host bastion
HostName 203.0.113.10
User admin
ForwardAgent yes

보안 주의: ForwardAgent는 Bastion 서버의 root가 에이전트 소켓에 접근할 수 있으므로, 신뢰할 수 있는 서버에서만 사용해야 합니다.

2. 접속 끊김 방지 (KeepAlive)

점프 호스트를 거치면 타임아웃이 발생하기 쉽습니다.

Host *
ServerAliveInterval 60
ServerAliveCountMax 3

60초마다 keepalive 패킷을 보내서 연결을 유지합니다.

3. 포트 포워딩과 함께 사용

Bastion을 거쳐 내부 DB 포트를 로컬로 포워딩할 수도 있습니다.

# 내부 PostgreSQL(5432)을 로컬 15432로 포워딩
ssh -J bastion -L 15432:10.0.1.50:5432 admin@10.0.1.50 -N

이제 localhost:15432로 내부 DB에 접속할 수 있습니다.

psql -h localhost -p 15432 -U postgres

SSH Config로 설정하면 더 깔끔합니다.

Host db-tunnel
HostName 10.0.1.50
User dbadmin
ProxyJump bastion
LocalForward 15432 localhost:5432
ssh -N db-tunnel
# 다른 터미널에서: psql -h localhost -p 15432 -U postgres

정리

상황명령어
단일 점프ssh -J bastion internal
다중 점프ssh -J bastion1,bastion2 final
파일 전송scp -J bastion file internal:/path
포트 포워딩ssh -J bastion -L 15432:db:5432 db-host -N

SSH ProxyJump는 Bastion 서버 환경에서 꼭 알아둘 만한 기능이에요. SSH Config에 한 번 설정해두면 내부 서버도 직접 연결된 것처럼 쓸 수 있어요.

NCP Hugo 블로그 배포

· 약 4분

이 글은 Hugo + PaperMod 블로그 세팅 시리즈의 두 번째 글이에요.

  1. Hugo + PaperMod로 기술 블로그 만들기
  2. NCP 서버에 Hugo 블로그 배포하기 ← 현재 글
  3. 커스텀 도메인 연결과 Let's Encrypt SSL 설정

배포 구조

[로컬 macOS] [NCP Rocky Linux]
content/*.md
↓ hugo --minify
public/ ──── rsync ────→ /var/www/blog/ ←── Nginx 서빙

Hugo가 생성한 정적 파일을 rsync로 서버에 전송하고, Nginx가 서빙하는 단순한 구조입니다. DB도 Node.js도 필요 없습니다.

1. SSH 키 인증 설정

매번 비밀번호를 입력하는 건 번거롭고, 스크립트 자동화도 불가능합니다. SSH 키 인증을 먼저 세팅합니다.

키 생성

ssh-keygen -t ed25519 -C "blog-deploy"
  • ed25519: RSA보다 짧고 안전한 알고리즘
  • passphrase: 비워도 되고, 입력하면 macOS Keychain이 기억해 줍니다

서버에 공개키 등록

ssh-copy-id -i ~/.ssh/id_ed25519.pub root@서버IP

이때 마지막으로 서버 비밀번호를 입력합니다. 이후로는 비밀번호 없이 접속 가능합니다.

SSH config 설정 (선택)

~/.ssh/config에 아래를 추가하면 ssh ncp-blog만으로 접속할 수 있습니다.

Host ncp-blog
HostName 서버IP
User root
IdentityFile ~/.ssh/id_ed25519

2. Nginx 설정 파일

Hugo 정적 파일을 서빙하기 위한 Nginx 설정입니다.

server {
listen 80;
server_name 서버IP;

root /var/www/blog;
index index.html;

# gzip 압축
gzip on;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;

# 정적 파일 캐싱 (30일)
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}

# Hugo 정적 사이트 서빙
location / {
try_files $uri $uri/ =404;
}

# 404 페이지
error_page 404 /404.html;
location = /404.html {
internal;
}

# 보안 헤더
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}

핵심 설정:

  • try_files $uri $uri/ =404: 파일 → 디렉토리(index.html) → 404 순서로 탐색
  • gzip: 텍스트 기반 파일을 압축해서 전송 (대역폭 절약)
  • expires 30d: CSS/JS/이미지를 30일 캐싱 (재방문 시 빠른 로딩)

3. 서버 세팅

SSH로 서버에 접속해서 실행합니다.

# Nginx 설치
dnf install -y nginx

# 블로그 디렉토리 생성
mkdir -p /var/www/blog

# Nginx 설정 파일 복사 (로컬에서)
scp deploy/nginx/blog.conf ncp-blog:/etc/nginx/conf.d/blog.conf

# 설정 검사 + 시작
ssh ncp-blog "nginx -t && systemctl enable nginx && systemctl start nginx"

systemctl enable은 서버가 재부팅되어도 Nginx가 자동 시작되게 합니다.

4. 배포 스크립트

매번 명령어를 입력하는 것은 번거로우니 스크립트로 만듭니다.

deploy.sh:

#!/bin/bash
set -e

SERVER_USER="root"
SERVER_HOST="서버IP"
SERVER_PORT="22"
REMOTE_DIR="/var/www/blog"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"

build() {
echo "=== Hugo 빌드 시작 ==="
hugo --minify
echo "=== 빌드 완료 ==="
}

deploy() {
echo "=== 서버 배포 시작 ==="
rsync -avz --delete \
-e "ssh -p ${SERVER_PORT}" \
public/ \
"${SERVER_USER}@${SERVER_HOST}:${REMOTE_DIR}/"
echo "=== 배포 완료 ==="
}

case "${1:-all}" in
build) build ;;
deploy) deploy ;;
all) build && deploy ;;
*) echo "사용법: $0 {build|deploy|all}" ; exit 1 ;;
esac

사용법:

chmod +x deploy.sh

./deploy.sh build # 빌드만
./deploy.sh deploy # 배포만
./deploy.sh # 빌드 + 배포

rsync의 --delete 옵션은 서버에서 로컬에 없는 파일을 삭제합니다. 글을 지웠을 때 서버에도 반영되게 하려면 필요합니다.

--delete 함정: 서버에 수동으로 올려둔 파일(임시 백업, 정적 다운로드 자료 등)도 같이 지워집니다. 빌드 산출물 외의 파일은 같은 디렉토리에 두지 마십시오. 보존이 필요하면 별도 디렉토리(/var/www/blog-extra/)로 분리하거나 --exclude='backups/' 패턴을 추가합니다.

5. NCP ACG (방화벽) 설정

서버에서 curl localhost는 되는데 외부에서 접속되지 않는다면, NCP ACG 설정을 확인해야 합니다.

NCP 콘솔 → Server → ACG → 인바운드 규칙:

프로토콜포트허용 소스
TCP800.0.0.0/0
TCP4430.0.0.0/0

NCP는 OS 레벨 방화벽(firewalld)과 별개로 ACG라는 네트워크 방화벽을 사용합니다. 둘 다 확인해야 합니다.

배포 확인

curl -s -o /dev/null -w "HTTP %{http_code}, %{time_total}s" http://서버IP/
# HTTP 200, 0.021s

다음 글

IP 주소로 접속하는 블로그는 아무래도 불편해요. 다음 글에서는 커스텀 도메인을 연결하고 HTTPS를 설정할게요.

커스텀 도메인 연결과 Let's Encrypt SSL 설정