一文詳解:專案如何從Docker慢慢演變成了K8s部署

努力的小雨發表於2024-11-25

今天,我們將深入探討一個專案部署的演變過程。在這篇文章中,為了緊扣主題,我們將從 Docker 開始講解,分析為什麼一個傳統的專案逐步演變成了今天流行的 Kubernetes(K8s)叢集部署架構。我們將透過一個簡單的 Java 專案來闡述這一過程。

為了更清晰地闡述,我在本地搭建了一個 gRPC 入門專案。考慮到篇幅和內容的專注性,我將這一部分的詳細講解單獨撰寫成了一篇文章。具體內容可以參考這篇文章獲取更多資訊和背景知識:https://www.cnblogs.com/guoxiaoyu/p/18555031

接下來,我們將逐步展開,深入講解從 Docker 容器化到 K8s 叢集化的過渡,分析這個過程中面臨的挑戰和技術演進,並討論為什麼 Kubernetes 已經成為現代雲原生應用部署的標配。

好了,現在我們正式開始本篇文章的講解。

Docker

這部分內容大家應該都比較熟悉了。對於個人開發者來說,Docker幾乎是每個專案中不可或缺的一部分,學習如何使用 Docker 命令是每個開發者的必修課。因此,關於 Docker 的安裝過程,我就不再贅述了。大家可以根據官方文件或者教程輕鬆完成安裝,過程也相對簡單明瞭。

在使用 Docker 進行專案部署時,首先需要一個名為 Dockerfile 的配置檔案,它定義瞭如何構建和封裝專案容器。具體內容如下:

# 使用官方的 OpenJDK 映象作為基礎映象
FROM openjdk:8-jdk-alpine

# 將構建好的Spring Boot JAR檔案複製到容器中
COPY grpc-server/target/grpc-server-1.0-SNAPSHOT.jar /app/grpc-server-1.0-SNAPSHOT.jar

# 設定工作目錄
WORKDIR /app

# 暴露 gRPC 應用程式的埠
EXPOSE 9090

# 執行 gRPC 服務
CMD ["java", "-jar", "grpc-server-1.0-SNAPSHOT.jar"]

在使用 Maven 編譯和打包 Java 專案時,如果我們需要啟動一個可執行的 JAR 包,就必須指定一個入口類,即啟動類。我們需要單獨配置一個編譯外掛,通常是 spring-boot-maven-plugin(如果是 Spring Boot 專案)。

這樣做可以確保在執行 mvn package 命令時,Maven 會將啟動類作為專案的入口,並生成正確的可執行 JAR 檔案。以下是一個配置示例:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

接下來,我們正常執行mvn命令即可。執行完後,我們通常可以在伺服器上直接透過docker命令進行構建docker映象。這樣所有環境整合都有了,直接啟動docker映象即可。跟我們本地的環境一點關係沒有。命令如下:

mvn clean package

docker build -t grpc-server .

docker run -p 9090:9090 -v /data/logs:/app/logs grpc-server

過程如圖所示:

image

我們暴露出來了一個伺服器埠供外網呼叫。這裡我測試一下,是正常的,效果如圖所示:

image

綜合上述所有條件,我們可以得出一個重要結論:Docker 在環境隔離方面確實具有顯著的優勢。透過 Docker,我們無需在本地系統上手動安裝和配置各種依賴環境,從而避免了因環境配置不同而導致的問題。只需要執行一個簡單的 Docker 命令,就能自動拉取所需的映象並啟動專案,這大大簡化了開發和部署過程。

尤其是對於大多數開源專案而言,幾乎都提供了官方或社群維護的 Docker 映象,這使得使用者能夠快速入門,無需深入瞭解複雜的配置細節。

那麼問題來了?

公司專案的部署遠遠不止於簡單地啟動一個 Docker 容器,而是涉及到多個複雜的元件和服務的協同工作。具體來說,除了 Docker 容器之外,我們通常還需要部署和配置 Nginx、前端服務、後端服務、資料庫等一系列基礎設施元件。每個專案都會根據實際需求涉及到不同的服務和環境配置,處理起來並不簡單。

