Skip to content
Merged
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
# 변경한 사람: 이소은
# tensorflow version 2.13.0-gpu -> 2.18.0-gpu

##### decs:260501#####
# 변경한 사람: 임준영
# Xfce, TigerVNC, noVNC 기반 GUI 접속 지원 추가

########################### version history end ##########################

# TensorFlow 2.18.0 GPU 공식 이미지 사용 (CUDA 12.3, cuDNN 8.9 포함, Ubuntu 22.04 기반)
Expand All @@ -31,6 +35,13 @@ vim \
wget \
curl \
ssh \
dbus-x11 \
xfce4 \
xfce4-terminal \
tigervnc-standalone-server \
tigervnc-common \
novnc \
websockify \
software-properties-common

# motd install
Expand Down Expand Up @@ -79,6 +90,9 @@ RUN mkdir /jupyter_config \
# entrypoint.sh 복사
COPY entrypoint.sh /

# noVNC 접속 포트. VNC 서버(5901)는 localhost에만 바인딩합니다.
EXPOSE 6080

# SSHD 서버를 실행하고, entrypoint 파일을 start/restart 시 마다 실행, dev/null에 entrypoint 로그를 저장
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["bash", "/entrypoint.sh"]
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@

| 이미지 태그 (Image Tag) | TensorFlow 버전 | CUDA / cuDNN | 베이스 OS (Base OS) | 주요 변경사항 및 설명 |
| :--- | :--- | :--- | :--- | :--- |
| `dguailab/decs:latest`<br>`dguailab/decs:260403` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(최신)** 계정/권한 검증 강화, Jupyter 비-root 실행, 의도되지 않은 MOTD 출력 방지 |
| `dguailab/decs:260201` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(이전 수정 버전)** 의도되지 않은 MOTD 출력 방지 버그 수정 |
| `dguailab/decs:251023` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(이전 안정 버전)** Jupyter Notebook 버전 변경으로 인한 오류 해결 버전 |
| `dguailab/decs:251002` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원 |
| `dguailab/decs:250926` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | **(이전 안정 버전)** TensorFlow 2.13.0 기반의 안정화 버전 |
| `dguailab/decs:latest`<br>`dguailab/decs:260501` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(최신)** Xfce + TigerVNC + noVNC 기반 브라우저 GUI 접속 지원 추가 |
| `dguailab/decs:260427` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | 기존 사용자 컨테이너 변경 시 `~/.bashrc`의 conda initialize 블록 갱신, 경고 주석 영문화 및 인덴트 수정 |
| `dguailab/decs:260403` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | 계정/권한 검증 강화, config-server 주입 기반 sudo 권한 축소, Jupyter 및 컨테이너 유지 프로세스 비-root 실행, gosu 추가 |
| `dguailab/decs:260201` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | 의도되지 않은 MOTD 출력 방지 버그 수정 |
| `dguailab/decs:251023` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(이전 안정 버전)** Jupyter Notebook 설정 파일 생성/경로 오류 해결, 랜덤 토큰 저장 방식 반영 |
| `dguailab/decs:251002` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원, Docker Hub 자동 빌드/푸시 워크플로 도입 |
| `dguailab/decs:250926` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | **(이전 안정 버전)** 사용자 홈 디렉토리 소유권 설정 순서 수정, 이슈/PR 템플릿 추가 |
| `dguailab/decs:250428` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | Jupyter Lab 설정 파일을 사용자 홈의 `.jupyter` 경로에서 생성하고 사용하도록 수정 |
| `dguailab/decs:250416` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | Jupyter Notebook 설정 파일 생성 로직 수정, 10자리 랜덤 토큰 생성 및 파일 저장 추가 |
| `dguailab/decs:250323` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | `useradd` 옵션 보완, MOTD 공지 추가, TensorFlow ASCII 출력 제거 |
| `dguailab/decs:250309` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | ldconfig permission denied 오류 방지, MOTD/Slack 공지 반영, Conda 최신화 반영 |
| `dguailab/decs:250218` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | Anaconda3 2024.10-1 설치로 Conda 24.9.2 업그레이드 |

## ⚙️ 사용 방법
필요한 버전의 이미지를 Docker Hub에서 pull 받아 사용합니다.
Expand All @@ -29,6 +36,37 @@ docker pull dguailab/decs:251002
docker pull dguailab/decs:250926
```

### GUI 접속(noVNC)
`260501` 버전부터 브라우저 기반 GUI 접속을 지원합니다. GUI 접속은 기본 비활성화 상태이며, 컨테이너 생성 시 `ENABLE_VNC=true` 환경변수를 전달한 경우에만 시작됩니다.

Comment on lines +39 to +41
- 컨테이너 내부 noVNC 포트: `6080`
- 컨테이너 내부 VNC 포트: `localhost:5901` 전용
- 접속 URL: `http://서버주소:외부포트/vnc.html`
- VNC 비밀번호 저장 위치: `/home/$USER_ID/decs_jupyter_lab/vnc_password.txt`

