<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
```