更重要的是,考慮到每次部署時可能都需要執行大量的命令來啟動這些服務,難道我們真的要把這些命令手動記錄在記事本中,然後每次上線時都逐一敲入這些命令嗎?看下這段命令:

docker run -d \
  --name grpc-server-container \                 # 設定容器名稱
  -p 9090:9090 \                                 # 對映容器的 9090 埠到主機的 9090 埠
  -p 8080:8080 \                                 # 對映容器的 8080 埠到主機的 8080 埠
  -v /data/logs:/logs \                          # 將主機的 /data/logs 目錄掛載到容器的 /logs 目錄
  -v /path/to/app/config:/app/config \           # 掛載應用配置資料夾
  -v /path/to/db:/var/lib/postgresql/data \      # 將主機資料庫目錄掛載到容器的資料庫目錄
  -e LOG_LEVEL=debug \                           # 設定環境變數 LOG_LEVEL
  -e DB_USER=admin \                             # 設定資料庫使用者名稱環境變數
  --restart unless-stopped \                     # 設定容器的重啟策略,除非手動停止,否則容器會自動重啟
  --network host \                               # 使用主機網路模式,可以與主機共享網路資源
  --log-driver=json-file \                       # 設定日誌驅動為 json-file(預設)
  grpc-server                                    # 使用 grpc-server 映象

那麼,針對這種繁瑣的手動輸入命令和配置的情況,是否存在一些工具或者方式,能夠幫助我們提前將這些命令和配置都寫好,並且每次只需執行一個檔案,就能順利啟動整個專案,避免重複操作和人為失誤呢?

答案是肯定的,正是基於這種需求,我們有了 Docker Compose 這樣的編排工具。

Docker-compose

編排檔案採用一種固定的格式來書寫,目的是確保 Docker 在執行時能夠正確地識別和啟動所需的所有服務和容器。

接下來,我們將以 grpc-server 專案為例,展示如何將該專案配置到 Docker Compose 的編排檔案中,檔案內容如下:

version: '3.8'

services:
  grpc-server:
    image: grpc-server           # 使用 grpc-server 映象
    ports:
      - "9090:9090"              # 對映埠 9090
    volumes:
      - /data/logs:/app/logs         # 掛載主機目錄 /data/logs 到容器的 /logs 目錄
    restart: unless-stopped      # 如果容器停止,除非手動停止,否則會重新啟動容器

其實,這個過程非常簡單和直觀。如果你需要啟動多個容器,只需在 Docker Compose 的編排檔案中繼續新增相應的服務配置,每個服務都會自動與其他服務進行協同工作。

對於每個容器的配置,你只需按需擴充套件,每新增一個容器,只要在檔案中繼續新增對應的服務定義即可,這樣一來,整個專案中的所有服務都會被包含在編排檔案內,實現一次性啟動多個容器。命令如下:

啟動多個指定容器:

docker-compose up

後臺啟動所有容器:

docker-compose up -d

透過使用編排檔案,我們幾乎不再需要手動維護各種 Docker 啟動命令,而是可以透過統一的配置檔案進行管理和部署。這種方式的最大優點在於,它顯著簡化了普通 Docker 啟動命令的維護和執行過程,避免了手動操作帶來的複雜性和出錯風險。

同時,這種自動化的部署方法基本上解決了小型公司在專案部署中的諸多困難,使得專案的部署更加高效、穩定和可重複,從而大大提升了團隊的生產力和專案的交付速度。

那麼問題來了

儘管專案本身的複雜度沒有顯著增加,但隨著使用者量的不斷上升,我們面臨的挑戰也隨之加劇。由於機器的頻寬和記憶體是有限的,單純依靠一個 Docker 例項已經無法滿足當前使用者的訪問需求。在這種情況下,我們通常需要透過增加機器來進行水平擴充套件,通常是在新的機器上重新執行相同的部署命令。

然而,這種反覆手動操作的方式會導致運維人員的工作量呈指數級增長,每次版本釋出和擴容時,運維人員不僅需要投入大量的精力,還容易感到身心疲憊,工作負擔越來越重,效率也大大降低。