직접 `docker run`을 사용할 때는 다음처럼 환경변수와 포트를 함께 지정합니다.

```
-e ENABLE_VNC=true -p 외부포트:6080
```

운영 스크립트(`~/uid`)에서는 `--enable_vnc true`를 주면 컨테이너 내부 `6080` 포트를 자동으로 추가하고, Docker 실행 환경변수에 `ENABLE_VNC=true`를 함께 전달합니다. FARM2 예시는 외부 포트 범위 `9100~9199` 중 하나가 `6080`에 연결되는 방식입니다.

```
--enable_vnc true
```

관련 환경변수:

| 환경변수 | 기본값 | 설명 |
| :--- | :--- | :--- |
| `ENABLE_VNC` | `false` | `true`로 설정한 경우에만 VNC/noVNC를 시작합니다. |
| `VNC_PASSWORD` | 랜덤 8자리 | 지정하지 않으면 자동 생성 후 `vnc_password.txt`에 저장합니다. |
| `VNC_RESOLUTION` | `1920x1080` | VNC 화면 해상도입니다. |
| `VNC_DEPTH` | `24` | VNC 색상 깊이입니다. |
| `VNC_DISPLAY` | `1` | VNC display 번호입니다. 기본 VNC 포트는 `5901`입니다. |
| `NOVNC_PORT` | `6080` | noVNC가 컨테이너 내부에서 listen할 포트입니다. |


# ⚒️ 빌드 자동화
이 Docker 이미지는 GitHub Actions를 통해 자동으로 빌드 및 배포됩니다.
Expand Down
112 changes: 111 additions & 1 deletion entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,97 @@ ensure_sshd_allow_user() {
fi
}

start_novnc() {
case "${ENABLE_VNC:-false}" in
true|TRUE|1|yes|YES|on|ON) ;;
*)
echo "VNC/noVNC disabled. Set ENABLE_VNC=true to enable it."
return 0
;;
esac

local vnc_dir="$USER_HOME/.vnc"
local vnc_display="${VNC_DISPLAY:-1}"
local vnc_resolution="${VNC_RESOLUTION:-1920x1080}"
local vnc_depth="${VNC_DEPTH:-24}"
local novnc_port="${NOVNC_PORT:-6080}"
local vnc_password_file="$JUPYTER_DIR/vnc_password.txt"
local vnc_password

vnc_display="${vnc_display#:}"
if ! [[ "$vnc_display" =~ ^[0-9]+$ && "$novnc_port" =~ ^[0-9]+$ && "$vnc_depth" =~ ^[0-9]+$ ]]; then
echo "Invalid VNC configuration. Check VNC_DISPLAY, NOVNC_PORT, and VNC_DEPTH."
return 1
fi
local vnc_port=$((5900 + vnc_display))

if ! command -v vncserver >/dev/null 2>&1 || ! command -v websockify >/dev/null 2>&1; then
echo "VNC/noVNC packages are not installed. Skipping GUI startup."
return 0
fi

mkdir -p "$vnc_dir" "$JUPYTER_DIR" /tmp/.X11-unix /tmp/.ICE-unix
chown root:root /tmp/.X11-unix /tmp/.ICE-unix
chmod 1777 /tmp/.X11-unix /tmp/.ICE-unix

if [[ -n "${VNC_PASSWORD:-}" ]]; then
vnc_password="$VNC_PASSWORD"
elif [[ -s "$vnc_password_file" ]]; then
# head가 먼저 종료되며 tr이 SIGPIPE를 받아 pipefail로 실패 판정되는 것을 막는다.
vnc_password=$(tr -d '\r\n' < "$vnc_password_file" | head -c 8 || true)
else
vnc_password=$(head -c 64 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8 || true)
fi
vnc_password="${vnc_password:0:8}"

if [[ -z "$vnc_password" ]]; then
echo "Failed to prepare VNC password. Skipping GUI startup."
return 1
fi

printf "%s\n" "$vnc_password" > "$vnc_password_file"
chmod 600 "$vnc_password_file"

printf "%s\n" "$vnc_password" | vncpasswd -f > "$vnc_dir/passwd"
chmod 600 "$vnc_dir/passwd"

cat > "$vnc_dir/xstartup" <<'EOF'
#!/bin/sh
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
export XDG_SESSION_TYPE=x11
export XKL_XMODMAP_DISABLE=1
xrdb "$HOME/.Xresources" 2>/dev/null || true
exec dbus-launch --exit-with-session startxfce4
EOF
chmod +x "$vnc_dir/xstartup"
chown -R "$TARGET_UID:$TARGET_GID" "$vnc_dir" "$vnc_password_file"

gosu "$USER_ID:$USER_GROUP" env HOME="$USER_HOME" USER="$USER_ID" \
vncserver -kill ":$vnc_display" >/tmp/vnc-kill.log 2>&1 || true

