.NET Core 使用 K8S ConfigMap的正確姿勢


背景

ASP.NET Core默認的配置文件定義在appsetings.jsonappsettings.{Environment}.json文件中。
這里面有一個問題就是,在使用容器部署時,每次修改配置文件都需要重新構建鏡像。當然你也可能會說,我的配置文件很穩定不需要修改,但你又如何確保配置文件中一些機密配置的安全問題呢?比如暴露了你的遠程數據庫的連接信息,哪天被員工不小心刪庫跑路了呢?
那接下來就來講解下如何在.NET Core 中正確使用ConfigMap。

ConfigMap/Secret

K8S中引入了ConfigMap/Secret來存儲配置數據,分別用於存儲非敏感信息和敏感信息。其目的在於將應用和配置解耦,以確保容器化應用程序的可移植性。

創建 ConfigMap

玩耍K8S,請先自行准備環境,Win10用戶可以參考我的上篇文章ASP.NET Core 借助 K8S 玩轉容器編排來准備環境。

ConfigMap的創建很簡單,一句命令就可以直接將appsettings.json文件轉換為ConfigMap。

PS:使用K8S一定要善用幫助命令,比如執行kubectl create configmap -h,你就可以了解到多種創建ConfigMap的方式。

> kubectl create configmap -h
Create a configmap based on a file, directory, or specified literal value.

A single configmap may package one or more key/value pairs.

When creating a configmap based on a file, the key will default to the basename of the file, and the value will default
to the file content.  If the basename is an invalid key, you may specify an alternate key.

When creating a configmap based on a directory, each file whose basename is a valid key in the directory will be
packaged into the configmap.  Any directory entries except regular files are ignored (e.g. subdirectories, symlinks,
devices, pipes, etc).

Aliases:
configmap, cm

Examples:
  # Create a new configmap named my-config based on folder bar
  kubectl create configmap my-config --from-file=path/to/bar

  # Create a new configmap named my-config with specified keys instead of file basenames on disk
  kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt

  # Create a new configmap named my-config with key1=config1 and key2=config2
  kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2

  # Create a new configmap named my-config from the key=value pairs in the file
  kubectl create configmap my-config --from-file=path/to/bar

  # Create a new configmap named my-config from an env file
  kubectl create configmap my-config --from-env-file=path/to/bar.env

其中我們可以看到可以通過指定--from-file來從指定文件創建。

kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt

Let's have a try!

1. 先行創建示例項目:dotnet new mvc -n K8S.NETCore.ConfigMap
2. 默認包含兩個配置文件appsettings.jsonappsettings.Development.json

3. 先來嘗試將appsettings.json轉換為ConfigMap:

> cd K8S.NETCore.ConfigMap
# 創建一個namespace,此步可選
> kubectl create namespace demo 
namespace "demo" created
# -n變量指定configmap創建到哪個namespace下
> kubectl create configmap appsettings --from-file=appsettings.json=./appsettings.json -n demo
configmap "appsettings" created
# 查看剛剛創建的configmap,-o指定輸出的格式
> kubectl get configmap appsettings -n demo -o yaml
apiVersion: v1
data:
  appsettings.json: "{\r\n  \"Logging\": {\r\n    \"LogLevel\": {\r\n      \"Default\":
    \"Warning\"\r\n    }\r\n  },\r\n  \"AllowedHosts\": \"*\"\r\n}\r\n"
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: appsettings
  namespace: demo

從上面的輸出結果來看,其中包含了\r\n換行符,顯然不是我們想要的結果。猜測是因為Windows和Linux系統換行符的差異導致的。先來插播下換行符的知識:

CR:Carriage Return,對應ASCII中轉義字符\r,表示回車
LF:Linefeed,對應ASCII中轉義字符\n,表示換行
CRLF:Carriage Return & Linefeed,\r\n,表示回車並換行
眾所周知,Windows操作系統采用兩個字符來進行換行,即CRLF;Unix/Linux/Mac OS X操作系統采用單個字符LF來進行換行;

所以解決方式就很簡單,將換行符切換為Linux系統的\n即可。操作方式很簡單:
對於VS Code 只需要按圖下所示操作即可,點擊右下角的CRLF,選擇LF即可。
vs code切換換行符

對於VS,如果VS打開json文件有下面的提示,直接切換就好。沒有,可以安裝Line Endings Unifier擴展來統一處理。
Vs inconsistent line endings

