규도자 개발 블로그

도커(Docker)로 CentOS 이미지 systemctl 사용하기 - 2 [failed to get D-Bus connection: Operation not permitted 에러 해결하기] 본문

DevOps/Docker

도커(Docker)로 CentOS 이미지 systemctl 사용하기 - 2 [failed to get D-Bus connection: Operation not permitted 에러 해결하기]

규도자 (gyudoza) 2018. 10. 28. 14:23

Docker로 CentOS 이미지 systemctl 사용하기

전의 게시글에서는 ubuntu이미지를 다운받아 컨테이너로 만든 후에 apache 웹서버를 만들어보았다. 전의 게시글을 보지 못했다면 한번 봐보는 것도 나쁘지 않지만 이미 컨테이너를 올리고 내리는 것과 로컬포트와 도커포트를 연결하는 데에 익숙하다면 이 게시물만 봐도 무방할 것이다.

도커(Docker)로 CentOS 이미지 systemctl 사용하기 - 1 [우분투 이미지로 컨테이너 만들어 웹서버 돌려보기]

전에는 우분투리눅스로 돌려봤으니 이제 이 글의 주제인 CentOS로 웹서버를 돌려보자.


CentOS컨테이너를 만들어서 apache웹서버 구동해보기


위 게시물에서 했던 과정과 똑같은 절차를 진행해보자. 일단 Docker Quickstart Terminal을 킨다. 그리고 아래의 명령어를 입력한다.

docker run -it -p 80:80 --name centos_demo centos:7.5.1804 /bin/bash

centos:7.5.1804가 아닌, latest태그로 묶인 이미지를 사용하고 싶으면 그냥 centos라고 적어도 된다. 그리고 ubuntu에서 했던 것과 마찬가지로 yum을 이용해 업데이트 및 아파치 웹서버를 설치해보자. 우분투의 apt-get과 비슷한 용도라고 보면 된다. 패키지 의존성 검사 및 설치를 수행해주는 도구이다. 다음의 명령어들을 실행하자.

yum -y update

yum -y install httpd

설치가 완료되면 우분투에서 했던 것처럼 웹서버 데몬을 시작해보자.

systemctl start httpd

우분투때와는 달리 실행하면 다음과 같은 화면을 목격할 수 있을 것이다.


그렇다. 이것이 우리가 싸워야할 failed to get D-Bus connection: Operation not permitted 에러이다.

원인

원인은 외국 포럼에서 찾아보니 daemon을 실행할 때 cgroup을 찾지 못하거나 권한문제로 이용할 수 없게 되어 일어나는 문제라고 한다. 도커는 완전가상화가 아닌 격리의 개념이기 때문에 리눅스의 커널시스템을 어느정도 차용하게 되는데 거기에 있어서 특히 cgroup이라는 놈은 리눅스의 자원을 그룹단위로 할당하는 역할을 한다. CentOS컨테이너에서 systemd를 통해 실행하는 daemon이 해당 자원을 할당받지 못하여 생기는 문제라고 한다. 자세하게 찾아보진 않았는데 대충 이런 맥락이라고 보면 된다.

해결책

cgroup을 사용할 수 있게 설정된 CentOS 이미지를 사용하면 된다.

FROM centos

ENV container docker

RUN yum -y update; yum clean all
RUN yum -y install systemd; yum clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;

VOLUME ["/sys/fs/cgroup"]
CMD ["/usr/sbin/init"]

 

위 내용으로 Dockerfile을 만든다. 명령어를 간단하게나마 설명하자면 FROM은 위 명령어로 빌드의 기반이 될 이미지를 정하는 것이고 ENV는 환경변수를 추가하는 것이다. RUN은 해당 명령어를 빌드된 이미지에서 실행하는 것이며 VOLUME은 local에 있는 해당 경로의 폴더를 마운트하겠다는 의미이다. CMD는 이미지가 컨테이너로 올라갈 때 실행되는 명령어를 의미한다.

위 내용을 Dockerfile로 만든 다음에 아래 명령어로 빌드한다.

docker build --rm -t centos7-systemd .

위 명령어의 .까지 포함시켜야 한다. 현재 폴더의 경로를 의미하기 때문이다.
빌드가 완료되었으면그리고 다음의 명령어로 컨테이너로 올린다.

docker run --privileged --name centos_demo -p 80:80 -itd -e container=docker -v /sys/fs/cgroup:/sys/fs/cgroup centos7-systemd /usr/sbin/init

이 명령어는 -d옵션이 껴있기 때문에 (위의 -itd부분) 백그라운드에서 실행되어 바로 shell에서 명령어를 날릴 수 없다. 해서 docker exec명령어나 kitematic을 이용해 접속해야 한다. 개발환경은 윈도우일 때가 많으니 kitematic으로 접속해보자.


