Docker,開源容器化平台,改變軟體開發與部署方式。它提供輕量、可攜、自包含的環境,促進應用程式在不同環境中無縫運行。這份學習筆記將引導你了解 Docker 的核心概念、指令和實作,助你在容器技術的世界中航行自如,提升你的開發和部署效率。
本筆記參考課程:Docker容器技术从入门到精通
講師筆記於此:www.docker.tip
安裝
Rocky Linux 9
參考此篇文章:How To Install and Use Docker on Rocky Linux 9
Raspberry (ARM)
安裝會有點慢,可能是 I/O 問題。
參考官方文件安裝到 Centos 上面:Install Docker Engine on CentOS
容器速成指引
簡易指令介紹
查看版本
docker version
docker info
在 windows 上執行會有以下訊息
Client:
Cloud integration: v1.0.29
Version: 20.10.21
API version: 1.41
Go version: go1.18.7
Git commit: baeda1f
Built: Tue Oct 25 18:08:16 2022
OS/Arch: windows/amd64
Context: default
Experimental: true
Server: Docker Desktop 4.15.0 (93002)
Engine:
Version: 20.10.21
API version: 1.41 (minimum version 1.12)
Go version: go1.18.7
Git commit: 3056208
Built: Tue Oct 25 18:00:19 2022
OS/Arch: linux/amd64
Experimental: true
containerd:
Version: 1.6.10
GitCommit: 770bd0108c32f3fb5c73ae1264f7e503fe7b2661
runc:
Version: 1.1.4
GitCommit: v1.1.4-0-g5fd4c4d
docker-init:
Version: 0.19.0
GitCommit: de40ad0
基本指令結構
docker [OPTIONS] COMMAND
docker [Management Commands] [Commands]
例如:
# 抓取名為 nginx 的 image
docker image pull nginx
# 停止名為 web 的 container
docker container stop web
Image and Container
Image
- image 是一個 read-only 的檔案
- 可以視為一個 template
- 其有分層的概念存在
Container
- 實質是複製 image 並在 image 最上層加上一層 read-write 的層,稱之為容器層 Container layer
- 同一個 image 可以建立多的 container
- container 大體上可以理解為執行中的 docker image
Basic command
指令有一些常用的是有提供簡單縮短長度的寫法,不是都一定要打完整的 Command,但是以初見者來說打完整的指令會較容易知道指令的用途。
操作 | 完整指令 | 簡易指令 |
---|---|---|
創建 Container | docker container run <image name> | docker run <image name> |
列出狀態為 up 的 Container | docker container ls | docker ps |
列出狀態為 up/exit 的 Container | docker container ls -a | docker ps -a |
停止 Container | docker container stop <name or ID> | docker stop <container name or ID> |
刪除 Container | docker container rm <name or ID> | docker rm <container name or ID> |
所有的指令都可以通過 –help 來查看解釋。
大量停止/刪除 Container 的技巧
介紹大量停止與大量刪除的用法:
簡單的作法是下
docker container ps
找出所有的 CONTAINER ID,然後下
docker container stop id1 id2 id3 id4
就可以停止 id1~4 的 Container
但是太麻煩了,可以改用以下複合指令
docker container stop $(docker container ps -aq)
同理刪除時可以使用
docker container rm $(docker container ps -aq)
- 這裡是用 doller 符號加上小括號,不是大括號。
其他常用刪除 Container 的指令
這是一個可以快速刪除沒有在使用的 Container 的指令
docker system prune
- 注意如果加了 -a 就會連 Image 也憶起刪除
Attached 與 Detached Mode
Attached Mode:前台執行,不建議使用 Detached Mode:後臺執行
要指定 Detached Mode 需要下一個 -d Option 如下:
docker container run -d -p 80:80 nginx
而要從 Attached Mode 轉為 Detached Mode 可以下以下指令:
docker attach id
Interactive Mode
如果只是要看 Container 裡面程式的 log,可以下:
docker container log id
但是如果要更複雜的操作,可以在 run 的時候啟動 shell ,就可以進行像一般 Console 中的操作
docker container run -it id sh
或者是對 Detached Mode 的 Container 下 exec 指令進入 sh
docker exec -it id sh
- 請注意要有 -it 這兩個 option
Difference between VM and Container
- Container 其實是 Process,這一點可以查看系統正在執行的 process 得知
- 從 Linux 上才觀察的到
- Container 中的 processes 會被限制對 CPU/Ram 等資源的存取。
- 如果把 Container Process 停掉,Container 就會關閉。
Container 中的 Process
如果先利用 docker container run -d nginx 來啟動一個 Container 再用 docker container top id 來觀察 container 的 PID 你會發現這個 ID 於 linux 下中使用 ps 指令會找到該 process 的。
但是如果進入到 Container 中去查看其內部 Process 的 PID,會與外部 linux 中看到的 PID 不一致,這是有關於 Container 隔離的議題。
- Windows 中無法查看。
docker container run 的執行步驟
如果我們下了以下指令:
$ docker container run -d --publish 80:80 --name webhost nginx
- -d 表示 detached mode
- –publish 表示 port 綁定
- –name 表示自訂 Container 的名稱,不要使用預設的隨機字串
則其系統會有以下動作:
- 在 Local 找是否有 nginx 這個image
- 如果沒有,就會去 image registry 找 nginx 這個 image 並且下載,預設是最新版 (nginx:latest)
- 預設的 registry 是 Docker Hub
- 基於 nginx image 來創建一個新的 Container,並且準備運行
- Docker engine 分配給這個 Container 一個虛擬 IP 位置
- 在宿主機上打開 80 port 並把容器的 80 port 轉發到宿主機上
- 啟動 Container,運行指定的命令(這裡是一個 shell script 去啟動nginx)
- 如果於 run 指令最後沒有加上要執行的 shell script,就會執行其 image 預設的 script。
What is tty?
點擊此文章閱讀:tty 到底是什么
從 Container 中取出檔案
為了測試方便,以下指令可以將檔案自 Container 中取出:cp
docker cp 6619ff360cce:/opt/h2-data/pkslow ./
docker cp 6619ff360cce:/opt/h2-data/pkslow/pksl
Image 的建立管理與發佈
Image 的取得
- 使用 pull from 指令:從某個 Registry 抓回來,其有分 private/public regisrty
- 使用 build from Dockerfile:從 Dockerfile 的設定中自己建立
- 使用 load from :從一個檔案中 import 進來。
常用的 Registry
可以注意一下免費版的使用限制
docker image 常用操作
Pull 操作
這裡以 DockerHub 為例,先到 DockerHub 輸入搜尋:nginx,選擇官方的 image
可以看到右手邊就會有指令提供輸入了:docker pull nginx
事實上是推薦 docker image pull nginx 的,因為那是早期 Docker 版本的指令
預設是會用 tag 為 lastest 的版本,如果需要其他版本,則可以去找相應的 tag 後再輸入於冒號後方:
docker image pull nginx:1.20.0
那如果要去 Quay 下載 image 也是同樣道理:
docker pull quay.io/bitnami/nginx
Image 的詳細資訊察看
先利用 ls 找到你要查看的 image 其 ID
docker image ls
然後就可以繼續使用 inspect option 去列出該特定 image 的詳細訊息。
docker image inspect image_id
console 接著會顯示 Json 格式的資料,目前比較值得注意的有以下幾個項目:
- “Os”:“linux”
- “Architecture”:“amd64”
- “layers”:[ “sha256:XXX”,“sha256:YYY”,… ]
Image 刪除
這裡要注意的是,只能刪除『沒有在執行』的 Image ,而且也沒有 Container 有在用這個 Image (包含已經停止中的 Container)
所以你可以先去刪除有用到這個 Image 的 Container,再去刪除 Image 才可以正常執行。
docker image rm image_id
docker system prune -a
其他常用刪除 Image 的指令
這是一個可以快速刪除沒有在使用的 Container 與 Image 的指令
docker system prune -a
- -a 不是只有刪除 Image 而已
Image 檔案匯出與匯入 (offline)
這種方法少用,是用在需要把 Image 轉給一台無法上網取的 Image 的電腦時可以使用
Save
docker image save nginx:1.20.0 -o nginx.image
- -o 表示 Output,後面接你要儲存的檔案位置與名稱
Load
docker image load -i .\nginx.image
- -i 表示 input,後面接妳要匯入的 Image 檔案名稱
Dockerfile 超簡易介紹
Dockerfile 是用來『建立』Image 用的檔案,裡面的內容會寫如何建立的『步驟』,而這些步驟的寫法會需要遵照其格式語法的規定來寫。
詳細寫法規則可以參照官方文件:Docker Reference
這裡有一個跑 Python 的 hello world 檔案在 ubuntu 上的 dockerfile 範例:
# 基礎 image
FROM ubuntu:20.04
# 下載 python
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev
# 加入檔案到 image 的 root 目錄
ADD hello.py /
# 執行 command
CMD ["python3", "/hello.py"]
詳細介紹請參考下一章節的說明。
Image 的建立與分享
Build
這一節介紹如果有了 Dockerfile 後要如何通過此檔案建立 Image。
將你要包含的檔案內容與 Dockerfile 檔案放在同一個目錄底下,執行:
docker image build -t hello:1.0 .
- -t 表示你要建立的 Image 其名稱與 tag,注意要是沒有冒號就會使用預設的 latest tag
- 請注意最後面還有一個點,這是表示你的 dockerfile 位置在哪裡,上面的範例是因為已經在同一層目錄底下所以用 . 表示,這個點很容易忽略請注意。
建立好 Image 就可以用 docker run 去跑了。
Push
Dockerhub 要推的話要先登入:
docker login
Dockerhub 中的 Image 命名是以『帳號ID/名稱』作為通用格式的,在推上 Dockerhub 時請注意遵照此規則。
當一個 ImageID 有多個名稱:Tag,要刪除的話只能用該 Image 名稱來刪除,不能用 ID
docker image push account/hello:1.0
Commit
Image 的建立還可以用 Container 反過來創建新 Image。
docker container commit containerID newName:newTag
如果 Container 內容有被修改過,停掉之後裡面的修改內容是會被保存的,再次開啟動 Container 是可以得到被修改過的內容的 Container。 但是如果利用 Image 再次開新的 Container 就會維持原本乾淨的容器。
所以如果想要永久保存這個 Container 的狀態,可以利用 Commit 指令來儲存目前狀態的 Container 為一個新 Image。
可以先使用一個 ubuntu image 創建 Container 並進入交互模式,把該 Container 內容改造成自己需求的樣子,再使用 Commit 保存為 Image。
空的 Image:Scratch Image
很少會使用這個 Image 當 Base ,一般都會用 Ubuntu、CentOS 等做基礎 Image。
因為什麼都沒有,所以其大小為 0 ,而且因為沒有包含基本需要的作業系統文件,並無法直接執行一些 Java、Python 等檔案,但是 C 語言編譯後是可以被執行的。
Dockerhub Pull 的次數限制
免費的 Personal 方案有以下限制:
- 200 image pulls per 6 hours
- 如果是未登入則只有 100 次
- 未登入是看你 IP 位置來限制的
如果要知道剩餘可拉次數,Dockerhub 有提供 API 去查詢目前剩餘次數。
About buildkit
參考此文章:Buildkit
這可以用來提高建構 Image 的速度,windows 開啟的方法如下:
請把 Json 裡面加入一個 feature 欄位:
"features": {
"buildkit": true
}
Dockerfile 完全指南
Dockerfile 檔案的寫法有自己的一套規則,需要記憶。
Base Image 的選擇:FROM
Image 選擇原則為以下順序:
- 官方 Image
- Opensource 的 Image
- 容量小的 Image
Alpine Linux 是可以准許超小容量的 Linux Container 版本,可低到 8MB 而已。
Image 中執行指令:RUN
使用 RUN 可以在 Image 中進行你需要的任何操作,包含安裝等等指令。
這裡要注意的是一個 RUN 指令就會多建立一層 Layer,一般不要用太多單獨的 RUN 指令,因為分層會變多,也就是會讓 Image 變得更大。
需要多個指令執行的話,可以採用拼接的方式寫在一個 RUN 就好,這樣可以有效減少 Image 體積大小。
未整合成一個 RUN 的寫法:
FROM ubuntu:20.04
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
寫成一個 RUN 的寫法:
FROM ubuntu:20.04
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
體積大小可以自行建構判斷。
檔案複製與資料夾操作:ADD、COPY、WORKDIR
複製檔案到 Image 中有 ADD 與 COPY 兩種指令:
- ADD:如果有符合其支援的壓縮格式,這個指令會順便解壓縮進 Image,而 COPY 則沒有,例如 gzip。
- 兩個指令在 Image 中發現沒有對應的資料夾的話會自動創建。
FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py
ADD hello.tar.gz /app/
另外創建資料夾的 WORKDIR 指令,其相當於 CD 指令,但是比較好用的是如果 Image 內沒有該資料夾,則會自動創建並切換到該位置下面。之後的所有檔案操作都會以該目錄進行作業。
複製檔案的條件限制
並不是任意路徑的檔案都可以直接指定複製到 Image 之中,僅有該 dockerifle 檔案位置開始往下的路徑才可以複製。
例如要複製一個在 dockerfile 所在位置的上一層資料夾中的某個檔案,會被 Docker 報錯,所以解決方法可以是在上一層目錄中在執行 docker 的建構。
Image 內的環境變數設置:ARG、ENV
ARG 跟 ENV 在用法上其實一樣,但本質上有很大的不同:
- ARG 關注在 Image 的建立期間,在 Image 創建完成後並啟動 Container 時,你不會在環境變數中看到這個指定變數
- 你可以透過一個 –build-arg 來指定建構時的變數,這在 ENV 無法做到
- ENV 則是關注在執行期間,其可以透過環境變數取得該數值。
在 Dockerfile 中可以使用錢符號來替代該變數。
ARG 寫法:
FROM ubuntu:20.04
ENV VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
如果 ARG 要改變建構時期的變數,可以使用以下指令寫法:
–build-arg 參數 + 變數=數值
docker image build -f .\Dockerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSION=2.0.0 .
ENV 寫法: 使用此寫法,可以進入該 Container 後下 env 指令就可以看到該變數有存在
FROM ubuntu:20.04
ARG VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
Container 啟動後的執行指令:CMD
如果於 Dockerfile 中有寫 CMD 的話,則其會在 Container 被啟動的時候被作為預設指令執行。 這裡要注意多個 CMD 只有最後一個會被執行,不會每個都執行。
以執行優先順位來看:
- 如果 docker container run 後面有指定指令則最優先
- Dockerfile 裡面有指定的最後一個 CMD
- 先前的 base image 中有指定 CMD
補充執行完就刪除 Container 的執行方法
一般來說如果多次使用 docker container run imageID,則每次執行都會建造 Container,Container 退出了是不會被刪除的。 如果要每次建造後、執行完退出後要立即刪除,可以使用 –rm option
docker container run --rm -it imageID
Container 啟動後的執行指令:ENTRYPOINT
ENTRYPOINT 跟 CMD 都可以在 Container 啟動時執行指令,以下說明差異:
- CMD 可以在 docker container run 的時候去覆蓋掉,但是 ENTRYPOINT 是一定會被執行的,無法被覆蓋
- CMD 和 ENTRYPOINT 可以聯合使用,ENTRYPOINT 作為指令,CMD 則做為其參數傳入
Shell 與 EXEC 格式
撰寫 CMD 跟 ENTRYPOINT 在寫法上有兩種:
- Shell:直接後面接指令
- CMD echo “hello docker”
- ENTRYPOINT echo “hello docker”
- EXEC:使用中括號包裹不同片段
- ENTRYPOINT [“echo”, “hello docker”]
- CMD [“echo”, “hello docker”]
CMD 在 Shell 寫法上是可以使用變數的,例如:
FROM alpine:3.17.0
ENV NAME=docker
CMD echo "hello $NAME"
但是改成 Exec 的寫法就要注意,直接打上變數是沒有用的,要特意在 CMD 中指明使用 shell 才行:
FROM alpine:3.17.0
ENV NAME=docker
CMD ["sh", "-c", "echo hello $NAME"]
簡易 Kotlin Hello world 實作
這裡記錄一下用 kotlin jar 的練習操作,還不涉及網路部分。
- 用 IDEA 開一個 kotlin gradle project,什麼都不用動
- 把 build.gradle 加入 shadowJar,這樣打包 jar 才會包含 kotlin library 進去
plugins {
/** 用 shadowJar plugin 打包 */
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'org.jetbrains.kotlin.jvm' version '1.7.21'
id 'application'
}
/** 加入 manifest 否則就要自己指定 */
shadowJar {
manifest {
attributes([
'Main-Class': 'MainKt'
])
}
}
- 在打包好的 jar 旁邊建立一個 Dockerfile:
FROM eclipse-temurin:17.0.4.1_1-jre-alpine
ENV NAME = Enix
COPY ./DockerExpert-1.0-all.jar .
ENTRYPOINT ["java","-jar","DockerExpert-1.0-all.jar"]
CMD []
- 建構 Image
docker image build -t kotlin-hello .
- 執行 Container
docker container run --rm -it kotlin-hello
- 可以看到 Console 顯示
Hello World!
Program arguments:
Dockerfile 使用技巧說明
合理使用暫存
docker 在建構的時候,會去使用暫存,如果前面有經過建構過的話。
Dockerfile 的每一行都會是一個新的 layer,所以只要 dockerfile 的某一行被修改了,這一行之後的所有建構指令都不會用暫存了。
所以我們可以把常常會改變的建構指令放在比較後的地方,這樣使用暫存的部分就會比較多,建構的速度也會變快。
.dockerignore 的使用
Docker 是一個 Server-Client 的架構,Server 跟 Client 是可以分開的。
在建構 Image 的時候,Client 會把指定的 BuildContext 中的內容送給 Server 做處理。
docker image build -t name . 後面的點是表示當下位置,也就是 BuildContext 了。
如果沒有去過濾該位置下的文件,你很有可能會把沒有必要納入建構流程的檔案也送給 Server ,這樣就會花費大量時間在傳輸。
這時候使用 .dockerignore 就可以過濾不需要的檔案,其用法跟 gitignore 差不多。
或者是有些較為機密的檔案,也可以設定在此 ignore 中。
範例:
.vscode/
env/
多階段建構
如果需要將 Code 編譯的部分也交給 Docker 執行,我們可以使用多階段建構的方法做,如此就算 Local 沒有設定 Java 的環境也可以做編譯。
將 Dockerfile 中的建構部分分成兩塊:編譯與執行階段,過程是將編譯完成的檔案自編譯階段取出後放入第二階段的 建構流程後再進行構建。
兩階段建構範例:
# 建構 Jar 階段
FROM gradle:7.4.2-jdk17-alpine as builder
WORKDIR /DockerExpert
# 複製資料夾也要寫對目的地資料夾名稱
COPY ./src /DockerExpert/src
COPY ./build.gradle /DockerExpert
# 因為前面有執行 WORKDIR 了,所以這一行的位置已經是在 DockerExpert 資料夾下面了
RUN gradle build
# 打包階段
FROM eclipse-temurin:17.0.4.1_1-jre-alpine
# 從上一階段的內容中取出編譯好的 jar
COPY --from=builder /DockerExpert/build/libs/DockerExpert-1.0-all.jar .
ENTRYPOINT ["java","-jar","DockerExpert-1.0-all.jar"]
CMD []
盡量不要使用 root 身分操作
Linux 使用者需要注意!!!!!!
原本在系統中沒有 Root 權限的帳號,但是該帳號有執行 Docker 的權限的話,就可以利用 Container 中的 Root 權限查看任何 Host 中的任何資料夾,甚至是賦予自己 SUDO ,非常危險。
故可以在建立 Image 的時候,在 Dockerfile 中寫明執行時的帳號為非 root 帳號,可以解決此問題。
請先於 Host 中創建一個帳號並賦予一樣的 Group
# 建立群組
groupadd zero
# 新建使用者
# 其實直接 useradd zero 也可以
useradd -g zero zero
再來在 Dockerfile 中使用 USER 指令就可以了。
USER zero
Summary
請仔細閱讀官方文件:Docker Reference
如果要參考學習對象,可以到官方 Image 的 Github 中參考其文件說明學習及模仿,例如 Java 的:docker-library/openjdk
找到不同版本的 Dockerfile 查看來學習寫法。
Container 的資料儲存
預設情況下,Container 所儲存的資料,雖然在停止的狀態下仍可以被儲存於其 Container 的 R/W Layer,但是這些資料不能被其他 Container 使用,而且只要 Container 被刪除,這些資料也會消失。
Docker 針對資料儲存的方法有三種:
- Data Volume:由 Docker 管理這些資料,常用
- Linux 會存在 /var/lib/docker/volumes/ Linux
- Bind Mount:自行決定資料會存在那個位置
- tmpfs mount:資料存於記憶體中,少用所以不介紹
Data Volumn
於 Dockerfile 中使用 VOLUME 指令,可以指定特定位置要使用 volumn。
但是這裡要注意,如果是寫在 Dockerfile 中,每次都會創建一個新的 volume 去儲存,這並不是我們的目的。
所以需要在 Run 新 Container 的時候去指定 Volumn 的名稱,這樣每次開新的 Container 才會使用一樣的 Volumn。
- 在 Linux 才可以輕易的看到 Volume 儲存的資料
- 在 Windows 你需要進入到 WSL 裡面才能看到你的 Volume 實際儲存內容。
- 或者簡單一點可以用 Docker Desktop 也可以直接查看檔案內容。
以下說明使用差異:-v 參數的使用
使用 -v 參數創建 Container
使用 -v 的話就不用於 Dockerfile 中使用 VOLUME 指令,只要於創建 Container 時指定自訂名稱與路徑就可以了。
-v 參數後面接『Volume名稱:路徑』
docker container run --rm -it -v vol01:/volumn docker-expert01
不使用 -v 參數創建 Container
這就是在 Dockerfile 寫 VOLUME 的方式。
操作 Volume
可以使用 volume inspect 指令 + Volume 名稱去查看該 Volume 資訊
docker volume inspect vol01
即可顯示詳細資訊:
[
{
"CreatedAt": "2022-12-29T08:45:48Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/vol01/_data",
"Name": "vol01",
"Options": null,
"Scope": "local"
}
]
如果要刪除 Volume 可以用 rm 或者是 system prune -f 也行
練習:Mysql 搭配 Volume
使用 Mysql 8 來操作。
docker pull mysql:8.0.31
再來關於此 Image 的詳細官方說明:How to use this image
官方說明沒有用 Volume,我們自己加:
docker container run --name mysql-expert -e MYSQL_ROOT_PASSWORD=mypw -d -v mysql-data:/var/lib/mysql mysql:8.0.31
- -d:背景執行
- -e:環境變數
docker container exec -it ??? sh
進入 mysql 後建立 database
mysql -u root -p
create database demo;
show databases;
將這個 Container stop 後再創一個 Container 起來,一樣進入 mysql ,你就可以看到剛剛在上一個 Container 中建立好的 database 是有出現的。
Bind Mount
相較於 VOLUME,Bind 的方式是直接將 Container 中的某位置接到 Host 上的某一個位置,不像 VOLUME 是在一個虛擬機上面的位置上建立空間。
使用方法一樣是用 -v 參數,並不是只能用 –mount,–mount 是早期版本使用的指令,不要搞混。
docker container run --rm -it -v ${pwd}:/volumn docker-expert01
- 可以使用參數的方式,在 Windows 的 powershell 中可以用錢符號+大括號
直接指定位置可以有以下範例寫法:
docker run -d --name my-http -v D:\DockerMountTest\my-http:/usr/local/apache2/htdocs -p 8080:80 httpd
練習:開發環境 – Bind Mount
就是把含有 JDK 環境的 Container 給掛一個開發用的含有 code 的資料夾,然後於 Container 中編譯、執行等等。
VS Code 有外掛可以這樣做,Intellij 未知。
跨裝置/網路共享 Volume 的做法
Docker 有提供一系列的 Driver 讓使用者可以將 Container 不只能存取 Local 建立的 Volume,可以存取像是 Amazon 的 S3 等等。
參考官方文件:Click
以下介紹其中一個簡單的 SSHFS Driver:
- 準備兩台裝置,通通都安裝 Driver
docker plugin install --grant-all-permissions vieux/sshfs
- 其中一台連到另一台建立 Volume
docker volume create --driver vieux/sshfs \
-o sshcmd=vagrant@192.168.200.12:/home/vagrant \
-o password=vagrant \
sshvolume
- 查看 Volume 並建立 Container 並掛上該 Volume
docker volume ls
docker run -it -v sshvolume:/app busybox sh
- 對其進行任意操作,例如建檔,可以看到另一台裝置上的目錄會有被操作的紀錄。
其他的 Volume driver 可以 Google 再去了解。
Container 的網路
網路的基礎知識
可以簡單蓋覽以下文章了解,無須太過深入。
- IP/Port、Subnet mask、Gateway、DNS 等概念:一文搞懂网络知识,IP、子网掩码、网关、DNS、端口号
- 從封包的角度理解網路:Traffic example, the full picture
網路相關常用指令
請注意 Linux 相關的指令。
作用 | windows | linux |
---|---|---|
查看 IP | ipconfig | ifconfig、ip addr |
IP 連通性 | ping | ping |
Port 連通性 | telnet | telnet |
路徑追蹤 | TRACERT.EXE | tracepath |
網路服務 | curl | curl |
- 可參考 curl 用法指南
Container 網路相關基礎問題
接下來的小節將對下列問題做說明:
- Container 為何能得到 IP ?
- 為何 Host 可以 Ping Container 的 IP ?
- 為何 Container 之間 IP 是互通的 ?
- 為何 Container 可以 Ping 到外網 ?
- Container 的 port 轉發是如何做到的 ?
Bridge 網路:Container 之間的網路互通
- 為何 Container 之間 IP 是可以互通的 ??
Linux Bridge 是一個純粹用軟體實作的一個虛擬交換器。
Docker 有建了一個預設的 Bridge,可以使用 docker network ls 查看
所有 Container 預設都有連到這個 Bridge 所以可以互通。
在 host 中 (Linux),可以使用 ip addr 找到這個名為 docker0 的 linux bridge。
[enixlin@enix-service ~]$ ip addr
...
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ed:95:b1:e7 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:edff:fe95:b1e7/64 scope link
valid_lft forever preferred_lft forever
...
PS C:\Users\EnixLin> docker network ls
NETWORK ID NAME DRIVER SCOPE
fb9c33ef3372 bridge bridge local
457de2d35734 host host local
46697b065a17 none null local
PS C:\Users\EnixLin> docker network inspect fb9
[
{
"Name": "bridge",
"Id": "fb9c33ef3372c304d910c28a0f481632cf59b7a3df19e602c71d68105209fd4e",
"Created": "2023-01-05T06:42:37.1678171Z",
...
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
...
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
Bridge 網路:Container 對外的網路互通
veth = Virtual Ethernet 虛擬網卡介面
Container 的對外會通過 docker0 再出去,這中間會經過 IP 轉發過程,詳細路徑轉換規則可以使用 ip route 查看
[enixlin@enix-service ~]$ ip route
default via 10.X.0.1 dev eth0 proto dhcp src 10.X.0.2 metric 100
10.X.0.1 dev eth0 proto dhcp scope link src 10.X.0.2 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
另外 iptable 的詳細轉換規則可由 sudo iptables –list -t nat -nvxL 查看,包含 port 的部分。
NAT (Network Address Translation):IP 轉換
Private IP 到 Public IP 之間的轉換過程,反過來也是。 另外 Private IP 到 Private IP 轉換也算是。
自建 Docker bridge
網路相關的指令是 docker network。
預設 Container 是會連接到名為 bridge 的 bridge。
# 建立 Bridge
docker network create -d bridge custom-bridge
# 查看現有網路
docker network ls
啟動的時候,使用 –network 指定要連接的網路。
# 指定網路連接並啟動
docker container run --rm -it --network custom-bridge alpine:3.17.0 sh
# 查看網路狀態
docker network inspect custom-bridge
已經啟動狀態之下也可以去連接新的或斷開舊的網路。 而且一個 Container 是可以連接多個網路的。
docker network connect custom-bridge container-name
docker network disconnect custom-bridge container-name
自訂的好處
預設的 bridge 是不提供 DNS 的功能的,也就是說同一個 Bridge 下的 Container 要互相 Ping 只能打 IP。
如果是連接到自己的一個自建 Bridge 就可以用 Container 的名稱來取代 IP,因為 Bridge 會自動解析這個名子到正確的 IP 上。
Subnet、Gateway 設定
在建立 Bridge 時,可以直接指定 Subnet、Gateway 的數值。
可以使用參數 subnet、gateway 設定
docker network create -d bridge --gateway 172.200.0.1 --subnet 172.200.0.0/16 my-bridge
Port Forwarding
內網到外網可以利用 NAT 達到效果,那反過來外網向內網就需要使用『連接阜轉發』。
Container 在創建的時候,就是使用 -p 參數去設定轉發的,Docker 會自動在 host 上加上設定。
這可以從下列指令查看:
sudo iptable -t nat -nvxL
Dockerfile 的 EXPORT 用途
The EXPOSE instruction does not actually publish the port. It functions as a type of documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published. To actually publish the port when running the container, use the -p flag on docker run to publish and map one or more ports, or the -P flag to publish all exposed ports and map them to high-order ports.
根據官方文件,EXPOSE 是用來讓使用者知道這個 Image 是有開這個 Port 的功能,如果要使用相關功能可於啟動 Container 時另外加 -p 參數來設置。
所以需要注意,只有寫 EXPOSE 而沒有於啟動的時候指定 port 還是等於沒有打開 port 的。
Host 網路、Null 網路
在建立 Network 的時候,除了可選擇 Bridge,還有另一種類型為 Host。
這是 Container 與 Host 共享同樣的網路。 讓 Container 能不透過 NAT 等轉換的消耗,可以直接使用 Host 上的 port,性能較好,但會有 port 占用問題的情況發生。
- Windows 不支援使用 host 網路
The host networking driver only works on Linux hosts, and is not supported on Docker for Mac, Docker for Windows, or Docker EE for Windows Server.
最後還有一種 Null 網路,平常不用,有些不需要網路的條件下才去考慮使用。
Linux Network Namespace
詳細示範直接看講師講義。
練習:建立一組含有 Redis 的服務
- 先建立一個 Bride 名為 custom-bridge,這樣就可以用 Container name 代替 IP
docker network create -d bridge custom-bridge
- 再來建立一個 Redis 於 local:
- 63790 是為了 local 測試看有沒有正常啟動用的,-p 非必要
docker pull redis:7.0.7-alpine3.17
docker run --name my-redis --network custom-bridge -p 63790:6379 -d redis:7.0.7-alpine3.17
- 建立一個 Jar 去讀取 Redis 的儲存的數字,每次讀取並累加其數字 10 後儲存
- 這裡用 Redis 去存取。
fun main(args: Array<String>) {
println("Hello World!")
println("Program arguments: ${args.joinToString()}")
val url = System.getenv("url") ?: "localhost"
val port = System.getenv("port") ?: "63790"
println("url:port is $url:$port")
val jedis = Jedis(url, port.toInt())
if (!jedis.exists("count")) jedis.set("count", "0")
(0..10).forEach {
val count = jedis.get("count").toInt()
println("count is $count")
jedis.set("count", (count + 1).toString())
Thread.sleep(500)
}
}
plugins {
/** 用 shadowJar plugin 打包 */
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'org.jetbrains.kotlin.jvm' version '1.7.21'
id 'application'
}
dependencies {
implementation 'redis.clients:jedis:4.3.1'
}
/** 加入 manifest 否則就要自己指定 */
shadowJar {
manifest {
attributes([
'Main-Class': 'com.enix.MainKt'
])
}
}
- 打包的 Dockerfile 如下:
FROM gradle:7.4.2-jdk17-alpine as builder
WORKDIR /DockerExpert
COPY ./src /DockerExpert/src
COPY ./build.gradle /DockerExpert
RUN gradle build
FROM eclipse-temurin:17.0.4.1_1-jre-alpine
COPY --from=builder /DockerExpert/build/libs/DockerExpert-1.0-all.jar .
ENTRYPOINT ["java","-jar","DockerExpert-1.0-all.jar"]
CMD []
docker image build -t docker-expert02 .
- 啟動的時候指定 Redis 的 port 與 url
- 這裡的 –env 可以由 System.getenv(“key”) 取得
- 可以發現數字在每次執行都會增加,表示正確存取 Redis
docker container run --rm -it --env port=6379 --env url=my-redis docker-expert02
Docker Compose
創建、啟動 Container 是由一連串固定套路的指令來完成的,Docker Componse 就是為了簡化這些繁瑣步驟而生,特別是一次要建立出多個 Container 與設定的時候。
所有的指令都寫成一個 yaml 的檔案中。
安裝
DockerComponse 是一個由 python 寫的工具。
如果是 Windows,在安裝 Docker Desktop 的時候就已經安裝了。
docker-compose --version
Linux 的話可以去 Github 上下載來安裝:DockerCompose Github release page
安裝範例指令:
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c
或者用 python 的 pip 來安裝:
pip install docker-compose
Compose 檔案的格式與用法
詳細可以參考官方文件:Compose specification
這是一個 yaml 格式的檔案。
基本結構如下:
# 此檔案的語法版本
version: "3.8"
# Container
services:
servicename: # Container 名稱,可以在 bridge網路中使用的 DNS name
image: # Image 的名字
command: # 可選,如果有設定則會覆蓋預設 Image 裡面的 CMD 指令
environment: # 可選,相當於 docker run 裡的 --env
volumes: # 可選,相當於docker run 裡的 -v
networks: # 可選,相當於 docker run 裡的 --network
ports: # 可選,相當於 docker run 裡的 -p
servicename2:
...
volumes: # 可選,相當於 docker volume create
networks: # 可選,相當於 docker network create
- docker-compose 檔案有版本之分,以初學者來說版本之間不需要關心差異,因為基礎與法基本一致。
Docker-compose 的基本使用指令
要使用 Docker-compose 就需要將目前位置移動到 docker-compose.yaml 的資料夾位置上,檔案名稱也不能亂改。 當然這也可以設定的,與 dockerfile 一樣的操作。 (可以使用 -f 參數指定位置)
基礎的指令:
指令 | 用途 | 說明 |
---|---|---|
up | Create and start containers | |
down | Stop and remove containers, networks | |
logs | View output from containers | |
ls | List running compose projects | |
ps | List containers | |
rm | Removes stopped service containers |
另外 Option 的部分有兩個值得記憶:
- -f:指定 docker-compose.yml 位置
- -p:指定專案名稱,這關乎到預設的 Container name、newwork name 等等的預設名稱的修改。
Image 的建立與拉取
在 docker-compose.yml 的設定中,如果 image 這一項在 local 找不到對應的 Image 的話,則會去 Dockerhub 中下載。
build 選項可以指定一個 dockerfile 去提供建立 image,而不是直接從 Dockerhub 上下載。
version: 3.4
services:
servicename:
build: # 可以指定建立位置
context: ./project
dockerfile: mydockerfile # 可以指定不同名稱,例如不同環境
image: projectname:tags
除此之外也可以先使用 pull 指令在真正建構服務前先將缺少的 Image 下載下來。
docker-compose build
docker-compose pull
Compose 如何處理更新行為?
“ 一般來說,若一個專案已經 up 了,而其中的 Code 有部分修改,如果直接 stop 再 up 是不會有任何改變的,因為沒有重建 Image。
以下介紹三種常用指令:
- 啟動時包含重建 Image 的行為
docker-compose up -d --build
- 如果有 Service 被移除了,DockerCompose 會提醒你要用這個選項去移除無用的服務
docker-compose up -d --remove-orphans
- 通常如果 Container 有掛載 Volume,其中可能包含了一些設定檔案,如果需要重啟重讀檔案,可以使用 restart。
docker-compose restart
網路議題
如果於 docker-compose.yml 中沒有設定 network,則其預設會創建一個 Bridge$^1$ 並且設定好 DNS ,於 Container 中就可以互 Ping 同一個 Project 中的 Container name。
$^1$單機下,預設的 network 的 driver 會是 Bridge,如果是 Swarm 等等情況就不會還是 Bridge 了。
當然每個 Container 都可以分開建立不同的 Network。
# 詳細去看文件,通通都有
network:
net1:
ipam:
driver: default
平行擴充
docker-compose 提供了一個水平擴充的功能:–scale
docker-compose up -d --scale servicename=3
根據前面的說明內容,當 Container 被創建後,因為有 DNS 功能,所以可以使用 Container Name 去 Ping,但是當水平擴充了的話,再去用同樣的名稱去 Ping 會如何??
答案是 docker-compose 會建立一個負載均衡的機制,所以每次 ping 都會是多台輪流 ping。
範例檔案:Download
講師範例中的示範專案,其結構如下: Nginx <–> Flask <–> Redis
Nginx 反向代理後端的 url 位置,而 url 的會由於附載均衡的關係,由所有的 Flask 服務去輪流執行,而最後共同存取最後端的 Redis 資料。
下面可以看到網路的設定結構:
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- backend
- frontend
redis-server:
image: redis:latest
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
- flask
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
# 兩段網路
networks:
backend:
frontend:
Nginx 的設定只有反向代理,負載均衡會由 DockerCompose 完成:
server {
listen 80 default_server;
location / {
proxy_pass http://flask:5000;
}
}
環境變數
如果於 docker-compose.yml 中需要輸入一些像是密碼的屬性的話,在非 Local 的環境是可以使用 Secret 的功能,而 Local 的話,就需另擇他法。
在 docker-compose.yml 的相同位置下創建 .env 這個檔案,裡面的參數是可以被 yml 讀取到的,也就是說可以藉由替換 .env 這個檔案達到隱蔽數值、切分環境等等效果。
範例如下: 可以使用錢符號 ${varible}
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
.env 檔案
REDIS_PASSWORD=ABC123
服務相依性與健康度檢查
dockerfile 與 docker-compose 都可以加入健康度檢查的指令。
dockerfile 健康度的用法
使用 HEALTHCHECK 這個指令去操作。 參考官方文件:HEALTHCHECK
例如:
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:5000/ || exit 1
- 每 30 秒檢查一次,失敗就會退出,返回 1
- shell 中 exit 0 是正常 retuen,1 等其他的數字就是非正常。
docker-compose 健康度的用法
改用 Docker-compose 的健康度寫法,原本寫在 dockerfile 的寫法就必須要移除。
參考官方文件:healthcheck
以下為範例:
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
# 跟 dockerfile 差不多的結構轉移
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
depends_on:
redis-server:
condition: service_healthy
networks:
- backend
- frontend
redis-server:
image: redis:latest
command: redis-server --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 3s
retries: 30
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
flask:
# 增加健康度檢查的條件
condition: service_healthy
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
networks:
backend:
frontend:
這裡額外要注意 depends_on 的用法:
- depends_on 只會因為其相依的 Container 並不是 up 狀態時才會停止創建。
- 也就是說就算健康度檢查失敗一樣會建立 Container。
- depends_on 可以增加額外的條件去判斷是否要正常啟動
一個還不錯的參考範例:Click
練習:講師提供的良好範例
dockersamples/example-voting-app
練習:自我練習
使用 Springboot + MySql + Nginx 示範:
Docker Swarm
DockerCompose 是以單一節點的服務,只要一掛,全 Container 就都會全死掉,是只適合開發環境下使用,Production 完全不建議。
kubernetes 在市佔率上是輾壓的 80%,所以這一章主要是要透過學習 Swarm 的概念去理解分散式架構的觀念,如此在之後的 k8s 學習會較為輕鬆。
單一節點:快速啟動與關閉
DockerSwarm 預設就是關閉的。 從 docker info 可以查看到其中一項:Swarm 的啟動狀態
docker info
Swarm: inactive
使用 Swarm 有兩種方式:
- 自己建立一個群集
- 加入一個群集
單一節點要啟動與關閉的指令分別如下:
# 啟動
docker swarm init
# 離開群集,最後一個離開需要再加上 --force
docker swarm leave
init 指令做了什麼?
主要是PKI和安全相關的自動化
- 創建 swarm 群集的根證書
- manager 節點的證書
- 其它節點加入集群需要的 tokens
- 創建 Raft 資料庫用於存儲證書,設定,密碼等資料
以下有 Raft 演算法相關的介紹可以閱讀:
- http://thesecretlivesofdata.com/raft/
- https://raft.github.io/
- https://docs.docker.com/engine/swarm/raft/
Swarm 單節點快速用例
- 建立單節點
docker swarm init
- 建立服務:以 Nginx 示範
docker service create nginx:1.23.3-alpine
- 查看該服務的狀態以及含有的 Container 資訊:
- 注意 ID 是不一樣意義的。
# 查看 Service ID 等等
docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
geyh2u98c7mb reverent_mclaren replicated 1/1 nginx:1.23.3-alpine
# 查看 Service 詳細資訊
docker service ps gey
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
ut0xg09vs8oy reverent_mclaren.1 nginx:1.23.3-alpine docker-desktop Running Running 50 seconds ago
- 增加服務的 Container 數量
# 增加數量
docker service update gey --replicas 3
# 查看 Service 詳細資訊
docker service ps gey
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
ut0xg09vs8oy reverent_mclaren.1 nginx:1.23.3-alpine docker-desktop Running Running 7 minutes ago
hx61xva89qd4 reverent_mclaren.2 nginx:1.23.3-alpine docker-desktop Running Running 40 seconds ago
i2xhew5fe4ay reverent_mclaren.3 nginx:1.23.3-alpine docker-desktop Running Running 40 seconds ago
- 如果其中一個 Container 炸了,例如我們手動刪除,Swarm 會自動維持其設定的數量,保持於 3。
三節點的練習
練習方法有三種:
- play with docker:這個網站可以快速練習,但是只有 4 小時的時間就會重置
- Local 跑三個虛擬機:Vagrant + Virtualbox
- 使用 GCP、Amazon 等等服務來建立
- Port 的部分需要注意要開,以及一些安全性設定
- TCP port 2376
- TCP port 2377
- TCP and UDP port 7946
- UDP port 4789
- Port 的部分需要注意要開,以及一些安全性設定
教學採用 Vagrant + Virtualbox 的方式建立三個虛擬機。 講師在 Youtube 的 Vagrant 課程 可以參考
Swarm 的網路
Swarm 網路可以分成兩類來理解:
- 整個群集對內與對外的流量:進入的流量由 Ingress 網路處理,Container 對外的流量由 Linux Bridge + NAT 處理
- 群集內 Container 互相溝通的流量:可以由 overlay 網路處理。
Overlay 網路
群集內的溝通所採用的,查看 network 可以看到其 Scope 是 Swarm。
Ingress 網路
外部要連通內部時使用。
多服務建構
Swarm 是一個正是環境使用的工具,不像 DockerCompose 可以在要使用的時候才去建立 Image,必須事先建立好
secret 與 volume 的使用
Skip。
Docker vs Podman
Podman 是 Red Hat 在 2018 年推出的,原始程式碼開放。
Podman 與 Docker 有以下的不同:最重要的是前兩項
- 最主要的區別是 Podman 是 Daemonless 的,而 Docker 在執行任務的時候,必須依存於後台的 docker daemon
- Podman 不需要使用 root user 或者 root 權限,所以更安全。
- Podman 可以創建 pod,pod 的概念和 Kubernetes 定義的 pod 類似
- Podman 運行把鏡像和容器存儲在不同的地方,但是 docker 必須存儲在docker engineer 所在的本地
- Podman 是傳統的 fork-exec 模式,而 Docker 是 client-server 架構
安裝
Windows 版本只能裝在 WSL2 上,講師表示建議是開虛擬機裝,但我還是要裝在 WSL2 不然不方便。
- Podman Desktop:這個 GUI 工具可以幫你裝好 Podman 在 WSL 上。
- WSL Distro Manager:管理 WSL 的運行實體。
- MobaXterm:可以連到 WSL 內的實體進行 SSH,不需額外設定。
Podman 的獨立環境
Podman 對於每個使用者帳號,Image、Container 是獨立分開的,互相 ls 是看不到的。
而 Docker 雖然在非 Docker 群組或者 root 使用者之外的帳號是無法執行指令的,但是每個帳號都還是可以藉由 ps 指令觀察的到已經起起來的 Container。
Pod 的概念
Podman 的 Pod 幾乎和 Kubernate 的 Pod 一樣。
# 創建 Pod
[user@DESKTOP-L47M39G ~]$ podman pod create --name my-pod
cf0be2b2c45795fd942bf9697e10933d62090ba19192fdc491d0702c4ad85109
# 列出 Pod
[user@DESKTOP-L47M39G ~]$ podman pod ls
POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
cf0be2b2c457 my-pod Created 6 seconds ago 752688ac274c 1
# 列出 Container 與其所屬 Pod
[user@DESKTOP-L47M39G ~]$ podman container ls -ap
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES POD ID PODNAME
752688ac274c localhost/podman-pause:4.4.1-1676629882 About a minute ago Created cf0be2b2c457-infra cf0be2b2c457 my-pod
- Pod 創建後,裡面會包含一個預設的 Container,用來處理其內部運作。
要於 Pod 內建立 Container 的話,只要在建立 Container 時指定 Pod 就可以了。
podman pull docker.io/library/nginx:1.23.3-alpine
Podman 要從 dockerhub 抓 Image 時,前面要加上 docker.io/library 才能正確抓取。
podman container run -d --name my-nginx --pod my-pod docker.io/library/nginx:1.23.3-alpine
在同一個 Pod 的 Container 有什麼特性??
同一個 Pod 的 Container 可以觀察到其共用了一個 IP,也就是說在同一個 Pod 下,所有的 Container 要互相連通只要打 127.0.0.1 與 port 就可以打的到。
相當於 Container 是運作在一個機器下。
Pod 的創建腳本
與 dockercompile 差不多,其也有一個 yaml 的檔案可以做 pod 的建立。
- 相關的指令為:podman play –help
詳細請參考 K8s 的內容。
Docker 的非 root 模式
根據官方文件,非 root 模式對 Docker 版本是有限定的。
Rootless mode was introduced in Docker Engine v19.03 as an experimental feature. Rootless mode graduated from experimental in Docker Engine v20.10.
非 root 模式之下,Docker 會變得有一些限制,例如 port 不能指定 1024 以下對外等等,詳細可以參考文件。 而因為 Podman 非 root 也能用,所以在使用上一樣會有一些限制。
關於 Kubernetes 停止對 Docker 支援這件事
可參考 Youtube 影片:Kubernetes停止支持Docker了?| 对我们有什么影响?| Docker还值得学习么?
- Dockershim 要被移除了。
- Docker 是 Container 體系的開山推廣者,還是學一下比較好
- 支援 CRI (Container Runtime Interface) 的 CR 僅有:
- Containerd
- Cri-O
多種架構的 Image 建立:ARM 與 X86
一般從 Dockerhub 看到比較大的 Image 都有支援多種架構。
本節說明如何建立支援多架構的 Image,而不需要把每種環境都要準備好才能建立。
ARM 的 Docker
樹梅派就是用 ARM,可以用樹梅派來玩玩。
- 就算該 Image 並沒有支援 local 的架構,還是可以 pull 下來,只是你不能跑起來而已。
- 在哪種架構下建立的 Image 就會是那種架構的 Image。
- 在某架構的系統下去 push image 到 Dockerhub 會直接蓋原本的 Image ,並不會並存。
使用 buildx 來建立多架構 Imgae
建立方法其實有很多,但此小節說明的 buildx 是其中一種比較簡單的做法。
Window/Mac 的 Docker 本身就已經有這個工具了,但是 Linux 需要額外安裝。
# 登入
docker login
# 列出建構環境
docker buildx ls
docker buildx create --name mybuilder --use mybuilder
# 建立後直接推上去
docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 -t xiaopeng163/flask-redis:latest .
- 如果建構 Image 時有錯誤,可以新建一個新的 buildx 環境嘗試看看。
包含已經對應架構的 Image 如何處理
可以在 dockerfile 中去定義,在建構不同架構的 Image 時要下載不同的 Image。
FROM alpine:3.16
ARG TARGETARCH=amd64 TERRAFORM_VERSION="1.2.9"
RUN apk update && apk add --no-cache curl
RUN curl \
--location \
--output /tmp/terraform.zip \
https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_${TARGETARCH}.zip \
&& unzip /tmp/terraform.zip -d /usr/local/bin \
&& rm -rf /tmp/terraform.zip
CMD []
- 使用 TARGETARCH 與 TERRAFORM_VERSION 來設定。
補充
- 如果要分享自己的 Image 的話,最好要能做多架構。
- manifest 指令:可以不用下載 Image 就可以 inspect 其訊息,但是需要有網路才能正常運作。
- 目前還是試驗階段
- 官方文件
GitHub Actions 與 CI/CD
Image 自動化構建與推送。
自動化部屬。
Dockerhub 的 Image 自動化構建服務
以前是免費的,現在要升級付費帳號才能用,爛,看看就好。
可以與 Github 連動,很簡單,但是不支援多架構的建構。
Github Action 使用
可以閱讀官方文件。
關於 Action 的部分,如果是比較常用的指令,應該都有人寫好了現成的 Action 可以套進來使用。 可以到 Marketplace 搜尋看看
Ansible – CD 工具
Ansible 是一款組態管理平台,可將儲存設備、伺服器與連網設備自動化。使用 Ansible 配置這些元件時,困難的手動工作就變得可重複執行,因此比較不容易出錯。 使用 Ansible 可大幅減少組態時間和部署準備工作。
教學影片可以參考:麦兜搞IT – Ansible入门
Container Security
以安全問題分層級來看,可以有以下層次:
此小節僅說明 Container 與 Image 的部分,但並不代表其他層次並不重要。
根據每個層面,能夠討論的部分有以下:
- Application:自己撰寫的程式
- Code 方面的漏洞
- Image:
- 請選擇官方的 Image
- Image 需要進行漏洞的弱點掃描
- Container:
- Container 需要進行漏洞的弱點掃描
- Container 需要有監控的機制
- Host:跑 Docker 的主機
- Linux Kernel
- Kernel namespaces
- Control groups
- 可以使用非 root 模式
Docker 執行環境的檢查
此工具可以幫助你檢查 Docker 的執行環境是否有風險存在。
Image 漏洞檢查
- Snyk:免費
- Aqua Security:可以在 Local 安裝後執行。
Snyk 可以連結 Github 進行掃描。
Container 監控
- Sysdig:需要付費
可以了解相關內容,像是 ELK 的運用等等。
Docker Interview Questions
參考此文件:docker-interview-questions.pdf
可以藉由 PDF 中的問題回顧自身所學。