1. 程式人生 > >ASP.NET Core 藉助 Helm 部署應用至K8S

ASP.NET Core 藉助 Helm 部署應用至K8S

前言

玩K8S也有一段時間了,藉助雲服務提供商的K8S控制檯,已經可以很方便的快速部署應用至K8S。通過簡單的點選,可以一次性幫忙建立K8S 物件:Deployment、Service、Ingress、ConfigMap等。但是當服務的規模上來後,這種方式就有點捉襟見肘。尤其是需要同時更新多個關聯服務時,就需要一個一個的去更改,就有點不太方便。為了解決這個問題,最近上手實操了一下Helm,發現生產力大大提升。

Helm 簡介

Helm 是一個為K8S打造的包管理器。通過Helm可以方便管理Kubernetes應用程式。Helm主要有兩大核心概念:Charts、Release。

  1. Chart:用來定義,安裝和升級K8S 應用。亦可分享及版本化控制。
  2. Release:類似Image之於Container,Release是Chart的執行例項。

目前Helm最新的版本為V3.1,較之前版本,在整體架構上移除服務端Tiller。
對於Windows系統而言可藉助Choco快速安裝:choco install kubernetes-helm,通過執行helm version確認是否安裝成功。
version.BuildInfo{Version:"v3.1.0", GitCommit:"b29d20baf09943e134c2fa5e1e1cab3bf93315fa", GitTreeState:"clean", GoVersion:"go1.13.7"}

在繼續往前,請確保已具備基礎的K8S基礎知識,並且確保本機已安裝Docker和K8S。安裝教程和K8S簡單入門可參考我的這篇文章ASP.NET Core 藉助 K8S 玩轉容器編排。

對於第一次接觸Helm .NETer 來說我們可以通過VS 2019來快速體驗一下。請確保已安裝Visual Studio Tools for Kubernetes。

建立 Chart (helm create)

開啟VS 建立專案,選擇Container Application for Kubernetes,建立一個空的ASP.NET Core Web 專案。

建立後,專案結構如下圖所示,與平時之間建立的Web專案而言,主要是多了一個charts

目錄、Dockerfile和一個azds.yaml

除了建立專案時通過選擇Container Application for Kubernetes型別外,我們也可以通過其他方式建立。我們這裡手動刪除charts目錄、Dockerfile和一個azds.yaml。然後如下圖步驟即可重新生成Helm Chart。

當然也可以通過helm create建立。

安裝 Chart (helm install)

在展開之前,先來簡要介紹Chart目錄:

k8shelmdemo/                         # Chart 目錄
├── charts                           # 這個 charts 依賴的其他 charts,始終被安裝
├── Chart.yaml                       # 描述這個 Chart 的相關資訊、包括名字、描述資訊、版本等
├── templates                        # 模板目錄
│   ├── deployment.yaml              # deployment 控制器的 Go 模板檔案
│   ├── _helpers.tpl                 # 以 _ 開頭的檔案不會部署到 k8s 上,可用於定製通用資訊
│   ├── ingress.yaml                 # ingress 的模板檔案
│   ├── NOTES.txt                    # Chart 幫助文字,安裝後會顯示給使用者,例如:如何使用、列出預設值
│   ├── service.yaml                 # service 的 Go 模板檔案
│   ├── secrets.yaml                 # secrets 的 Go 模板檔案
│   └── tests
│       └── test-connection.yaml
└── values.yaml                      # 模板的值檔案,這些值會在安裝時應用到 GO 模板生成部署檔案

簡單來說,Helm Chart 定義常用的K8S 物件模板,通過values.yaml來填充模板。那我們就來看看填充後的輸出結果是怎樣的。開啟命令提示符,進入到Chart目錄,通過helm template --debug [release name] [chart dir]命令就可以測試Chart(亦可通過helm --dry-run --debug [release name] [chart dir]測試)。可以看到輸出了Service和Deployment。這裡你可能就納悶了,不是定義了4個K8S物件模板嗎,為什麼就是輸出2個yaml檔案呢。這裡先按住不表。

PS \K8S.Helm.Demo\charts> helm template --debug k8s-helm-demo .\k8shelmdemo\
install.go:158: [debug] Original chart version: ""
install.go:175: [debug] CHART PATH: \K8S.Helm.Demo\charts\k8shelmdemo
---
# Source: k8shelmdemo/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: k8shelmdemo
  labels:
    app: k8shelmdemo
    chart: k8shelmdemo-0.1.0
    release: k8s-helm-demo
    heritage: Helm
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: k8shelmdemo
    release: k8s-helm-demo