만들어져있는 컨테이너는 위와 같다. 여기에서 위의 exec버튼을 누르면 해당 컨테이너에 명령어를 날릴 수 있는 커맨드창을 띄울 수 있다. 창을 띄웠으면 다시 위에서 apache2웹서버를 가동하려 했던 명령어를 실행해보자. 명령어는 아래와 같다.

yum -y update(사실 이건 Dockerfile에 포함돼있는 명령어이기 때문에 실행해도 아무런 변경이 없다.)

yum -y install httpd

systemctl start httpd

까지 입력하고 kitematic에서 WEB PREVIEW을 누르면

짜잔. 멋지게 실행되는 모습을 볼 수 있을 것이다. 그래서, 이것이 완전히 해결된 것이냐? 아니다. 한번 컨테이너를 지웠다가 다시 만들어보자. 위에서 해당 이미지를 컨테이너에 올렸을 때 썼던 명령어

docker run --privileged --name centos_demo -p 80:80 -itd -e container=docker -v /sys/fs/cgroup:/sys/fs/cgroup centos7-systemd /usr/sbin/init

를 다시 실행하면 아래와 같은 결과가 나온다.

Error response from daemon: cgroups: cannot find cgroup mount destination: unknow

아니 이게 무슨 말이오 ㅜㅜ... 힘들게 CentOS에서 systemd를 사용하려고 직접 Dockerfile도 만들었을 뿐더러 이런저런 옵션이 주렁주렁 달린 명령어를 통해 힘들게 실행했건만 재실행이 안된다 하면...

 하지만 오류 메세지에서 힌트를 찾을 수 있다. 위에서 말했다시피 cgroup은 자원할당을 하는 역할이다. 이 친구를 찾을 수 없다고 하니... 직접 찾아주는 수밖에 없다. 아, 그리고 오류는 났지만 컨테이너는 그대로 만들어져서 올라가있을 것이다. 컨테이너를 rm명령어를 통해 지워주도록 하자.

커맨드창에 아래의 명령어를 입력하자.

docker-machine ssh default

그러면 다음과 같은 화면이 나타날 것이다.

boot2docker란 전의 게시물에서도 말했다시피 리눅스기반에서 돌아가는 Docker를 윈도우에서도 실행할 수 있기 가상의 리눅스환경을 제공하는 것이다. 그러니까 윈도우에서 실행하는 Docker 컨테이너들은 1차 가상화, 2차 격리의 개념으로 실행되는 것이라고... 설명한 적이 있다. 아무튼 각설하고, 이 boot2docker는 이 방법이 아닌 oracle vm virtualbox로도 접속할 수 있다.

 oracle vm virtualbox를 실행해보자. 그러면 컴퓨터를 처음 키고 docker quickstart terminal을 실행했을 때 초기화하면서 만들어낸 default라는 가상이미지가 있다.

이친구를 더블클릭하면

docker-machine명령어를 통해 볼 수 있었던 모습을 그대로 볼 수 있다. 짐작하겠지만 docker-machine에서 날리는 명령어와 같은 영향을 받는다. 하지만 oracle vm으로 들어오는 과정은 좀 더 복잡하므로 docker-machine명령어를 통한 방법으로 진행하겠다.

 이 oracle vm을 종료할 때 주의할 점이 있는데 일련의 과정을 겪어야만 docker가 초기화됐을 때의 환경처럼 유지할 수 있다. 일단 닫기 버튼을 누르면 어떤 옵션을 골라도 해당 인스턴스는 무조건 종료되게 된다. 그러니 현재 시스템상태 저장하기에 체크된 채로 확인을 누른다. 그리고 아까는 보이기라고 써져있던 화살표가 시작으로 바뀌어있는 걸 확인할 수 있을 것이다.

이 시작버튼 오른쪽의 아래로 향하는 자그마한 화살표를 누른 뒤에 헤드리스 시작 버튼을 눌러야지 맨 처음에 docker quickstart terminal을 실행할 때 처럼 백그라운드에서 docker가상 인스턴스가 실행되는 환경을 유지할 수 있다. 그러면 다시 docker-machine명령어를 실행했을 때로 돌아가자.

그리고 아래 명령어들을 입력한다.

sudo -i

root권한을 획득하는 명령어이다. root권한이 필요한 명령어 때마다 sudo를 따로 붙이지 않아도 된다.

mkdir /sys/fs/cgroup/systemd

해당 경로에 systemd라는 폴더를 만든다. 보통 만들어져있어서 이미 존재한다는 경고가 뜨지만 혹시 모르니 해주자.

mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd

 해당 명령어는 현재 가상화되어 올라와있는 default이미지에 /sys/fs/cgroup/systemd라는 디렉터리를 systemd cgroup이라는 이름으로 마운트하겠다는 의미이다. docker는 완전한 가상화가 아니라 격리의 개념이라 어느정도는 본래 리눅스 시스템에 있는 것들을 차용하게 되는데 거기에 있어서 systemd를 사용할 때의 cgroup을 통한 자원할당을 받지 못해 해당 폴더를 만든 뒤에 마운트해줘서 systemd를 사용할 때 cgroup을 통한 자원할당을 받게 해주는 게 아닌가... 하는 내 추측이다. 아무튼 이 과정을 겪었으면 exit를 입력하여 나오도록 하자. exit를 한번만 입력하면 sudo-i명령어에서만 나오게 되므로 두 번 입력하여 애초에 docker quickstart terminal을 실행했을 때 나오는 모습이 출력되게 하자.