正因如此,叢集部署的需求變得尤為迫切,而 Kubernetes(K8s)作為現代化容器編排平臺應運而生,並迅速成為解決這一問題的利器。

K8s

很多人其實也聽說過Docker Swarm,它是Docker原生的叢集部署方式,具有一定的自動化和容錯能力,但相比於Google設計的Kubernetes(K8s),其功能和生態系統的完善程度明顯不足,因此未能獲得像K8s那樣廣泛的應用和好評。

話不多說,我們現在回到重點,繼續深入講解K8s的部署方式。為了方便演示,我已經在本地提前配置好了一個簡化版的K8s環境,接下來的內容將不再贅述其具體安裝過程。

如果對Kubernetes(K8s)還不太瞭解的朋友,可以先參考我之前寫的這篇文章,它將幫助你快速掌握K8s的基本概念和架構:https://www.cnblogs.com/guoxiaoyu/p/17876335.html

面臨的一個實際問題是:大多數人手中都有現成的Docker Compose編排檔案,而如何將這些檔案高效地遷移到K8s環境中呢?幸運的是,這裡有一個非常流行的工具,它能夠幫助你輕鬆實現這一遷移,不需要手動編寫複雜的K8s部署檔案或進行繁瑣的配置。

kompose:Convert your Docker Compose file to Kubernetes

Kompose的主要目標就是幫助開發者快速將現有的Docker Compose編排檔案轉換為Kubernetes(K8s)所需的資源配置檔案,簡化從Docker Compose到K8s的遷移過程。我們看下如何使用,需要透過以下命令安裝Kompose:

Linux:curl -L https://github.com/kubernetes/kompose/releases/download/v1.34.0/kompose-linux-amd64 -o kompose

Linux ARM64:curl -L https://github.com/kubernetes/kompose/releases/download/v1.34.0/kompose-linux-arm64 -o kompose

chmod +x kompose

sudo mv ./kompose /usr/local/bin/kompose

上面的命令執行完後,基本上我們就可以正常使用了。效果如圖所示:

image

我們直接透過執行檔案的方式轉化我們的編排檔案。命令如下:

kompose convert -f docker-compose.yaml

image

執行完後,通常情況下官方的教程是直接就可以進行kubelet apply部署,但是注意一些坑,我們一起來看下。

首先,在 Kubernetes 中,每個容器都可以透過掛載目錄來持久化其資料。儘管 Kubernetes 預設會將容器的日誌儲存在 /var/log/containers/ 目錄下,但對於像 MySQL 這樣的資料庫服務,僅依賴 Kubernetes 自帶的日誌目錄是不足夠的。因為容器的重啟或停啟操作可能會導致資料丟失,因此必須透過掛載持久化儲存卷(例如,使用 Persistent Volumes 或 HostPath 等方式)來保證資料庫的資料安全和永續性。

在本示例中,我僅僅是為了演示目的,簡單地掛載了日誌目錄,而未涉及更復雜的資料儲存掛載配置。

資料掛載

這裡有幾個新的關鍵名詞,在之前我是沒有講過的,如下:

PV(Persistent Volume):是一個具體的儲存資源(可以是本地磁碟、雲端儲存等),由管理員配置。PV 儲存的內容是持久化的,不會因容器的銷燬而丟失。

PVC(Persistent Volume Claim):是使用者或應用向 Kubernetes 請求儲存資源的方式。使用者宣告他們需要多大的儲存、訪問模式等,Kubernetes 會根據這些要求找到合適的 PV。

PV 與 PVC 的關係:PVC 向 Kubernetes 提出儲存需求。PV 提供實際的儲存資源,且由 Kubernetes 根據 PVC 的要求動態繫結。

透過將 PVC 掛載到 Pod 中,容器就能使用持久化儲存的資料。透過這種方式,Kubernetes 可以高效地管理儲存,確保容器應用的資料不會丟失,即使容器被銷燬或重新部署,資料仍然得以保留。