---
# Source: k8shelmdemo/templates/deployment.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: k8shelmdemo
  labels:
    app: k8shelmdemo
    chart: k8shelmdemo-0.1.0
    draft: draft-app
    release: k8s-helm-demo
    heritage: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app: k8shelmdemo
      release: k8s-helm-demo
  template:
    metadata:
      labels:
        app: k8shelmdemo
        draft: draft-app
        release: k8s-helm-demo
      annotations:
        buildID: ""
    spec:
      containers:
        - name: k8shelmdemo
          image: "k8shelmdemo:stable"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          env:
          resources:
            {}

這裡目前需要注意一點就是上面輸出的Deployment中使用的映象為image: "k8shelmdemo:stable"。所以在安裝該Chart之前,我們需要構造映象。映象構造很簡單,在Vs中右鍵Dockerfile選擇構建就好(請確保Docker已啟動)。

觀察VS輸出視窗,會有以下輸出:
1>------ Rebuild All started: Project: K8S.Helm.Demo, Configuration: Debug Any CPU ------ 1>K8S.Helm.Demo -> D:\Programming\Coding\dotnet\K8S.Ocelot.Demo\src\K8S.Helm.Demo\bin\Debug\netcoreapp3.1\K8S.Helm.Demo.dll 1>Docker version 19.03.1, build 74b1e89 1>docker build -f "d:\programming\coding\dotnet\k8s.ocelot.demo\src\k8s.helm.demo\dockerfile" --force-rm -t k8shelmdemo --label "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=K8S.Helm.Demo" "d:\programming\coding\dotnet\k8s.ocelot.demo\src" 1>Sending build context to Docker daemon 6.192MB 1> 1>Step 1/18 : FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base 1>Step 2/18 : WORKDIR /app 1> ---> e28362768eed 1> ---> Using cache 1> ---> 6457841dbdf1 1>Step 3/18 : EXPOSE 80 1> ---> Using cache 1> ---> bb9dc51530fe 1>Step 4/18 : FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build 1> ---> 0a4c7c13f9d6 1>Step 5/18 : WORKDIR /src 1> ---> Using cache 1> ---> ddc36742b71c 1>Step 6/18 : COPY ["K8S.Helm.Demo/K8S.Helm.Demo.csproj", "K8S.Helm.Demo/"] 1> ---> d18831c83acd 1>Step 7/18 : RUN dotnet restore "K8S.Helm.Demo/K8S.Helm.Demo.csproj" 1> ---> Running in 64d3624eb9c0 1> Restore completed in 7.19 sec for /src/K8S.Helm.Demo/K8S.Helm.Demo.csproj. 1>Removing intermediate container 64d3624eb9c0 1> ---> ba3624442138 1>Step 8/18 : COPY . . 1> ---> 43c4f6c4769f 1>Step 9/18 : WORKDIR "/src/K8S.Helm.Demo" 1> ---> Running in 145e155d3a5d 1>Removing intermediate container 145e155d3a5d 1>Step 10/18 : RUN dotnet build "K8S.Helm.Demo.csproj" -c Release -o /app/build 1> ---> e547e8caed4a 1> ---> Running in 146df981f291 1>Microsoft (R) Build Engine version 16.4.0+e901037fe for .NET Core 1>Copyright (C) Microsoft Corporation. All rights reserved. 1> 1> Restore completed in 27.08 ms for /src/K8S.Helm.Demo/K8S.Helm.Demo.csproj. 1> K8S.Helm.Demo -> /app/build/K8S.Helm.Demo.dll 1>Build succeeded. 1> 0 Warning(s) 1> 1>Time Elapsed 00:00:02.09 1> 0 Error(s) 1>Removing intermediate container 146df981f291 1>Step 11/18 : FROM build AS publish 1> ---> 94f07ad82c1c 1> ---> 94f07ad82c1c 1>Step 12/18 : RUN dotnet publish "K8S.Helm.Demo.csproj" -c Release -o /app/publish 1> ---> Running in 60d63984fe28 1>Microsoft (R) Build Engine version 16.4.0+e901037fe for .NET Core 1> 1>Copyright (C) Microsoft Corporation. All rights reserved. 1> Restore completed in 26.94 ms for /src/K8S.Helm.Demo/K8S.Helm.Demo.csproj. 1> K8S.Helm.Demo -> /src/K8S.Helm.Demo/bin/Release/netcoreapp3.1/K8S.Helm.Demo.dll 1> K8S.Helm.Demo -> /app/publish/ 1>Removing intermediate container 60d63984fe28 1>Step 13/18 : FROM base AS final 1> ---> 85d893dc4a81 1> ---> bb9dc51530fe 1>Step 14/18 : WORKDIR /app 1> ---> Running in 69b7fd56c371 1>Removing intermediate container 69b7fd56c371 1> ---> 219310025c54 1>Step 15/18 : COPY --from=publish /app/publish . 1>Step 16/18 : ENTRYPOINT ["dotnet", "K8S.Helm.Demo.dll"] 1> ---> 6e63a4449dbb 1> ---> Running in a43a0516c6dc 1>Removing intermediate container a43a0516c6dc 1> ---> 36f422c923fd 1>Step 17/18 : LABEL com.microsoft.created-by=visual-studio 1> ---> Running in 88d100227ee1 1>Removing intermediate container 88d100227ee1 1> ---> 4a71f8e5e761 1>Step 18/18 : LABEL com.microsoft.visual-studio.project-name=K8S.Helm.Demo 1> ---> Running in f609881010ad 1>Removing intermediate container f609881010ad 1> ---> 3301427c0fb8 1>Successfully built 3301427c0fb8 1>Successfully tagged k8shelmdemo:latest 1>SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories. ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
最終構建的映象名稱為k8shelmdemo:latest。與我們上面Chart中使用的映象k8shelmdemo:stable不一致。如果現在安裝Chart,那麼應用將無法找對應的映象無法啟動。那怎麼辦呢。檢視deployment.yaml模板檔案,我們發現其映象引用定義為image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"。檢視values.yaml發現如下定義:

