Try   HackMD
tags: tutorials docker ubuntu linux cuda GPU NVIDIA python deep learning AI

Dockerfile 建立教學

Introduction

Dockerfile 其實就像是用很多 linux 指令的組合來構建想要的系統 image,一般我們都會用 DockerHub 上現存的 image 在網上建構,最常用的官方 imageubuntu,還有近年因機器學習而竄起的 nvidia cuda

Example

詳細指令內容請看官方教學,接下來只會大概介紹 FROMARGWORKDIRRUN

接下來用以下我寫的 nvidia cuda Dockerfile 講解

以下文件結尾的 \ 是表示換行接續符

# you can use `--build-arg CUDA=<cuda_version>` to specify pyton version # use `--build-arg CUDA=10.0-cudnn7-runtime-ubuntu18.04` to set cuda10 w/ cudnn7 as docker cuda # you can find cuda versions on: https://hub.docker.com/r/nvidia/cuda/tags # NOTE: default `CUDA` is `10.0-cudnn7-runtime-ubuntu18.04` # you can use `--build-arg PYTHON_VERSION=<version_num>` to specify pyton version # use `--build-arg PYTHON_VERSION=3.5` to set python3.5 as docker default python version # NOTE: default `PYTHON_VERSION` is `3` # you can use `--build-arg TZ=<timezone>` to specify timezone # use `--build-arg TZ=Asia/Taipei` to set Asia/Taipei as docker default timezone # you can find timezones on: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones # NOTE: default `TZ` is `Asia/Taipei` # NOTE: all arg set need to add prefix `--build-arg` # that is to say, if you want to specify cuda version, python version, and timezone # you have to write # `--build-arg CUDA=<cuda_version> --build-arg PYTHON_VERSION=<version_num> --build-arg TZ=<timezone>` # to do this ARG CUDA=10.0-cudnn7-runtime-ubuntu18.04 FROM nvidia/cuda:$CUDA ARG PYTHON_VERSION=3 ARG TZ=Asia/Taipei WORKDIR home # set timezone RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata && \ ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # install packages RUN apt-get update && \ apt-get install -y \ cmake \ wget \ curl \ git \ vim \ software-properties-common \ libgl1-mesa-glx libsm6 libxrender1 libxext-dev # register python dependency(ppa) # NOTE: Register ppa may take more time # More info: https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa # Note: Python2.7 (all), # Python 3.5 (16.04, xenial), Python 3.6 (18.04, bionic), Python 3.8 (20.04, focal) # are not provided by deadsnakes as upstream ubuntu provides those packages # (it means they don't need to register ppa) RUN ubuntu=$(lsb_release -r | grep "Release:") && ubuntu=${ubuntu##*:} && \ if [ "$PYTHON_VERSION" != "2.7" ] && [ "$PYTHON_VERSION" != "3" ] && \ [ \( ${ubuntu} = "16.04" -a "$PYTHON_VERSION" != "3.5" \) -o \ \( ${ubuntu} = "18.04" -a "$PYTHON_VERSION" != "3.6" \) -o \ \( ${ubuntu} = "20.04" -a "$PYTHON_VERSION" != "3.8" \) ]; then \ add-apt-repository ppa:deadsnakes/ppa && \ apt-get update; \ fi # install specific python version RUN apt-get install -y \ python$PYTHON_VERSION \ python$PYTHON_VERSION-dev && \ # set default `python` to `python$PYTHON_VERSION` ln -sf /usr/bin/python$PYTHON_VERSION /usr/bin/python # install pip RUN if [ $PYTHON_VERSION \> 3 ]; then \ apt-get install -y python3-distutils-extra && \ ln -sf /usr/bin/python$PYTHON_VERSION /usr/bin/python3; \ fi && \ curl -O https://bootstrap.pypa.io/get-pip.py && \ python get-pip.py && \ rm get-pip.py # uncomment it to not use default color prompt, # replace PS1 2nd occurence line (PS1 color setting line) # RUN sed -i '0,/PS1.*/! {0,/PS1.*/ s/PS1.*/'"\ PS1=\'\$\{debian_chroot:\+\(\$debian_chroot\)\}\\\[\\\033\[01;32m\\\]\\\u\\\[\\\033\[00;37m\\\]@\\\[\\\033\[01;35m\\\]\\\h\\\[\\\033\[00m\\\]:\\\[\\\033\[01;34m\\\]\\\w\\\[\\\033\[00m\\\]# \'"'/}' ~/.bashrc # set ~/.bashrc RUN sed -i 's/^#force_color_prompt=yes/force_color_prompt=yes/' ~/.bashrc && \ # above enable color prompt of docker in terminal # set alias echo "alias nv='nvidia-smi'" >> ~/.bash_aliases && \ echo "alias wnv='watch -n 1 nvidia-smi'" >> ~/.bash_aliases && \ echo "alias wwnv='watch -n 0.1 nvidia-smi'" >> ~/.bash_aliases

