
왜 똑같은 mkdir 명령어가 다른 결과를 만들까요?
분명 호스트 서버에서는 아무 문제 없이 잘 실행되던 mkdir 명령어가, 컨테이너 환경으로 들어가는 순간 갑자기 Permission denied 오류를 내뱉으며 멈춰버린 경험이 있으신가요? 혹은 분명히 컨테이너 안에서 디렉터리를 만들었는데, 정작 호스트 쪽에서는 그 흔적을 찾을 수 없어 당황했던 적은 없으신가요? 이런 현상은 단순히 명령어가 틀려서 생기는 문제가 아니에요.
리눅스 기반의 인프라를 운영하다 보면 컨테이너와 호스트라는 두 세계가 만나는 지점에서 수많은 파일 시스템 충돌을 겪게 돼요. 겉보기에는 똑같은 리눅스 환경처럼 보이지만, 실제로는 커널이 관리하는 네임스페이스(Namespace)와 권한 체계가 완전히 격리되어 있기 때문이에요. 이 경계를 제대로 이해하지 못하면 디렉터리 하나를 만드는 단순한 작업조차 자동화 스크립트를 망가뜨리는 커다란 장애로 이어질 수 있어요.
오늘 이 글에서는 단순한 명령어 사용법을 넘어, 왜 환경에 따라 결과가 달라지는지 그 근본적인 원인을 파헤쳐 보려고 해요. 컨테이너와 호스트 사이의 간극을 메우는 실무적인 통찰력을 얻어 가실 수 있을 거예요.
이 글을 읽고 나면 다음 내용들을 확실히 알 수 있어요.
- mkdir 명령어의 기본 동작과 컨테이너 환경의 결정적 차이
- 네임스페이스와 사용자 격리가 디렉터리 생성에 미치는 영향
- 볼륨 마운트 시 발생하는 권한 충돌 해결 방법
- 실무에서 바로 적용 가능한 환경별 디렉터리 관리 전략
실행 전 반드시 알아야 할 핵심 개념과 체크리스트
컨테이너 환경에서 디렉터리 생성 문제를 해결하려면 먼저 우리가 발을 딛고 있는 환경이 어떤 구조인지 이해해야 해요. 무턱대고 mkdir -p 옵션을 남발하기 전에, 현재 작업 중인 환경이 격리된 공간인지 아니면 공유된 공간인지를 먼저 판단하는 것이 순서예요.
파일 시스템 격리를 결정하는 3대 요소
가장 먼저 살펴봐야 할 것은 커널이 제공하는 격리 기술이에요. 컨테이너는 호스트의 커널을 공유하면서도, 마치 독립된 컴퓨터처럼 행동하기 위해 몇 가지 장치를 사용해요.
- Mount Namespace: 컨테이너가 자신만의 파일 시스템 구조를 가질 수 있게 해줘요. 컨테이너에서 만든 디렉터리가 호스트에서 안 보이는 이유는 바로 이 네임스페이스가 분리되어 있기 때문이에요.
- User Namespace: 컨테이너 내부의 root 사용자가 호스트의 실제 root가 아니게 만드는 핵심 기술이에요. 이 차이가 권한 오류의 주범이 되곤 해요.
- Storage Driver: Overlay2 같은 드라이버는 레이어 구조를 통해 디렉터리를 관리해요. 이 레이어 구조 때문에 생성 속도나 저장 방식이 호스트와 다를 수 있어요.
컨테이너 내부에서 생성한 디렉터리는 기본적으로 컨테이너의 쓰기 가능 레이어(Writable Layer)에 저장돼요. 따라서 컨테이너가 삭제되면 함께 사라지므로, 데이터를 보존하려면 반드시 볼륨을 사용해야 해요.
환경별 디렉터리 생성 특성 비교
작업을 시작하기 전에, 현재 본인이 처한 환경이 아래 표의 어느 지점에 있는지 확인해 보세요.
| 구분 항목 | 호스트 환경 | 컨테이너 환경 (Local) | 볼륨/바인드 마운트 환경 |
|---|---|---|---|
| 가시성 범위 | 시스템 전체 | 컨테이너 내부로 한정 | 호스트와 공유됨 |
| 주요 권한 주체 | 실제 root/사용자 | 가상 root (Namespace) | 호스트 UID/GID와 일치 필요 |
| 데이터 지속성 | 영구적 | 컨테이너 삭제 시 소멸 | 영구적 (호스트 저장) |
| 성능 영향 | 매우 빠름 | CoW 오버헤드 발생 가능 | 호스트 성능과 유사 |
이 표를 통해 알 수 있듯이, 볼륨 마운트 환경이 가장 주의가 필요해요. 호스트와 컨테이너라는 두 세계의 권한이 충돌하는 지점이기 때문이죠. 이제 구체적으로 어떻게 실행하고 관리해야 하는지 단계별로 알아볼까요?
상황별 mkdir 실행 및 환경 격리 대응 가이드
실무에서 마주하는 상황은 크게 네 가지로 나눌 수 있어요. 각 상황에 따라 mkdir 명령어를 어떻게 다루고, 발생하는 문제를 어떻게 해결해야 하는지 상세히 설명해 드릴게요.
STEP 1. 기본 명령어 활용과 계층 구조 생성
가장 기초적인 단계예요. 리눅스에서 디렉터리를 생성할 때 가장 많이 사용하는 방식은 단일 생성과 계층 생성이에요. 단순히 폴더 하나를 만든다면 mkdir folder_name을 사용하지만, 실무에서는 하위 디렉터리까지 한 번에 만드는 경우가 훨씬 많아요.
이때 반드시 기억해야 할 옵션이 -p (parents)예요. 만약 mkdir -p /data/logs/2024/october라고 입력하면, 중간 경로인 /data와 /logs, /2024가 존재하지 않더라도 커널은 이를 자동으로 생성해 줘요. 컨테이너의 쉘 스크립트(Dockerfile의 RUN 명령 포함)를 작성할 때 이 옵션을 빠뜨리면, 경로가 없는 상태에서 명령이 실패하여 빌드 자체가 중단되는 불상사가 생길 수 있어요.
STEP 2. 네임스페이스를 통한 경로 격리 이해하기
컨테이너 내부에서 mkdir /app/data를 실행했을 때, 왜 호스트의 /app/data에는 아무 변화가 없을까요? 이것은 Mount Namespace 때문이에요. 컨테이너가 시작될 때 커널은 해당 컨테이너만을 위한 별도의 마운트 테이블을 할당해요.
컨테이너 안에서 보는 루트(/)는 호스트의 루트와 완전히 다른 지점을 가리키고 있어요. 즉, 컨테이너 안의 디렉터리 생성 작업은 컨테이너가 할당받은 격리된 파일 시스템 영역 안에서만 일어나는 일이에요. 만약 호스트의 특정 경로에 디렉터리를 만들고 싶다면, 단순히 컨테이너 안에서 명령어를 치는 것이 아니라 반드시 볼륨(Volume) 혹은 바인드 마운트(Bind Mount) 설정을 먼저 완료해야 해요.
STEP 3. 사용자 네임스페이스와 UID/GID 권한 매핑
이 부분이 엔지니어들을 가장 괴롭히는 지점이에요. 컨테이너 내부에서 mkdir을 실행했는데 Permission denied가 뜬다면, 그것은 대부분 User Namespace와 관련된 문제예요. 컨테이너 안에서 whoami를 쳤을 때 root라고 나와도, 호스트 관점에서는 권한이 없는 일반 사용자(예: UID 1000)일 수 있거든요.
특히 볼륨을 통해 호스트의 디렉터리를 컨테이너에 연결했을 때 이 문제가 극명하게 나타나요. 예를 들어, 호스트의 /mnt/data 폴더 소유자가 root(UID 0)인데, 컨테이너 안에서 node(UID 1000) 사용자로 실행 중인 프로세스가 mkdir을 시도하면 당연히 거부당해요. 이럴 때는 컨테이너를 실행할 때 --user 옵션을 사용하여 호스트의 UID와 일치시키거나, 호스트에서 미리 폴더의 권한을 조정해 두어야 해요.
컨테이너 내부에서
chmod 777로 권한을 푸는 방식은 임시방편일 뿐이에요. 보안상 매우 취약하며, 근본적인 해결책은 호스트와 컨테이너 간의 UID/GID를 일치시키는 설정을 구성하는 것이에요.STEP 4. Overlay2 파일 시스템과 CoW 메커니즘
컨테이너의 저장 방식인 Overlay2는 레이어 구조를 가져요. 새로운 디렉터리를 생성하면 기존의 읽기 전용(Read-Only) 레이어를 수정하는 것이 아니라, 최상단의 쓰기 가능(Writable) 레이어에 새로운 정보를 기록해요. 이를 Copy-on-Write (CoW)라고 불러요.
이 과정에서 디렉터리를 생성할 때 미세한 성능 저하가 발생할 수 있어요. 특히 매우 많은 수의 디렉터리를 짧은 시간 안에 생성해야 하는 대규모 마이크로서비스 환경에서는, 컨테이너 내부 파일 시스템에 직접 생성하기보다는 호스트의 고성능 디스크를 직접 바인드 마운트하여 사용하는 것이 운영 안정성과 성능 면에서 훨씬 유리해요.
STEP 5. 실무 시나리오: 사이드카 패턴에서의 디렉터리 공유
최근 쿠버네티스(Kubernetes) 환경에서는 두 개 이상의 컨테이너가 하나의 디렉터리를 공유하는 사이드카(Sidecar) 패턴을 자주 사용해요. 예를 들어, 메인 앱 컨테이너가 로그를 생성하면, 사이드카 컨테이너가 해당 디렉터리를 읽어 클라우드 저장소로 전송하는 구조죠.
이 시나리오에서 디렉터리 생성의 주체는 누구여야 할까요? 보통은 앱 컨테이너가 동작하기 전에 emptyDir 같은 볼륨을 통해 미리 생성된 공간을 공유받아요. 이때 두 컨테이너의 사용자 권한(UID)이 다르면, 한쪽에서 만든 디렉터리에 다른 쪽이 접근하지 못하는 상황이 빈번해요. 따라서 securityContext 설정을 통해 두 컨테이너의 fsGroup을 동일하게 맞춰주는 작업이 필수적이에요.
실무 적용 예시: 권한 문제를 해결한 디렉터리 생성 절차
아래는 호스트의 디렉터리를 컨테이너에 연결하고 권한 충돌 없이 작업을 완료하는 표준적인 흐름이에요.
- 호스트 측: 공유할 디렉터리를 미리 생성하고 권한을 확인해요.
sudo mkdir -p /data/app_logs && sudo chown 1000:1000 /data/app_logs - 실행 명령: 컨테이너 실행 시 해당 경로를 연결해요.
docker run -v /data/app_logs:/logs --user 1000:1000 my-app - 컨테이너 내부: 이제 앱이
mkdir /logs/new_dir를 실행하면, 호스트의/data/app_logs/new_dir에 정상적으로 생성돼요.
자주 하는 실수와 해결법 및 FAQ
실무에서 엔지니어들이 가장 많이 겪는 실수들을 유형별로 정리했어요. 비슷한 상황을 겪고 있다면 즉시 적용해 보세요.
자주 하는 실수와 해결법
❌ 실수: 컨테이너 안에서 mkdir로 만든 폴더가 호스트에서 안 보여요.
⚠️ 원인: 볼륨 마운트를 하지 않고 컨테이너 내부의 로컬 레이어에 생성했기 때문이에요.
✅ 해결법: 실행 시 -v 또는 --mount 옵션을 사용하여 호스트 경로와 연결하세요.
❌ 실수: 마운트된 폴더에 mkdir를 하면 Permission denied가 떠요.
⚠️ 원인: 호스트 폴더의 소유자(UID/GID)와 컨테이너 실행 사용자의 권한이 일치하지 않기 때문이에요.
✅ 해결법: 호스트에서 chown으로 권한을 맞추거나, 컨테이너 실행 시 --user 옵션을 사용하세요.
❌ 실수: mkdir -p를 썼는데도 중간 경로 생성에 실패해요.
⚠️ 원인: 중간 경로에 대한 쓰기 권한이 없는 상위 디렉터리가 존재하기 때문이에요.
✅ 해결법: 상위 디렉터리의 권한을 먼저 확인하거나, 권한이 있는 위치에서 작업을 시작하세요.
❌ 실수: 컨테이너를 재시작하면 mkdir로 만든 디렉터리가 사라져요.
⚠️ 원인: 컨테이너의 쓰기 가능 레이어에 저장되었기 때문이에요.
✅ 해결법: 데이터를 영구 보존해야 한다면 반드시 Docker Volume이나 Bind Mount를 사용해야 해요.
❌ 실수: Dockerfile의 RUN mkdir 명령이 빌드 시에는 성공하는데 컨테이너 실행 시엔 안 돼요.
⚠️ 원인: 빌드 시점의 사용자와 런타임 시점의 사용자가 다르거나, 마운트된 볼륨이 빌드 시 생성한 디렉터리를 덮어버렸기 때문이에요.
✅ 해결법: 런타임에 필요한 디렉터리는 진입점(Entrypoint) 스크립트에서 생성하거나 볼륨 설정을 점검하세요.
자주 묻는 질문
Q. 컨테이너 안에서 root로 mkdir를 하면 호스트에서도 root 소유로 생기나요?
네, 맞아요. 만약 볼륨 마운트를 한 상태에서 컨테이너 내부의 root 사용자가 디렉터리를 생성하면, 호스트에서도 해당 디렉터리의 소유자는 root(UID 0)로 나타나요. 이 때문에 나중에 호스트의 일반 사용자가 해당 파일을 수정하지 못하는 문제가 생길 수 있으니 주의해야 해요.
Q. mkdir -p 옵션은 컨테이너 환경에서도 성능 차이가 있나요?-p 옵션 자체는 파일 시스템 트리를 탐색하는 로직이라 성능 차이가 미미해요. 하지만 컨테이너의 레이어 구조(Overlay2) 특성상, 아주 깊은 계층 구조를 반복적으로 생성하면 호스트의 네이티브 파일 시스템보다는 아주 약간의 오버헤드가 발생할 수 있어요.
Q. 왜 어떤 컨테이너는 별도 설정 없이도 mkdir가 잘 되고, 어떤 건 안 되나요?
그건 해당 컨테이너가 어떤 사용자로 실행되느냐에 달려 있어요. 기본적으로 root 사용자로 실행되는 이미지는 자유롭지만, 보안을 위해 일반 사용자(non-root user)로 설정된 이미지는 권한이 없는 곳에 디렉터리를 만들 수 없어요.
Q. 볼륨 마운트 경로를 미리 만들어둬야 하나요?
Docker의 경우, 호스트에 없는 경로를 바인드 마운트하면 Docker가 자동으로 해당 경로를 root 소유의 디렉터리로 생성해 줘요. 하지만 이 방식은 권한 문제를 일으키기 딱 좋으므로, 가급적 호스트에서 미리 디렉터리를 만들고 권한을 설정해 두는 것을 추천해요.
Q. kubectl에서 디렉터리 생성 권한을 어떻게 관리하나요?
쿠버네티스에서는 Pod의 securityContext를 활용해요. fsGroup을 설정하면, 볼륨에 마운트된 디렉터리의 소유 권한을 특정 그룹으로 일괄 변경해 주어 권한 충돌을 효과적으로 방지할 수 있어요.
성공적인 파일 관리를 위한 마지막 점검
디렉터리 생성은 단순해 보이지만, 컨테이너와 호스트라는 이질적인 두 세계를 연결하는 아주 중요한 작업이에요. 오늘 배운 내용을 바탕으로 환경별 차이를 명확히 인지한다면, 인프라 운영 중 발생하는 수많은 권한 문제를 사전에 차단할 수 있어요.
- 컨테이너 내부의 mkdir는 네임스페이스에 의해 격리되어 호스트에 즉시 나타나지 않아요.
- 호스트와 데이터를 공유하려면 반드시 볼륨(Volume)이나 바인드 마운트를 사용해야 해요.
- 권한 오류 발생 시 컨테이너 내부 UID와 호스트 UID의 일치 여부를 가장 먼저 확인하세요.
- 계층 구조 생성이 필요할 때는
mkdir -p옵션을 습관화하세요. - 보안과 성능을 위해 컨테이너 내부 레이어보다는 외부 볼륨 활용을 권장해요.
- 쿠버네티스 환경에서는
fsGroup설정을 통해 권한을 제어하세요.
이제 실전으로 나아갈 시간이에요. 오늘 배운 내용을 바탕으로 다음 단계를 실천해 보세요.
- 오늘 할 일: 현재 운영 중인 컨테이너의 실행 사용자(UID)와 호스트 마운트 경로의 소유 권한 비교해 보기
- 이번 주 할 일: Dockerfile 작성 시
RUN mkdir명령어를 사용할 때 권한 이슈가 없는지 테스트 스크립트 작성하기 - 실행 직전 할 일: 중요한 데이터를 다루는 볼륨 마운트 설정 시, 반드시
--user옵션이나chown전략 세우기
컨테이너와 호스트에서 같은 명령을 직접 실행하며 그 차이를 눈으로 직접 확인해 보는 과정이 가장 빠른 학습 방법이에요. 지금 바로 터미널을 열어 테스트해 보세요!
관련해서 더 깊이 있는 리눅스 운영 지식이 필요하시다면 리눅스 파일관리 명령어 모음 글도 함께 읽어보시는 것을 추천해요.