.NET Core 使用 K8S ConfigMap的正確姿勢(shì)

背景

ASP.NET Core默認(rèn)的配置文件定義在appsetings.jsonappsettings.{Environment}.json文件中胚泌。
這里面有一個(gè)問(wèn)題就是尖殃,在使用容器部署時(shí)拧簸,每次修改配置文件都需要重新構(gòu)建鏡像。當(dāng)然你也可能會(huì)說(shuō)弄捕,我的配置文件很穩(wěn)定不需要修改侯谁,但你又如何確保配置文件中一些機(jī)密配置的安全問(wèn)題呢消略?比如暴露了你的遠(yuǎn)程數(shù)據(jù)庫(kù)的連接信息堡称,哪天被員工不小心刪庫(kù)跑路了呢?
那接下來(lái)就來(lái)講解下如何在.NET Core 中正確使用ConfigMap艺演。

ConfigMap/Secret

K8S中引入了ConfigMap/Secret來(lái)存儲(chǔ)配置數(shù)據(jù)却紧,分別用于存儲(chǔ)非敏感信息和敏感信息桐臊。其目的在于將應(yīng)用和配置解耦,以確保容器化應(yīng)用程序的可移植性晓殊。

創(chuàng)建 ConfigMap

玩耍K8S断凶,請(qǐng)先自行準(zhǔn)備環(huán)境,Win10用戶可以參考我的上篇文章ASP.NET Core 借助 K8S 玩轉(zhuǎn)容器編排來(lái)準(zhǔn)備環(huán)境巫俺。

ConfigMap的創(chuàng)建很簡(jiǎn)單认烁,一句命令就可以直接將appsettings.json文件轉(zhuǎn)換為ConfigMap。

PS:使用K8S一定要善用幫助命令介汹,比如執(zhí)行kubectl create configmap -h却嗡,你就可以了解到多種創(chuàng)建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

其中我們可以看到可以通過(guò)指定--from-file來(lái)從指定文件創(chuàng)建嘹承。

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. 先行創(chuàng)建示例項(xiàng)目:dotnet new mvc -n K8S.NETCore.ConfigMap
2. 默認(rèn)包含兩個(gè)配置文件appsettings.jsonappsettings.Development.json

3. 先來(lái)嘗試將appsettings.json轉(zhuǎn)換為ConfigMap:

> cd K8S.NETCore.ConfigMap
# 創(chuàng)建一個(gè)namespace窗价,此步可選
> kubectl create namespace demo 
namespace "demo" created
# -n變量指定configmap創(chuàng)建到哪個(gè)namespace下
> kubectl create configmap appsettings --from-file=appsettings.json=./appsettings.json -n demo
configmap "appsettings" created
# 查看剛剛創(chuàng)建的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

從上面的輸出結(jié)果來(lái)看叹卷,其中包含了\r\n換行符撼港,顯然不是我們想要的結(jié)果。猜測(cè)是因?yàn)閃indows和Linux系統(tǒng)換行符的差異導(dǎo)致的骤竹。先來(lái)插播下?lián)Q行符的知識(shí):

CR:Carriage Return帝牡,對(duì)應(yīng)ASCII中轉(zhuǎn)義字符\r,表示回車
LF:Linefeed瘤载,對(duì)應(yīng)ASCII中轉(zhuǎn)義字符\n否灾,表示換行
CRLF:Carriage Return & Linefeed,\r\n鸣奔,表示回車并換行
眾所周知墨技,Windows操作系統(tǒng)采用兩個(gè)字符來(lái)進(jìn)行換行,即CRLF挎狸;Unix/Linux/Mac OS X操作系統(tǒng)采用單個(gè)字符LF來(lái)進(jìn)行換行扣汪;

所以解決方式就很簡(jiǎn)單,將換行符切換為L(zhǎng)inux系統(tǒng)的\n即可锨匆。操作方式很簡(jiǎn)單:
對(duì)于VS Code 只需要按圖下所示操作即可崭别,點(diǎn)擊右下角的CRLF,選擇LF即可恐锣。

vs code切換換行符

對(duì)于VS茅主,如果VS打開(kāi)json文件有下面的提示,直接切換就好土榴。沒(méi)有诀姚,可以安裝Line Endings Unifier擴(kuò)展來(lái)統(tǒng)一處理。

Vs inconsistent line endings

# 先刪除之前創(chuàng)建的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