이렇게 말이다.

 이제 다시, 아까 실행했던 Docker run 명령어를 실행해보자. 위에서 찾아보기 귀찮은 사람을 위해 다시 한번 써넣겠다.

docker run --privileged --name centos_demo -p 80:80 -itd -e container=docker -v /sys/fs/cgroup:/sys/fs/cgroup centos7-systemd /usr/sbin/init

실행하면 아래와 같이

다시 컨테이너가 되어 올라갈 수 있는 모습을 목격할 수 있다. 만약에 오류가 난다면 centos_demo라는 다른 이름으로 올라가있는 컨테이너가 없는지 확인하자.

 여기까지 진행했으면 다시 kitematic으로 가서 httpd를 실행하여 가상의 웹서버가 제대로 작동하는지 확인해보자. 여기서부터의 과정 또한 위와 같다.

yum -y update

yum -y install httpd

systemctl start httpd

와우. 다시 볼 수 있는 반가운 화면이다.

이대로 끝?

이었으면 얼마나 좋을까. 컴퓨터를 껐다가 키면 가상화상태의 인스턴스들은 바뀐 부분들이 다 날아가는 특성이 있다. 아주 중요한 예로 우리가 docker-machine을 통해 cgroup을 마운트해서 오류가 나지 않게 설정했던 부분들 말이다. 당장에 컴퓨터를 껐다가 키고 다시 실험해봐도 마운트를 진행하기 전 처럼 처음엔 되다가 두번째부터는 정상적으로 docker run명령어가 먹지 않는 오류를 만날 수 있을 것이다.

 윈도우에서 도커를 편하게 사용하기 위해서, 이젠 docker의 default이미지가 초기화될 때마다 자동적으로 해당 명령어를 실행할 수 있게 해주는 과정이 필요하다. 이 부분은 docker에서 c:/users/[계정명]이하의 파일만 매핑할 수 있게 되는 문제와도 관련있다. 다시 아래 명령어를 입력하여 Boot2Docker로 들어가도록 하자.

docker-machine ssf default

그리고 아래 명령어로 bootlocal.sh파일을 열도록 하자.

sudo vi /var/lib/boot2docker/bootlocal.sh

그리고 에디터에서 다음 명령어를 추가한다.

mkdir /sys/fs/cgroup/systemd

mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd

그리고 저장하고 나온다. (vi에디터 사용법을 모르는 사람은 vi에디터 사용법부터 알아보길 바란다.)

또 이 명령어를 통해 모든 사용자에게 실행권한을 줌으로서 default VM이 재부팅될 때마다 위의 쉘 명령어 파일이 실행되게 한다.

sudo chmod +x /var/lib/boot2docker/bootlocal.sh

위 과정을 거치면 default VM이 재부팅되거나 host컴퓨터 자체가 재부팅되더라도 그때마다 default VM에 들어가서 따로 마운트과정을 거치지 않아도 된다. docker-machine stopdocker-machine start명령어를 통해 default VM을 재부팅한 뒤에 다시 한번 실험해보자.

docker-machine ssh default를 통해 boot2docker에 접속해서 우리가 변경한 뒤에 저장했던 bootlocal.sh파일을 열어보면 내가 변경했던 내역이 그대로 저장돼있는 걸 볼 수 있다. 그리고 다음부터는 위 과정을 반복할 필요 없이 새로운 CentOS컨테이너의 생성삭제를 수행할 수 있다. 이 방법은 docker에서 컨테이너와 매핑하는 폴더를 c:/users/[계정명] 이하의 디렉터리 이외의 디렉터리들도 매핑시킬 수 있게 할 때도 유용하다. oracle vm에서 해당 인스턴스와 공유할 폴더를 추가시킨 다음에 위 bootlocal.sh파일을 편집하여 해당 폴더 또한 mount시켜주면 되기 때문이다. 이 과정은 다음에 한번 더 포스트로 다뤄야겠다.

리눅스에서도 이 복잡한 과정을 거쳐야 하는지?

리눅스에서는 이 복잡한 과정을 거칠 필요는 없다. 단지 cgroup에서 systemd가 사용할 자원을 할당받지 못하는 문제는 똑같이 발생하므로 위에 있는 Dockerfile을 빌드해서 run하는 과정은 똑같이 거쳐야 한다. cgroup의 systemd를 마운트하는 과정과 dockerlocal.sh파일을 수정하여 초기설정을 변경하는 등의 과정은 windows에서 docker를 실행할 때의 특성(가상화와 격리 2단계를 거치는)때문에 해야할 설정이다. 

Comments