什麼是 Docker?基本觀念和實際應用

一般人可能沒有聽過 Docker,但如果你是軟體工程師,那你一定有聽過 Docker!

Docker 是一個用來開發與部署應用程式的一個開源平台,它可以將我們正在開發的應用程式與我們現有的硬體架構完全區分,讓我們更快速的完成開發。

透過使用 Docker,我們可以快速的完成開發與測試程式碼的環節,並且大幅度降低開發與部署的產品週期。

“為什麼同一份程式碼在我的電腦上可以跑,在別人的電腦上卻不行?”

想必各位工程師們一定都問過自己以上這樣的問題。會遇到這種問題,基本上就是對方電腦的架設環境或是硬體參數和你的不同。

比如說在一個 MacOS 作業系統下開發的軟體,搬到 Windows 系統上就會沒辦法成功執行。

而 Docker 的核心理念,就是要幫助大家解決這個問題。簡單來說,Docker 透過容器 (Container) 來讓程式在虛擬化的環境下執行。

但是在沒有 Docker 之前,我們是怎麼解決這個問題的呢?想必大家都聽過虛擬機 Virtual Machine (VM) 吧?VM 同樣也可以做到虛擬化環境以解決上述問題。

那麼重點來了,Docker 跟 VM,兩者差在哪裡呢?

Docker Container 對決 Virtual Machine

Docker Container

Docker container 是一個在你的電腦上獨立的運作的空間。一台電腦中可以同時運行很多個 Containers,而每一個 Container 都有他自己獨立運作的程序和 dependencies。

由上圖我們可以看到,每一個 Container 都是分享同一個 OS,也因為如此,Container 的大小通常只會佔用到幾十個 MBs 而已,而且啟動速度非常快。

Virtual Machine

Virtual machine 就是在自己的電腦上安裝其他的 OS 系統。

最常見的例子就是 Mac 的使用者在電腦上安裝 Windows 系統來執行 Windows 相關的應用程式。而因為每一個 virtual machine 都有自己的 OS,因此安裝起來會非常大,常常是幾十個 GBs。

一個表格來了解他們的主要差異:

Docker Container Virtual Machine
輕巧且快速 繁重且緩慢
分享同一個OS 每個VM擁有自己的OS
佔用較少記憶體 (MBs) 佔用較多記憶體 (GBs)
只是程序獨立,較為不安全 每個VM完全獨立,較為安全

Docker 基本概念

想要了解 Docker 如何運作,我們首先要先解釋三個專有名詞:

  • 倉庫 Registry
  • 映像檔 Image
  • 容器 Container

倉庫 Registry

倉庫 Registry 就是 Docker 用來儲存映像檔的地方,分為公有及私有兩種。

最大的公有倉庫叫做 Docker Hub,所有人都可以對其進行訪問,而 Docker 默認的設置也是從 Docker Hub 中找尋映像檔。而你也可以自行設置自己的私有倉庫。

使用者可以用 docker pull 或是 docker run 等命令從設置的倉庫下載或執行映像檔,並使用 docker push 將自己建置好的映像檔上傳到設置的倉庫。

映像檔 Image

映像檔 Image 就是一個唯讀的模板,在這個模板中含有 Docker 該如何創建一個 Container 的所有步驟。

一個映像檔常常是基於另一個映像檔所產生的,比如說你可以創建一個基於 Ubuntu 的映像檔,並在其中下載 Python 環境以及你要執行的應用程式。

你可以創建自己的映像檔或是直接使用別人創建好的,這些映像檔會存在一個倉庫裡。

如果想要創建自己的映像檔,那麼你應該要撰寫一個檔名叫 Dockerfile 的文件。在 Dockerfile 中,你會定義該如何創建並執行映像檔的每一個步驟。

容器 Container

容器 Container 是透過映像檔創建出來的,同一個映像檔可以創建非常多獨立的容器。

容器是可以被啟動、開始、停止、刪除的,並且每個容器都是相互隔離的,以保證系統的安全。我們甚至可以基於現在這個容器的狀態創建一個新的映像檔。

在容器中,你也可以自由控制每個容器的網路限制、儲存空間、使用者空間等等。

Docker 實例

了解 Docker 的基本概念後,我們就來透過一個實際的例子來更加了解 Docker!

以下的例子是在 Mac 上面運行,但基本上應該大同小異。

在這個例子中,我們將一起寫我們的第一個 Dockerfile,並利用這個 Dockerfile 建立映像檔,最後在 Container 中執行簡單的 Python 程式碼!步驟如下:

  1. 下載 Docker 並建立帳號
  2. 建立第一個 Dockerfile
  3. 了解 Dockerfile
  4. 建立映像檔 Image
  5. 運行容器 Container

下載 Docker 並建立帳號

首先,我們可以去 官網 直接下載 Docker。

下載完後,我們可以在終端機裡運行 docker --version 的指令確認安裝成功!如果安裝成功,應該會看到類似如下輸出:

$ docker --version
Docker version 20.10.5, build 55c4c88

接著,我們來到 Docker Hub官網 來建立帳號。這是為了讓我們可以從 Docker Hub 上獲取映像檔。

建立好帳號後,我們回到終端機執行 docker login,輸入剛剛建立的帳號密碼就完成啦!

建立第一個 Dockerfile

首先,我們來建立一個新的資料夾,隨便你想叫什麼。

