{"id":19990862,"url":"https://github.com/fbeltrao/ConfigMapFileProvider","last_synced_at":"2025-05-04T10:30:48.103Z","repository":{"id":47888268,"uuid":"208475182","full_name":"fbeltrao/ConfigMapFileProvider","owner":"fbeltrao","description":".NET Core configuration based on Kubernetes config maps with auto reload support","archived":false,"fork":false,"pushed_at":"2021-08-12T03:09:27.000Z","size":69,"stargazers_count":54,"open_issues_count":1,"forks_count":17,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-09T23:14:51.175Z","etag":null,"topics":["configuration-files","csharp-code","dotnet-core","kubernetes","logging"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fbeltrao.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-14T17:12:48.000Z","updated_at":"2025-03-28T08:02:08.000Z","dependencies_parsed_at":"2022-09-08T23:10:16.286Z","dependency_job_id":null,"html_url":"https://github.com/fbeltrao/ConfigMapFileProvider","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbeltrao%2FConfigMapFileProvider","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbeltrao%2FConfigMapFileProvider/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbeltrao%2FConfigMapFileProvider/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fbeltrao%2FConfigMapFileProvider/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fbeltrao","download_url":"https://codeload.github.com/fbeltrao/ConfigMapFileProvider/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252319994,"owners_count":21729055,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration-files","csharp-code","dotnet-core","kubernetes","logging"],"created_at":"2024-11-13T04:51:30.521Z","updated_at":"2025-05-04T10:30:47.676Z","avatar_url":"https://github.com/fbeltrao.png","language":"C#","funding_links":[],"categories":["C\\#"],"sub_categories":[],"readme":"# .NET Configuration in Kubernetes config maps with auto reload\r\n\r\n![Log level configuration in config map](media/article-preview.png)\r\n\r\nKubernetes config maps allows the injection of configuration into an application. The contents of a config map can be injected as environment variables or mounted files.\r\n\r\nFor instance, imagine you want to configure the log level in a separated file that will be mounted into your application.\r\n\r\nThe following config map limits the verbosity to errors: \r\n\r\n```yaml\r\napiVersion: v1\r\nkind: ConfigMap\r\nmetadata:\r\n  name: demo-config\r\ndata:\r\n  appsettings.json: |-\r\n    {\r\n      \"Logging\": {\r\n        \"LogLevel\": {\r\n          \"Default\": \"Error\",\r\n          \"System\": \"Error\",\r\n          \"Microsoft\": \"Error\"\r\n        }\r\n      }\r\n    }\r\n```\r\n\r\nThe file below deploys an application, mounting the contents of the config map into the /app/config folder.\r\n\r\n```yaml\r\napiVersion: apps/v1\r\nkind: Deployment\r\nmetadata:\r\n  name: demo-deployment\r\n  labels:\r\n    app: config-demo-app\r\nspec:\r\n  replicas: 1\r\n  selector:\r\n    matchLabels:\r\n      app: config-demo-app\r\n  template:\r\n    metadata:\r\n      labels:\r\n        app: config-demo-app\r\n    spec:\r\n      containers:\r\n      - name: configmapfileprovidersample\r\n        image: fbeltrao/configmapfileprovidersample:1.0\r\n        ports:\r\n        - containerPort: 80\r\n        volumeMounts:\r\n        - name: config-volume\r\n          mountPath: /app/config\r\n      volumes:\r\n      - name: config-volume\r\n        configMap:\r\n          name: demo-config\r\n```\r\n\r\nIn order to read configurations from the provided path (`config/appsettings.json`) the following code changes are required:\r\n\r\n```c#\r\npublic static IWebHostBuilder CreateWebHostBuilder(string[] args) =\u003e\r\n    WebHost.CreateDefaultBuilder(args)\r\n        .ConfigureAppConfiguration(c =\u003e\r\n        {\r\n           c.AddJsonFile(\"config/appsettings.json\", optional: true, reloadOnChange: true);\r\n        })\r\n        .UseStartup\u003cStartup\u003e();\r\n```\r\n\r\n\r\nDeploy the application:\r\n```bash\r\nkubectl apply -f configmap.yaml\r\nkubectl apply -f deployment.yaml\r\n```\r\n\r\nWe can peek into the running pod in Kubernetes, looking at the files stored in the container:\r\n\r\n```bash\r\nkubectl exec -it \u003cpod-name\u003e -- bash\r\nroot@demo-deployment-844f6c6546-x786b:/app# cd config/\r\nroot@demo-deployment-844f6c6546-x786b:/app/config# ls -la\r\n\r\nrwxrwxrwx 3 root root 4096 Sep 14 09:01 .\r\ndrwxr-xr-x 1 root root 4096 Sep 14 08:47 ..\r\ndrwxr-xr-x 2 root root 4096 Sep 14 09:01 ..2019_09_14_09_01_16.386067924\r\nlrwxrwxrwx 1 root root   31 Sep 14 09:01 ..data -\u003e ..2019_09_14_09_01_16.386067924\r\nlrwxrwxrwx 1 root root   53 Sep 14 08:47 appsettings.json -\u003e ..data/appsettings.json\r\n```\r\n\r\nAs you can see, the config map content is mounted using a [symlink](https://en.wikipedia.org/wiki/Symbolic_link). \r\n\r\nLet's change the log verbosity to `debug`, making the following changes to the config map:\r\n\r\n```yaml\r\napiVersion: v1\r\nkind: ConfigMap\r\nmetadata:\r\n  name: demo-config\r\ndata:\r\n  appsettings.json: |-\r\n    {\r\n      \"Logging\": {\r\n        \"LogLevel\": {\r\n          \"Default\": \"Debug\",\r\n          \"System\": \"Error\",\r\n          \"Microsoft\": \"Error\"\r\n        }\r\n      }\r\n    }\r\n```\r\nand redeploying it\r\n\r\n```bash\r\nkubectl apply -f configmap.yaml\r\n```\r\n\r\nEventually the changes will be applied to the mounted file inside the container, as you can see below:\r\n\r\n```bash\r\nroot@demo-deployment-844f6c6546-gzc6j:/app/config# ls -la\r\ntotal 12\r\ndrwxrwxrwx 3 root root 4096 Sep 14 09:05 .\r\ndrwxr-xr-x 1 root root 4096 Sep 14 08:47 ..\r\ndrwxr-xr-x 2 root root 4096 Sep 14 09:05 ..2019_09_14_09_05_02.797339427\r\nlrwxrwxrwx 1 root root   31 Sep 14 09:05 ..data -\u003e ..2019_09_14_09_05_02.797339427\r\nlrwxrwxrwx 1 root root   53 Sep 14 08:47 appsettings.json -\u003e ..data/appsettings.json\r\n```\r\n\r\nNotice that the appsettings.json last modified date does not change, only the referenced file actually gets updated.\r\n\r\nUnfortunately, the build-in reload on changes in .NET core file provider does not work. The config map does not trigger the configuration reload as one would expect.\r\n\r\nBased on my investigation, it seems that the .NET core change discovery relies on the file last modified date. Since the file we are monitoring did not change (the symlink reference did), no changes are detected.\r\n\r\n## Working on a solution\r\n\r\nThis problem is tracked [here](https://github.com/aspnet/Extensions/issues/1175). Until a fix is available we can take advantage of the extensible configuration system in .NET Core and implement a file based configuration provider that detect changes based on file contents.\r\n\r\nThe setup looks like this:\r\n```c#\r\npublic static IWebHostBuilder CreateWebHostBuilder(string[] args) =\u003e\r\n    WebHost.CreateDefaultBuilder(args)\r\n        .ConfigureAppConfiguration(c =\u003e\r\n        {\r\n            c.AddJsonFile(ConfigMapFileProvider.FromRelativePath(\"config\"), \r\n                \"appsettings.json\", \r\n                optional: true, \r\n                reloadOnChange: true);\r\n        })\r\n        .UseStartup\u003cStartup\u003e();\r\n```\r\n\r\nThe provided implementation detect changes based on the hash of the content. Check the sample project files for more details.\r\n\r\nDisclaimer: this is a quick implementation, not tested in different environments/configurations. Use at your own risk.\r\n\r\n### Testing the sample application\r\n\r\nClone this repository then deploy the application:\r\n```bash\r\nkubectl apply -f configmap.yaml\r\nkubectl apply -f deployment.yaml\r\n```\r\n\r\nIn a separated console window stream the container log:\r\n```bash\r\nkubectl logs -l app=config-demo-app -f\r\n```\r\n\r\nOpen a tunnel to the application with kubectl port-forward\r\n```bash\r\n kubectl port-forward \u003cpod-name\u003e 60000:80\r\n```\r\n\r\nVerify that the log is in error level, by opening a browser and navigating to `http://localhost:60000/api/values`. Look at the pod logs. You should see the following lines:\r\n```log\r\nfail: ConfigMapFileProviderSample.Controllers.ValuesController[0]\r\n      ERR log\r\ncrit: ConfigMapFileProviderSample.Controllers.ValuesController[0]\r\n      CRI log\r\n```\r\n\r\nChange the config map:\r\nReplace `\"Default\": \"Error\"` to `\"Default\": \"Debug\"` in the configmap.yaml file, then redeploy the config map.\r\n```bash\r\nkubectl apply -f configmap.yaml\r\n```\r\n\r\nVerify that the log level changes to Debug (it can take a couple of minutes until the file change is detected) by issuing new requests to `http://localhost:60000/api/values`. The logs will change to this:\r\n```log\r\ndbug: ConfigMapFileProviderSample.Controllers.ValuesController[0]\r\n      DBG log\r\ninfo: ConfigMapFileProviderSample.Controllers.ValuesController[0]\r\n      INF log\r\nwarn: ConfigMapFileProviderSample.Controllers.ValuesController[0]\r\n      WRN log\r\nfail: ConfigMapFileProviderSample.Controllers.ValuesController[0]\r\n      ERR log\r\ncrit: ConfigMapFileProviderSample.Controllers.ValuesController[0]\r\n      CRI log\r\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffbeltrao%2FConfigMapFileProvider","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffbeltrao%2FConfigMapFileProvider","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffbeltrao%2FConfigMapFileProvider/lists"}