現(xiàn)在ConfigMap的格式正常了玷禽。下面我們嘗試把appsettings.Development.json也合并到一個(gè)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可能就會(huì)包含\n字符,就像這樣:
    appsettings.Development.json: "{\n \"Logging\": {\n \"LogLevel\": {\n \"Default\": \"Debug\",\n \"System\": \"Information\",\n \"Microsoft\": \"Information\"\n \ }\n }\n} \n"糯笙。解決辦法就是保存文件時(shí)記得格式化文件就好了贬丛,或者手動(dòng)刪除多余空格。
  2. 創(chuàng)建ConfigMap的時(shí)候可以指定--dry-run參數(shù)進(jìn)行試運(yùn)行给涕,避免直接創(chuàng)建到服務(wù)器豺憔。
  3. 從文件創(chuàng)建ConfigMap時(shí),可以不指定Key够庙,默認(rèn)會(huì)以文件名為Key焕阿。
    kubectl create configmap appsettings --from-file=./appsettings.json --from-file=./appsettings.Development.json -n demo --dry-run -o yaml

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

應(yīng)用 ConfigMap

ConfigMap的應(yīng)用很簡(jiǎn)單暮屡,只需要將configmap掛載到容器內(nèi)的獨(dú)立目錄即可。

先來(lái)看一下借助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目錄下。先執(zhí)行docker build -t k8s.netcore.configmap:dev . 構(gòu)建鏡像钥飞。

我們來(lái)新建一個(gè)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        

這里有必要解釋兩個(gè)參數(shù):

  1. volumes:-configMap:指定引用哪個(gè)ConfigMap
  2. volumeMounts:用來(lái)指定將ConfigMap中的配置掛載到容器的哪個(gè)路徑
  3. subPath:用來(lái)指定引用ConfigMap的哪個(gè)配置節(jié)點(diǎn)莺掠。

創(chuàng)建Deployment之前先修改下ConfigMap的配置,以方便確認(rèn)最終成功從ConfigMap掛載配置读宙。將Logging:LogLevel:Default:節(jié)點(diǎn)的默認(rèn)值改為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

修改完畢后,執(zhí)行后續(xù)命令來(lái)創(chuàng)建Deployment结闸,并驗(yàn)證唇兑。

# 創(chuàng)建deployment
> kubectl apply -f .\k8s-deploy.yaml -n demo
deployment.extensions/k8s-configmap-demo created
# 獲取創(chuàng)建的pod
> kubectl get pods -n demo
NAME                                  READY   STATUS    RESTARTS   AGE
k8s-configmap-demo-7cfbdfff67-xdrcx   1/1     Running   0          12s
# 進(jìn)入pod內(nèi)部
> 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"
    }
  }
}

從以上輸出可以看出,默認(rèn)的配置項(xiàng)已被ConfigMap的配置覆蓋桦锄。

熱更新

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

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

對(duì)于這種情況图甜,也很好處理碍粥,將ConfigMap掛載到/app目錄下一個(gè)單獨(dú)目錄就好,比如掛載到/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如何應(yīng)用ConfigMap進(jìn)行了詳細(xì)的介紹。其中最關(guān)鍵在于appsettings.json到ConfigMap的轉(zhuǎn)換,以及掛載目錄的指定低斋。希望對(duì)你有所幫助。
而至于Secret的應(yīng)用匪凡,原理相通了膊畴,關(guān)鍵在于Secret的生成,這里就交給你自己探索了病游。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唇跨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衬衬,更是在濱河造成了極大的恐慌买猖,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滋尉,死亡現(xiàn)場(chǎng)離奇詭異玉控,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)狮惜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)高诺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人碾篡,你說(shuō)我怎么就攤上這事虱而。” “怎么了开泽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵牡拇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我穆律,道長(zhǎng)惠呼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任峦耘,我火速辦了婚禮罢杉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贡歧。我一直安慰自己滩租,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布利朵。 她就那樣靜靜地躺著律想,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绍弟。 梳的紋絲不亂的頭發(fā)上技即,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音樟遣,去河邊找鬼而叼。 笑死身笤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葵陵。 我是一名探鬼主播液荸,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脱篙!你這毒婦竟也來(lái)了娇钱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绊困,失蹤者是張志新(化名)和其女友劉穎文搂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體秤朗,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煤蹭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了取视。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疯兼。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贫途,靈堂內(nèi)的尸體忽然破棺而出吧彪,到底是詐尸還是另有隱情,我是刑警寧澤丢早,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布姨裸,位于F島的核電站,受9級(jí)特大地震影響怨酝,放射性物質(zhì)發(fā)生泄漏傀缩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一农猬、第九天 我趴在偏房一處隱蔽的房頂上張望赡艰。 院中可真熱鬧,春花似錦斤葱、人聲如沸慷垮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)料身。三九已至,卻和暖如春衩茸,著一層夾襖步出監(jiān)牢的瞬間芹血,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幔烛,地道東北人啃擦。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像饿悬,于是被迫代替她去往敵國(guó)和親令蛉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容