300x250

원격 서버에서 docker를 활용하여 모델 학습 및 검증을 하는데, GUI가 없어서 화면을 띄워 visualization을 할 수가 없다.

대부분 jupyter 환경에서 띄워볼 수는 있지만, open3d의 경우에는 이마저도 거의 불가능하다.

따라서, container의 화면을 local(맥)에서 띄우는 방법을 알아보고자 한다.

 

 

 

 

 

XQuartz Installation

 

XQuartz

 

리눅스에서의 GUI(Graphic User Interface)는 프로그램의 개념이다. 즉, 리눅스 커널 위에 GUI 역할을 해주는 프로그램인 Xorg 등을 얹어둔 형태이다.

이러한 원격 display 정보(X Window)를 맥에 받아오기 위해 필요한 프로그램이 바로 XQuartz이다.

 

먼저, 맥에서 다음 링크에 들어가 XQuartz를 다운받고, 재부팅해준다.

https://www.xquartz.org/

 

XQuartz

The XQuartz project is an open-source effort to develop a version of the X.Org X Window System that runs on macOS. Together with supporting libraries and applications, it forms the X11.app that Apple shipped with OS X versions 10.5 through 10.7. Quick Down

www.xquartz.org

 

다운받은 XQuartz 앱을 실행하면 다음과 같은 흰색 터미널이 뜬다.

 

XQuartz 실행 화면

 

앱의 preference로 들어가서 'Security'탭에서 'Allow connections from network clients'를 체크해준다.

 

Setting

 

 

 

 

 

Create a bridge with socat

 

X Window를 공유하기 위해서는 socat을 통해 tcp를 노출시켜야 한다.

맥의 터미널에서 다음 명령어로 socat을 설치하자.

brew install socat

 

아래 명령어는 socat을 통해 포트를 노출시키는 과정이다. 계속 작동되므로 창을 공유하는 동안은 계속 켜 두고, 터미널을 사용하려면 새 창을 띄워야 한다.

socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"&DISPLAY\"

 

이제 port 번호가 6000(X window system의 기본 포트)인 TCP listener가 있는 network socat과 맥 OS에 있는 X window 서버를 연결해주는 다리가 생긴 것이다.

 

ctrl + C로 socat 명령어를 중지하고 다시 명령어를 쳤을 때, 'Address already in use'라는 에러가 뜨는 경우가 있는데, 이는 tcp connection이 아직 꺼지지 않았기 때문이다. 완전히 연결을 끝내려면 다음 명령어를 통해 port가 6000인 연결을 확인하고, 직접 kill 해주어야 한다.

lsof -n -i | grep 6000
kill -9 [process number]

 

예를 들어, lsof 결과가 아래와 같다면,

 

lsof result

 

'kill -9 58169'로 프로세스를 종료해줄 수 있다.

 

 

 

 

 

 

Docker Run

 

이제 local ip와 연결된 DISPLAY 환경 변수를 포함하여 docker run을 해줄 것이다.

먼저 local ip는 터미널에서 다음 명령어로 확인해볼 수 있다.

ifconfig en0

 

en0: flags= ...
	options= ...
	ether ... 
	inet6 ...
	inet ... netmask ... broadcast ...
	nd6 options= ...
	media: autoselect
	status: active

결과로 위와 같은 정보가 보일텐데, 여기서 'inet' 다음에 나오는 부분(xxx.xxx.x.xxx와 같은 형태)이 IP이다.

 

그리고 아래와 같이 docker image를 빌드할 때 사용할 Dockerfile을 만든다. 링크에서의 docker run에 몇 가지 추가된 형태이다.

FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu18.04

ENV TZ=Asia/Seoul
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# software installation
# RUN rm /etc/apt/sources.list.d/cuda.list && \
#     rm /etc/apt/sources.list.d/nvidia-ml.list

RUN apt-get update

# basic
RUN apt install -y --no-install-recommends \
    nano tmux wget iputils-ping git g++ net-tools curl zip unzip ffmpeg

# miniconda (https://github.com/ContinuumIO/docker-images/blob/master/miniconda3/debian/Dockerfile)
RUN apt install -y --no-install-recommends \
    bzip2 \
    ca-certificates \
    libglib2.0-0 \
    libsm6 \
    libxext6 \
    libxrender1 \
    mercurial \
    openssh-client \
    procps \
    subversion
