還不懂 Docker? 一文帶你瞭解清楚!
Why Docker?
Docker 容器化技術是當今最重要的基礎設施之一,或者說它已經成爲服務程序
的標準化運行環境。
先不談它相比傳統的虛擬化
技術有多少優勢,站在軟件工程角度,筆者認爲,Docker 有兩個重要的意義:
一)「提供一致性的運行環境。讓我們的程序在一致性的環境中運行:不管是開發環境、測試環境、還是生產環境;不管是開發時、構建時、還是運行時」。
比如開發時可以使用 Docker Dev Environments, 可以配合 VsCode Remote 開發,從而實現跳槽時或者換設備,可以快速 Setup 自己的開發環境。有興趣的可以看看掘友寫的 Docker 化一個前端基礎開發環境:簡潔高效的選擇
構建時,現在 CI/CD 平臺都是基於 Docker 來提供多樣化的構建環境需求。
運行時,‘巨輪’ K8S 已經是雲時代的重要基礎設施。
「二)標準化的服務程序封裝技術。」
在沒有容器之前,使用不同編程語言或框架編寫的程序,部署和運行的方式千差萬別。比如 Java 會生成 jar 包或者 war 包,運行環境需要預裝指定版本的 JDK…
而現在,容器鏡像
成爲了標準的服務程序封裝技術。鏡像中包含了程序
以及程序對運行環境的依賴
。
不管前後端應用都可以使用鏡像的形式進行分發和流通。這應該就是 Docker Logo,那條鯨魚馱着貨運箱的解釋吧:就像我們平時下載、傳遞 Zip 文件一樣, 鏡像是雲時代’通用貨幣’,可以在研發的不同環節、區域中流通。
這種標準化的打包格式、輕量化的運行時,不僅給開發者和運維帶來便利, 也催生了強大的容器管理工具比如 K8S, 「K8S 現在已經是容器和集羣管理的標準。」
「那 Docker 之於前端意義是啥?」
Docker 對前端的意義也很重大。實際上,Docker 的世界裏,並不區分什麼前端、後端,沒有人說只適合後端、不適合前端 … 在運維的眼裏更是如此
爲了照顧那些不太懂 Docker 的開發者,本文會循序漸進、由淺入深地講解。如果你需要 Docker 入門教程,推薦你看看 Docker —— 從入門到實踐
主要分成三個部分:
-
標準化的 CI/CD。講講怎麼基於 Docker 來構建前端應用,這裏提出了一個重要的觀點:就是基於 Dockerfile 來實現 ‘跨 CI/CD’ 的任務執行,我們可以在 Dockerfile 中執行各種任務,包括環境初始化、單元測試、構建等等
-
標準化部署和運行。怎麼部署基於 Docker 的前端應用,包括靜態資源、NodeJS 程序、微前端。
-
一些高級的話題。講講容器化後的前端應用怎麼實現 ’「一份基準代碼,多份部署」‘、灰度發佈、藍綠髮布等高級發佈需求。
標準化 CI/CD
市面上有很多 CI/CD 產品,比如 GitLab、Github Action、Jenkins、Zadig… 它們的構建配置、腳本語法差異都挺大,基本上是不能共用的。
比如我們公司前不久引入了 Zadig,原本基於 Jenkinks 的構建配置幾乎需要重新適配。
** 有沒有跨‘平臺’的方式?** 於是,我開始探索將前端 CI/CD 的流程完全集成到 Docker 鏡像構建中去。
從簡單的單元測試開始
我們先從簡單的任務開始。先來寫一個簡單的單元測試:
FROM node:20-slim
# 🔴 pnpm 安裝
RUN corepack enable
# 🔴 拷貝源代碼
COPY . /app
WORKDIR /app
# 🔴 安裝依賴
RUN pnpm install
# 🔴 執行測試
RUN pnpm test
❝
⁉️ corepack? NodeJS 的包管理碎片化越來越驗證了,以前我們區分 npm、yarn、pnpm, 現在還要繼續分裂版本,pnpm v7、pnpm v8…
NodeJS 官方推出的 Corepack 應該可以救你一命❞
別忘了 .dockerignore
node_modules
.git
.gitignore
*.md
dist
❝
⁉️ 爲什麼不能遺漏
.dockerignore
呢?❞
構建運行:
$ docker build . --progress=plain
#1 [internal] load build definition from Dockerfile.for_test
#1 transferring dockerfile: 40B done
#1 DONE 0.0s
#2 [internal] load .dockerignore
#2 transferring context: 34B done
#2 DONE 0.0s
#3 [internal] load metadata for docker.io/library/node:20-slim
#3 DONE 1.5s
#4 [1/6] FROM docker.io/library/node:20-slim@sha256:6eea4330e89a0c6a8106d0bee575d3d9978b51aac16ffe7f6825e78727815631
#4 CACHED
#5 [internal] load build context
#5 transferring context: 227B done
#5 DONE 0.0s
#6 [2/6] RUN corepack enable
#6 DONE 0.2s
#7 [3/6] COPY . /app
#7 DONE 0.0s
#8 [4/6] WORKDIR /app
#8 DONE 0.0s
#9 [5/6] RUN pnpm install
#9 4.878 Lockfile is up to date, resolution step is skipped
#9 4.880 Progress: resolved 1, reused 0, downloaded 0, added 0
#9 4.881 Packages: +1
#9 4.881 +
#9 6.603 Progress: resolved 1, reused 0, downloaded 1, added 0
#9 6.643 Packages are hard linked from the content-addressable store to the virtual store.
#9 6.643 Content-addressable store is at: /root/.local/share/pnpm/store/v3
#9 6.643 Virtual store is at: node_modules/.pnpm
#9 6.659
#9 6.659 dependencies:
#9 6.659 + lodash 4.17.21
#9 6.659
#9 6.661 Done in 2s
#9 7.608 Progress: resolved 1, reused 0, downloaded 1, added 1, done
#9 DONE 7.7s
#10 [6/6] RUN pnpm test
#10 0.497 測試通過
#10 DONE 0.5s
#11 exporting to image
#11 exporting layers
#11 exporting layers 0.2s done
#11 writing image sha256:9d61ce0fd5d96685aa62fb268db37b3dea4cfa1699df73d8d6a7de259c914a8d done
#11 DONE 0.2s
二次運行的結果:
#1 [internal] load build definition from Dockerfile.for_test
#1 transferring dockerfile: 40B done
#1 DONE 0.0s
#2 [internal] load .dockerignore
#2 transferring context: 34B done
#2 DONE 0.0s
#3 [internal] load metadata for docker.io/library/node:20-slim
#3 DONE 3.2s
#4 [1/6] FROM docker.io/library/node:20-slim@sha256:6eea4330e89a0c6a8106d0bee575d3d9978b51aac16ffe7f6825e78727815631
#4 DONE 0.0s
#5 [internal] load build context
#5 transferring context: 227B done
#5 DONE 0.0s
#6 [2/6] RUN corepack enable
#6 CACHED <- 🔴 緩存了
#7 [4/6] WORKDIR /app
#7 CACHED
#8 [3/6] COPY . /app
#8 CACHED
#9 [5/6] RUN pnpm install
#9 CACHED
#10 [6/6] RUN pnpm test
#10 CACHED <- 🔴 緩存了
...
Docker 鏡像是多層存儲
的, 每一層是在前一層的基礎上進行的修改。換句話說, Dockerfile
文件中的每條指令 (Instruction) 都是在構建新的一層。
Docker 使用了緩存
來加速鏡像構建,所以上面執行結果可以看出只要上一層
和當前層的輸入
沒有變動,那麼執行結果就會被緩存下來。
現在你可以隨便更動 src/*
或者 package.json
, 再執行構建,會發現,從 COPY
指令那裏重新開始執行了:
# ...
#3 [internal] load metadata for docker.io/library/node:20-slim
#3 DONE 1.3s
#4 [1/6] FROM docker.io/library/node:20-slim@sha256:75404fc5825f24222276501c09944a5bee8ed04517dede5a9934f1654ca84caf
#4 DONE 0.0s
#5 [internal] load build context
#5 transferring context: 525B done
#5 DONE 0.0s
#6 [2/6] RUN corepack enable
#6 CACHED
# 🔴 變更點
#7 [3/6] COPY . /app
#7 DONE 0.0s
#8 [4/6] WORKDIR /app
#8 DONE 0.0s
#9 [5/6] RUN pnpm install
#....
也就是,又從 0 開始進行 pnpm install
…
緩存處理
前端構建會深度依賴緩存來加速,比如 node_modules、Webpack 的模塊緩存、vite 的 prebundle、Typescript 的 tsBuildInfoFile …
上面從零開始 pnpm install 顯然是無法接受的。每次都是從頭開始,構建的過程會變得很慢。有什麼解決辦法呢?
「解決辦法 1)利用 Docker 分層緩存」
pnpm
依賴的安裝,其實只需要 package.json
、pnpm-lock.yaml
等文件就夠了,那我們是不是可以把 COPY 拆分從兩步?採用動靜分離策略,分離 package.json 和源代碼的變更。畢竟 package.json 的變更頻率要低得多:
FROM node:20-slim
RUN corepack enable
WORKDIR /app
# 拷貝依賴聲明
COPY package.json pnpm-lock.yaml /app/
RUN pnpm install
# 拷貝源代碼
COPY . /app
RUN pnpm test
「解決辦法 2) RUN 掛載緩存」
方案 1 還是有很多缺陷,比如 package.json 只要變動一個字節,都會導致 pnpm 重新安裝。能不能在運行 build 的時候掛載緩存目錄進去?把 node_modules
或者 pnpm store
緩存下來?
Docker build 確實支持掛載 (BuildKit, 需要 Docker 18.09+)。以下是緩存 pnpm 的示例 (來自官方文檔):
FROM node:20-slim
RUN corepack enable
WORKDIR /app
# 拷貝依賴聲明
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
COPY package.json pnpm-lock.yaml /app/
# 掛載緩存
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install
# 拷貝源代碼
COPY . /app
RUN pnpm test
❝
💡你也可以通過設置
DOCKER_BUILDKIT=1
環境變量來啓用BuildKit
❞
RUN —mount
參數可以指定要掛載的目錄,對應的緩存會存儲在宿主機器
中。這樣就解決了 Docker 構建過程的外部緩存問題。
同理其他的緩存,比如 vite、Webpack,也是通過 —mount
掛載。一個 RUN
支持指定多個 —mount
❝
⚠️ 因爲採用掛載形式,這種跨設備會導致
pnpm
回退到拷貝模式 (pnpm store → node_modules),而不是鏈接模式,所以安裝性能會有所損耗。❞
❝
如果是 npm 通常需要緩存
~/.npm
目錄❞
多階段構建
假設我們要在原有單元測試的基礎上,加入編譯任務。並且要求兩個命令支持**「獨立執行」**,比如在代碼 commit 到遠程倉庫時只執行單元測試,發佈時才執行單元測試 + 編譯。
第一種解決辦法就是創建兩個 Dockerfile, 這個方案的缺點就是指令重複 (比如 pnpm 安裝依賴)。另一個缺點就是如果任務之間有依賴或文件交互,那麼整合起來也會比較麻煩。
更好的辦法就是多階段構建(Multi-Stage)。Docker 允許將多個構建步驟整合在一個 Dockerfile 文件中,這個構建步驟之間可以存在依賴關係,也可以進行文件傳遞,還可以更好地利用緩存。
# 🔴 階段 1,安裝依賴
FROM node:20-slim as base
RUN corepack enable
WORKDIR /app
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
# 拷貝依賴聲明
COPY package.json pnpm-lock.yaml /app/
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install
# 🔴 階段 2,單元測試
FROM base as test
# 拷貝源代碼
COPY . /app
RUN pnpm test
# 🔴 階段 3,構建
FROM test as build
RUN pnpm build
通過 FROM * as NAME
的形式創建一個階段。FROM 可以指定依賴的其他步驟。
現在我們運行:
$ docker build .
默認會執行最後一個階段。即 build。
如果我們只想跑 test,可以通過 —target
參數指定:
$ docker build --target=test .
我們再來看一個典型的複雜例子,Nextjs
程序構建:
FROM node:19-alpine AS base
# 0. 構建依賴, 爲什麼要分開一步構建依賴呢,這是爲了利用 Docker 的構建緩存
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json .npmrc pnpm-lock.yaml* ./
RUN npm i -g pnpm@7 && pnpm install
# 1. 第一步構建編譯
FROM base AS builder
WORKDIR /app
# COPY 依賴
COPY --from=deps /app/node_modules /app/node_modules
# COPY 源代碼
COPY . .
# COPY .env.production.sample .env.production
RUN env && ls -a && npm run build
# 2. 第二步,運行
FROM base AS runner
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
WORKDIR /app
COPY --from=builder --chown=nextjs:nodejs app/public /app/public
COPY --from=builder --chown=nextjs:nodejs app/.next/standalone /app
COPY --from=builder --chown=nextjs:nodejs app/.next/static /app/.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
多階段構建的另一個好處是隱藏構建的細節: 比如上游構建的過程中傳遞的一些敏感信息、隱藏源代碼等。
在上面的 Next.js 例子中, 最終構建的是 runner, 它從 builder 中拷貝編譯的結果,對最終的鏡像使用者來說,是查看不到 builder 的構建細節和內容的。
構建參數
程序在構建時可能會有一些微調變量,比如調整 Webpack PublicPath、編譯產物的目標平臺、調試開關等等。
在 DockerFile 下可以通過 ARG
指令來聲明構建參數
:
# 聲明構建參數,支持默認值
ARG DOCKER_USERNAME=library
# 可以在 DockerFile 中作爲 '模板變量' 使用
FROM ${DOCKER_USERNAME}/alpine
# 打印 library
RUN echo ${DOCKER_USERNAME}
# 打印 包含 DOCKER_USERNAME=library
RUN env
ARG
和 ENV
的效果一樣,都是設置**「環境變量」**。不同的是,ARG
所設置是構建時
的環境變量,在將來容器運行時
是不會存在這些環境變量的。
❝
⚠️ 注意,儘量不要在
ARG
放置敏感信息,因爲docker history
可以看到構建的過程❞
通過 docker build --build-arg Key=[Value]
設置構建參數:
$ docker build --build-arg BABEL_ENV=test .
# 🔴 或者只指定 KEY, Value 自動獲取
$ docker build --build-arg BABEL_ENV .
怎麼支持更復雜的構建需求?
Dockerfile 中不建議放置複雜的邏輯,而且它語法支持也很有限。如果有複雜的構建需求,更應該通過 Shell 腳本或者 Node 程序來實現。
集成到 CI/CD 平臺
上文,我們探索了使用 Docker 來實現‘跨平臺’(CI/CD) 的構建任務。看起來還不錯,應該能夠滿足我們的需求。
通常這些平臺對 Docker 鏡像構建的支持都是開箱即用的, 如果使用 Dockerfile 方案,我們可以免去一些額外的聲明,比如構建依賴的軟件包、緩存配置、構建腳本等等。
現在只需要關注 Dockerfile
構建, 下圖以 Zadig
爲例。在 Zadig 中,我們只需要告訴 Dockerfile 在哪,其餘的工作 (比如鏡像 tag、鏡像發佈) 都不需要操心:
接入其他構建平臺也是類似的,「我們只需要學習對應平臺如何構建鏡像就行」。
標準化部署和運行
上一節, 講到將 Docker 作爲‘跨平臺’的任務執行環境。下一步就是發佈、部署、運行。注意接下內容可能需要你對 K8S 有基本的瞭解。
鏡像發佈就不用展開說了,就和 npm 發佈一樣簡單。本節的重點在於討論,前端‘應用’在容器環境如何對外服務。
目前比較主流的前端應用可以分爲三類:
-
純靜態資源。
-
NodeJS 程序。包括 NodeJS 的純後端服務、還有 NextJS、NuxtJS 這裏 SSR 服務
-
微前端。主要指**「以基座爲核心的中心化的微前端方案」**, 比如
qiankun
。這類程序需要基座和子應用相互搭配才能對外服務。
純靜態資源
估計 80% 以上的前端應用都是純靜態的。
筆者嘗試過多種部署的方式。在我們將前端應用容器化的初期, 有過這樣一種中間的演進形態:
在改造之前我們所有的前端靜態資源都堆在一個靜態資源服務器中 (上圖左側),所有人都有部署權限、所有人都能改 Nginx 配置、目錄混亂。部署方式也是各顯神通,有 Jenkins 自動部署、有 FTP/rsync 手動上傳… 就是一個極其原始的狀態。
在容器化改造的初期,運維把靜態資源服務器
轉換成爲了 Nginx 容器
,而原本 Nginx 的配置通過配置映射
(Config Map)來掛載到容器內部。
前端應用也做了非常簡單的改造, 就是簡單把靜態資源 COPY 到鏡像中:
FROM busybox:latest
COPY dist /data
運行時,前端應用以 Nginx 容器
的 Sidecar
形式存在,在啓動時向共享的 PVC (數據卷) 拷貝靜態資源。
更理想的情況是每個前端應用能夠獨立對外服務, 對鏡像的使用者來說,他應該是開箱即用的、自包含、透明的。
所以我們對部分比較獨立的應用進行了重構:
如上圖, 前端應用基於 nginx 運行,流量會通過 Ingress 來分發到不同的應用,分發的方式通常有域名、請求路徑等等。
這也進一步簡化了運維的工作,運維只需要前端後兩個鏡像就可以將一套系統部署起來。
我們稍微改造一下上文的 Dockerfile 來支持 nginx 部署:
# 🔴 階段 1,安裝依賴
FROM node:20-slim as base
RUN corepack enable
WORKDIR /app
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
# 拷貝依賴聲明
COPY package.json pnpm-lock.yaml /app/
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install
# 🔴 階段 2,單元測試
FROM base as test
# 拷貝源代碼
COPY . /app
RUN pnpm test
# 🔴 階段 3,構建
FROM test as build
RUN pnpm build
# 🔴 階段 4,運行
FROM nginx:stable-alpine as deploy
COPY --from=build /app/dist/ /usr/nginx/wwwroot
# 如果需要自定義 nginx 配置,可以開啓這行
#COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
NodeJS 程序
這個和普通後端服務沒什麼區別,狹義上不屬於前端的範疇,沒有太多可以講的,可以參考上文的 Next.js 示例。
微前端
我在微前端的落地和治理實戰 中簡單介紹過:
我們公司目前採用的是上圖的 Sidecar 模式。每個子應用都是一個 Sidecar,啓動時將自己‘註冊’到基座中,由基座統一對外服務。
好處:基座可以統一管理所有子應用。比如可以實現‘子應用發現’、動態配置替換之類的工作
壞處:依賴 PVC 共享存儲。我們也有遇到部分客戶環境不支持共享 PVC 的。
對於不支持共享 PVC 的場景,我們也會進行回退:
讓每個子應用獨立對外服務,每個子應用都有自己的前綴, Ingress 根據前綴來分發流量。
好處就是子應用可以自己管理自己,升級和流量控制會更加靈活。缺點就是基座無法感知到這些子應用的存在,需要手動配置這些子應用的信息。
如果要更進一步,可以將基座定義爲類似後端 “註冊中心
”, 子應用主動向基座註冊,有點後端微服務的味道了。如果真需要複雜到這一步,也沒有必要自己造輪子,複用後端的技術棧不是更香?
除此之外,還有很多手段,比如基座提供發佈服務,子應用調用基座發佈服務,將自己的應用信息、靜態資源提交給基座。
不是銀彈
上面我們介紹了基於 Docker 容器的前端應用部署的各種方式和場景。但這並不是銀彈!前端也不一定非得就要容器化。
很多大廠都有自己成熟的發佈、部署流程和系統平臺,他們需要應付各種複雜的情況, 比如大流量、CDN 同步、熔斷降級、灰度發佈、藍綠髮布,回滾… 那本文講到的各種‘樸素’的技巧,就是一種雕蟲小技
「那它對我們爲什麼有用?」
我們主要做 ToB 業務,容器化的方案可以應付私有化交付、私有化部署需求。開發和運維會面對各種千奇百怪的運行環境、公有云、私有云。但大部分甲方都會提供基礎的 K8S 環境,容器化對我們來說就是一個最簡單且高效的方案。
另外,依託於 K8S 這類強大容器管理平臺,大部分問題都有解決方案,何必造輪子呢?
一些高級話題
「一份基準代碼,多份部署」
12-factors 裏有一個原則:一份基準代碼,多份部署。如果放在容器這個上下文中,就是一個鏡像應該能夠在不同的環境部署,而不需要任何修改。
這對我們做 ToB 的也很重要,如果我們爲一個客戶做一次私有化部署,就要將所有的應用重新構建一遍,這顯然無法接受。
對於後端服務來說,很容易做到,要麼通過環境變量
,要麼就從配置中心
動態拉取。
而對於前端來說,靜態資源的各種 URL (比如 CDN 鏈接) 和配置可能在構建時
就固定下來了。而且我們的代碼不運行在服務端,因此也不能通過環境變量來動態配置。
當然,也有解決辦法:
-
使用 SSR。理論上可以解決,但是現代前端框架不是純動態的,也會有一個編譯的過程
-
模板替換。可以參考 微前端的落地和治理實戰 ,運行容器。
-
還有古老的 SSI(ServerSideInclude) 技術。
下面以 Nginx
SSI + Vite
爲例, 演示一下 SSI:
vite 配置:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
experimental: {
renderBuiltUrl(filename) {
return "<!--# echo var='public_url' -->" + filename
}
}
})
<!--# echo var='public_url' -->
是 SSI 的指令語法。這裏使用 Vite 實驗性的 renderBuiltUrl 來配置(因爲直接使用 base 會有問題)。
Dockerfile:
FROM nginx:stable-alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
# 這裏是需要顯式告訴 envsubst 要替換的環境變量,如果有多個環境變量,使用 ',' 分割
# 因爲 nginx 變量的語法和 環境變量相似,如果不顯式設置,envsubst 可能會誤替其他 nginx 變量
CMD (cat /etc/nginx/nginx.conf | envsubst '${PUBLIC_URL}' >/etc/nginx/nginx.conf) && cat /etc/nginx/nginx.conf && nginx -g 'daemon off;'
「nginx 配置文件中無法愉快地引用環境變量」,所以曲線救國, 使用 envsubst
來替換 nginx.conf 中的環境變量佔位符。
Nginx 配置:
# ... 省略
location / {
# 開啓 ssi
ssi on;
ssi_last_modified on;
# 支持 html、js、css 等文件
ssi_types text/html application/javascript text/css;
# 設置變量,將由 envsubst 替換,格式爲 ${NAME-defaultValue}
set $public_url "${PUBLIC_URL-/}";
root /usr/share/nginx/html;
index index.html index.htm;
}
# ... 省略
自己試試看吧!
如何做灰度發佈、藍綠髮布…?
在 K8S 環境,有挺多簡單的手段可以實現灰度 (金絲雀發佈) 發佈、藍綠髮布這些功能,比如:
-
通過 Service。一個 Service 實際上可以映射到多個 Deployment。通過調整不同版本 Deployment 的副本數,即可調整不同版本服務的權重,實現灰度發佈。
-
通過 Nginx Ingress。Nginx Ingress 則更加強大一些,支持基於 Header、Cookie 和服務權重三種流量切分的策略
還有很多實現手段,因爲不是本文的重點,就不贅述了。如果大家有更好更簡單的方式也可以評論區交流。
「那如果按照上文講的微前端部署方式,怎麼實現子應用灰度呢?」
這裏不需要用到複雜的流量分發技術,因爲基座自己會收集子應用的信息,那麼只需要在子應用註冊表上做文章就行了。例如:
-
基座會收集到所有的已部署的子應用。一個子應用可能有多個版本。子應用版本之間使用版本號區分目錄:
/apps/ foo/ v1/ manifest.json # 應用描述信息 index.html js/ ... v2/ manifest.json index.html js/ ... current.json # 保存當前對外服務的應用版本信息。實際上也可以保存一些灰度條件匹配之類的配置信息 bar/ v1/ manifest.json current.json ...
-
基座會提供一個管理平臺,供運維和開發者 pick 要對外服務的版本,或者配置灰度匹配條件等等。
-
當瀏覽器發起入口文件請求時,基座計算最終要返回的子應用配置列表,不同人羣可能拿到的結果不一樣,從而實現灰度發佈功能。
這個思路看起來和後端的服務發現
平臺 (比如 Nacos) 很像,後端服務實現灰度基本也是依靠這些平臺來實現的。
總結
回顧一下本文。Docker 發佈已經十年,大家對它應該已經熟悉不過了,它對現代的軟件工程有非常重要的意義。
我在這篇文章中分了兩個維度來討論它, 一是將它作爲一個’跨平臺’的任務運行環境,它讓我們可以在一致的環境中運行單測、構建、發佈等任務;二是講怎麼將前端應用容器化,對齊後端,利用現有的容器管理平臺來實現複雜的部署需求。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/3CXK_WF22s2i7e1mGhXBSQ