RAID5 2026.09.08 7 min read

RAID5 NFS와 백업에서 권한 거부를 해결한 과정

NFS에 쓰기 권한이 있는데도 App과 백업에서 접근이 거부됐다. UID/GID와 root_squash를 확인해 수정하고, 두 Worker의 파일 공유와 MySQL 백업 복원을 시험했다.

MeetBack의 이미지와 첨부파일은 App이 어느 Worker에서 실행되든 같아야 했다. DB 백업과 서비스 로그를 별도 장비에 보관할 공간도 필요했다.

접근하는 서비스가 달라 NFS를 두 역할로 나눴다.

  • NFS 1: 애플리케이션 업로드 파일
  • NFS 2: MySQL 덤프와 서비스 로그

RAID5가 Linux에서 한 개 디스크로 보인 이유

NFS 장비는 하드웨어 RAID 컨트롤러에서 여러 디스크를 RAID5 논리 디스크로 묶은 상태였다. Linux의 lsblk에는 개별 물리 디스크 대신 /dev/sda 하나만 표시됐다.

처음에는 RAID 설정이 빠진 것으로 보였다. 확인해 보니 컨트롤러가 디스크를 묶어 OS에 하나의 논리 볼륨으로 제공하는 정상 상태였다. 물리 디스크와 RAID 상태는 컨트롤러 도구에서, 파일시스템은 Linux에서 따로 확인해야 했다.

논리 디스크 위에는 LVM을 구성했다. 루트와 NFS 데이터 영역을 분리하고 ext4를 /srv/nfs에 마운트했다. 이후 용량을 조정하거나 사용량을 확인할 때 두 영역을 따로 다룰 수 있게 했다.

한 export에 모든 데이터를 넣지 않았다

업로드 파일과 DB 백업은 접근 계정과 보관 목적이 달랐다. 한 export에 넣으면 App 계정이 백업에 접근할 수 있고, 업로드 파일이 늘면서 백업 공간까지 차지할 수 있었다.

NFS 1의 App 데이터는 두 Worker에만 공유했다. NFS 2의 DB 백업은 DB 두 대에, 서비스 로그는 필요한 Worker에만 공유했다. NFSv4.2와 TCP를 사용하고 root_squash는 유지했다.

MySQL 데이터 디렉터리는 로컬 디스크에 뒀다. DB가 실행되는 동안 NFS의 지연이나 장애에 직접 영향을 받지 않게 하고, 완료된 덤프만 NFS로 보냈다.

rw인데도 Permission denied가 발생했다

Export에 rw가 설정돼 있었는데도 컨테이너가 NFS 볼륨을 열지 못했다. Swarm 태스크는 애플리케이션을 시작하기도 전에 Rejected 상태가 됐다. 네트워크와 마운트 옵션을 의심했지만, 실제로 맞지 않았던 것은 POSIX UID/GID였다.

NFS는 숫자 UID/GID로 권한을 판단한다. App 컨테이너는 10001:10001로 실행되는데 디렉터리 소유자의 ID가 달라 쓰기가 거부됐다. root_squash가 있어 root로 시험해도 익명 사용자로 처리될 수 있었다.

no_root_squash를 적용하는 대신 App 디렉터리 소유자를 컨테이너의 UID/GID로 맞췄다. 접근 모드는 0750으로 제한했다. 변경 후 두 Worker의 App 컨테이너가 같은 공유 디렉터리를 사용할 수 있었다.

백업 조회도 실제 서비스 계정으로 해야 했다

백업 디렉터리를 root로 find했을 때도 Permission denied가 나왔다. 여기서는 root_squash가 의도한 대로 적용된 결과였다.

백업은 전용 dbbackup 계정으로 실행했다. NFS와 DB 서버에서 UID/GID를 같게 만들고 디렉터리 소유자도 맞췄다. 검증할 때도 이 계정으로 실행하도록 명령을 바꿨다.

sudo -u dbbackup find /mnt/meetback-db-backup \
  -type f -name '*.sql.gz'

백업 작업이 쓸 계정으로 파일을 찾고 읽을 수 있는지 확인했다. root로 접근이 되지 않는다는 이유만으로 권한을 넓힐 필요는 없었다.

automount가 비어 보이는 것도 오류가 아니었다

