<center> <image width=75% src=https://hackmd.io/_uploads/BJj4FrLDyg.png> </center> **Docker允許所有團隊成員在同一環境中建置和測試。** 有這麼好康 ?? 在本機環境中處理多個 c++ 專案時,系統上會安裝各種程式庫像是 OpenCV, Open3D 等等。 接下來會遇到多個環境問題: 1. 開始新專案時, 如果新專案的依賴項可以使用多個版本 - 可能會有API衝突 - 可能使用效能比較差的版本, **沒有人知道**. 2. 回顧舊專案時, - 從建立於 OpenCV3 的專案中休息一下... 再處理 OpenCV4 的專案... 如果再次回到這個 OpenCV3 專案怎麼辦? 如果使用 ```find_package(OpenCV REQUIRED)```,沒有人知道會取得哪個版本的函式庫。 3. 團隊合作時, 當某成員將新程式推上至 git, 建置於另一成員的電腦卻崩潰... <br> 解決上述問題是非常惱人的, 1. 需要反覆確認函式庫版本是否相同, 開始進行一系列的清理... ~~砍掉linux重灌比較快~~ 2. 需要確認每個成員都使用相同版本的函式庫... <br> 使用 Docker 會變得很輕鬆, 1. 先建構多個函式庫 (i.g. OpenCV) 的基礎映像 - 使用 Docker 在上預先安裝 clear Ubuntu 20.04,並建置多個函式庫(i.g. ) - 將結果打包成映像,避免每次重複建構 2. 再去建構專案程式碼 ## 基礎概念 1. Docker 是一種 Container Container(容器) 是一種輕量級、可攜帶、獨立的軟體打包與運行技術。它將應用程式及其所有的相依環境(如程式庫、設定檔、依賴檔案)打包在一起,確保應用程式在不同的環境中都能一致地運作。 2. Virtual machine v.s. Docker 與虛擬機(Virtual Machine)不同,Container 不需要內建完整的作業系統,它們直接共享主機(Host)的作業系統核心(Kernel),因此啟動速度更快、資源占用更少。 <center> <image width=85% src=https://hackmd.io/_uploads/SkImRNvvye.png> </center> ## 安裝 Docker 1. 安裝方式 :::spoiler 極速安裝(不推薦) Docker版本較舊,建議參考官方文件進行安裝。 ```shell= sudo apt-get install docker.io ``` ::: :::spoiler 官方安裝懶人包, 建議使用[官方安裝方式](https://docs.docker.com/engine/install/ubuntu/) 1. 移除 docker 舊版本 ```shell= for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done ``` 2. 安裝 docker by apt - 設定 docker 基本配備 ```shell= # Add Docker's official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update ``` - 安裝 docker 套件 ```shell= sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` - 驗證 docker ```shell= sudo docker run hello-world ``` <image width=65% src=https://hackmd.io/_uploads/r1xcYMDjwJe.png> ::: 2. 檢查 Docker 服務有無正常啟動 ```shell= service docker status ``` 正常啟動會顯示 ```Active: active (running)``` 3. 將自己使用者帳號加入 Docker 群組 擺脫每次執行 docker 皆需要 ```sudo```. ```shell= sudo usermod -aG docker <username> ``` 重新登入 : 通常用戶組更改需要重新登入才會生效. ```shell= newgrp docker ``` ## Docker : image v.s. container - 映像檔 image image 是一種靜態模板, 包含應用程式及其所有依賴. 其內容不可被變更. - 容器 container - container 是 image 的運行實例. - container 建立時會疊加一個可寫層在 image 之上。任何檔案的新增、修改或刪除都發生在這個可寫層。一旦 container 停止或被刪除, 其可寫層也會損毀. 通常會採用共享資料夾方式, 來避免資料損毀. - 可採用多個 container 共享一個 image ## 建立乾淨 Ubuntu 映像檔於 Docker ```shel= # 建立乾淨 Ubuntu, 並在後台執行該容器 sudo docker run -dit ubuntu:latest sudo docker run -dit ubuntu:20.04 # 查看正在執行的容器 sudo docker ps # 列印所有映像檔 sudo docker images ``` <image width=65% src=https://hackmd.io/_uploads/HkWBDPivJx.png> ## 創建 Dockerfile 用於自動化建置 每次建立容器都得執行指令,會過於繁雜。我們可以透過 Dockerfile 來達成自動化創建環境作業(```touch Dockerfile```)。 - 撰寫 Dockerfile 該專案需要 opencv, c++, cmake. 若 opencv 需要指定特定版本, 可以參考[opencv官網編譯流程](https://docs.opencv.org/4.5.3/d7/d9f/tutorial_linux_install.html#tutorial_linux_install_quick_build_contrib). opencv的cmake需要特別注意是否使用windows. ```shell= # Use Ubuntu 20.04 as the base image FROM ubuntu:20.04 MAINTAINER <your name> LABEL maintainer="<your gmail>" # Avoid interactive prompts during installation ARG DEBIAN_FRONTEND=noninteractive # Install C++ development tools and dependencies RUN apt-get update && apt-get install -y \ build-essential \ cmake \ git \ wget \ libopencv-dev \ pkg-config \ libeigen3-dev \ libboost-all-dev \ libopencv-core-dev \ libopencv-highgui-dev \ libopencv-imgproc-dev \ libopencv-videoio-dev \ libopencv-imgcodecs-dev \ libopencv-calib3d-dev \ libopencv-objdetect-dev \ libopencv-features2d-dev \ libopencv-contrib-dev \ && apt-get clean # Intall GUI dependency RUN apt-get install -y \ x11-apps \ libgtk2.0-dev \ libcanberra-gtk-module \ libcanberra-gtk3-module # Create a working directory WORKDIR /app # Copy local code to the container's working directory COPY . /app # Create and build the CMake project RUN mkdir -p build && \ cd build && \ cmake .. && \ make # Set the default command to run when the container starts CMD ["bash"] ``` - 建置專案之映像檔 執行建置指令在包含```Dockerfile```的當前目錄, ```-t```是tag的意思. ```shell= docker build -t <your-image-name> . # 查詢是否成功建置出 image docker images ``` ```shell= # 自定義 Dockerfile 的名稱需要額外加入 -f docker build -t <your-image-name> -f <your-Dockerfile-name> . ``` - 建立 arm64 於 x86 的 cross-compile 環境 ```shell= docker buildx create --use docker buildx build \ --platform linux/arm64 \ -f dockerFile_ros1_rpy5 \ -t kalibr-rpy5 \ --load . ``` - 執行乘載專案映像檔之容器 ```-i```是交互介面的意思, ```-t```開啟terminal. ```shell= docker run -it <your-image-name> ``` ## 刪除 Docker 容器/映像檔 假如我們想刪除```<none>```標籤的映像檔 (dangling images), 通常是重複建置相同 image 名稱造成的。 <image width=65% src=https://hackmd.io/_uploads/SkiEzIQYke.png> - 假使刪除**所有** ```<none>```標籤的映像檔 ```shell= docker images -f "dangling=true" -q | xargs -r docker rmi ``` 如果容器停止但還沒刪除就會出現 : ```Error response from daemon: conflict: unable to delete 0e81ff9da00c (must be forced) - image is being used by stopped container 5ba8210f9b58``` 運行以下命令用來刪除已經停止的容器 ```shell= docker container prune -f ``` ## 如何取出資料從 Docker 容器 - 使用 docker cp 指令 1. 查詢容器 ID 或名稱 ```shell= docker ps ``` 會看到以下輸出: ```shell= CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS   NAMES a1b2c3d4e5f6 <your_image_name> "bash" 10 minutes ago Up 10 minutes amazing_brown ``` - container 名稱就會是```amazing_brown```. - image 名稱就是```<your_image_name>```. 2. 從容器複製資料至本機端當前目錄 ```shell= docker cp amazing_brown:/app/output_image.jpg ./output_image.jpg ``` - 使用共享資料夾 啟動容器時掛載目錄 : 假設本機的目錄為 ```/home/images```, 在容器內,將影像儲存到 ```/app/output```. ```shell= docker run -it -v /home/images:/app/output <your_image_name> ``` ## 如何於 Docker 容器中開啟 GUI 功能 1. 需要於主機端與容器端皆安裝 x11, RTX ```shell= apt-get install -y \ x11-apps \ xauth \ libgtk2.0-dev \ libcanberra-gtk-module \ libcanberra-gtk3-module ``` 驗證安裝完成透過 ```xclock``` 2. 主機端需確認 x11 相關權限 - ```.Xauthority``` - ```/tmp/.X11-unix``` 3. 執行指令 ```shell= docker run -it \ -e DISPLAY=$DISPLAY \ -e XAUTHORITY={Xauthority Path} \ -v {Xauthority Path}:{Xauthority Path} \ -v /tmp/.X11-unix:/tmp/.X11-unix \ --net=host \ {imageID_or_repo:Tag} xclock ``` - ```{Xauthority Path}``` 可以藉由 ```ps aux | grep Xorg``` 獲得相關位置. - ```-e``` 設定 container 環境變數指令 - DISPLAY=$DISPLAY: 將 container 內部的 DISPLAY 參數使用的顯示 port 設成和外部使用者同一個 - XAUTHORITY={Xauthority Path}: 將 container 內部 X server 授權文件的 XAUTHORITY 參數設成和外部使用者同一個顯示授權文件 - ```-v``` 外部授權相關文件掛載進 container - 將 ```{Xauthority Path}``` 掛載進 container 的 ```{Xauthority Path}``` - 將 ```/tmp/.X11-unix``` 掛載進 container 的 ```/tmp/.X11-unix``` ## 離開 Docker 1. 退出容器, 但是讓容器於後台執行 使用快捷键 ```Ctrl + P + Q``` 2. 停止並退出容器 同時這也會損毀可寫層 ```shell= exit ```