ARG

ARG CUDA=10.0-cudnn7-runtime-ubuntu18.04
ARG PYTHON_VERSION=3
ARG TZ=Asia/Taipei

21 行、23 行及 24 行都是 ARG 變數,這種變數是只會存在於 Dockerfile 中,並不會留到之後的 image 或是 container 中,並且可以在使用 Dockerfile 建立 image 時使用而外的 --build-arg {ARG_param_name}={ARG_value} 來改變 ARG 變數的值

也就是 CUDA 變數預設為 10.0-cudnn7-runtime-ubuntu18.04,而 PYTHON_VERSION 變數預設為 3,可以在建立image 時加上 --build-arg CUDA=9.0-cudnn7-runtime-ubuntu16.04--build-arg PYTHON_VERSION=3.8 來將預設值改變

更多建立 image 的教學請看這裡

runtime 版本是輕量版的 nvidia-cuda image,如果要更完整版請使用 devel 版本

例如 10.0-cudnn7-devel-ubuntu18.04


FROM

FROM nvidia/cuda:$CUDA

docker 基本上就像是疊積木,簡單來說 FROM 就是決定你要從哪個 base image 開始疊起,而這裡是使用 nvidia/cuda repository 下的 $CUDA tag 的 image,而這裡的 $CUDA 指的是上面 ARG 變數的 CUDA 值(預設為10.0-cudnn7-runtime-ubuntu18.04)

依照預設值展開,這行就等價於 FROM nvidia/cuda:10.0-cudnn7-runtime-ubuntu18.04


WORKDIR

WORKDIR home

WORKDIR 是拿來設定 RUN, CMD, ENTRYPOINT, COPYADD 指令的相對路徑,也是預設進入的路徑,若是不存在該路徑則會自動建立


RUN

RUN 就是執行某個 shell 指令

29 ~ 31

RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata && \ ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
  • apt-get update: 更新套件
  • apt-get install: 安裝套件
    • -y: 安裝時預設 yes,不用詢問
  • 這裡是設定時區的一些必要設置

34 ~ 42

RUN apt-get update && \ apt-get install -y \ cmake \ wget \ curl \ git \ vim \ software-properties-common \ libgl1-mesa-glx libsm6 libxrender1 libxext-dev
  • apt-get update: 更新套件
  • apt-get install: 安裝套件
    • -y: 安裝時預設 yes,不用詢問
    • cmake: cmake 編譯器套件
    • wget: wget 下載指令套件
    • curl: curl 下載指令套件
    • git: git 套件
    • vim: 文字編輯套件
    • software-properties-common: 額外可添加套件
    • 剩餘的是一些 opencv 所需的必要套件

51 ~ 58