在這個資料夾中,我們來建立兩個檔案,一個叫做 main.py,另一個叫做 Dockerfile注意,就叫做 Dockerfile,他是沒有副檔名的。

我們來看看兩個檔案分別長什麼樣子:

Dockerfile

FROM alpine:latest
WORKDIR test/
COPY main.py .
RUN apk update
RUN apk add python3

main.py

print("I am here")

了解 Dockfile

接下來,我們就來逐步解析 Dockerfile 裡的每一行在做什麼。

  • FROM alpine:latest

每一個 Dockerfile 的第一行一定都是由 FROM 開始,這行是用來定義我們的 base image。Base image 可以是任何映像檔,可以來自公開 Registry 或是本地端之前建立的映像檔。

而我們今天使用的 alpine:latest 就是來自 Docker Hub。Alpine 是一個非常輕量化的 Linux 作業系統,大小只有幾個 MB 而已,非常適合用來作為範例。

  • WORKDIR test/

WORKDIR 這個指令是用來定義現在的工作目錄的,如果定義的工作目錄不存在,那麼該目錄就會被創建。

以這個例子來講,我們創建了一個叫做 /test 的檔案夾。而接下來的指令,都會在當前工作目錄被執行。

  • COPY main.py .

COPY 這個指令應該就很清楚了,看名字就知道就是用於複製。

以這個例子來說,這個指令複製了我們剛剛創建的 main.py 到當前的工作目錄 .。因此,這個指令執行完後,我們的 /test 資料夾就會有 main.py

  • RUN apk updateRUN apk add python3

RUN 這個指令就是在 Container 內執行後面的指令。

在這個例子中,我們更新了 apk 並且安裝了 Python3 讓我們可以執行 Python 程式碼。

建立映像檔 Image

構建好 Dockerfile 後,我們就在我們創建的資料夾內執行以下指令來創建映像檔。

$ docker build -t docker-example .

我們來分析一下這個指令:

  • docker build:這個指令就是專門用來創建映像檔的。
  • -t docker-example:這是用來將印象檔取名並且標記的。以這個例子來說,我們將創建的映像檔取名為 docker-example。如果沒有特別標記,那麼預設的標記都是 latest
  • .:這個很不起眼的小東西非常重要。很多時候常常會忘記,這告訴 Docker 應該要在哪裡找 Dockerfile。這個例子來說,就是在當前目錄找尋。

執行以上指令後,我們會在終端機看到類似已下輸出:

[+] Building 10.3s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 125B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/alpine:latest 1.2s
=> [1/5] FROM docker.io/library/alpine:latest@sha256:8914eb54f968791faf6a8638949e480fef 1.0s
=> => resolve docker.io/library/alpine:latest@sha256:8914eb54f968791faf6a8638949e480fef 0.0s
=> => sha256:49176f190c7e9cdb51ac85ab6c6d5e4512352218190cd69b08e6fd803f 1.47kB / 1.47kB 0.0s
=> => sha256:c158987b05517b6f2c5913f3acef1f2182a32345a304fe357e3ace5fad 3.37MB / 3.37MB 0.8s
=> => sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b39768351 1.64kB / 1.64kB 0.0s
=> => sha256:c0d488a800e4127c334ad20d61d7bc21b4097540327217dfab52262adc0238 528B / 528B 0.0s
=> => extracting sha256:c158987b05517b6f2c5913f3acef1f2182a32345a304fe357e3ace5fadcad71 0.2s
=> [internal] load build context 0.0s
=> => transferring context: 52B 0.0s
=> [2/5] WORKDIR test/ 0.0s
=> [3/5] COPY main.py . 0.0s
=> [4/5] RUN apk update 2.3s
=> [5/5] RUN apk add python3 4.9s
=> exporting to image 0.6s
=> => exporting layers 0.6s
=> => writing image sha256:e8f4d4f10d55b8a8a95a145d0d0f80a128aad4297940249d1c0538171efd 0.0s
=> => naming to docker.io/library/docker-example 0.0s

我們可以執行 docker images 來確認我們有成功建立映像檔,如果有成功建立,我們會看到類似如下輸出:

$ docker images
REPOSITORY       TAG       IMAGE ID       CREATED       SIZE
docker-example   latest    e8f4d4f10d55   1 hours ago   58.7MB

運行容器 Container

建立的映像檔後,我們就可以透過映像檔來建立容器了!我們執行以下指令來建立容器:

$ docker run -it docker-example

如果執行正確,我們就應該會在容器裡面了!

我們可以透過 ls 來確認確實有 main.py/test 這個資料夾中。再來我們就可以透過運行 python main.py 看到結果了!

實際運行後會看到類似這個輸出:

$ ls
main.py
$ python3 main.py
I'm here

想要離開容器,直接打 exit 就可以了。我們可以透過運行 docker ps -a 來看到我們剛才執行的容器,因為容器在我們離開時就停止了,所以需要用 -a 才能看到非運行狀態的容器。

$ docker ps -a
CONTAINER ID   IMAGE            COMMAND     CREATED         STATUS                     PORTS     NAMES
7474c04c1968   docker-example   "/bin/sh"   2 minutes ago   Exited (0) 4 seconds ago             jolly_hugle

總結

到這邊基本上就是 Docker 的基本觀念和應用了!當然 Docker 遠遠不止於此,他還有一系列的指令、網路、Layers 等等概念。

以後我還會寫其他有關 Docker 的教學文章,但這章目前就到這邊啦!希望你們有學到新東西!