[Docker] docker in docker의 Permission denied 문제

도커 컨테이너 안에서 도커를 사용하는 방법은 Host의 /var/run/docker.sock를 컨테이너에서 bind mount하면 된다. 쉽게 설명해 docker run 할 때 아래처럼 -v /var/run/docker.sock:/var/run/docker.sock 옵션을 주면 된다는 뜻이다. 물론 컨테이너 안에는 도커가 설치되어 있어야 한다.

docker run -it --name ubuntu -v /var/run/docker.sock:/var/run/docker.sock ubuntu:20.04

이렇게 하면 도커 컨테이너 안에서 /var/run/docker.sock의 소유자가 root:docker 로 생기는데 이때의 그룹 id는 호스트의 그룹 id 값을 따르게 된다. 내 맥에서는 docker 그룹의 gid가 134이고, ubuntu 컨테이너의 docker 그룹의 gid는 1000이다.

$ cat /etc/group | grep docker # Host에서..
docker:x:134:dasomoli

컨테이너 안에서 /var/run/docker.sock를 살펴보면 아래처럼 소유자가 root:134 이다.

root@ce1351052f58:/# ls -l /var/run/docker.sock
srw-rw---- 1 root 134 0 Dec 21 13:19 /var/run/docker.sock

그러나 컨테이너 내에서 도커 설치 후 docker 그룹의 gid는 호스트의 gid인 134가 아닌 컨테이너 내부 환경에서 주어진다. 아래에서는 999이다.

root@ce1351052f58:/# cat /etc/group | grep docker
docker:x:999:

따라서 컨테이너 내부에서 root가 아닌 일반 유저로 도커를 사용하려고 하면 아래처럼 Permission denied 에러가 발생하게 된다.

$ id
uid=1000(dasomoli) gid=1000(dasomoli) groups=1000(dasomoli)

$ docker ps -a
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1": dial unix /var/run/docker.sock: connect: permission denied

이 문제의 해결 방법으로 사람들이 이야기하는 방법은 chown으로 컨테이너 내부에서 owner를 root:docker 로 바꾸고 사용하는 방법, 또는 chmod로 아예 a+rw 를 주는 방법을 이야기하는데, chown으로 owner를 바꾸면 컨테이너 외부의 호스트 파일의 소유자 정보도 바뀌는 문제가 있다. 위의 docker의 gid가 컨테이너에서는 999, 호스트에서는 134인 경우에 컨테이너에서 chown root:docker를 해보면 호스트에서 gid가 999인 그룹에 속한 파일로 나온다. 즉, 호스트의 도커 실행 환경에 문제가 생긴다.

$ ls -l /var/run/docker.sock
srw-rw---- 1 root systemd-coredump 0 12월 21 22:19 /var/run/docker.sock # docker가 아니고 systemd-coredump 그룹 소유로 바뀌었다!!

$ cat /etc/group | grep systemd-coredump
systemd-coredump:x:999:

따라서 이 방법보다는 둘 모두의 docker 그룹의 gid를 맞춰주는 방법이 더 낫다. 컨테이너 안에서 groupmod로 다음을 실행한다. 아래에서 134는 호스트에서의 docker 그룹의 gid이다.

root@ce1351052f58:/# groupmod -g 134 docker

root@ce1351052f58:/# chown root:docker /var/run/docker.sock # 컨테이너가 실행 중이었다면 owner를 바꾼다.

root@ce1351052f58:/# ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Dec 21 22:19 /var/run/docker.sock

호스트에서도 확인해보자.

$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 12월 21 22:19 /var/run/docker.sock

도커 이미지를 빌드할 때 Dockerfile 내에서 아예 먼저 해준다면, 실행 시에 따로 해주지 않아도 된다.

# Dockerfile 안의 내용
ARG HOST_DOCKER_GID="134"

RUN groupmod -g ${HOST_DOCKER_GID} docker

위의 Dockerfile로 도커 이미지를 빌드할 때는 다음과 같은 방식으로 넘겨도 된다.

$ docker build --build-arg HOST_DOCKER_GID=$(stat -c "%g" /var/run/docker.sock) -t ubuntu20.04

위에서 사용한 호스트에서 docker 그룹의 gid를 얻는 방법은 “[Linux/Mac] group id 얻기” 글을 참조한다. 호스트와 컨테이너 둘 간의 gid 충돌은 유의하자.

[Linux] fstab의 구조와 옵션

fstab이란?

fstab은 Linux 시스템의 file system table을 뜻한다. mount를 쉽게 하기 위한 configuration table이다.

fstab의 구조

6개의 항목이 순서대로 구성되어야 한다.

  1. 디바이스 (Device): 보통 mount되는 디바이스의 이름 혹은 UUID이다. 예를 들면, sda1
  2. 마운트 위치 (Mount point): mount될 디렉토리의 위치
  3. 파일 시스템 타입 (File System Type): 사용되는 file system의 type
  4. 옵션 (Options): mount 옵션. 여러개를 쓸 때는 콤마(,)로 구분한다.
  5. 백업 동작 (Backup Operation): 0은 백업하지 않음. 1은 dump로 backup을 할지를 결정. 오래된 backup 방법이라서 0으로 설정해서 사용하지 않도록 한다.
  6. 파일 시스템 체크 순서 (File System Check Order): 0은 fsck로 체크하지 않음. 1은 root file system, 다른 파티션들은 2로 설정되어야 한다. 3, 4, … 로 한다고 해서 순서가 되지 않으므로 순서를 설정하지 않도록 한다. 그냥 다른 모든 파티션은 2이다.

예제

proc            /proc           proc    defaults          0       0
PARTUUID=5e3da3da-01  /boot           vfat    defaults          0       2
PARTUUID=5e3da3da-02  /               ext4    defaults,noatime  0       1
UUID=678dcc13-1b44-4ee8-80cf-7f186587054d       /mnt/NAS        ext4    defaults,noatime,rw     0       2
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that

다른 mount 옵션

  • auto / noauto: 부팅 시에 자동으로 mount할지 말지.
  • exec / noexec: 그 파티션이 바이너리를 실행할 수 있는지 아닌지. 보통 보안 목적으로 noexec로 설정된다.
  • ro / rw: ro는 읽기 전용 (read-only), rw는 읽기 쓰기 (read-write)
  • nouser / user: user가 mount권한을 갖을지 말지.
  • atime / noatime / relatime: access time (atime) 을 기록할지 말지. relatime은 access time이 atime data가 마지막으로 update된 (mtime) 이후에 파일이 수정되었을 때, 또는 마지막으로 access된 후 일정 시간 (기본값은 하루)이 지난 후에만 업데이트한다.
  • suid / nosuid: suid와 sgid 비트 동작을 허용할지 말지.
  • dev / nodev: character나 block special device를 interpret할지 말지.
  • defaults: 기본값을 사용. rw, suid, dev, exec, auto, nouser, async 와 같다.

참고 자료