# 先刪除之前創建的configmap
> kubectl delete configmap appsettings -n demo
> kubectl create configmap appsettings --from-file=appsettings.json=./appsettings.json -n demo
configmap "appsettings" created
> kubectl get configmap appsettings -n demo -o yaml
apiVersion: v1
data:
  appsettings.json: |
    {
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: appsettings
  namespace: demo

現在ConfigMap的格式正常了。下面我們嘗試把appsettings.Development.json也合並到一個ConfigMap中。

> kubectl delete configmap appsettings -n demo
> kubectl create configmap appsettings --from-file=appsettings.json=./appsettings.json --from-file=appsettings.Development.json=./appsettings.Development.json -n demo
configmap "appsettings" created
> kubectl get configmap appsettings -n demo -o yaml
apiVersion: v1
data:
  appsettings.Development.json: |
    {
      "Logging": {
        "LogLevel": {
          "Default": "Debug",
          "System": "Information",
          "Microsoft": "Information"
        }
      }
    }
  appsettings.json: |
    {
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: appsettings
  namespace: demo

PS:

  1. 如果你的配置文件包含多余的空格,則生成的ConfigMap可能就會包含\n字符,就像這樣:
    appsettings.Development.json: "{\n \"Logging\": {\n \"LogLevel\": {\n \"Default\": \"Debug\",\n \"System\": \"Information\",\n \"Microsoft\": \"Information\"\n \ }\n }\n} \n"。解決辦法就是保存文件時記得格式化文件就好了,或者手動刪除多余空格。
  2. 創建ConfigMap的時候可以指定--dry-run參數進行試運行,避免直接創建到服務器。
  3. 從文件創建ConfigMap時,可以不指定Key,默認會以文件名為Key。
    kubectl create configmap appsettings --from-file=./appsettings.json --from-file=./appsettings.Development.json -n demo --dry-run -o yaml

至此,完成了appsetting到configmap的切換。

應用 ConfigMap

ConfigMap的應用很簡單,只需要將configmap掛載到容器內的獨立目錄即可。

先來看一下借助VS幫生成的Dockerfile。

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
WORKDIR /src
COPY ["K8S.NETCore.ConfigMap.csproj", ""]
RUN dotnet restore "./K8S.NETCore.ConfigMap.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "K8S.NETCore.ConfigMap.csproj" -c Release -o /app

FROM build AS publish
RUN dotnet publish "K8S.NETCore.ConfigMap.csproj" -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "K8S.NETCore.ConfigMap.dll"]

可以看出文件中定義的WORKDIR /app指定的工作目錄為/app,所以需要把ConfigMap掛載到/app目錄下。先執行docker build -t k8s.netcore.configmap:dev . 構建鏡像。

我們來新建一個configmap-deploy.yaml文件配置如下:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: k8s-configmap-demo
spec:
  selector:
    matchLabels:
      app: k8s-configmap-demo
  template:
    metadata:
      labels:
        app: k8s-configmap-demo
    spec:
      containers:
      - name: k8s-configmap-demo
        image: k8s.netcore.configmap:dev 
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80
        volumeMounts:
          - mountPath: /app/appsettings.json
            name: test
            readOnly: true
            subPath: appsettings.json
          - mountPath: /app/appsettings.Development.json
            name: test
            readOnly: true
            subPath: appsettings.Development.json         
      volumes:
      - configMap:
          defaultMode: 420
          name: appsettings
        name: test        

這里有必要解釋兩個參數:

  1. volumes:-configMap:指定引用哪個ConfigMap
  2. volumeMounts:用來指定將ConfigMap中的配置掛載到容器的哪個路徑
  3. subPath:用來指定引用ConfigMap的哪個配置節點。

創建Deployment之前先修改下ConfigMap的配置,以方便確認最終成功從ConfigMap掛載配置。將Logging:LogLevel:Default:節點的默認值改為Error。

> kubectl edit configmap appsettings -n demo
configmap/appsettings edited
> kubectl get cm appsettings -n demo -o yaml
apiVersion: v1
data:
  appsettings.Development.json: |-
    {
      "Logging": {
        "LogLevel": {
          "Default": "Error",
          "System": "Information",
          "Microsoft": "Information"
        }
      }
    }
  appsettings.json: |
    {
      "Logging": {
        "LogLevel": {
          "Default": "Error"
        }
      },
      "AllowedHosts": "*"
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2019-09-02T22:50:14Z"
  name: appsettings
  namespace: demo
  resourceVersion: "445219"
  selfLink: /api/v1/namespaces/demo/configmaps/appsettings
  uid: 07048d5a-cdd4-11e9-ad6d-00155d3a3103

修改完畢后,執行后續命令來創建Deployment,並驗證。

# 創建deployment
> kubectl apply -f .\k8s-deploy.yaml -n demo
deployment.extensions/k8s-configmap-demo created
# 獲取創建的pod
> kubectl get pods -n demo
NAME                                  READY   STATUS    RESTARTS   AGE
k8s-configmap-demo-7cfbdfff67-xdrcx   1/1     Running   0          12s
# 進入pod內部
> kubectl exec -it k8s-configmap-demo-7cfbdfff67-xdrcx /bin/bash -n demo
root@k8s-configmap-demo-7cfbdfff67-xdrcx:/app# cat appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Error"
    }
  },
  "AllowedHosts": "*"
}
root@k8s-configmap-demo-7cfbdfff67-xdrcx:/app# cat appsettings.Development.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Error",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

從以上輸出可以看出,默認的配置項已被ConfigMap的配置覆蓋。

熱更新

以Volume方式掛載的ConfigMap支持熱更新(大概需要10s左右)。但一種情況例外,就是指定subPath的情況下,更新ConfigMap,容器中掛載的ConfigMap是不會自動更新的。

 A container using a ConfigMap as a subPath volume will not receive ConfigMap updates.

對於這種情況,也很好處理,將ConfigMap掛載到/app目錄下一個單獨目錄就好,比如掛載到/app/config目錄,然后修改配置文件的加載路徑即可。

hostBuilder.ConfigureAppConfiguration((context, builder) =>
{
    builder.SetBasePath(Path.Join(AppContext.BaseDirectory, "config"))
        .AddJsonFile("appsettings.json")
        .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true);
});

最后

本文就.NET Core如何應用ConfigMap進行了詳細的介紹。其中最關鍵在於appsettings.json到ConfigMap的轉換,以及掛載目錄的指定。希望對你有所幫助。
而至於Secret的應用,原理相通了,關鍵在於Secret的生成,這里就交給你自己探索了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM