背景
ASP.NET Core默認(rèn)的配置文件定義在appsetings.json
和appsettings.{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.json
和appsettings.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
即可恐锣。
對(duì)于VS茅主,如果VS打開(kāi)json文件有下面的提示,直接切換就好土榴。沒(méi)有诀姚,可以安裝Line Endings Unifier擴(kuò)展來(lái)統(tǒng)一處理。
# 先刪除之前創(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:
- 如果你的配置文件包含多余的空格呀打,則生成的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)刪除多余空格。- 創(chuàng)建ConfigMap的時(shí)候可以指定
--dry-run
參數(shù)進(jìn)行試運(yùn)行给涕,避免直接創(chuàng)建到服務(wù)器豺憔。- 從文件創(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ù):
- volumes:-configMap:指定引用哪個(gè)ConfigMap
- volumeMounts:用來(lái)指定將ConfigMap中的配置掛載到容器的哪個(gè)路徑
- 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的生成,這里就交給你自己探索了病游。