程式P.01 - Docker
🚀 三句話總結
- 了解為什麼我們會需要Docker這類容器化產品
- 學會Docker的file、image、container、compose、hub…etc
- 了解Docker的網路系統為什麼能映射到本機上
☘️ 為什麼我要了解
因為在上一回中提到Senior為了預防問題產生,所以有個產品是前後端都會需要的,就是Docker。
🎨 改變了什麼
了解Docker的原理和一系列的Docker操作,而我個人的專案就運用Docker實現標準化的CI/CD,而不是像以前使用個人電腦出版後才把出版資料部屬雲端,讓依賴的直接轉移成可以標準化的「容器」,而不是依賴我不乾淨的電腦。
✍️ 總結和心得
為什麼需要Docker?
在軟體業中,我們常把程式分為好幾個branch進行版控,每個branch都有他的定義,而業界常採用的方案為:
- Git Flow
- GitHub Flow
- GitLab Flow
而每種Flow定義都不一樣,但不外乎都有「生產branch」,即產線的程式版本就是這個Branch的程式版本,由此可知這個Branch至關重要。
而程式還需要編譯才能變成「產品」,因為程式的編譯常常被很多外部環境干擾,像是Windows版本、編譯器版本、環境變數 …etc,而這些如果用開發者電腦進行編譯,勢必有很大的風險,因為開發者電腦最「髒」,所以我們常需要額外的 VM、出版機 …etc,用來專門編譯「產品」。
而不論是VM還是出版機,他們的容量都非常的大具有繁多的產品不需要的功能(藍芽驅動、鍵盤驅動、螢幕驅動..etc),並且還是有可能被開發者「玷汙」,無法標準化和最佳化出版環境,只能靠公司團隊治理才能規避,所以有個工具叫做「Docker」,該產品能夠標準化出版和最佳化環境,並且只需要靠軟體人員就能構建,不像是出版機還需要額外硬體部門支援,所以Docker是現代標準化容器系統的工具。
Ex:
小明寫了個javaScript程式,然後他要進行「出版」,如果沒有Docker,他要安裝VM於是他進行了以下操作
- 選擇OS、配置硬碟容量、安裝各種javaScript不需要的驅動 ..etc
- 進入OS後 下載瀏覽器、下載並安裝NVM、下載並安裝指定NodeJs版本
- 安裝依賴套件
- 執行出版
- 刪除VM
而上述步驟有一步做錯,出版環境就被玷汙了,但如果有了Docker會變成如何?
- 利用Docker產生Container
- 基於Container執行出版
- 利用Docker刪除Container
Docker是什麼:
想像一下,今天你購買了一個蛋糕
- 如果是VM,他會就地烘烤蛋糕,而為了蛋糕於是你被迫購買了 烤箱、攪拌機 …etc,最終你會獲得蛋糕與一堆烤箱、攪拌機。
- 如果是Docker,你會獲得一個貨櫃,貨櫃裡裝的就是一個蛋糕而已。
在上述範例中蛋糕就是程式、烤箱和攪拌機則是驅動軟體,所以綜上所述,我們可以整理出Docker的特性:
- Docker是一個工具,能夠幫助我們更方便的創建、運行、部屬程式
- Docker占用資源少,資源利用率高,運行快
- Docker能提供一次性服務,常被使用在雲服務中和微服務架構
而貨櫃這比喻,在Docker中則稱為 Container
Docker 原理:
Docker之所以能生成一個貨櫃,實現與VM一樣的完全隔離效果主要是利用Linux的功能,如下:
我們利用Linux的功能,將Container完全獨立,就像是VM一樣,所以貨櫃中的程式,就必須要有個Linux系統基底,不然無法實做Docker,但幸運的Linux有各種發行版本,最小的版本可以達到容量不到100M而已。
所以在程式中貨櫃中會裝有兩個東西:
- Linux系統
- 你的程式
那麼上述中的貨櫃(Container)怎麼來的?
- Container由Image創建,Container也就是貨櫃
- Image由File提供,Image也就是蛋糕模具、蛋糕材料 ..etc所組裝的「烘焙組合包」
- File由廚師所設計,File也就是蛋糕要用什麼巧克力品種、奶油幾克,模具應該長怎樣,一張文字清單
下載Docker
網址:Docker
雖然Docker是基於Linux實做的,但我們仍可以在Mac or Windows上安裝並使用他,是因為
- Windows 使用 Hyper V 技術安裝Linux
- Mac 使用 Virtualbox 技術安裝Linux
注意:這邊只是我們為了使用Docker會透過VM方式安裝,但實際打包出來的Container 並不包含任何VM。只有在非Linux系統使用Docker才要透過VM安裝。
而安裝步驟這邊不多贅述,但是要特別注意的是,Docker是一個Client to Server的架構
這邊的Client是非常陽春的Command Line介面 (CLI = Command line interface),而我們如果要使用CLI呼叫Docker的功能我們就必須指定要用哪台Docker機器 (Server)。
正常情況下,Docker Machine預設的機器都是我們上面講到的VM容器,但你也可以指定另一台實體Linux機器,可以是GCP、AWS、樹莓派 …etc 有點像是SSH遠端功能。
1 | 於終端機輸入 docker version: |
這邊Client是你的電腦,明顯我是使用Windows系統,而我透過Dokcer安裝軟體自動幫我用 Hyper V 創建的 Linxu/amd64 被當作我預設的Docker Machine。
而如果你想要使用別人的Linux機當作你的Docker Server,可以自行搜尋 Docker Machine 指令說明進行切換Server,這邊不多闡述。
Dockerfile
安裝好Docker後就來實作出一個Container,而我們上面說過
Container -> Image -> File
Dockerfile是所有一切源頭,所以就先來聊聊Dockerfile。
Dockerfile其實是一個文字檔(該文字檔檔名必須叫做「Dockerfile」不能是「Dockerfile.txt」否則Docker軟體不會偵測到),其裡面記載了貨櫃
- 使用的基底,通常是某個Linux版本 (debian、ubuntu、alpine …etc)
- 依賴的東西(使用VM會自動安裝不必要的東西,但是這邊可以指定你要的東西就不會亂裝不必要的東西)
- 要做的事(通常依賴的東西下載好後,都要安裝,又或是設定你的程式需要的配置)
Ex:
1 | FROM selenium/standalone-chrome:99.0 |
FROM 使用哪個人的帳號名稱/那個人的作品名稱:版本號
這邊使用的是「別人的做好的 File」,當然你也可以自己使用各種發行版的Linux,而發行版的Linux是Docker官方維護的所以無須指定「使用哪個人的帳號名稱」。
其結構就如下:
1 | FROM ubuntu:latest |
這邊FROM的對象如果沒有特別寫URL,則通常是指 DockerHub,這是類似Github的倉庫,只是Docker官方運營的倉庫,專門存放別人做好的Docker Image供任何人使用。
而我們知道類似Github的倉庫有很多Gitlab ..etc,所以Docker倉庫也有很多種:
1 | FROM gcp.io/foo/bar:123 |
其意思是使用gcp.io這個docker倉庫,並使用foo這個人的bar專案,其版本號為123。
而撰寫好Dockerfile後,就必須把他build 成Image。
1 | docker build -t lucas-project . |
-t 代表Image到時候要叫什麼名稱,而如果要丟到 DockerHub命名方式必須是「帳號名稱/專案名稱」
. 代表Dockerfile的位置,這邊只有一個「.」代表Dockerfile在當前目錄下,當然也可以使用絕對路徑
1 | docker build -t lucas-project C:\Users\jlthu\OneDrive\桌面\Lucas\shopee-coins-bot |
Docker Image
上述可以透過指令把 Dockerfile build成 Image,而此時Docker Image指是一個檔案而已,毫無任何作用,你無法直接交互他,你如果要使用他,就必須把它 run 成一個Container
1 | docker run -d -p 123:80 lucas-project |
而一個Image可以生成多個Container,用完即刪,以此達到標準化功能。
所以實務上,我們可以寫一個出版專用的Image,裡面包含了所有出版會需要的依賴。以後只要我的Code修改了,我只用把這Image生成一個Container就可以進行編譯,用完後就刪掉,下次Source Code又改就可以再次使用Image生成,再次利用。
PS: -d 是代表這個Container跑起來後就進入背景模式,與之相反的是 -it,這個Container跑起來後我要進入這個Container
PS: -p 123:80 是說本機中123 Port 對接這個Container的 80 Port
Docker Compose
我們知道怎麼從Dockerfile To Container後,其實Docker的作用並不只是出版專用這麼簡單而已,在業界中系統架構有很多種,而其中「微服務」是現今主流的架構,而為了實現微服務功能我們會需要使用Docker部屬各個微小的服務,以此進行獨立並解偶。
假設我們有個訂單系統,我們會有三個基本單元 前端、後端、DBM,這就是三個服務也就是三個Container,但是他們要如何溝通呢?因為根據Docker的特性他們是虛擬化隔離
以網路隔離
甚至與父層級的本機也是隔離的,那們要讓前端下單 -> 後端寫單 -> DB儲存
這樣的溝通要怎麼實現?
在Docker中有一個概念網域,我們可以建立一個網域,然後把 前端、後端、DB 的Container塞入,讓他們可以彼此溝通得到,類似以下指令
1 | let network = []; |
PS: 詳細如何建立網路的指令和把容器加入自定義網域,請自行搜尋因為不常用,Docker有另外一個指令可以取代他。
但Docker有個指令可以更簡單實作該功能,就是Docker Compose。
Compose會自動建立一個網域,然後所有被Compose Run起來的容器,都會被自動塞進這個網域中。Compose可以用來解決容器與容器之間的合作關係,並且功能非常多,甚至你可以透過Comopse就取代上面的 Docker run、build指令,所以Compose算是蠻方便的工具。
而要使用Docker Compose功能就必須建立一個檔案「docker-compose.yml」
1 | version: "3.9" |
該檔案是yaml格式是種常見的config格式,類似python的空白規則,從上述yaml檔案看得出來,有兩個Container名叫filebeat_0
、kafka_1
1 | filebeat_0: |
- filebeat_0的Image是由filebeat資料夾內的Dockerfile所build而得,並且那個Dockerfile需要提供兩個自定義的參數(CONFIG_NAME、KAFKA_PORT)
- kafka_1的Image是由DockerHub中bitnami這人所提供
1 | C:\Users\jlthu\OneDrive\桌面\Lucas\docker-filebeat-kafka-clickhouse |
上面就是Docker Compose取代Docker build的功能
1 | filebeat_0: |
上面就是Docker Compose取代Docker run的功能
- filebeat_0 的Container Run起來後,監聽本機9000對應到Container內的9000,並且把Container中
/var/log
路徑的東西同步到本機的./filebeat/input
- kafka_1 的Container Run起來後,監聽本機9100對應到Container內的2181,並且把Container中
/bitnami
路徑的東西同步到本機的./kafka/kafka_1/persistence
並設定Container的環境變數(ENV)
當這些設置文件都配置好後,我們就可以使用docker-compose up
,就會根據docker-compose.yml
記載所運行,而要關掉則可以docker-compose down --rmi local
,就會把compose的容器都關掉,並且rmove local image也就是filebeat這種本地build的Image檔案。
1 | C:\Users\jlthu>docker network ls |
上面就是Docker Compose取代Docker Network的功能,使用docker-compose up
指令會自動建立一個network,他的名字是docker-filebeat-kafka-clickhouse_default
規則是 $資料夾名稱_default
,而我們可以使用docker inspect
進行檢查docker-filebeat-kafka-clickhouse_default
內的Containers有誰
1 | C:\Users\jlthu\OneDrive\桌面\Lucas\docker-filebeat-kafka-clickhouse> docker inspect docker-filebeat-kafka-clickhouse_default |
顯然Containers
欄位就有我們在Compose記載的Container
Docker Network
在上述我們大致了解Network,只有在同一個網域內的Containers 才能互相溝通到,但是在Docker中Network算是一門學問,所以這邊稍微解釋下。
1 | C:\Users\jlthu>docker network ls |
在安裝Docker後,Docker會自動建立三個Network,這三個是無法被刪除的,也是最常用的Docker Network Drivers
PS:Docker有七種Network Drivers,這邊僅簡述其中三種,畢竟還有NAT、GateWay、veth ..etc 概念,詳細規則可能日後會單獨寫一篇文章。
Null
這類型如其名,就是null,無法進行與外部和其他容器任何連結
1 | C:\Users\jlthu>docker run -it --network=none ubuntu:14.04 /bin/bash |
你可以從上面的各種指令看得出來,這個Container的網卡並沒有能力向外發出連線能力,甚至curl
和apt
這類的網路指令都無法運作,完全屏蔽了網路隔離
但是並沒有完全屏蔽虛擬化隔離
,也就是說雖然無法收發任何網路封包,但是我們可以透過 volumes 同步Container內的資源
如果你今天需要執行一個不需要網路需求的Container,可以考慮用null類型。
Bridge
這是Docker默認的網路模式,我們可以透過 port 參數進行橋接,Host只能透過 Conatiner Export 的Port 進行網路溝通(Container內的Port對應到Host的Port),而其他網路功能完全與Host隔絕。
而在相同的網路中,彼此的Container可以互相溝通到,但是但是
- 如果Container的網路不是自定義的Bridge,換句話說也就是使用系統自動建立的Bridge網路的那些Container,不會享有Conatiner Name map to DNS 功能,要溝通必須透過ping ip,不能ping Name
1 | C:\Users\jlthu>docker inspect bridge |
- 如果Container的網路是使用者自定義的Bidge(包含Docker-compose自動建立的網域),將會享有同個網路內的 Conatiner Name map to DNS 功能,可以使用 ping Name。
1 | C:\Users\jlthu>docker network create -d bridge lucas |
Host
這是一個可以把Container直接運作在Host的網域,完全捨棄網域隔離的功能,也就是可以不經過 -p 80:80這種指定export Port方式,因為Conatiner的網域就是Host網域
1 | docker run -itd --rm --network host --name nginx-test nginx |
然後你在網頁上輸入127.0.0.1:80
就可以看到畫面了,因為nginx
預設是80 port,並且host的網域。
注意!!!!
host網域有限制!!!
如果你是透過 docker desktop 安裝Docker,請注意這邊的host網域並非是你主機網域。
因為我們說過 Docker是基於Linux系統,而你之所以能在Mac or Windwos上運行,全因為是 Hyper-V
or Virtualbox
,所以你host的對象是他們而不是你的主機,換而言之,Host網域無法運作在Windwos Mac
Prerequisites
- This procedure requires port 80 to be available on the Docker host. To make Nginx listen on a different port, see the documentation for the nginx image
- The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac, Docker Desktop for Windows, or Docker EE for Windows Server.
PS: 詳細請參考 Docker官方說明、stackoverflow
docker swarm
這個可以不用學,因為業界都在使用 K8S ,K8S可以完全取代他,而K8S還可以運行非Docker的容器。所以去學K8S吧。
PS: 誰說容器化只有Docker的,市面上還有很多類似Docker的產品。