image:
  repository: k8shelmdemo
  tag: stable
  pullPolicy: IfNotPresent

所以改法就很簡單了,將values.yaml的tag更改為latest即可。更改後在執行helm template --debug [release name] [chart dir] 驗證下。接下來通過helm install來安裝Chart。在執行之前,我們先通過kubectl create ns helmdemo建立一個獨立的名稱空間以方便確認和清理。再執行helm install k8shelmdemo .\k8shelmdemo\ -n helmdemo 安裝(-n 指定我們剛剛建立的名稱空間)。具體命令如下:

PS \K8S.Helm.Demo\charts> kubectl create ns helmdemo
namespace/helmdemo created
PS \K8S.Helm.Demo\charts> helm install k8shelmdemo .\k8shelmdemo\ -n helmdemo
NAME: k8shelmdemo
LAST DEPLOYED: Sun Feb 23 17:33:54 2020
NAMESPACE: helmdemo
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace helmdemo -l "app=k8shelmdemo,release=k8shelmdemo" -o jsonpath="{.items[0].metadat
a.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80
PS \K8S.Helm.Demo\charts>

我們看到同時輸出了模板資料夾下的NOTES模板,這時說明helm已經安裝了。那怎樣確保是否安裝成功了呢。繼續執行以下命令:

PS \K8S.Helm.Demo\charts> helm list -n helmdemo #檢視指定名稱空間下已安裝的chart,也就是執行中的Release
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
k8shelmdemo     helmdemo        1               2020-02-23 17:33:54.2196357 +0800 CST   deployed        k8shelmdemo-0.1.0       1.0
PS \K8S.Helm.Demo\charts> kubectl get all -n helmdemo # 檢視指定名稱空間下K8S下所有的物件
NAME                               READY   STATUS    RESTARTS   AGE
pod/k8shelmdemo-689bd54677-fcfx7   1/1     Running   0          5m8s

NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/k8shelmdemo   ClusterIP   10.97.204.227   <none>        80/TCP    5m8s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/k8shelmdemo   1/1     1            1           5m8s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/k8shelmdemo-689bd54677   1         1         1       5m8s

至此我們可以確定Chart成功安裝。那如何訪問剛剛部署的Web應用呢,安裝剛剛Chart的安裝Notes,通過kubectl port-forward配置埠轉發,來完成。從上面的輸出我們已經知道對應的Pod Name 為k8shelmdemo-689bd54677-fcfx7。執行kubectl port-forward k8shelmdemo-689bd54677-fcfx7 8090:80

PS \K8S.Helm.Demo\charts> kubectl port-forward k8shelmdemo-689bd54677-fcfx7 8090:80 -n helmdemo
Forwarding from 127.0.0.1:8090 -> 80
Forwarding from [::1]:8090 -> 80

換一個命令列執行curl -l http://localhost:8090 會看到輸出Hello world