echo "trying TigerVNC on localhost:$vnc_port..."
if ! gosu "$USER_ID:$USER_GROUP" env HOME="$USER_HOME" USER="$USER_ID" \
vncserver -localhost yes ":$vnc_display" -geometry "$vnc_resolution" -depth "$vnc_depth" >/tmp/vncserver.log 2>&1; then
echo "TigerVNC startup failed. See /tmp/vncserver.log."
cat /tmp/vncserver.log
return 1
fi
echo "TigerVNC listening on localhost:$vnc_port"

pkill -f "websockify.*$novnc_port" >/dev/null 2>&1 || true
# /tmp/novnc.log를 사용자 소유로 미리 만들어 강등된 권한에서도 append 가능하게 한다.
: > /tmp/novnc.log
chown "$TARGET_UID:$TARGET_GID" /tmp/novnc.log
chmod 640 /tmp/novnc.log
echo "trying noVNC on 0.0.0.0:$novnc_port..."
# 비특권 포트(6080)이므로 root 권한이 불필요하다. 사용자 권한으로 강등 실행.
nohup gosu "$USER_ID:$USER_GROUP" env HOME="$USER_HOME" USER="$USER_ID" \
websockify --web=/usr/share/novnc "0.0.0.0:$novnc_port" "localhost:$vnc_port" \
>/tmp/novnc.log 2>&1 &
echo "noVNC listening on port $novnc_port. VNC password saved to $vnc_password_file"
}

apt-get update
apt-get install -y auditd

Expand All @@ -83,6 +174,17 @@ mkdir -p "$USER_HOME"
chown "$TARGET_UID:$TARGET_GID" "$USER_HOME"
chmod 750 "$USER_HOME"

# 사용자 홈에 dotfile이 없으면 /etc/skel에서 부트스트랩한다.
# 로그인 셸이 ~/.profile → ~/.bashrc 체인으로 PS1을 정상화하도록 보장한다.
for skel_file in .profile .bashrc .bash_logout; do
src="/etc/skel/$skel_file"
dst="$USER_HOME/$skel_file"
if [[ -f "$src" && ! -e "$dst" ]]; then
install -m 644 -o "$TARGET_UID" -g "$TARGET_GID" "$src" "$dst"
fi
done
unset skel_file src dst

# MOTD 공지 출력하도록 설정
sed -i 's/^#\?UsePAM .*/UsePAM yes/' /etc/ssh/sshd_config
ensure_sshd_allow_user "svmanager"
Expand Down Expand Up @@ -112,6 +214,11 @@ for file in /etc/update-motd.d/60-unminimize /etc/update-motd.d/10-help-text; do
done
sed -i.bak '/echo -e "\\e\[1;31m"/d; /cat<<TF/,/TF/d' /etc/bash.bashrc

# TF 베이스 이미지가 박아둔 tf-docker PS1과 자동 cd $HOME 라인 제거.
# 로그인 셸에서 /etc/profile -> /etc/bash.bashrc 경로로 tf-docker 프롬프트가
# 노출되는 현상을 방지한다.
sed -i.bak '/tf-docker/d; /^cd "\$HOME"$/d' /etc/bash.bashrc

if [[ -f "/etc/legal" ]]; then
rm /etc/legal
fi
Expand All @@ -134,14 +241,17 @@ fi
sed -i "1i c.JupyterApp.config_file_name = 'jupyter_notebook_config.py'\nc.NotebookApp.allow_origin = '*'\nc.NotebookApp.ip = '0.0.0.0'\nc.NotebookApp.open_browser = False\nc.NotebookApp.allow_remote_access = True\nc.NotebookApp.allow_root = False\nc.NotebookApp.notebook_dir='$JUPYTER_DIR'" "$JUPYTER_CONFIG_FILE"
chown "$TARGET_UID:$TARGET_GID" "$JUPYTER_CONFIG_FILE"

# noVNC 기동. 외부에서는 컨테이너의 6080 포트를 매핑해서 접속합니다.
start_novnc || echo "VNC/noVNC startup failed."

# ldconfig permission 오류 방지
# bash.bashrc에서 ldconfig 명령어 삭제 후 명령어 실행 및 결과 출력
sed -i '/ldconfig/d' /etc/bash.bashrc
ldconfig && echo "ldconfig executed successfully" || echo "ldconfig failed"

# Jupyter 실행과 컨테이너 유지는 비-root 사용자로 전환한다.
exec gosu "$USER_ID:$USER_GROUP" bash -lc '
TOKEN=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 10)
TOKEN=$(head -c 64 /dev/urandom | tr -dc A-Za-z0-9 | head -c 10 || true)
echo "$TOKEN" > "'"$JUPYTER_DIR"'/jupyter_token.txt"
chmod 600 "'"$JUPYTER_DIR"'/jupyter_token.txt"
echo "trying jupyter lab..."
Expand Down
Loading