我們部署在 kubernetes 集羣上的每個 ASP.NET Core 應用的 appsettings.Production.json 都保存在各個應用的 ConfigMap 中,這些 appsettings.Production.json 中有些重複的配置,如果要修改這些配置,需要到各個應用中一個一個修改,很是麻煩。
針對這個麻煩,我們想到一個解決方法,將這些重複的配置放到一個公用的 ConfigMap 中(appsettings.shared.json),但是要到各個應用的 deployment 配置文件中通過 volumeMounts 一個一個 mount 這個 ConfigMap 也很是麻煩。
針對新的麻煩,我們又想到一個解決方法,在代碼中直接讀取 ConfigMap,選用的 Kubernetes C# 客戶端是 KubernetesClient,實現方法如下。
安裝 nuget 包
dotnet add package KubernetesClient
在 Program 中添加讀取 ConfigMap 的方法實現
private static byte[] ReadK8sConfigMap()
{
var config = KubernetesClientConfiguration.InClusterConfig();
IKubernetes client = new Kubernetes(config);
var cm = client.ReadNamespacedConfigMap(name: "appsettings.shared.json", "production");
return System.Text.Encoding.UTF8.GetBytes(cm.Data["appsettings.shared.json"]);
}
將從 ConfigMap 讀取到的數據以 json 的方式加載到 IConfiguration 中
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true)
.AddJsonStream(new MemoryStream(ReadK8sConfigMap()))
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
config.AddEnvironmentVariables();
});
webBuilder.UseStartup<Startup>();
});
改進後的代碼
namespace Microsoft.Extensions.Configuration
{
public static class ConfigMapExtensions
{
public static IConfigurationBuilder AddJsonKubeConfigMap(this IConfigurationBuilder builder, string name, string @namespace, string key)
{
var config = KubernetesClientConfiguration.IsInCluster() ?
KubernetesClientConfiguration.InClusterConfig() :
KubernetesClientConfiguration.BuildDefaultConfig();
IKubernetes client = new Kubernetes(config);
var json = client.ReadNamespacedConfigMap(name, @namespace)?.Data[key];
if (!string.IsNullOrEmpty(json))
{
builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json)));
}
return builder;
}
}
}
在 pod 中讀取 ConfigMap 報錯
Unhandled exception. Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'Forbidden'
at k8s.Kubernetes.ReadNamespacedConfigMapWithHttpMessagesAsync(String name, String namespaceParameter, Nullable`1 exact, Nullable`1 export, String pretty, Dictionary`2 customHeaders, CancellationToken cancellationToken)
at k8s.KubernetesExtensions.ReadNamespacedConfigMapAsync(IKubernetes operations, String name, String namespaceParameter, Nullable`1 exact, Nullable`1 export, String pretty, CancellationToken cancellationToken)
at k8s.KubernetesExtensions.ReadNamespacedConfigMap(IKubernetes operations, String name, String namespaceParameter, Nullable`1 exact, Nullable`1 export, String pretty)
at Microsoft.Extensions.Configuration.ConfigMapExtensions.AddJsonKubeConfigMap(IConfigurationBuilder builder, String name, String namespace, String key)
在 pod 中用 curl 命令進行請求
curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/production/configmaps/appsettings.shared.json
響應如下
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "configmaps \"appsettings.shared.json\" is forbidden: User \"system:serviceaccount:production:default\" cannot get resource \"configmaps\" in API group \"\" in the namespace \"production\"",
"reason": "Forbidden",
"details": {
"name": "appsettings.shared.json",
"kind": "configmaps"
},
"code": 403
}
原來是 pod 的默認 service account system:serviceaccount:production:default
沒有權限請求 api。
通過添加 Role 與 RoleBinding 解決了這個問題,詳見博問:k8s 中如何授權 pod 內可以訪問指定的 ConfigMap
後來將 AddJsonKubeConfigMap 擴展方法放到了 github 倉庫 https://github.com/cnblogs/KubernetesClient.Extensions