更新 Chart (helm upgrade)

假設我現在想將輸出更新為Hello Helm,我們來看下怎麼辦。對於當前應用來說,更新輸出,只需要更改Startup的Hello World改為Hello Helm 就好,然後重新構建映象。
這裡思考一下,因為重新構建的映象Tag還是k8shelmdemo:latest,所以無需對當前Helm Chart做任何改動,所以也就無需更新。那我們該如何更新應用呢。如果有K8S基礎的同學應該很快就能想到,直接刪除Pod即可。因為從上面kubectl get all -n helmdemo的輸出中,我們可以看到Chart為我們的應用自動建立了一個ReplicaSet例項,ReplicaSet主要用於確保應用始終保持指定數量的例項執行。所以如果刪除一個Pod,K8S會按照ReplicaSet的定義,重新啟用一個新的Pod。再重新執行kubectl port-forward,會發現應用已更新。

PS: 因為當前demo使用的是本地映象,所以刪除Pod後,重新執行的pod能夠輸出更新後的結果。如果映象來源並非本地,那麼對於同一個映象tag來說,就要考慮更新映象拉取策略。

PS \K8S.Helm.Demo\charts> kubectl delete pod/k8shelmdemo-689bd54677-fcfx7 -n helmdemo # 刪除pod
PS \K8S.Helm.Demo\charts> kubectl get all -n helmdemo
NAME                               READY   STATUS    RESTARTS   AGE
pod/k8shelmdemo-689bd54677-mrr64   1/1     Running   0          29s # 新的Pod建立成功

NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/k8shelmdemo   ClusterIP   10.97.204.227   <none>        80/TCP    33m

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/k8shelmdemo   1/1     1            1           33m

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/k8shelmdemo-689bd54677   1         1         1       33m

假設現在需要服務部署後直接通過指定埠訪問,無需通過kubeclt port-forward進行埠轉發。那麼我們就需要對Chart進行相應更新,將生成的Service的型別由預設的Cluster更改為LoadBalancer模式。更新values.yaml中的service節點如下:

service:
  type: LoadBalancer
  port: 8093

緊接著通過執行helm upgrade [release name] [chart dir]命令更新應用,如下。

PS \K8S.Helm.Demo\charts> helm upgrade k8shelmdemo .\k8shelmdemo\ -n helmdemo
Release "k8shelmdemo" has been upgraded. Happy Helming!
NAME: k8shelmdemo
LAST DEPLOYED: Sun Feb 23 18:39:30 2020
NAMESPACE: helmdemo
STATUS: deployed
REVISION: 3
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running 'kubectl get svc -w k8shelmdemo'
  export SERVICE_IP=$(kubectl get svc --namespace helmdemo k8shelmdemo -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:8093
PS \K8S.Helm.Demo\charts> kubectl get all -n helmdemo
NAME                               READY   STATUS    RESTARTS   AGE
pod/k8shelmdemo-689bd54677-pj5pd   1/1     Running   0          22m

NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/k8shelmdemo   LoadBalancer   10.97.204.227   localhost     8093:30035/TCP   65m

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/k8shelmdemo   1/1     1            1           65m

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/k8shelmdemo-689bd54677   1         1         1       65m
PS \K8S.Ocelot.Demo\src\K8S.Helm.Demo\charts> curl -l localhost:8093
Hello Helm!
PS \K8S.Helm.Demo\charts> helm ls -n helmdemo # 版本已更新
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
k8shelmdemo     helmdemo        2                2020-02-23 18:39:30.2059395 +0800 CST   deployed        k8shelmdemo-0.1.0       1.0

刪除 Chart(helm delete)

演示完畢,那如何刪除已釋出的Release呢,執行helm delete k8shelmdemo -n helmdemo

PS \K8S.Helm.Demo\charts> helm delete k8shelmdemo -n helmdemo
release "k8shelmdemo" uninstalled
PS \K8S.Helm.Demo\charts> helm ls -n helmdemo
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION
PS \K8S.Helm.Demo\charts> kubectl get all -n helmdemo
No resources found.

執行後,可以發現建立的K8S資源也已清理乾淨。

最後

以上僅是對 ASP.NET Core 如何使用 Helm 部署到K8S的簡單介紹,希望對入門的你有所幫助!對於Helm複雜的應用,主要在於模板填充的複雜應用,大家可以結合官方Helm文件以及eShopOnContainer中Helm示例進行學習。

參考資料:
Get started with Visual Studio Kubernetes Tools
玩K8S不得不會的HELM