{"id":11551,"date":"2025-08-10T14:31:49","date_gmt":"2025-08-10T14:31:49","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=11551"},"modified":"2025-08-10T14:31:49","modified_gmt":"2025-08-10T14:31:49","slug":"a-simple-web-api-in-various-languages-and-deployable-to-kubernetes-c","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/a-simple-web-api-in-various-languages-and-deployable-to-kubernetes-c\/","title":{"rendered":"A simple web API in various languages and deployable to Kubernetes (C#)"},"content":{"rendered":"<p><strong>Introduction<\/strong><\/p>\n<p>I&#8217;m always interested in how different programming languages and their libs\/frameworks tackle the same problem. Recently the topic of writing web API&#8217;s in whatever language we wanted came up and so I thought, well let&#8217;s try to do just that.<\/p>\n<p>The service is maybe too simple for a really good explanation of the frameworks and language features of the languages I&#8217;m going to use, but at the same time, I wanted to just do the bare minimum to have something working but enough.<\/p>\n<p>The service is a &#8220;echo&#8221; service, it will have an endpoint that simply passes back what&#8217;s sent to it (prefixed with some text) and also supply livez and readyz as I want to also create a Dockerfile and the associated k8s yaml files to deploy the service. <\/p>\n<p><em>The healthz endpoint is deprecated as of k8s v1.16, so we&#8217;ll leave that one out.<\/em><\/p>\n<p>It should be noted that there are (in some cases) other frameworks that can be used and optimisations, my interest is solely to get some basic Web API deployed to k8s that works, so you may have preferences for other ways to do this.<\/p>\n<p><strong>C# Minimal API<\/strong><\/p>\n<p>Let&#8217;s start with an ASP.NET core, minimal API, web API&#8230;<\/p>\n<ul>\n<li>Create an ASP.NET core Web API project in Visual Studio<\/li>\n<li>Enable container support and I&#8217;ve chosen Linux OS<\/li>\n<li>Ensure Container build type is set to Dockerfile<\/li>\n<li>I&#8217;m using minimal API so ensure &#8220;User Controllers&#8221; is not checked<\/li>\n<\/ul>\n<p>Now let&#8217;s just replace Program.cs with the following<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing Microsoft.AspNetCore.Diagnostics.HealthChecks;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\nbuilder.Services.AddEndpointsApiExplorer();\r\nbuilder.Services.AddSwaggerGen();\r\nbuilder.Services.AddHealthChecks();\r\n\r\nvar app = builder.Build();\r\n\r\nif (app.Environment.IsDevelopment())\r\n{\r\n    app.UseSwagger();\r\n    app.UseSwaggerUI();\r\n}\r\n\r\napp.UseHttpsRedirection();\r\n\r\napp.MapGet(&quot;\/echo&quot;, (string text) =&gt;\r\n    {\r\n        app.Logger.LogInformation($&quot;C# Echo: {text}&quot;);\r\n        return $&quot;Echo: {text}&quot;;\r\n    })\r\n    .WithName(&quot;Echo&quot;)\r\n    .WithOpenApi();\r\n\r\napp.MapHealthChecks(&quot;\/livez&quot;);\r\napp.MapHealthChecks(&quot;\/readyz&quot;, new HealthCheckOptions\r\n{\r\n    Predicate = _ =&gt; true\r\n});\r\n\r\napp.Run();\r\n<\/pre>\n<p><strong>Docker<\/strong><\/p>\n<p>Next we need to copy the Dockerfile from the csproj folder to the sln folder &#8211; for completeness here&#8217;s the Dockerfile generated by Visual Studio (comments removed)<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nFROM mcr.microsoft.com\/dotnet\/aspnet:8.0 AS base\r\nUSER $APP_UID\r\nWORKDIR \/app\r\nEXPOSE 8080\r\nEXPOSE 8081\r\n\r\nFROM mcr.microsoft.com\/dotnet\/sdk:8.0 AS build\r\nARG BUILD_CONFIGURATION=Release\r\nWORKDIR \/src\r\nCOPY &#x5B;&quot;EchoService\/EchoService.csproj&quot;, &quot;EchoService\/&quot;]\r\nRUN dotnet restore &quot;.\/EchoService\/EchoService.csproj&quot;\r\nCOPY . .\r\nWORKDIR &quot;\/src\/EchoService&quot;\r\nRUN dotnet build &quot;.\/EchoService.csproj&quot; -c $BUILD_CONFIGURATION -o \/app\/build\r\n\r\nFROM build AS publish\r\nARG BUILD_CONFIGURATION=Release\r\nRUN dotnet publish &quot;.\/EchoService.csproj&quot; -c $BUILD_CONFIGURATION -o \/app\/publish \/p:UseAppHost=false\r\n\r\nFROM base AS final\r\nWORKDIR \/app\r\nCOPY --from=publish \/app\/publish .\r\nENTRYPOINT &#x5B;&quot;dotnet&quot;, &quot;EchoService.dll&quot;]\r\n<\/pre>\n<p><em>Note: In Linux port 80 might be locked down, hence we use port 8080 by default.<\/em><\/p>\n<p>To build this, run <\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndocker build -t putridparrot.echo-service:v1 .\r\n<\/pre>\n<p><em>Don&#8217;t forget to change the name to your preferred name.<\/em><\/p>\n<p>and to test this, run<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndocker run -p 8080:8080 putridparrot.echo-service:v1\r\n<\/pre>\n<p>and we can text using &#8220;http:\/\/localhost:8080\/echo?text=Putridparrot&#8221;<\/p>\n<p><strong>Kubernetes<\/strong><\/p>\n<p>If all wen well we&#8217;ve not tested our application and see it working from a docker image, so now we need to create the deployment etc. for Kubernete&#8217;s. Let&#8217;s assume you&#8217;ve pushed you image to Docker or another container registry such as Azure &#8211; I&#8217;m call my container registry <em>putridparrotreg<\/em>.<\/p>\n<p>I&#8217;m also not going to use helm at this point as I just want a (relatively) simple yaml file to run from kubectl, so create a deployment.yaml file, we&#8217;ll store all the configurations, deployment, service and ingress in this one file jus for simplicity.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\napiVersion: apps\/v1\r\nkind: Deployment\r\nmetadata:\r\n  name: echo\r\n  namespace: dev\r\n  labels:\r\n    app: echo\r\nspec:\r\n  replicas: 1\r\n  selector:\r\n    matchLabels:\r\n      app: echo\r\n  template:\r\n    metadata:\r\n      labels:\r\n        app: echo\r\n    spec:\r\n      containers:\r\n      - name: echo\r\n        image: putridparrotreg\/putridparrot.echo-service:v1\r\n        ports:\r\n        - containerPort: 8080\r\n        resources:\r\n          requests:\r\n            memory: &quot;100Mi&quot;\r\n            cpu: &quot;100m&quot;\r\n          limits:\r\n            memory: &quot;200Mi&quot;\r\n            cpu: &quot;200m&quot;\r\n        livenessProbe:\r\n          httpGet:\r\n            path: \/livez\r\n            port: 8080\r\n          initialDelaySeconds: 30\r\n          periodSeconds: 10\r\n        readinessProbe:\r\n          httpGet:\r\n            path: \/readyz\r\n            port: 8080\r\n          initialDelaySeconds: 5\r\n          periodSeconds: 5\r\n\r\n---\r\napiVersion: v1\r\nkind: Service\r\nmetadata:\r\n  name: echo-service\r\n  namespace: dev\r\n  labels:\r\n    app: echo\r\nspec:\r\n  type: ClusterIP\r\n  selector:\r\n    app: echo \r\n  ports:\r\n  - name: http\r\n    port: 80\r\n    targetPort: 8080\r\n    protocol: TCP\r\n---\r\napiVersion: networking.k8s.io\/v1\r\nkind: Ingress\r\nmetadata:\r\n  name: echo-ingress\r\n  namespace: dev\r\n  annotations:\r\n    kubernetes.io\/ingress.class: &quot;nginx&quot;\r\n    nginx.ingress.kubernetes.io\/rewrite-target: \/\r\nspec:\r\n  rules:\r\n  - host: mydomain.com\r\n    http:\r\n      paths:\r\n      - path: \/\r\n        pathType: Prefix\r\n        backend:\r\n          service:\r\n            name: echo-service\r\n            port:\r\n              number: 80\r\n<\/pre>\n<p>Don&#8217;t forget to change the &#8220;host&#8221; and image to suit, also this assume you created a namespace &#8220;dev&#8221; for your app. See <a href=\"https:\/\/putridparrot.com\/blog\/creating-a-local-container-registry\/\" target=\"_blank\">Creating a local container registry<\/a> for information on setting up your own container registry.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction I&#8217;m always interested in how different programming languages and their libs\/frameworks tackle the same problem. Recently the topic of writing web API&#8217;s in whatever language we wanted came up and so I thought, well let&#8217;s try to do just that. The service is maybe too simple for a really good explanation of the frameworks [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[200,3,102,314],"tags":[],"class_list":["post-11551","post","type-post","status-publish","format-standard","hentry","category-asp-net-core","category-c","category-docker","category-kubernetes"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11551","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=11551"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11551\/revisions"}],"predecessor-version":[{"id":11583,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11551\/revisions\/11583"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=11551"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=11551"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=11551"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}