在之前的文章中,我們瞭解了Kubernetes中的基本概念,其硬件結構,不同的軟件組件(例如Pod、Deployment、StatefulSet、Services、Ingress和Persistent Volumes),並瞭解瞭如何在服務之間與外部進行通信。
在本文中,我們將瞭解到:
-
使用MongoDB數據庫創建NodeJS後端
-
編寫Dockerfile來容器化我們的應用程序
-
創建Kubernetes Deployment腳本以啓動Pod
-
創建Kubernetes Service腳本以定義容器與外界之間的通信接
-
部署Ingress Controller以請求路由
-
編寫Kubernetes Ingress腳本來定義與外界的通信。
由於我們的代碼可以從一個節點重定向到另一個節點(例如,一個節點沒有足夠的內存,所以工作將重新調度到另一個具有足夠內存的節點上),因此保存在節點上的數據容易丟失 ,意味着MongoDB 數據不穩定。在下一篇文章中,我們將討論數據持久性問題以及如何使用Kubernetes持久卷安全地存儲我們的持久數據。
在本文中,我們將使用NGINX作爲Ingress Controller和Azure容器鏡像倉庫來存儲我們的自定義Docker鏡像。文中編寫所有腳本都可以在Stupid Simple Kubernetes git repo中找到,如有需要可訪問鏈接獲取:
http://GitHub - CzakoZoltan08/StupidSimpleKubernetes-AKS
請注意:這些腳本不限定於某個平臺,因此您可以使用其他類型的雲提供程序或帶有K3s的本地集羣來實踐本教程。我之所以建議使用K3s,因爲它非常輕量,所有依賴項都被打包在一個小於100MB的單個二進制文件中。更重要的是,它是一種高可用的、經過CNCF認證的Kubernetes發行版,專門用於資源受限的環境中的生產工作負載。有關更多信息,您可以訪問官方文檔:
前期準備
在開始本教程之前,請確保您已安裝Docker。同時也要安裝kubectl。
Kubectl安裝鏈接:
https://kubernetes.io/docs/tasks/tools/#install-kubectl-on-windows
在本教程中使用的Kubectl命令可以在Kubectl cheat sheet(https://kubernetes.io/docs/reference/kubectl/cheatsheet/)中找到。
在本教程中,我們將使用Visual Studio Code,但這不是必要的,你也可以使用其他的編輯器。
創建可用於生產的微服務架構
將應用程序容器化
第一步,創建NodeJS後端的Docker鏡像。創建鏡像後,我們會將其推送到容器鏡像倉庫中,在該鏡像倉庫中可以訪問它,並且可以通過Kubernetes服務(在本例中爲Azure Kubernetes Service)拉取。
The Docker file for NodeJS:
FROM node:13.10.1
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
# Bundle app source
COPY . .
EXPOSE 3000
CMD [ "node", "index.js" ]
在第一行中,我們需要根據要創建後端服務的鏡像進行定義。在這種情況下,我們將使用Docker Hub中13.10.1版的官方節點鏡像。
在第3行中,我們創建一個目錄來將應用程序代碼保存在鏡像中。這將是您的應用程序的工作目錄。
該鏡像已經安裝了Node.js和NPM,因此下一步我們需要使用npm命令安裝您的應用程序依賴項。
請注意,要安裝必需的依賴項,我們不用複製整個目錄,而只需複製package.json,這使我們可以利用緩存的Docker層。
有關高效Dockerfile的更多信息,請訪問以下鏈接:
http://bitjudo.com/blog/2014/03/13/building-efficient-dockerfiles-node-dot-js/
在第9行中,我們將源代碼複製到工作目錄中,在第11行中,將其暴露在端口3000上(如果需要,您可以選擇另一個端口,但請確保同步更改Kubernetes Service腳本。)
最後,在第13行,我們定義了運行應用程序的命令(在Docker容器內部)。請注意,每個Dockerfile中應該只有一個CMD指令。如果包含多個,則只有最後一個纔會生效。
現在,我們已經定義了Dockerfile,我們將使用以下Docker命令從該Dockerfile中構建鏡像(使用Visual Studio Code的Terminal或在Windows上使用CMD):
docker build -t node-user-service:dev .
請注意Docker命令末尾的小圓點,這意味着我們正在從當前目錄構建鏡像,因此請確保您位於Dockerfile所在的同一文件夾中(在本例中,是repo的根文件夾)。
要在本地運行鏡像,我們可以使用以下命令:
docker run -p 3000:3000 node-user-service:dev
若要將此鏡像推送到我們的Azure容器鏡像倉庫,我們必須使用以下格式標記它
docker tag node-user-service:dev stupidsimplekubernetescontainerregistry.azurecr.io/node-user-service:dev
最後一步是使用以下Docker命令將其推送到我們的容器鏡像倉庫中:
docker push stupidsimplekubernetescontainerregistry.azurecr.io/node-user-service:dev
使用部署腳本創建Pod
NodeJs後端
接下來,定義Kubernetes Deployment腳本,該腳本將自動爲我們管理Pod。
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-user-service-deployment
spec:
selector:
matchLabels:
app: node-user-service-pod
replicas: 3
template:
metadata:
labels:
app: node-user-service-pod
spec:
containers:
- name: node-user-service-container
image: stupidsimplekubernetescontainerregistry.azurecr.io/node-user-service:dev
resources:
limits:
memory: "256Mi"
cpu: "500m"
imagePullPolicy: Always
ports:
- containerPort: 3000
Kubernetes API可以查詢和操作Kubernetes集羣中對象的狀態(例如Pod、命名空間、ConfigMap等)。如第一行中所指定,這個API的當前穩定版本爲1。
在每個Kubernetes .yml腳本中,我們必須使用kind關鍵字定義Kubernetes資源類型(Pods、Deployments、Service等)。因此,你可以看到,我們在第2行中定義了我們想使用Deployment資源。
Kubernetes允許您向資源中添加一些元數據。這樣一來,您就可以更輕鬆地識別、過濾和參考資源。
在第5行中,我們定義了該資源的規範。在第8行中,我們指定此Deployment應僅應用於標籤爲app:node-user-service-pod的資源中,在第9行中可以看出我們想要創建同一Pod的3個副本。
Template(從第10行開始)定義了Pod。在這裏,我們將標籤app:node-user-service-pod添加到每個Pod。這樣,Deployment將識別它們。在第16和17行中,我們定義了應在pod內部運行哪種Docker容器。如您在第17行中看到的那樣,我們將使用Azure容器鏡像倉庫中的Docker鏡像,該鏡像是在上一節中構建並推送的。
我們還可以爲Pod定義資源限制,避免Pod資源不足(當其中一個Pod使用所有資源而其他Pod無法使用它們時)。此外,當您爲Pod中的容器指定資源請求時,調度程序將使用此信息來決定將Pod放置在哪個節點上。當您爲容器指定資源限制時,kubelet會強制執行這些限制,從而不允許運行中的容器使用超出您設置的資源限制。kubelet還至少保留該系統資源的“請求”量。請注意,如果您沒有足夠的硬件資源(例如CPU或內存),則永遠無法調度pod。
最後一步是定義用於通信的端口。在本例中,我們使用端口3000。此端口號應與Dockerfile中暴露的端口號相同。
MongoDB
MongoDB數據庫的Deployment腳本非常相似。唯一的區別是我們必須指定卷掛載(數據會被保存到節點上的文件夾中)。
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-db-deployment
spec:
selector:
matchLabels:
app: user-db-app
replicas: 1
template:
metadata:
labels:
app: user-db-app
spec:
containers:
- name: mongo
image: mongo:3.6.4
command:
- mongod
- "--bind_ip_all"
- "--directoryperdb"
ports:
- containerPort: 27017
volumeMounts:
- name: data
mountPath: /data/db
resources:
limits:
memory: "256Mi"
cpu: "500m"
volumes:
- name: data
persistentVolumeClaim:
claimName: static-persistence-volume-claim-mongo
在本例中,我們直接從DockerHub使用了官方MongoDB鏡像(第17行)。在第24行中定義了卷安裝。在討論Kubernetes持久卷時,我們將在下一篇文章中解釋最後四行。
創建用於網絡訪問的服務
現在我們已經啓動了Pod,並開始定義容器之間以及與外部世界的通信。爲此,我們需要定義一個服務。Service與Deployment之間的關係是一對一的,因此對於每個Deployment,我們都應該有一個Service。Deployment還可以管理Pod的生命週期,並且負責監控它們,而Service負責啓用對一組Pod的網絡訪問。
apiVersion: v1
kind: Service
metadata:
name: node-user-service
spec:
type: ClusterIP
selector:
app: node-user-service-pod
ports:
- port: 3000
targetPort: 3000
這個.yml腳本的重要部分是selector,它定義瞭如何識別要從此Service引用的Pod(由Deployment創建)。在第8行中我們可以看到的,Selector 爲app:node-user-service-pod,因爲先前定義的Deployment中的Pod被標記爲這樣。另一個重要的事情是定義容器端口和服務端口之間的映射。在這種情況下,傳入請求將使用第10行中定義的3000端口,並將它們路由到第11行中定義的端口。
MongoDB pod的Kubernetes Service腳本非常相似。我們只需要更新Selector和端口。
apiVersion: v1
kind: Service
metadata:
name: user-db-service
spec:
clusterIP: None
selector:
app: user-db-app
ports:
- port: 27017
targetPort: 27017
配置外部流量
爲了與外界通信,我們需要定義一個Ingress Controller並使用Ingress Kubernetes資源指定路由規則。
要配置NGINX ingress controller,我們將使用可以以下鏈接中的腳本:
這是一個通用腳本,無需修改即可應用(詳細解釋NGINX Ingress Controller不在本文討論範圍之內)。
下一步是定義“負載均衡器”,該負載均衡器將用於使用公共IP地址路由外部流量(雲提供商提供負載均衡器)。
kind: Service
apiVersion: v1
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
externalTrafficPolicy: Local
type: LoadBalancer
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
現在我們已經啓動並運行了Ingress controller和負載均衡器,於是我們可以定義Ingress Kubernetes資源來指定路由規則。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: node-user-service-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: stupid-simple-kubernetes.eastus2.cloudapp.azure.com
http:
paths:
- backend:
serviceName: node-user-service
servicePort: 3000
path: /user-api(/|$)(.*)
# - backend:
# serviceName: nestjs-i-consultant-service
# servicePort: 3001
# path: /i-consultant-api(/|$)(.*)
在第6行中,我們定義了Ingress Controller類型(這是Kubernetes的預定義值;Kubernetes當前支持和維護GCE和nginx controller)。
在第7行中,我們定義了重寫目標規則,在第10行中,我們定義了主機名。
對於應該從外部訪問的每個服務,我們應該在路徑列表中添加一個條目(從第13行開始)。在此示例中,我們僅爲NodeJS用戶服務後端添加了一個條目,可通過端口3000對其進行訪問。/ user-api唯一標識我們的服務,因此任何以stupid-simple-kubernetes.eastus2.cloudapp azure.com/user-api開頭的請求將被路由到此NodeJS後端。如果要添加其他服務,則必須更新此腳本(請參見注釋掉的代碼)。
應用.yml腳本
要應用這些腳本,我們將使用kubectl。應用文件的kubectl命令如下:
kubectl apply -f
在本例中,如果你在Stupid Simple Kubernetes repo的根文件夾中,您需要執行以下命令:
kubectl apply -f .\manifest\kubernetes\deployment.yml
kubectl apply -f .\manifest\kubernetes\service.yml
kubectl apply -f .\manifest\kubernetes\ingress.yml
kubectl apply -f .\manifest\ingress-controller\nginx-ingress-controller-deployment.yml
kubectl apply -f .\manifest\ingress-controller\ngnix-load-balancer-setup.yml
應用這些腳本後,一切準備就緒,進而我們可以從外部調用後端(如使用Postman)。
總結
在本教程中,我們學習瞭如何在Kubernetes中創建各種資源,例如Pod、Deployment、Services、Ingress和Ingress Controller。我們使用MongoDB數據庫創建了一個NodeJS後端,並使用3個pod的副本容器化並部署了NodeJS和MongoDB容器。
在下一篇文章中,我們將瞭解持久保存數據的問題,並將介紹Kubernetes中的持久卷。
作者簡介
Czako Zoltan,一位經驗豐富的全棧開發人員,在前端,後端,DevOps,物聯網和人工智能等多個領域都擁有豐富的經驗。