ENV PATH /opt/conda/bin:$PATH
ARG CONDA_VERSION=py39_4.10.3
RUN set -x && \
    UNAME_M="$(uname -m)" && \
    if [ "${UNAME_M}" = "x86_64" ]; then \
    MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION}-Linux-x86_64.sh"; \
    SHA256SUM="1ea2f885b4dbc3098662845560bc64271eb17085387a70c2ba3f29fff6f8d52f"; \
    elif [ "${UNAME_M}" = "s390x" ]; then \
    MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION}-Linux-s390x.sh"; \
    SHA256SUM="1faed9abecf4a4ddd4e0d8891fc2cdaa3394c51e877af14ad6b9d4aadb4e90d8"; \
    elif [ "${UNAME_M}" = "aarch64" ]; then \
    MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION}-Linux-aarch64.sh"; \
    SHA256SUM="4879820a10718743f945d88ef142c3a4b30dfc8e448d1ca08e019586374b773f"; \
    elif [ "${UNAME_M}" = "ppc64le" ]; then \
    MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION}-Linux-ppc64le.sh"; \
    SHA256SUM="fa92ee4773611f58ed9333f977d32bbb64769292f605d518732183be1f3321fa"; \
    fi && \
    wget "${MINICONDA_URL}" -O miniconda.sh -q && \
    echo "${SHA256SUM} miniconda.sh" > shasum && \
    if [ "${CONDA_VERSION}" != "latest" ]; then sha256sum --check --status shasum; fi && \
    mkdir -p /opt && \
    sh miniconda.sh -b -p /opt/conda && \
    rm miniconda.sh shasum && \
    ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
    echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \
    echo "conda activate base" >> ~/.bashrc && \
    find /opt/conda/ -follow -type f -name '*.a' -delete && \
    find /opt/conda/ -follow -type f -name '*.js.map' -delete && \
    /opt/conda/bin/conda clean -afy

# @option: SSH server
RUN apt install -y --no-install-recommends openssh-server

# limit password quality
RUN apt install -y libpam-pwquality

RUN echo 'PASS_MIN_LEN 8' >> /etc/login.defs
RUN echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config

ARG DEBIAN_FRONTEND=noninteractive
RUN apt update \
    && apt install -y firefox \
                      openssh-server \
                      xauth \
    && mkdir /var/run/sshd \
    && mkdir /root/.ssh \
    && chmod 700 /root/.ssh \
    && ssh-keygen -A \
    && sed -i "s/^.*PasswordAuthentication.*$/PasswordAuthentication no/" /etc/ssh/sshd_config \
    && sed -i "s/^.*X11Forwarding.*$/X11Forwarding yes/" /etc/ssh/sshd_config \
    && sed -i "s/^.*X11UseLocalhost.*$/X11UseLocalhost no/" /etc/ssh/sshd_config \
    && sed -i "s/^.*PermitUserEnviroment.*$/PermitUserEnviroment yes/" /etc/ssh/sshd_config \
    && grep "^X11UseLocalhost" /etc/ssh/sshd_config || echo "X11UseLocalhost no" >> /etc/ssh/sshd_config
RUN echo "[로컬의 '.ssh' 경로에 저장된 ssh public 보안 키 (id_rsa.pub 파일의 내용)]" >> ~/.ssh/authorized_keys
RUN service ssh restart

ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES graphics,utility,compute,display
RUN /bin/sh -c echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf \
    && echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf
ENV LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu:/usr/local/nvidia/lib:/usr/local/nvidia/lib64
ENV export LIBGL_ALWAYS_INDIRECT=1
RUN env | egrep -v "^(HOME=|USER=|MAIL=|LC_ALL=|LS_COLORS=|LANG=|HOSTNAME=|PWD=|TERM=|SHLVL=|LANGUAGE=|_=)" >> /etc/environment

