티스토리 뷰

데일리

0915 Docker volume permission 문제(해결완료)

꿀벌의달콤한여행 2020. 9. 15. 21:26

Docker volume permission 문제 해결방법

(이 문서는 아티클을 참고해서 번역하고 정리한 글입니다.)
(우리 프로젝트에 적용한 사례도 있습니다)

프로젝트를 진행하면서 도커로 컨테이너를 띄웠는데 log파일을 백업하기 위해 지정한 volume 디렉토리에 파일을 생성할 수 없는 오류가 있었다.
자꾸 권한이 없다고만 해서 권한을 777로 주니 되긴 하였으나, 이는 보안상 취약점이 될 수 있어서 다른 방법을 찾고 있었다.
찾던 도중 좋은 글을 발견하였고 초반부의 필요한 부분을 번역해보았고 이후 우리 프로젝트에서 어떻게 적용하였는지를 정리했다.

리눅스의 uid와 gid

도커 컨테이너 내에서 uid, gid가 호스트 시스템에서 어떻게 매핑이 되는지 아는 것은 안전한 시스템을 구축하는 데에 있어서 중요한 부분이다.
특별히 옵션을 주지 않으면 컨테이너 내에 있는 프로세스는 root로 실행될 것이다(Dockerfile에서 다른 uid를 주지 않는 이상).

리눅스 커널은 uid와 gid을 관리하는 것과 커널레벨의 시스템콜의 권한을 확인하는 데에 책임이 있다.
예를 들어서 프로세스가 파일을 쓰려고 할 때 이 프로세스를 실행한 uid/gid가 파일을 수정할 권한이 있는지 검사한다.
중요한 점은 username은 여기서 쓰이지 않고 uid가 쓰인다는 것이다

도커 컨테이너를 서버에서 실행시킬 때, 여전히 하나의 커널만이 존재한다.
컨테이너화를 함으로써 얻을 수 있는 큰 이점은 컨테이너로 분리된 각각의 프로세스들이 여전히 하나의 커널을 공유할 수 있다는 점이다.
이것은 도커 컨테이너를 실행하고 있는 서버 내의 전체 uid, gid를 하나의 커널이 관리하고 있다는 것을 의미한다.

그러다보니 컨테이너들 내에서 같은 uid를 가진 다른 user를 가질 수 없다.
그 이유는 일반적인 리눅스 내의 username(group name도 마찬가지)은 커널의 한 부분이 아니라 외부 툴에 의해서 관리되고 있기 때문이다(/etc/passwd, LDAP, Kerberos 등등).
그래서 다른 컨테이너 내에서 같은 username을 가질 수는 있어도 같은 uid/gid에 대해서 다른 권한을 가질 수는 없다.

간단한 도커 실행 예시

docker group에 있는 일반 유저(marc)로 서버를 실행시켜볼 건데, docker group 내에 있으니 sudo 명령어 없이 컨테이너를 띄울 수 있다.
도커를 띄워보고 프로세스가 어떻게 나타나는지를 관찰해보자.

marc@server:~$ docker run -d ubuntu:latest sleep infinity
92c57a8a4eda60678f049b906f99053cbe3bf68a7261f118e411dee173484d10
marc@server:~$ ps aux | grep sleep
root 15638 0.1 0.0 4380 808 ? Ss 19:49 0:00 sleep infinity

눈에 띄는 점은 sudo을 타이핑 한 적이 없고, root 계정도 아님에도 불구하고 root 유저가 실행, root 권한을 가진 채로 시작하였다.
이것은 컨테이너 안에 있는 root가 컨테이너 밖의 root와 같기 때문이다.
앞서 언급했듯이 하나의 커널만이 존재하고, 하나의 공유되는 uid, gid 풀이 있다.
위에서 나타난 username은 컨테이너 밖에서 "root"로 나타나고, 우리는 여기서 컨테이너 내의 프로세스가 uid = 0을 가진 user에 의해 실행되었다는 것을 알 수 있다.

우리 프로젝트에 적용

웹소켓 서버를 도커 컨테이너로 띄우는데 log파일의 VOLUME을 저장하는 데 있어서 아래와 같은 문제가 있었다.

  1. 웹소켓 서버는 매일 새로운 log파일(ex: 2020-09-15.log)을 만들고 그곳에 로깅데이터를 저장함
  2. 볼륨을 지정하여 호스트에도 log파일을 저장하려 함.
  3. log파일을 새로 생성할 때마다 볼륨으로 지정된 호스트 디렉토리에도 log파일이 생성되어야하는데 이때 해당 디렉토리에 쓰기 권한이 없어서 오류가 남.
  4. log파일의 디렉토리 권한을 777로 설정하면 오류가 나지 않지만 최소한의 권한만 열고 싶음.