RUN ubuntu=$(lsb_release -r | grep "Release:") && ubuntu=${ubuntu##*:} && \ if [ "$PYTHON_VERSION" != "2.7" ] && [ "$PYTHON_VERSION" != "3" ] && \ [ \( ${ubuntu} = "16.04" -a "$PYTHON_VERSION" != "3.5" \) -o \ \( ${ubuntu} = "18.04" -a "$PYTHON_VERSION" != "3.6" \) -o \ \( ${ubuntu} = "20.04" -a "$PYTHON_VERSION" != "3.8" \) ]; then \ add-apt-repository ppa:deadsnakes/ppa && \ apt-get update; \ fi

這段是比較有條件的指令

39
確認 ubuntu 版本,因為 CUDA 參數是在 FROM 之前,所以在之後會無法得知該參數的數值,因而要另外確認
  • ubuntu=$(lsb_release -r | grep "Release:"): 首先使用 lsb_release -r 列出系統版本資訊,接著使用 grep 找出 Release: 部分(也就是釋出版本)
  • ubuntu=${ubuntu##*:}: 將剛剛那行只取 : 之後的部分(版本號部分)

lsb_release -r 輸出為

Release:        18.04

而使用 grep "Release:" 後得到的值為 Release:18.04
使用 ubuntu=${ubuntu##*:} 會將其值只保留最後的 18.04

52
限制如果 $PYTHON_VERSION 不是 2.73 才需要進條件(預設不需要進此條件)
53 ~ 55
限制如果沒有依照各版本所配置的 python 版本才需要進條件(預設不需要進此條件)

(ubuntu16.04 且不是 python3.5) 或 (ubuntu18.04 且不是 python3.6) 或 (ubuntu20.04 且不是 python3.8)

56 ~ 57
條件執行內容
  • 55 行表示如果沒有要使用系統預設的 python 版本,則需要添加第三方套件(所以需要安裝 41 行的 software-properties-common 套件)
  • 57 行則是在剛剛添加完第三方套件後要執行更新讓系統知道有新的套件可安裝

61 ~ 65

RUN apt-get install -y \ python$PYTHON_VERSION \ python$PYTHON_VERSION-dev && \ # set default `python` to `python$PYTHON_VERSION` ln -sf /usr/bin/python$PYTHON_VERSION /usr/bin/python
  • 61 ~ 63 行都是安裝設定的 python 版本
  • 65 行則是把預設的 python 指令軟連結設為剛剛安裝的指定 pyhton 版本

79

# RUN sed -i '0,/PS1.*/! {0,/PS1.*/ s/PS1.*/'"\ PS1=\'\$\{debian_chroot:\+\(\$debian_chroot\)\}\\\[\\\033\[01;32m\\\]\\\u\\\[\\\033\[00;37m\\\]@\\\[\\\033\[01;35m\\\]\\\h\\\[\\\033\[00m\\\]:\\\[\\\033\[01;34m\\\]\\\w\\\[\\\033\[00m\\\]# \'"'/}' ~/.bashrc
  • 使用 sed 修改色彩控制的 PS1 參數
    • 是修改 PS1~/.bashrc 檔案出現的第 2 次

      • 原始 PS1
        ​​​​​​​​​​​​PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
        • 效果為 {user_name}@{host_name}:{work_path}$ 
      • 修改為
        ​​​​​​​​​​​​PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u\[\033[00;37m\]@\[\033[01;35m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\# '
        • 效果為 {user_name}@{host_name}:{work_path}# 
      • \u: user name,使用者名稱
      • \h: host name,電腦名稱
      • \w: work path,當前工作路徑

      簡單來說就是

      1. @ 之前加入 \[\033[01;37m\]
      2. \h 之前加入 \[\033[01;35m\]
  • 如果有想要使用修改後的顏色就把 79 行取消註解

68 ~ 74

RUN if [ $PYTHON_VERSION \> 3 ]; then \ apt-get install -y python3-distutils-extra && \ ln -sf /usr/bin/python$PYTHON_VERSION /usr/bin/python3; \ fi && \ curl -O https://bootstrap.pypa.io/get-pip.py && \ python get-pip.py && \ rm get-pip.py
  • python3-distutils-extra: 安裝 distutils 避免一些 python 安裝錯誤
  • ln -sf /usr/bin/python$PYTHON_VERSION /usr/bin/python3: 將 python3 軟連結設定為指定的 python3 版本
  • 72 ~ 74 行: 安裝 pip

82 ~ 87

RUN sed -i 's/^#force_color_prompt=yes/force_color_prompt=yes/' ~/.bashrc && \ # above enable color prompt of docker in terminal # set alias echo "alias nv='nvidia-smi'" >> ~/.bash_aliases && \ echo "alias wnv='watch -n 1 nvidia-smi'" >> ~/.bash_aliases && \ echo "alias wwnv='watch -n 0.1 nvidia-smi'" >> ~/.bash_aliases
  • 82 行: 將 ~/.bashrc 中的 #force_color_prompt=yes 取代為 force_color_prompt=yes 來打開 containerbash 提示色
  • 85 行: 設定新指令 nv,該指令等價於後面的 nvidia-smi 指令
  • 86 行: 設定新指令 wnv,該指令等價於後面的 watch -n 1 nvidia-smi 指令,也就是每 1 秒執行並刷新 nv 指令
  • 87 行: 設定新指令 wwnv,該指令等價於後面的 watch -n 0.1 nvidia-smi 指令,也就是每 0.1 秒執行並刷新 nv 指令

alias 要寫在 ~/.bash_aliases 檔案內