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

背景

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

ConfigMap/Secret

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

建立 ConfigMap

玩耍K8S,請先自行準備環境,Win10用戶能夠參考個人上篇文章ASP.NET Core 藉助 K8S 玩轉容器編排來準備環境。json

ConfigMap的建立很簡單,一句命令就能夠直接將appsettings.json文件轉換爲ConfigMap。api

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來從指定文件建立。bash

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 mvc

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

> 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系統換行符的差別致使的。先來插播下換行符的知識:ui

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的生成,這裏就交給你本身探索了。

相關文章
相關標籤/搜索