使用Yarn workspace,TypeScript,esbuild,React和Express構(gòu)建 K8S 云原生應(yīng)用(一)
本文將指導(dǎo)您使用 K8S ,Docker,Yarn workspace ,TypeScript,esbuild,Express 和 React 來設(shè)置構(gòu)建一個基本的云原生 Web 應(yīng)用程序。在本教程的最后,您將擁有一個可完全構(gòu)建和部署在 K8S 上的 Web 應(yīng)用程序。
設(shè)置項目
該項目將被構(gòu)造為 monorepo。 monorepo 的目標(biāo)是提高模塊之間共享的代碼量,并更好地預(yù)測這些模塊如何一起通信(例如在微服務(wù)架構(gòu)中)。出于本練習(xí)的目的,我們將使結(jié)構(gòu)保持簡單:
- app,它將代表我們的 React website。
- server,它將使用 Express 服務(wù)我們的 app。
- common,其中一些代碼將在 app 和 server 之間共享。
設(shè)置項目之前的唯一要求是在機器上安裝 yarn。 Yarn 與 npm 一樣,是一個程序包管理器,但性能更好,功能也略多。您可以在官方文檔中閱讀有關(guān)如何安裝它的更多信息。
Workspaces(工作區(qū))
進(jìn)入到要初始化項目的文件夾,然后通過您喜歡的終端執(zhí)行以下步驟:
- 使用 mkdir my-app 創(chuàng)建項目的文件夾(可以自由選擇所需的名稱)。
- 使用 cd my-app 進(jìn)入文件夾。
- 使用 yarn init 初始化它。這將提示您創(chuàng)建初始 package.json 文件的相關(guān)問題(不用擔(dān)心,一旦創(chuàng)建文件,您可以隨時對其進(jìn)行修改)。如果您不想使用 yarn init 命令,則始終可以手動創(chuàng)建文件,并將以下內(nèi)容復(fù)制到其中:
- {
- "name": "my-app",
- "version": "1.0.0",
- "license": "UNLICENSED",
- "private": true // Required for yarn workspace to work
- }
現(xiàn)在,已經(jīng)創(chuàng)建了 package.json 文件,我們需要為我們的模塊app,common 和 server 創(chuàng)建文件夾。為了方便 yarn workspace 發(fā)現(xiàn)模塊并提高項目的可讀性(readability),我們將模塊嵌套在 packages 文件夾下:
- my-app/
- ├─ packages/ // 我們當(dāng)前和將來的所有模塊都將存在的地方
- │ ├─ app/
- │ ├─ common/
- │ ├─ server/
- ├─ package.json
我們的每個模塊都將充當(dāng)一個小型且獨立的項目,并且需要其自己的 package.json 來管理依賴項。要設(shè)置它們中的每一個,我們既可以使用 yarn init(在每個文件夾中),也可以手動創(chuàng)建文件(例如,通過 IDE)。
軟件包名稱使用的命名約定是在每個軟件包之前都使用 @my-app/* 作為前綴。這在 NPM 領(lǐng)域中稱為作用域。您不必像這樣給自己加上前綴,但以后會有所幫助。
一旦創(chuàng)建并初始化了所有三個軟件包,您將具有如下所示的相似之處。
app 包:
- {
- "name": "@my-app/app",
- "version": "0.1.0",
- "license": "UNLICENSED",
- "private": true
- }
common 包:
- {
- "name": "@my-app/common",
- "version": "0.1.0",
- "license": "UNLICENSED",
- "private": true
- }
server 包:
- {
- "name": "@my-app/server",
- "version": "0.1.0",
- "license": "UNLICENSED",
- "private": true
- }
最后,我們需要告訴 yarn 在哪里尋找模塊,所以回去編輯項目的 package.json 文件并添加以下 workspaces 屬性(如果您想了解更多有關(guān)詳細(xì)信息,請查看 Yarn 的 workspaces 文檔)。
- {
- "name": "my-app",
- "version": "1.0",
- "license": "UNLICENSED",
- "private": true,
- "workspaces": ["packages/*"] // 在這里添加
- }
您的最終文件夾結(jié)構(gòu)應(yīng)如下所示:
- my-app/
- ├─ packages/
- │ ├─ app/
- │ │ ├─ package.json
- │ ├─ common/
- │ │ ├─ package.json
- │ ├─ server/
- │ │ ├─ package.json
- ├─ package.json
現(xiàn)在,您已經(jīng)完成了項目的基礎(chǔ)設(shè)置。
TypeScript
現(xiàn)在,我們將第一個依賴項添加到我們的項目:TypeScript。TypeScript 是 JavaScript 的超集,可在構(gòu)建時實現(xiàn)類型檢查。
通過終端進(jìn)入項目的根目錄,運行 yarn add -D -W typescript。
- 參數(shù) -D 將 TypeScript 添加到 devDependencies,因為我們僅在開發(fā)和構(gòu)建期間使用它。
- 參數(shù) -W 允許在工作空間根目錄中安裝一個包,使其在 app、common 和 server 上全局可用。
您的 package.json 應(yīng)該如下所示:
- {
- "name": "my-app",
- "version": "1.0",
- "license": "UNLICENSED",
- "private": true,
- "workspaces": ["packages/*"],
- "devDependencies": {
- "typescript": "^4.2.3"
- }
- }
這還將創(chuàng)建一個 yarn.lock 文件(該文件確保在項目的整個生命周期中依賴項的預(yù)期版本保持不變)和一個 node_modules 文件夾,該文件夾保存依賴項的 binaries。
現(xiàn)在我們已經(jīng)安裝了 TypeScript,一個好習(xí)慣是告訴它如何運行。為此,我們將添加一個配置文件,該文件應(yīng)由您的 IDE 拾取(如果使用 VSCode,則會自動獲取)。
在項目的根目錄下創(chuàng)建一個 tsconfig.json 文件,并將以下內(nèi)容復(fù)制到其中:
- {
- "compilerOptions": {
- /* Basic */
- "target": "es2017",
- "module": "CommonJS",
- "lib": ["ESNext", "DOM"],
- /* Modules Resolution */
- "moduleResolution": "node",
- "esModuleInterop": true,
- /* Paths Resolution */
- "baseUrl": "./",
- "paths": {
- "@flipcards/*": ["packages/*"]
- },
- /* Advanced */
- "jsx": "react",
- "experimentalDecorators": true,
- "resolveJsonModule": true
- },
- "exclude": ["node_modules", "**/node_modules/*", "dist"]
- }
您可以輕松地搜索每個 compileoptions 屬性及其操作,但對我們最有用的是 paths 屬性。例如,這告訴 TypeScript 在 @my-app/server 或 @my-app/app 包中使用 @my-app/common 導(dǎo)入時在哪里查找代碼和 typings。
您當(dāng)前的項目結(jié)構(gòu)現(xiàn)在應(yīng)如下所示:
- my-app/
- ├─ node_modules/
- ├─ packages/
- │ ├─ app/
- │ │ ├─ package.json
- │ ├─ common/
- │ │ ├─ package.json
- │ ├─ server/
- │ │ ├─ package.json
- ├─ package.json
- ├─ tsconfig.json
- ├─ yarn.lock
添加第一個 script
Yarn workspace 允許我們通過 yarn workspace @my-app/* 命令模式訪問任何子包,但是每次鍵入完整的命令將變得非常多余。為此,我們可以創(chuàng)建一些 helper script 方法來提升開發(fā)體驗。打開項目根目錄下的 package.json,并向其添加以下 scripts 屬性。
- {
- "name": "my-app",
- "version": "1.0",
- "license": "UNLICENSED",
- "private": true,
- "workspaces": ["packages/*"],
- "devDependencies": {
- "typescript": "^4.2.3"
- },
- "scripts": {
- "app": "yarn workspace @my-app/app",
- "common": "yarn workspace @my-app/common",
- "server": "yarn workspace @my-app/server"
- }
- }
現(xiàn)在可以像在子包中一樣執(zhí)行任何命令。例如,您可以通過鍵入 yarn server add express 來添加一些新的依賴項。這將直接向 server 包添加新的依賴項。
在后續(xù)部分中,我們將開始構(gòu)建前端和后端應(yīng)用程序。
準(zhǔn)備 Git
如果計劃使用 Git 作為版本控制工具,強烈建議忽略生成的文件,例如二進(jìn)制文件或日志。
為此,請在項目的根目錄下創(chuàng)建一個名為 .gitignore 的新文件,并將以下內(nèi)容復(fù)制到其中。這將忽略本教程稍后將生成的一些文件,并避免提交大量不必要的數(shù)據(jù)。
- # Logs
- yarn-debug.log*
- yarn-error.log*
- # Binaries
- node_modules/
- # Builds
- dist/
- **/public/script.js
文件夾結(jié)構(gòu)應(yīng)如下所示:
- my-app/
- ├─ packages/
- ├─ .gitignore
- ├─ package.json
添加代碼
這部分將著重于將代碼添加到我們的 common、app 和 server 包中。
Common
我們將從 common 開始,因為此包將由 app 和 server 使用。它的目標(biāo)是提供共享的邏輯(shared logic)和變量(variables)。
文件
在本教程中,common 軟件包將非常簡單。首先,從添加新文件夾開始:
src/ 文件夾,包含包的代碼。
創(chuàng)建此文件夾后,將以下文件添加到其中:
src/index.ts
- export const APP_TITLE = 'my-app';
現(xiàn)在我們有一些要導(dǎo)出的代碼,我們想告訴 TypeScript 從其他包中導(dǎo)入它時在哪里尋找它。為此,我們將需要更新 package.json 文件:
package.json
- {
- "name": "@my-app/common",
- "version": "0.1.0",
- "license": "UNLICENSED",
- "private": true,
- "main": "./src/index.ts" // 添加這一行來為 TS 提供入口點
- }
我們現(xiàn)在已經(jīng)完成了 common 包!
結(jié)構(gòu)提醒:
- common/
- ├─ src/
- │ ├─ index.ts
- ├─ package.json
App
依賴項
該 app 包將需要以下依賴項:
- react
- react-dom
從項目的根目錄運行:
- yarn app add react react-dom
- yarn app add -D @types/react @types/react-dom (為 TypeScript 添加類型typings)
package.json
- {
- "name": "@my-app/app",
- "version": "0.1.0",
- "license": "UNLICENSED",
- "private": true,
- "dependencies": {
- "@my-app/common": "^0.1.0", // Notice that we've added this import manually
- "react": "^17.0.1",
- "react-dom": "^17.0.1"
- },
- "devDependencies": {
- "@types/react": "^17.0.3",
- "@types/react-dom": "^17.0.2"
- }
- }
文件
要創(chuàng)建我們的 React 應(yīng)用程序,我們將需要添加兩個新文件夾:
- 一個 public/ 文件夾,它將保存基本 HTML 頁面和我們的 assets。
- 一個 src/ 文件夾,其中包含我們應(yīng)用程序的代碼。
一旦創(chuàng)建了這兩個文件夾,我們就可以開始添加 HTML 文件,該文件將成為我們應(yīng)用程序的宿主。
public/index.html
- <!DOCTYPE html>
- <html>
- <head>
- <title>my-app</title>
- <meta name="description" content="Welcome on my application!" />
- </head>
- <body>
- <noscript>You need to enable JavaScript to run this app.</noscript>
- <!-- 這個 div 是我們將注入 React 應(yīng)用程序的地方 -->
- <div id="root"></div>
- <!-- 這是包含我們的應(yīng)用程序的腳本的路徑 -->
- <script src="script.js"></script>
- </body>
- </html>
src/index.tsx
- import * as React from 'react';
- import * as ReactDOM from 'react-dom';
- import { App } from './App';
- ReactDOM.render(<App />, document.getElementById('root'));
此代碼從我們的 HTML 文件掛接到 root div 中,并將 React組件樹 注入其中。
src/App.tsx
- import { APP_TITLE } from '@flipcards/common';
- import * as React from 'react';
- export function App(): React.ReactElement {
- const [count, setCount] = React.useState(0);
- return (
- <div>
- <h1>Welcome on {APP_TITLE}!</h1>
- <p>
- This is the main page of our application where you can confirm that it
- is dynamic by clicking the button below.
- </p>
- <p>Current count: {count}</p>
- <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
- </div>
- );
- }
這個簡單的 App 組件將呈現(xiàn)我們的應(yīng)用標(biāo)題和動態(tài)計數(shù)器。這將是我們的 React tree 的入口點。隨意添加您想要的任何代碼。
就是這樣!我們已經(jīng)完成了非?;镜?React 應(yīng)用程序。目前它并沒有太大的作用,但是我們總是可以稍后再使用它并添加更多功能。
結(jié)構(gòu)提醒:
- app/
- ├─ public/
- │ ├─ index.html
- ├─ src/
- │ ├─ App.tsx
- │ ├─ index.tsx
- ├─ package.json
Server
依賴項
server 軟件包將需要以下依賴項:
- cors
- express
從項目的根目錄運行:
- yarn server add cors express
- yarn server add -D @types/cors @types/express(為 TypeScript 添加類型typings)
package.json
- {
- "name": "@my-app/server",
- "version": "0.1.0",
- "license": "UNLICENSED",
- "private": true,
- "dependencies": {
- "@my-app/common": "^0.1.0", // 請注意,我們已手動添加了此導(dǎo)入
- "cors": "^2.8.5",
- "express": "^4.17.1"
- },
- "devDependencies": {
- "@types/cors": "^2.8.10",
- "@types/express": "^4.17.11"
- }
- }
文件
現(xiàn)在我們的 React 應(yīng)用程序已經(jīng)準(zhǔn)備就緒,我們需要的最后一部分是服務(wù)器來為其提供服務(wù)。首先為其創(chuàng)建以下文件夾:
- 一個 src/ 文件夾,包含我們服務(wù)器的代碼。
接下來,添加 server 的主文件:
src/index.ts
- import { APP_TITLE } from '@flipcards/common';
- import cors from 'cors';
- import express from 'express';
- import { join } from 'path';
- const PORT = 3000;
- const app = express();
- app.use(cors());
- // 服務(wù)來自 "public" 文件夾的靜態(tài)資源(例如:當(dāng)有圖像要顯示時)
- app.use(express.static(join(__dirname, '../../app/public')));
- // 為 HTML 頁面提供服務(wù)
- app.get('*', (req: any, res: any) => {
- res.sendFile(join(__dirname, '../../app/public', 'index.html'));
- });
- app.listen(PORT, () => {
- console.log(`${APP_TITLE}'s server listening at http://localhost:${PORT}`);
- });
這是一個非常基本的 Express 應(yīng)用程序,但如果除了單頁應(yīng)用程序之外我們沒有任何其他服務(wù),那么這就足夠了。
結(jié)構(gòu)提醒:
- server/
- ├─ src/
- │ ├─ index.ts
- ├─ package.json
構(gòu)建應(yīng)用
Bundlers(打包構(gòu)建捆綁器)
為了將 TypeScript 代碼轉(zhuǎn)換為可解釋的 JavaScript 代碼,并將所有外部庫打包到單個文件中,我們將使用打包工具。JS/TS 生態(tài)系統(tǒng)中有許多捆綁器,如 WebPack、Parcel 或 Rollup,但我們將選擇 esbuild。與其他捆綁器相比,esbuild 自帶了許多默認(rèn)加載的特性(TypeScript, React),并有巨大的性能提升(快了 100 倍)。如果你有興趣了解更多,請花時間閱讀作者的常見問題解答。
這些腳本將需要以下依賴項:
- esbuild 是我們的捆綁器
- ts-node 是 TypeScript 的 REPL,我們將使用它來執(zhí)行腳本
從項目的根目錄運行:yarn add -D -W esbuild ts-node。
package.json
- {
- "name": "my-app",
- "version": "1.0",
- "license": "UNLICENSED",
- "private": true,
- "workspaces": ["packages/*"],
- "devDependencies": {
- "esbuild": "^0.9.6",
- "ts-node": "^9.1.1",
- "typescript": "^4.2.3"
- },
- "scripts": {
- "app": "yarn workspace @my-app/app",
- "common": "yarn workspace @my-app/common",
- "server": "yarn workspace @my-app/server"
- }
- }
Build(編譯構(gòu)建)
現(xiàn)在,我們擁有構(gòu)建應(yīng)用程序所需的所有工具,因此讓我們創(chuàng)建第一個腳本。
首先在項目的根目錄下創(chuàng)建一個名為 scripts/ 的新文件夾。
我們的腳本將用 TypeScript 編寫,并從命令行使用 ts-node 執(zhí)行。盡管存在用于 esbuild 的 CLI,但是如果您要傳遞更復(fù)雜的參數(shù)或?qū)⒍鄠€工作流組合在一起,則可以通過 JS 或 TS 使用該庫,這更加方便。
在 scripts/ 文件夾中創(chuàng)建一個 build.ts 文件,并在下面添加代碼(我將通過注釋解釋代碼的作用):
scripts/build.ts
- import { build } from 'esbuild';
- /**
- * 在構(gòu)建期間傳遞的通用選項。
- */
- interface BuildOptions {
- env: 'production' | 'development';
- }
- /**
- * app 包的一個構(gòu)建器函數(shù)。
- */
- export async function buildApp(options: BuildOptions) {
- const { env } = options;
- await build({
- entryPoints: ['packages/app/src/index.tsx'], // 我們從這個入口點讀 React 應(yīng)用程序
- outfile: 'packages/app/public/script.js', // 我們在 public/ 文件夾中輸出一個文件(請記住,在 HTML 頁面中使用了 "script.js")
- define: {
- 'process.env.NODE_ENV': `"${env}"`, // 我們需要定義構(gòu)建應(yīng)用程序的 Node.js 環(huán)境
- },
- bundle: true,
- minify: env === 'production',
- sourcemap: env === 'development',
- });
- }
- /**
- * server 軟件包的構(gòu)建器功能。
- */
- export async function buildServer(options: BuildOptions) {
- const { env } = options;
- await build({
- entryPoints: ['packages/server/src/index.ts'],
- outfile: 'packages/server/dist/index.js',
- define: {
- 'process.env.NODE_ENV': `"${env}"`,
- },
- external: ['express'], // 有些庫必須標(biāo)記為外部庫
- platform: 'node', // 為 Node 構(gòu)建時,我們需要為其設(shè)置環(huán)境
- target: 'node14.15.5',
- bundle: true,
- minify: env === 'production',
- sourcemap: env === 'development',
- });
- }
- /**
- * 所有軟件包的構(gòu)建器功能。
- */
- async function buildAll() {
- await Promise.all([
- buildApp({
- env: 'production',
- }),
- buildServer({
- env: 'production',
- }),
- ]);
- }
- // 當(dāng)我們從終端使用 ts-node 運行腳本時,將執(zhí)行此方法
- buildAll();
該代碼很容易解釋,但是如果您覺得遺漏了部分,可以查看 esbuild 的 API文檔 以獲取完整的關(guān)鍵字列表。
我們的構(gòu)建腳本現(xiàn)已完成!我們需要做的最后一件事是在我們的 package.json 中添加一個新命令,以方便地運行構(gòu)建操作。
- {
- "name": "my-app",
- "version": "1.0",
- "license": "UNLICENSED",
- "private": true,
- "workspaces": ["packages/*"],
- "devDependencies": {
- "esbuild": "^0.9.6",
- "ts-node": "^9.1.1",
- "typescript": "^4.2.3"
- },
- "scripts": {
- "app": "yarn workspace @my-app/app",
- "common": "yarn workspace @my-app/common",
- "server": "yarn workspace @my-app/server",
- "build": "ts-node ./scripts/build.ts" // Add this line here
- }
- }
現(xiàn)在,您可以在每次對項目進(jìn)行更改時從項目的根文件夾運行 yarn build 來啟動構(gòu)建過程(如何添加hot-reloading,稍后討論)。
結(jié)構(gòu)提醒:
- my-app/
- ├─ packages/
- ├─ scripts/
- │ ├─ build.ts
- ├─ package.json
- ├─ tsconfig.json
Serve(提供服務(wù))
我們的應(yīng)用程序已經(jīng)構(gòu)建好并可以提供給全世界使用,我們只需要向 package.json 添加最后一個命令即可:
- {
- "name": "my-app",
- "version": "1.0",
- "license": "UNLICENSED",
- "private": true,
- "workspaces": ["packages/*"],
- "devDependencies": {
- "esbuild": "^0.9.6",
- "ts-node": "^9.1.1",
- "typescript": "^4.2.3"
- },
- "scripts": {
- "app": "yarn workspace @my-app/app",
- "common": "yarn workspace @my-app/common",
- "server": "yarn workspace @my-app/server",
- "build": "ts-node ./scripts/build.ts",
- "serve": "node ./packages/server/dist/index.js" // Add this line here
- }
- }
由于我們現(xiàn)在正在處理純 JavaScript,因此可以使用 node 二進(jìn)制文件啟動服務(wù)器。因此,繼續(xù)運行 yarn serve。
如果您查看控制臺,您將看到服務(wù)器正在成功偵聽。你也可以打開一個瀏覽器,導(dǎo)航到 http://localhost:3000 來顯示你的 React 應(yīng)用🎉!
如果你想在運行時改變端口,你可以用一個環(huán)境變量作為前綴來啟動 serve 命令: PORT=4000 yarn serve。
Docker 🐳
本節(jié)將假定您已經(jīng)熟悉容器的概念。
為了能夠根據(jù)我們的代碼創(chuàng)建鏡像,我們需要在計算機上安裝 Docker。要了解如何基于 OS 進(jìn)行安裝,請花一點時間查看官方文檔 。
Dockerfile
要生成 Docker 鏡像,第一步是在我們項目的根目錄下創(chuàng)建一個 Dockerfile(這些步驟可以完全通過 CLI 來完成,但是使用配置文件是定義構(gòu)建步驟的默認(rèn)方式)。
- FROM node:14.15.5-alpine
- WORKDIR /usr/src/app
- # 盡早安裝依賴項,以便如果我們應(yīng)用程序中的
- # 某些文件發(fā)生更改,Docker無需再次下載依賴項,
- # 而是從下一步(“ COPY ..”)開始。
- COPY ./package.json .
- COPY ./yarn.lock .
- COPY ./packages/app/package.json ./packages/app/
- COPY ./packages/common/package.json ./packages/common/
- COPY ./packages/server/package.json ./packages/server/
- RUN yarn
- # 復(fù)制我們應(yīng)用程序的所有文件(.gitignore 中指定的文件除外)
- COPY . .
- # 編譯 app
- RUN yarn build
- # Port
- EXPOSE 3000
- # Serve
- CMD [ "yarn", "serve" ]
我將嘗試盡可能詳細(xì)地說明這里發(fā)生的事情以及這些步驟的順序為什么很重要:
- FROM 告訴 Docker 將指定的基礎(chǔ)鏡像用于當(dāng)前上下文。在我們的案例中,我們希望有一個可以運行 Node.js 應(yīng)用程序的環(huán)境。
- WORKDIR 設(shè)置容器中的當(dāng)前工作目錄。
- COPY 將文件或文件夾從當(dāng)前本地目錄(項目的根目錄)復(fù)制到容器中的工作目錄。如您所見,在此步驟中,我們僅復(fù)制與依賴項相關(guān)的文件。這是因為 Docker 將每個構(gòu)建中的命令的每個結(jié)果緩存為一層。因為我們要優(yōu)化構(gòu)建時間和帶寬,所以我們只想在依賴項發(fā)生更改(通常比文件更改發(fā)生的頻率小)時重新安裝它們。
- RUN 在 shell 中執(zhí)行命令。
- EXPOSE 是用于容器的內(nèi)部端口(與我們的應(yīng)用程序的 PORT env 無關(guān))。這里的任何值都應(yīng)該很好,但是如果您想了解更多信息,可以查看官方文檔。
- CMD 的目的是提供執(zhí)行容器的默認(rèn)值。
如果您想了解更多有關(guān)這些關(guān)鍵字的信息,可以查看 Dockerfile參考。
添加 .dockerignore
使用 .dockerignore 文件不是強制性的,但強烈建議您使用以下文件:
- 確保您沒有將垃圾文件復(fù)制到容器中。
- 使 COPY 命令的使用更加容易。
如果您已經(jīng)熟悉它,它的工作原理就像 .gitignore 文件一樣。您可以將以下內(nèi)容復(fù)制到與 Dockerfile 相同級別的 .dockerignore 文件中,該文件將被自動提取。
- README.md
- # Git
- .gitignore
- # Logs
- yarn-debug.log
- yarn-error.log
- # Binaries
- node_modules
- */*/node_modules
- # Builds
- */*/build
- */*/dist
- */*/script.js
隨意添加任何您想忽略的文件,以減輕您的最終鏡像。
構(gòu)建 Docker Image
現(xiàn)在我們的應(yīng)用程序已經(jīng)為 Docker 準(zhǔn)備好了,我們需要一種從 Docker 生成實際鏡像的方法。為此,我們將向根 package.json添加一個新命令:
- {
- "name": "my-app",
- "version": "1.0.0",
- "license": "MIT",
- "private": true,
- "workspaces": ["packages/*"],
- "devDependencies": {
- "esbuild": "^0.9.6",
- "ts-node": "^9.1.1",
- "typescript": "^4.2.3"
- },
- "scripts": {
- "app": "yarn workspace @my-app/app",
- "common": "yarn workspace @my-app/common",
- "server": "yarn workspace @my-app/server",
- "build": "ts-node ./scripts/build.ts",
- "serve": "node ./packages/server/dist/index.js",
- "docker": "docker build . -t my-app" // Add this line
- }
- }
docker build . -t my-app 命令告訴 docker 使用當(dāng)前目錄(.)查找 Dockerfile,并將生成的鏡像(-t)命名為 my-app。
確保運行了 Docker 守護(hù)進(jìn)程,以便在終端中使用 docker 命令。
現(xiàn)在該命令已經(jīng)在我們項目的腳本中,您可以使用 yarn docker 運行它。
在運行該命令后,您應(yīng)該期望看到以下終端輸出:
- Sending build context to Docker daemon 76.16MB
- Step 1/12 : FROM node:14.15.5-alpine
- ---> c1babb15a629
- Step 2/12 : WORKDIR /usr/src/app
- ---> b593905aaca7
- Step 3/12 : COPY ./package.json .
- ---> e0046408059c
- Step 4/12 : COPY ./yarn.lock .
- ---> a91db028a6f9
- Step 5/12 : COPY ./packages/app/package.json ./packages/app/
- ---> 6430ae95a2f8
- Step 6/12 : COPY ./packages/common/package.json ./packages/common/
- ---> 75edad061864
- Step 7/12 : COPY ./packages/server/package.json ./packages/server/
- ---> e8afa17a7645
- Step 8/12 : RUN yarn
- ---> 2ca50e44a11a
- Step 9/12 : COPY . .
- ---> 0642049120cf
- Step 10/12 : RUN yarn build
- ---> Running in 15b224066078
- yarn run v1.22.5
- $ ts-node ./scripts/build.ts
- Done in 3.51s.
- Removing intermediate container 15b224066078
- ---> 9dce2d505c62
- Step 11/12 : EXPOSE 3000
- ---> Running in f363ce55486b
- Removing intermediate container f363ce55486b
- ---> 961cd1512fcf
- Step 12/12 : CMD [ "yarn", "serve" ]
- ---> Running in 7debd7a72538
- Removing intermediate container 7debd7a72538
- ---> df3884d6b3d6
- Successfully built df3884d6b3d6
- Successfully tagged my-app:latest
就是這樣!現(xiàn)在,我們的鏡像已創(chuàng)建并注冊在您的機器上,供 Docker 使用。如果您希望列出可用的 Docker 鏡像,則可以運行 docker image ls 命令:
- → docker image ls
- REPOSITORY TAG IMAGE ID CREATED SIZE
- my-app latest df3884d6b3d6 4 minutes ago 360MB
像這樣運行命令
通過命令行運行一個可用的 Docker 鏡像非常簡單:docker run -d -p 3000:3000 my-app
- -d 以分離模式運行容器(在后臺)。
- -p 設(shè)置暴露容器的端口(格式為[host port]:[container port])。因此,如果我們想將容器內(nèi)部的端口 3000(還記得 Dockerfile 中的 EXPOSE 參數(shù))暴露到容器外部的端口 8000,我們將把 8000:3000 傳遞給 -p 標(biāo)志。
你可以確認(rèn)你的容器正在運行 docker ps。這將列出所有正在運行的容器:
- → docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- 71465a89b58b my-app "docker-entrypoint.s…" 7 seconds ago Up 6 seconds 0.0.0.0:3000->3000/tcp determined_shockley
現(xiàn)在,打開瀏覽器并導(dǎo)航到以下URL http://localhost:3000,查看您正在運行的應(yīng)用程序🚀!