RUN apt clean
RUN rm -rf /var/lib/apt/lists/*

RUN echo 'alias ca="conda activate"' >> /root/.bashrc
RUN echo 'conda activate' >> /root/.bashrc

WORKDIR /root/dev

# entry script
COPY entry.sh /entry.sh
RUN chmod +x /entry.sh
ENTRYPOINT [ "/entry.sh" ]

 

여기서 중요한 부분은 아래쪽의 'ARG DEBIAN_FRONTEND= ...'부분부터 'RUN env | egrep -v ...'부분이다. 이 부분이 X11 forwarding을 위해 추가된 부분이다.

 

이 파일이 위치한 경로에서 아래 명령어로 빌드해준다. (점(.)을 빼먹지 말자!)

docker build -t [image name]:[tag] .

 

이에 따라 아래 명령어로 docker run을 진행한다. 빌드한 이미지의 이름과 태그는 'docker images'명령어로 확인할 수 있다.

docker run --gpus all -id \
--name [container name] \
-e DISPLAY=unix$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v /data2/dataset:/root/dev \
--ipc host \
--restart unless-stopped \
-p [forwarding_ip]:22 \
-p [jupyter_ip]:8888 \
[image name]:[image tag]

 

-e 옵션, 첫 번째 -v 옵션(volume을 잡아주는 부분), 첫 번째 -p 옵션(forwarding_ip를 설정해주는 부분)이 X11 forwarding과 관련된 부분이다.

forwarding_ip와 jupyter_ip는 원하는 값(50000 미만)으로 설정하면 된다.

 

간단한 예시로 제대로 동작하는지 확인해보자.

먼저 XQuartz의 터미널에서 ssh -X로 원격 서버의 컨테이너에 접속한다.

ssh -X root@[server_ip] -p [forwarding_ip] # docker run으로 컨테이너 만들 때 [forwarding_ip]:22로 설정해준 port

 

그러면 비밀번호를 입력하라는 창이 뜨고, 설정한 비밀번호를 입력하면 터미널에서 접속할 때와 마찬가지로 서버의 컨테이너에 접속이 된다.

가상환경을 하나 만들어 python, matplotlib을 설치해보자.

conda create -n practice python=3.7
conda activate practice
conda install -c conda-forge matplotlib

 

그리고 간단한 그래프를 그리는 test.py 파일을 만들자.

import matplotlib.pyplot as plt

plt.plot([1, 2, 3, 4])
plt.ylabel('example')
plt.show()

 

이제 XQuartz 터미널에서 'python test.py'로 파일을 실행하면 다음과 같이 오류가 뜰 것이다.

Error

이는 원격 서버 + docker 컨테이너와 로컬 간의 통신이 필요한 환경이기 때문에 발생하는 문제인 듯 한데, 아직까지 이것의 해결 방법을 찾지는 못했다.

대신, 'gedit'을 설치하여 gedit으로 실행하면 그래프 화면을 볼 수는 없으나 뭔가 통신을 하고는 있다는 것을 알 수 있다.

apt install gedit
gedit test2.py

내 경우 test.py로 다른 것을 실행해본 상태여서, test2.py에 matplotlib을 사용한 예제를 작성했다.

이를 XQuartz 터미널에서 실행하면 다음과 같은 결과를 보인다.

 

조금 부족한 result

 

원하는 결과(그래프 화면 띄우기)는 아니지만, 가능성 정도는 보인다..!

 

 

 

In Container

 

이제 컨테이너 내에서 설정할 것들을 알아보자. 이는 opengl 환경변수 설정과 관련된 과정이다.

먼저 apt-get을 사용하여 build를 위한 몇몇 dependency를 설치한다.

apt-get install llvm-6.0 freeglut3 freeglut3-dev

 

그리고 컨테이너의 root에 wget 명령어로 Mesa를 다운받고 압축을 푼다. 그리고 해당 디렉토리로 들어간다.

wget https://archive.mesa3d.org/mesa-18.3.3.tar.gz
tar -zxvf mesa-18.3.3.tar.gz
cd mesa-18.3.3

 

다음과 같은 패키지들을 다운받는다.

apt-get install pkg-config zlib1g-dev libexpat1-dev

 

그리고 mesa-18.3.3의 'configure'라는 파일로 설정을 해준다. 쉘파일을 만들어서 편하게 진행할 수 있다.

vim config.sh # 또는 편하게 vscode상에서 new file -> config.sh라는 파일명 부여

config.sh 파일의 내용은 다음과 같이 작성한다.

./configure --prefix=/usr/local \
--enable-opengl --disable-gles1 --disable-gles2 \
--disable-va --disable-xvmc --disable-vdpau \
--enable-shared-glapi \
--disable-texture-float \
--enable-gallium-llvm --enable-llvm-shared-libs \
--with-gallium-llvm --enable-llvm-shared-libs \
--with-gallium-drivers=swrast,swr \
--disable-dri --with-dri-drivers= \
--disable-egl --with-egl-platforms= --disable-gbm \
--disable-glx \
--disable-osmesa --enable-gallium-osmesa \
ac_cv_path_LLVM_CONFIG=llvm-config-6.0

이 쉘파일을 다음 명령어로 실행해준다.

sh config.sh

 

이제 'make'라는 명령어로 빌드를 해준다.

make -j20
make install

 

그런데 .. 아직 컨테이너 내에서 open3d나 matplotlib 등의 패키지를 이용한 화면을 출력하려 하면 GLFW 오류, opengl 관련 오류가 생긴다. 이는 좀 더 시스템에 대한 이해가 필요한 부분이라, 추후에 더 알아보고 해결되면 내용을 수정해볼 것이다.

 

 

728x90
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기