RAID5 NFS와 백업에서 권한 거부를 해결한 과정
MeetBack에는 사용자가 올리는 이미지와 첨부파일이 있다. App replica가 어느 Worker에서 실행되더라도 같은 파일을 봐야 한다. DB backup과 service log도 별도 장비에 보관해야 했다.
나는 NFS를 두 역할로 나눴다.
- NFS 1: application upload data
- NFS 2: MySQL dump와 service log
RAID5가 Linux에서 한 개 디스크로 보인 이유
NFS 장비는 hardware RAID controller에서 여러 disk를 RAID5 logical disk로 묶었다. Linux의 lsblk에는 개별 physical disk가 아니라 하나의 /dev/sda만 보였다.
처음에는 RAID 구성이 사라진 것처럼 보였지만 정상 동작이었다. Hardware controller가 physical disk를 추상화하고 OS에는 logical volume 하나만 제공하기 때문이다. RAID 상태는 controller 관리 도구에서, filesystem 상태는 Linux에서 각각 확인해야 했다.
Logical disk 위에는 LVM을 구성했다. Root와 NFS data 영역을 분리하고 ext4 /srv/nfs로 mount했다. 이 구조는 용량 확장과 역할 구분을 단순하게 해 줬다.
한 export에 모든 데이터를 넣지 않았다
Application file과 DB backup은 접근 주체와 보존 목적이 다르다. 같은 export에 두면 app account가 backup까지 접근할 수 있고, upload 사용량이 backup 공간을 잠식할 수 있다.
NFS 1의 app data는 두 Worker에만 export했다. NFS 2의 DB backup은 DB 두 대에, service log는 필요한 Worker에만 export했다. NFSv4.2와 TCP를 사용하고 root_squash를 유지했다.
MySQL data directory 자체는 NFS에 두지 않았다. DB 실행 경로에 network filesystem latency와 장애를 직접 넣지 않고, DB data는 local disk에 두며 dump 결과만 NFS로 보냈다.
rw인데도 Permission denied가 발생했다
Export option에 rw가 있는데 container가 NFS volume을 열지 못했고 Swarm task가 시작 전에 Rejected 됐다. 처음에는 network나 mount option 문제를 의심했지만 원인은 POSIX UID/GID였다.
NFS는 사용자 이름이 아니라 숫자 UID/GID를 기준으로 권한을 판단한다. App container는 10001:10001로 실행되는데 export directory owner가 다른 ID면 쓸 수 없다. root_squash 때문에 root로 시험해도 익명 사용자로 바뀌어 실패할 수 있다.
나는 no_root_squash로 우회하지 않았다. App directory owner를 container UID/GID에 맞추고 mode를 0750으로 제한했다. 그 결과 두 Worker의 app container가 같은 공유 directory를 정상적으로 사용했다.
백업 조회도 실제 서비스 계정으로 해야 했다
DB backup directory를 root로 find했을 때 Permission denied가 나왔다. 장애처럼 보이지만 root_squash가 의도대로 작동한 결과였다.
Backup service는 전용 dbbackup 계정으로 실행된다. NFS server와 DB server에서 같은 UID/GID를 사용하고 directory owner도 그 계정에 맞췄다. 검증 명령도 root가 아니라 실제 실행 주체로 바꿨다.
sudo -u dbbackup find /mnt/meetback-db-backup \
-type f -name '*.sql.gz'
권한 검증에서 중요한 것은 관리자 root가 읽을 수 있는지가 아니라 서비스가 자신의 계정으로 필요한 작업만 할 수 있는지였다.
automount가 비어 보이는 것도 오류가 아니었다
Systemd automount를 사용하면 access 전에는 mount나 findmnt에서 기대한 모습이 보이지 않을 수 있다. 경로를 실제로 접근한 뒤 mount가 생성되는 방식이기 때문이다.
나는 unit의 active 상태, automount point, 실제 file access 후 findmnt를 함께 확인했다. 설정 파일 한 줄만 보고 mount 성공이라고 판단하지 않았다.
백업은 생성보다 복원을 기준으로 설계했다
DB1에서 매일 mysqldump를 만들고 gzip으로 압축한 뒤 SHA-256 sidecar와 함께 NFS 2에 저장하도록 했다. Timer에는 persistent 실행과 randomized delay를 적용했다.
최종 검증에서는 다음을 확인했다.
- Timer
enabled,active (waiting) - 최근 backup service result
success - 최신
.sql.gz파일 존재 - owner와 mode가 전용 계정·
0600 gzip -t무결성 PASS- SHA-256 계산 성공
- 일회용 MySQL에 복원 후 schema와 table 확인
Replica의 backup timer는 평상시 꺼 두었다. 수동 failover가 발생하면 DB role뿐 아니라 backup 책임도 새 Primary로 넘겨야 한다. 이 작업을 failover runbook에 포함했다.
두 replica가 정말 같은 파일을 보는지 시험했다
NFS volume inspect 결과만으로 shared storage를 증명하지 않았다. Worker 1의 App container에서 marker를 만들고 Worker 2에서 동일 내용을 읽었다. Worker 2에서 삭제한 뒤 Worker 1에서도 사라졌는지 확인했다.
이 시험으로 container UID, NFS export, mount option, network route와 shared visibility를 한 번에 확인할 수 있었다.
남은 한계
RAID5는 한 disk 장애에 대한 보호이지 NFS server 장애에 대한 HA가 아니다. NFS 1이 멈추면 upload 경로가 영향을 받고, NFS 2가 멈추면 새 backup과 log 저장이 중단된다.
장기적으로는 다른 장비로 snapshot 또는 object storage 복제, NFS active/standby, restore drill 자동화를 검토해야 한다. 현재 구조의 강점은 disk 보호와 역할 분리, 최소권한, 복원 검증이고 서버 이중화까지 포함하지 않는다.
다음 글에서는 이 내부 서비스를 인터넷에 연결한 Cloudflare Tunnel 기본 경로와 NPM 예비 경로를 정리한다.
실제 검증 화면
- NFS 1의 logical disk, LVM, ext4 mount와 Worker 2대에 제한한 app-data export를 확인한 화면이다.
- NFS 2는 DB backup과 service log를 역할별 client에 나눠 export했다.
- Backup timer가 active 상태였고, 실제
dbbackup계정으로 최신 파일의 gzip 무결성과 SHA-256을 검사했다. - Worker 1에서 Docker volume option과 실제 NFS mount를 확인한 결과다.
- Worker 2도 같은 export와 NFSv4.2 옵션을 사용하고 있었다.




