
你寫了一個 HTTP 服務在自己電腦上運行得很好。把代碼發(fā)給同事后他說“啟動失敗依賴版本不對?!蹦阈藓靡蕾囉职逊詹渴鸬椒掌?。過了幾天訪問量上來了于是你啟動了三個副本。現(xiàn)在新的問題來了三個副本該放在哪些機器上某臺機器壞了誰來補一個副本發(fā)布新版本時怎樣讓服務持續(xù)可用副本的地址變了用戶該訪問誰Docker 主要幫我們處理“怎樣把應用及其運行環(huán)境交付出去”。Kubernetes通常簡稱 K8s主要幫我們處理“怎樣讓這些應用在一組機器上持續(xù)運行”。這篇文章就沿著這個小服務的部署過程把兩者串起來。一、先別急著學命令容器是什么假設你的服務依賴 Python、幾個第三方庫和一份配置文件。傳統(tǒng)部署方式是先準備一臺服務器再逐項安裝、配置。只要服務器上的版本與開發(fā)環(huán)境有差異就可能出現(xiàn)“本地正常線上報錯”。容器提供了另一種交付方式把運行應用需要的文件、依賴和配置方式打包成鏡像再用鏡像啟動容器。這里有三個詞需要分清名詞可以怎樣理解鏡像 Image一份用于啟動應用的打包結果容器 Container鏡像運行起來后的實例鏡像倉庫 Registry存放、分發(fā)鏡像的地方一個鏡像可以啟動多個容器就像同一份程序可以運行多個進程。不過容器并不只是“換了個名字的進程”它通常擁有隔離的文件系統(tǒng)、網(wǎng)絡視圖等環(huán)境也可以被限制使用多少 CPU 和內(nèi)存。容器和虛擬機最大的區(qū)別是虛擬機通常運行自己的客戶操作系統(tǒng)Linux 容器共享宿主機內(nèi)核隔離的是進程的運行環(huán)境。因此容器一般更輕啟動也更快。這里說的是 Linux 容器在 Windows 或 macOS 上使用 Docker Desktop 運行它們時底層通常還有一個 Linux 虛擬機。你暫時不用記住 namespace、cgroups 等內(nèi)核術語。先記住一句話容器讓應用以相對隔離、可重復的環(huán)境運行但它仍然依賴宿主機提供的內(nèi)核能力。Docker 官方容器入門二、Docker 是怎樣把代碼變成容器的假設我們有一個監(jiān)聽8080端口的小服務入口文件叫app.py。我們可以寫一個 Dockerfile告訴 Docker 怎樣制作鏡像FROM python:3.13-slim WORKDIR /app COPY . . EXPOSE 8080 CMD [python, app.py]逐行看其實沒有什么神秘的FROM以哪個現(xiàn)成鏡像為基礎。WORKDIR后續(xù)操作在哪個目錄執(zhí)行。COPY把代碼復制進鏡像。EXPOSE記錄應用使用的端口它本身不會把端口開放給宿主機。CMD容器啟動時默認運行什么命令。接著構建并運行dockerbuild-thello-api:1.0.dockerrun--rm-p8080:8080 hello-api:1.0-p 8080:8080的左邊是宿主機端口右邊是容器端口。訪問宿主機的8080請求才能轉(zhuǎn)發(fā)到容器的8080。還要確保應用在容器里監(jiān)聽的是0.0.0.0而不只是127.0.0.1。至此我們完成了一條最基本的鏈路代碼 → Dockerfile → 鏡像 → 容器 → 對外提供服務如果要把鏡像交給另一臺機器還需要給它標記倉庫地址、推送到鏡像倉庫然后在目標機器拉取并運行。鏡像讓交付更一致但不保證應用一定正確環(huán)境變量寫錯、數(shù)據(jù)庫連不上照樣會啟動失敗。Docker 官方鏡像構建指南一個很常見的困惑容器里的localhost是誰假設應用容器需要訪問數(shù)據(jù)庫容器。你在應用配置里寫localhost:5432通常會連不上。因為站在應用容器內(nèi)部看localhost指的是應用容器自己不是數(shù)據(jù)庫容器更不是宿主機。這也是容器網(wǎng)絡要解決的問題讓容器能夠通過合適的網(wǎng)絡和名稱互相訪問。用 Docker Compose 管理一組本地容器時應用通??梢酝ㄟ^數(shù)據(jù)庫的服務名找到它例如db:5432。另一個常見困惑容器刪除后數(shù)據(jù)去哪了容器適合被創(chuàng)建、停止和替換。如果把數(shù)據(jù)庫數(shù)據(jù)只寫在容器自身的可寫層里刪除容器時這些數(shù)據(jù)通常也會跟著消失。所以數(shù)據(jù)庫這類需要長期保存的數(shù)據(jù)要放在容器生命周期之外例如使用 Docker volume。你可以把它理解為容器負責運行程序持久化存儲負責留住數(shù)據(jù)。到這里Docker 已經(jīng)能很好地幫助我們制作鏡像、運行容器以及在一臺機器上組織幾個相關服務。接下來問題轉(zhuǎn)向多臺機器。三、為什么有了 Docker還需要 K8s現(xiàn)在老板要求我們的服務始終保持三個副本。你可以手動在幾臺服務器上執(zhí)行docker run但隨后就要持續(xù)回答這些問題三個副本各跑在哪臺機器一個副本退出后誰發(fā)現(xiàn)并重啟它整臺機器失聯(lián)后誰在別處補齊副本發(fā)布新鏡像時怎樣逐步替換舊副本副本隨時可能更換流量該發(fā)往哪里K8s 把這些事情組織成一套機制。你告訴它想要的狀態(tài)例如“運行三個副本使用這個鏡像”它持續(xù)觀察實際狀態(tài)并設法縮小兩者的差距。這叫聲明式管理??梢赃@樣理解兩者的分工Docker把應用制成鏡像并能運行容器 K8s在集群里安排、維護和連接容器化應用這里有一個面試中容易被問到的細節(jié)**K8s 可以運行 Docker 構建的鏡像但節(jié)點不一定使用 Docker Engine 來運行容器。**K8s 通過容器運行時接口與兼容的運行時協(xié)作常見選擇包括 containerd如果使用 Docker Engine則需要相應的適配組件。Kubernetes 官方容器運行時文檔四、K8s 集群里誰負責什么先看全局。一個 K8s 集群通常由控制平面和工作節(jié)點 Node組成。工作節(jié)點是實際運行應用的機器??刂破矫尕撠熃邮漳愕囊?、記錄集群狀態(tài)、決定任務放到哪里并持續(xù)檢查實際情況。不用一開始就背所有組件先順著一次部署看你提交一份配置“運行三個hello-api副本?!盇PI Server接收這個請求。集群把期望狀態(tài)記錄下來etcd是保存集群數(shù)據(jù)的關鍵存儲??刂破靼l(fā)現(xiàn)“期望三個實際零個”于是創(chuàng)建相應的工作負載。Scheduler為尚未分配節(jié)點的 Pod 選擇合適的 Node。Node 上的kubelet配合容器運行時把容器真正運行起來。如果后來一個副本消失相關控制器會繼續(xù)嘗試讓實際數(shù)量回到三個。注意“嘗試”二字如果集群資源不足、鏡像拉取失敗或配置有誤K8s 不可能憑空讓服務正常運行。它會暴露狀態(tài)和事件供你排查。Kubernetes 官方集群組件文檔五、Pod、Deployment、Service最重要的三個對象初學 K8s 容易被大量名詞勸退。先抓住三個已經(jīng)能解釋一條基本部署鏈路。Pod應用實際運行的地方**Pod 是 K8s 部署和調(diào)度的基本單位。**最常見的 Pod 里只有一個主要業(yè)務容器一個 Pod 也可以包含多個需要緊密協(xié)作的容器。同一個 Pod 內(nèi)的容器共享網(wǎng)絡環(huán)境可以通過localhost相互訪問。Pod 不適合被當成一臺永久不變的小服務器。它可能被替換地址也可能變化。因此別把“某個 Pod 的 IP”寫死在客戶端配置里。Deployment保持副本數(shù)量管理版本更新如果你直接創(chuàng)建一個 Pod它消失后誰來確保業(yè)務還保持三個副本通常我們會創(chuàng)建Deployment。它描述使用哪個鏡像、運行多少副本以及怎樣更新。例如apiVersion:apps/v1kind:Deploymentmetadata:name:hello-apispec:replicas:3selector:matchLabels:app:hello-apitemplate:metadata:labels:app:hello-apispec:containers:-name:hello-apiimage:your-registry/hello-api:1.0ports:-containerPort:8080先忽略 YAML 的層級只看兩處replicas: 3我們希望有三個副本。image: ...:1.0這些副本運行哪個鏡像。template則是在說新建出來的 Pod 應該長什么樣。發(fā)布新版時修改鏡像版本Deployment 可以逐步創(chuàng)建新 Pod、替換舊 Pod出現(xiàn)問題時也可以回滾。它適合管理我們這個無狀態(tài)的 HTTP 服務。Kubernetes 官方 Deployment 文檔Service給變化的 Pod 一個穩(wěn)定入口假設三個 Pod 的地址分別是 A、B、C。B 故障后被替換為 D??蛻舳瞬粦撁看味贾匦抡J識這些地址。Service提供相對穩(wěn)定的訪問方式并把請求導向符合條件的后端 Pod。它通過標簽選擇目標apiVersion:v1kind:Servicemetadata:name:hello-apispec:selector:app:hello-apiports:-port:80targetPort:8080這里的selector要與 Pod 的標簽對應。port: 80是 Service 提供的端口targetPort: 8080是應用容器監(jiān)聽的端口。集群內(nèi)的其他應用可以通過 Service 名稱訪問它而不需要記住每個 Pod 的 IP。Kubernetes 官方 Service 文檔把三個對象連起來就是Deployment我想保持 3 個副本 ↓ Pod三個實際運行應用的單位 ↑ Service為訪問這些 Pod 提供穩(wěn)定入口如果要讓集群外的用戶通過域名訪問通常還要配置面向外部流量的入口例如 Gateway API、Ingress或者在支持的環(huán)境中使用LoadBalancer類型的 Service。Service 解決穩(wěn)定訪問后端的問題不意味著應用自動擁有公網(wǎng)域名。Kubernetes 官方網(wǎng)絡概念六、應用跑起來了為什么還是會出故障到這里我們已經(jīng)可以發(fā)布服務但“進程存在”不等于“服務可用”。服務還沒準備好就開始接請求應用可能要加載配置、連接數(shù)據(jù)庫啟動需要十幾秒。此時容器雖然運行了卻還不能處理請求。**就緒探針readiness probe**回答的是“現(xiàn)在能接流量嗎”未就緒的 Pod 不會通過對應的 Service 接收流量。**存活探針liveness probe**回答的是“這個容器是否已經(jīng)卡死需要重啟”兩者不是一回事。把一個啟動很慢的服務錯誤地配置為“沒準備好就重啟”反而可能造成反復重啟。Kubernetes 官方探針文檔副本數(shù)量寫了三個卻只跑起來一個可能是節(jié)點沒有足夠 CPU 或內(nèi)存也可能是鏡像拉取失敗。K8s 中的資源請求會參與調(diào)度應用至少需要多少資源資源限制則約束它最多能使用多少。填寫這些數(shù)值需要依據(jù)應用的實際運行情況而不是隨意復制示例。數(shù)據(jù)庫也直接按同樣方法部署三個副本先停一下。我們的 HTTP 服務可以把任意請求交給任意一個副本副本之間通??梢曰Q這叫無狀態(tài)應用。數(shù)據(jù)庫有持久數(shù)據(jù)、身份和復制關系部署方式復雜得多。理解了 Deployment不等于所有服務都應該套用同一份 YAML。配置也要單獨考慮普通配置可以使用 ConfigMap密碼、令牌等敏感數(shù)據(jù)可以使用 Secret同時仍需正確設置訪問權限。數(shù)據(jù)持久化則涉及卷和持久卷聲明等機制。Kubernetes 官方配置概念七、遇到故障時怎樣查只背對象名面試時很容易露餡。更有用的是知道排查順序。假設發(fā)布后用戶訪問失敗可以沿著請求路徑問外部入口 → Service → Pod → 容器進程 → 應用依賴逐層看**Pod 根本沒創(chuàng)建**看 Deployment 的期望數(shù)量和事件。**Pod 一直 Pending**看是否無法調(diào)度例如資源不足。**Pod 在重啟**看容器日志、退出原因和探針配置。**Pod 正常Service 卻訪問不到**檢查 Service 的標簽選擇器、端口和就緒狀態(tài)。**容器正常但接口報錯**再看應用日志、配置和數(shù)據(jù)庫連接。常用命令不多先熟悉這些就夠了kubectl get pods kubectl describe podpod-namekubectl logspod-namekubectl get deployment kubectl getservice關鍵不是背命令而是知道每條命令要驗證哪個猜想。get看總體狀態(tài)describe看詳細信息和事件logs看應用輸出。八、現(xiàn)在用兩分鐘把 Docker 和 K8s 講給面試官聽你可以這樣說但最好換成自己的表達我理解 Docker 首先解決的是應用交付和運行環(huán)境一致性的問題。開發(fā)者用 Dockerfile 把代碼、依賴和啟動方式構建成鏡像鏡像可以推送到倉庫再在其他機器上啟動為容器。鏡像是一份打包結果容器是運行中的實例。當應用需要在多臺機器上運行多個副本時還需要處理調(diào)度、故障恢復、更新和服務發(fā)現(xiàn)。Kubernetes 就是管理這些容器化應用的平臺。我們通常用 Deployment 聲明鏡像和副本數(shù)量它管理 Pod 的創(chuàng)建與更新Pod 是實際運行容器的基本單位Service 則為可能不斷變化的 Pod 提供穩(wěn)定的訪問入口。比如我要發(fā)布三個 HTTP 服務副本會先構建并推送鏡像再創(chuàng)建一個副本數(shù)為三的 Deployment 和一個 Service。某個 Pod 消失后控制器會嘗試補齊發(fā)布新版本時可以通過 Deployment 逐步替換舊 Pod。我會用就緒探針控制何時接流量并通過 Pod 事件和日志排查啟動問題。如果你能講清這段話再回答“容器和虛擬機有什么區(qū)別”“Pod 和容器有什么區(qū)別”“為什么需要 Service”就已經(jīng)具備和同事、面試官展開基礎討論的能力了。最后把整篇文章壓縮成一張腦圖寫好應用 ↓ Dockerfile 定義怎樣打包 ↓ 構建鏡像推送到鏡像倉庫 ↓ K8s 的 Deployment 聲明鏡像和副本數(shù)量 ↓ Pod 在各個 Node 上運行容器 ↓ Service 把請求送到可用的 Pod ↓ 控制器持續(xù)檢查實際狀態(tài)是否符合期望狀態(tài)Docker 讓“把應用帶過去并運行”變得可重復K8s 讓“在多臺機器上持續(xù)運行這些應用”變得可管理。學到這里不必馬上鉆進網(wǎng)絡插件、調(diào)度算法或集群安裝細節(jié)。先親手完成一次“構建鏡像 → 運行容器 → 部署三個 Pod → 通過 Service 訪問 → 刪除一個 Pod 看它恢復”這些概念就會從名詞變成你親眼見過的過程。