為了實現檔案共享和資料儲存,我們需要搭建一個NFS(Network File System)伺服器。這個伺服器可以選擇由雲服務商提供,也可以使用本地伺服器的磁碟資源。在本例中,我們將以本地伺服器為例,詳細介紹如何搭建一個NFS 伺服器,並進行相關配置。

NFS 伺服器

我們不能直接使用kubelet apply部署,相反,我們需要對 PVC 檔案進行一定的修改,以確保它能夠正確地建立所需的儲存資源。具體來說,我們需要在 PVC 檔案中指定使用的 StorageClass。

使用以下命令獲取當時叢集的StorageClass,如果無StorageClass則需要先建立。

kubelet get storageclass

因為我這是剛搭建的K8s環境,所以我這裡顯示的是沒有,那麼先搭建一個本地NFS伺服器。

檢視系統是否已安裝NFS

rpm -qa | grep nfs

rpm -qa | grep rpcbind

安裝NFS 、RPC

yum -y install nfs-utils rpcbind

啟動服務

systemctl start nfs rpcbind

建立目錄

mkdir -p /nfs-server/log/

chmod 666 /nfs-server/log/

編輯export檔案

vim /etc/exports

/nfs-server *(rw,sync,no_root_squash)

image

配置生效

exportfs -r

啟動rpcbind、nfs服務

systemctl restart rpcbind

systemctl restart nfs

自我測試一下是否可以聯機

showmount -e localhost

測試效果正常:

image

Storageclass啟動

首先,我們需要啟動StorageClass,檔案內容如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs
provisioner: kubernetes.io/nfs
parameters:
  server: 192.168.56.5
  path: /nfs-server

可以看到這裡是以本地IP為NFS伺服器的。直接使用命令啟動即可。

k apply -f st.yaml

kubectl get storageclass

效果如下,啟動成功:

image

PV啟動

然後配置一下pv檔案,並啟動:

kind: PersistentVolume
apiVersion: v1
metadata:
  name: nfs-pv-test
  namespace: database
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1.5Gi
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /nfs-server/log
    server: 192.168.56.5

kubectl apply -f pv.yaml

啟動成功,效果如圖所示:

image

專案啟動

接下來,我們就需要啟動當時kompose生成的三個檔案,我們挨個執行,命令如下:

k apply -f grpc-server-xiaoyu-claim0-persistentvolumeclaim.yaml

k apply -f grpc-server-xiaoyu-deployment.yaml

k apply -f grpc-server-xiaoyu-service.yaml

最後,正常啟動service服務後可以看到正常分配了內網ip,但並不會被外網訪問到。

image

為了方便演示,我們簡單修改一下grpc-server-xiaoyu-service.yaml ,讓外網ip可以訪問到,內容如下:

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert -f docker-compose.yml
    kompose.version: 1.34.0 (cbf2835db)
  labels:
    io.kompose.service: grpc-server-xiaoyu
  name: grpc-server-xiaoyu
spec:
  ports:
    - name: "9091"
      port: 9093
      targetPort: 9093
  type: NodePort
  selector:
    io.kompose.service: grpc-server-xiaoyu

再次啟動,效果如圖所示:

image

最後啟動成功:

image

改下我們本地的連線埠:

public class Main {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("192.168.56.5", 30238)
                .usePlaintext()
                .build();

        // 建立一元式阻塞式存根
        GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(channel);

        // 建立請求物件
        HelloRequest request = HelloRequest.newBuilder()
                .setName("World")
                .build();

        // 傳送請求麥位列表資訊並接收響應
        HelloReply response = blockingStub.sayHello(request);

        // 處理麥位列表資訊響應
        System.out.println("Received  response: " + response);

    }
}

日誌輸出成功:

image

在 Kubernetes 中,容器的日誌預設儲存在宿主機的 /var/log/containers/ 目錄下。每個容器的日誌檔名通常包括容器的名稱、Pod 名稱、名稱空間和容器執行的唯一識別符號(例如 Pod 的 UID)。這種路徑結構確保了每個容器的日誌都是唯一的,不會與其他容器的日誌混合。

image

我們看下預設日誌目錄下生成的日誌。

image

由於我們已經成功掛載了資料盤,因此相應的掛載目錄也已建立並可供使用。效果如圖所示:

image

動態擴容

為了應對容器編排中可能出現的容器例項數量增加的問題,Kubernetes 提供了靈活的擴充套件機制,既可以透過自動擴容來根據負載自動增加或減少容器例項數量,也可以透過手動擴容的方式來快速響應突發的高併發使用者訪問需求。

在此,我們將演示如何手動增加容器例項的數量。命令如下:

kubectl scale deployment grpc-server-xiaoyu --replicas=2

執行成功,效果如圖所示:

image

總體而言,Kubernetes 以其靈活性和強大的功能,基本上已經能夠滿足現代化專案的絕大部分需求,尤其是在容器例項擴充套件和自動化管理等方面,極大地降低了手動干預的複雜度。

那麼問題來了

綜上所述,雖然 Kubernetes(K8s)在日常運維和管理方面為開發者和運維團隊提供了極大的便利,自動化的擴充套件、負載均衡、容錯處理等功能也大大提升了系統的可靠性和可維護性,但在初期的伺服器搭建和叢集配置過程中,K8s 的複雜性卻無可避免地帶來了不少挑戰。

由於 K8s 本身是一個高度模組化的系統,其元件間的依賴性較強,任何一項配置錯誤都可能導致叢集執行異常。這就增加了叢集部署和維護的難度,尤其對於中小型企業或者缺乏深厚運維經驗的團隊來說,如何快速部署並確保叢集穩定性,依然是一個需要解決的難題。那麼,面對這一挑戰,如何能夠降低 K8s 部署和管理的門檻,並使其更易於使用呢?

上雲

在這方面,實際上不必過多贅述。當前,各大雲服務提供商的 Kubernetes 叢集部署服務已經相當成熟,基本上能夠滿足絕大多數企業對叢集服務的需求,並且提供了完善的生態支援。比如,監控與報警系統、日誌收集、自動化擴容、負載均衡等一整套解決方案,幾乎涵蓋了現代化應用所需的所有基礎設施和運維功能。

image

相較於自行搭建和管理 Kubernetes 叢集,使用雲廠商提供的服務無疑是更加便捷和高效的。只需透過雲平臺的控制檯進行幾次點選,相關服務就能自動化部署,極大地縮短了上線週期。對於技術團隊來說,這種便捷的叢集部署方式,幾乎可以做到即插即用,快速上手,降低了對複雜操作和配置的依賴。

此外,雲服務商通常都會提供豐富的官方文件支援,幫助使用者解決常見問題。在遇到更復雜的情況時,使用者還能直接提交工單,享受一對一的專屬技術支援,這對於解決實際運維中的疑難問題至關重要。因此,相比於傳統的自行搭建叢集,雲服務的方案在穩定性、易用性和服務質量上都有著無可比擬的優勢。

總結

透過本文的深入探討,我們已經詳細瞭解了一個專案從 Docker 容器化到 Kubernetes 叢集化的演變過程。在這個過程中,我們不僅分析了 Docker 的基礎使用方法和 Docker Compose 的便捷性,還介紹了 Kubernetes 在處理大型、複雜系統中的重要作用。

雖然 Kubernetes 在初期的配置和維護上可能帶來一定的複雜性,但隨著雲服務的成熟,各大雲平臺提供了全面的 Kubernetes 叢集管理解決方案,極大地簡化了部署流程。雲服務商的自動化工具和技術支援,幫助企業快速上手 Kubernetes,避免了許多傳統叢集部署中的難題。

因此,Kubernetes 已經成為現代雲原生應用部署的標配,它的靈活性、擴充套件性和高度自動化特性,使得它在容器編排和微服務架構的管理中佔據了無可替代的地位。對於開發者來說,掌握 Kubernetes 是未來工作中不可或缺的一項技能,它不僅能提高專案的交付速度,還能有效降低運維複雜度,為企業提供更高效、可靠的服務。

最後,無論採用何種方式,我們都應根據實際情況出發,避免盲目追求華而不實,無論是 Docker、編排工具,還是 Kubernetes,總有一種方式最適合你的需求。


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章