log파일의 디렉토리 권한을 777로 설정하는 것은 말이 안되고 문제를 어떻게 해결할지 생각해본 결과 먼저 컨테이너의 권한 정도를 알아보고자 하였다.

도커 컨테이너의 권한

아래는 처음 작성한 docker-compose.yml다.

version: "2"
services:
  node:
    container_name: websocket
    image: "node:14"
    user: "node"
    working_dir: /home/node/app
    environment:
      - NODE_ENV=production
    volumes:
      - .:/home/node/app
      # 호스트 유저도 node
      - /home/node/node_logs:/home/node/app/logs
    expose:
      - "3000"
    ports:
      - 3000:3000
    # restart: always
    command: "npm start"

user: "node"라고 명시하여 해당 컨테이너에서 node라는 user를 사용하였다. (node는 노드 이미지에서 미리 생성해주는 user이다)
위에서 정리한 내용들(도커 컨테이너도 하나의 커널에서 관리됨)을 알고 난 뒤에 ps -ef | grep node를 통해서 알아보니 다음과 같이 나왔다.

ubuntu     13827 13802  0 20:51 ?        00:00:00 npm
ubuntu     13923 13827  0 20:51 ?        00:00:00 sh -c nodemon --experimental-modules ./app.js
ubuntu     13924 13923  0 20:51 ?        00:00:00 node /home/node/app/node_modules/.bin/nodemon --experimental-modules ./app.js
ubuntu     13937 13924  0 20:51 ?        00:00:00 sh -c node --experimental-modules ./app.js
ubuntu     13939 13937  0 20:51 ?        00:00:00 node --experimental-modules ./app.js
ubuntu   14635 14622  0 21:00 pts/0    00:00:00 grep --color=auto node

실행한 user가 ubuntu로 나와있는데 echo $UID로 확인해보면 1000이 나온다. 이 친구도 제일 처음에 생성되어 있는 기본 user이다.
이 말은 즉슨, node:14 도커 이미지에서 미리 생성해준 node는 UID가 1000인 user라는 의미이다.

이제 내 웹소켓 컨테이너가 어떤 권한을 가지고 있는지 알게 되었으니, 그래서 왜 log파일을 생성하지 못하였는지 생각해보았다.

VOLUME으로 지정된 디렉토리에 log파일을 생성하지 못한 이유

문제의 원인은 log파일을 저장하는 디렉토리의 owner가 root로 설정되어서 해당 디렉토리에서 쓰기가 거부당한 것이다.
log파일이 저장되는 /home/node/node_logs 디렉토리는 미리 생성하지 않아서 볼륨이 지정되면서 생성되었다.
이때 root의 권한으로 디렉토리가 생성되었고 owner가 root로 설정되었다. 그러니 root의 권한이 있어야 해당 디렉토리에서 파일을 생성할 수 있게 된 것이다.
도커 컨테이너 내의 프로세스(log파일을 생성하고 쓰는 프로세스)는 기본 user인 ubuntu였기에 권한이 부족해서 파일을 생성할 수 없었던 것이다.

참고로 디렉토리의 권한은 drwxr-xr-x로 되어있었다. 하지만 owner는 root...

문제 해결 방법

먼저 도커 관리용으로 새로운 유저를 생성하고 docker 그룹에 넣어주었다(도커 실행 권한을 주기 위해서).
이후 VOLUME으로 지정되는 디렉토리를 내가 원하는 user가 owner가 되도록 직접 미리 mkdir 해서 디렉토리의 쓰기 권한도 해결하였다.
이후 docker-compose.yml도 새로 생성한 user의 UID에 맞게 수정해주었다.

version: "2"
services:
  node:
    container_name: websocket
    image: "node:14"
    user: "1001"
    working_dir: /home/node/app
    environment:
      - NODE_ENV=production
    volumes:
      - .:/home/node/app
      # 호스트 유저도 node
      - /home/node/node_logs:/home/node/app/logs
    expose:
      - "3000"
    ports:
      - 3000:3000
    # restart: always
    command: "npm start"

참고로 user: "1001"과 같이 적으면 컨테이너 내에서 user는 이름이 없다고 나타날 것이다.
핵심은 도커 컨테이너 내에서 실행한 프로세스의 username이 아니라, UID로 컨테이너 외부와 UID가 매핑이 되어서 권한 역시 매핑이 되는 것이다.
username은 편의상 사용하는 것이고, 하나의 커널로 관리되기에 UID로 매핑이 되는 것이다.

참고문서

링크

댓글