systemd automount에서는 경로에 접근하기 전까지 mount나 findmnt에 예상한 NFS 연결이 보이지 않을 수 있다. 실제 접근이 들어와야 마운트되는 방식이었다.

자동 마운트 유닛의 활성 상태와 대상 경로를 확인했다. 이어서 파일 경로에 접근하고 findmnt를 다시 조회해 NFS가 연결됐는지 확인했다.

백업은 생성보다 복원을 기준으로 설계했다

DB1에서 매일 mysqldump를 생성했다. gzip으로 압축하고 SHA-256 파일을 함께 만들어 NFS 2에 저장하도록 했다. 타이머에는 놓친 실행을 보완하는 persistent 설정과 실행 시각을 분산하는 지연 설정을 적용했다.

최종 검증에서는 다음을 확인했다.

  • 타이머 enabled, active (waiting)
  • 최근 백업 서비스 실행 결과 success
  • 최신 .sql.gz 파일 존재
  • 전용 계정 소유와 접근 모드 0600
  • gzip -t 무결성 PASS
  • SHA-256 계산 성공
  • 일회용 MySQL로 복원 후 스키마와 테이블 확인

Replica의 백업 타이머는 평상시 꺼 뒀다. 수동으로 Replica를 승격하면 백업도 새 Primary에서 실행해야 하므로 이 작업을 장애 전환 절차에 포함했다.

두 replica가 정말 같은 파일을 보는지 시험했다

두 Worker가 같은 파일을 보는지도 직접 시험했다. Worker 1의 App 컨테이너에서 표식 파일을 만들고 Worker 2에서 내용을 읽었다. Worker 2에서 삭제한 뒤 Worker 1에서도 사라졌는지 확인했다.

컨테이너 UID, NFS export, 마운트 옵션과 통신 경로가 맞아야 이 읽기·삭제가 모두 성공한다. 볼륨 설정 조회 결과에 더해 실제 공유 결과까지 확인한 것이다.

남은 한계

RAID5로 물리 디스크 한 개의 장애를 견딜 수 있지만 NFS 서버 자체의 장애에는 대응하지 못한다. NFS 1이 멈추면 업로드 경로가, NFS 2가 멈추면 새 백업과 로그 저장이 영향을 받는다.

현재는 디스크 보호, 역할별 권한 분리, 백업 복원을 확인한 상태다. 서버까지 이중화하려면 다른 장비나 오브젝트 스토리지로 사본을 보내는 방법, NFS의 active/standby 구성, 복원 시험 자동화를 더 검토해야 한다.

내부 서비스를 외부로 연결한 Cloudflare Tunnel과 NPM 경로는 별도 글에서 정리했다.

실제 검증 화면

  1. NFS 1의 논리 디스크, LVM, ext4 마운트와 Worker 두 대에 제한한 App 데이터 export를 확인했다.
  2. NFS 2는 DB 백업과 서비스 로그의 접근 허용 서버를 나눴다.
  3. 백업 타이머가 활성 상태였고 dbbackup 계정으로 최신 파일의 gzip 무결성과 SHA-256을 검사했다.
  4. Worker 1에서 Docker 볼륨 옵션과 실제 NFS 마운트를 확인했다.
  5. Worker 2에서도 같은 export와 NFSv4.2 옵션을 확인했다.
    첫 번째 NFS 서버의 LVM 구성, 마운트 상태와 Worker에 공유한 앱 데이터 디렉터리 조회 결과

두 번째 NFS 서버의 LVM 구성과 DB 백업·서비스 로그 디렉터리의 공유 설정 조회 결과

root의 접근 거부 이후 백업 계정으로 SQL gzip 무결성 PASS와 SHA256을 확인한 터미널

첫 번째 Worker에서 앱 데이터 볼륨이 NFSv4.2로 마운트된 것을 확인한 결과

두 번째 Worker에서 같은 앱 데이터 공유 경로가 NFSv4.2로 마운트된 것을 확인한 결과

SEARCH JOURNAL

기록 검색

글 제목, 요약, 태그에서 찾습니다.

검색을 준비하고 있어요.

검색어를 입력해 주세요.

    Ctrl/⌘ K 검색 Esc 닫기 ↑ ↓ 결과 이동