4

I have two ASP.NET Core apps. One is a Headless CMS (API), and the other one is a Razor Pages blog front-end (with a REST client that communicates with the Headless CMS/API).

I then have an Azure AKS cluster. In it I have an ingress resource with the following routes (as per the instructions from the following AKS docs: https://learn.microsoft.com/en-us/azure/aks/ingress-tls#create-an-ingress-route ). Each route is mapped to each of the apps/services mentioned above:

spec:
  rules:
  - host: mydomain.westeurope.cloudapp.azure.com
    http:
      paths:
      - backend:
          serviceName: headless-cms-svc
          servicePort: 80
        path: /
      - backend:
          serviceName: blog-svc
          servicePort: 80
        path: /blog

When I now navigate to the first route, mydomain.westeurope.cloudapp.azure.com, the headless CMS app works as expected but when I navigate to the second route , mydomain.westeurope.cloudapp.azure.com/blog, I get a bunch of 404:s because the blog apps root path is now relative to the /blog ingress route which in turn breaks all the resources (css, javascript, images etc) in the wwwroot folder.

How should I configure my ASP.NET Core blog app and/or my ingress object?

404:s

https://mydomain.westeurope.cloudapp.azure.com/css/site.min.css?v=kHvJwvVAK1eJLN4w8xygUR3nbvlLmRwi5yr-OuAO90E

https://mydomain.westeurope.cloudapp.azure.com/images/banner1.svg

https://mydomain.westeurope.cloudapp.azure.com/images/banner2.svg

https://mydomain.westeurope.cloudapp.azure.com/js/site.min.js?v=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU

If I add the URL segment /blog the resources get served properly. https://mydomain.westeurope.cloudapp.azure.com/blog/images/banner1.svg <- works

And here is a regular img tag in the Index.cshtml Razor page (from a default ASP.NET Core 2.1 Razor Pages web application). I haven't changed anything in the code.

<img src="~/images/banner1.svg" alt="ASP.NET" class="img-responsive" />
vladimir
  • 13,428
  • 2
  • 44
  • 70
PussInBoots
  • 11,028
  • 9
  • 52
  • 84
  • If I got you correctly, your proxy path rewriting works. It should add some info about original path, host, protocol to http headers. Now your app must respect these headers. That's described here: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1 – Christoph Lütjen Sep 19 '18 at 12:25
  • Tried some of the examples but can't get it to work. In particular I've tested all three examples in "Deal with path base and proxies that change the request path". Not sure though if I'm doing it right because I'm finding it very difficult to debug since the execution takes place in Startup.cs plus I'm using Azure AKS with an ingress which makes it difficult to test locally. – PussInBoots Sep 21 '18 at 07:18
  • Your problem is, that your app generates wrong links. Correct? - Can you add one link including expected and wrong url and how you generate that link in your code? – Christoph Lütjen Sep 21 '18 at 10:23
  • For debugging I'd add logging (path + protocol). There's a copy and paste example https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1#troubleshoot – Christoph Lütjen Sep 21 '18 at 10:26
  • I have updated the question. Regarding logging; I don't see anything unusual about the headers that code snippet spits out. – PussInBoots Sep 21 '18 at 12:31
  • We need to know if your proxy sends the request with or without /blog segment. Both is not unusual ;-) What do you get? – Christoph Lütjen Sep 21 '18 at 13:31

3 Answers3

4

Problem

It seems that your proxy rewrites the path.

  • Before proxy: /blog/images/banner1.png
  • After proxy: /images/banner1.png

Asp generates absolute (host relative) links (path only, but starting with a slash "/"). That means, we have to tell the framework that it must prefix all URLs with "/blog".

Solution

Do this (for asp.net core 2.1) by inserting the following snipped in your Startup class:

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/blog");
    return next();
});

Code sample from: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1

Insert this snipped before any other middleware in your Configure method.

You can test this on your local machine too. All generated links should be prefixed with "/blog" - so they will be broken on your dev machine.

Use Configuration

You will need to make it configurable e.g. like so:

        var basePath = Configuration.GetSection("BASE_PATH").Value;
        if (basePath != null)
        {
            Console.WriteLine($"Using base path '{basePath}'");
            // app.Use().. goes here
        }

(Assuming you read configuration from env vars in your startup.)

… and provide this env var in your kubernetes depoyment:

...
 containers:
  - name: myapp
    image: myappimage
    env:
      - name: BASE_PATH
        value: "/blog"
Christoph Lütjen
  • 5,403
  • 2
  • 24
  • 33
  • Seems to work. How do I know where to put those lines? I checked the entire middleware documentation but it lacks good examples. Because order is important. – PussInBoots Sep 21 '18 at 14:56
  • Think about what "middleware" means: It's just one function (middleware) calling the next function (middlware) in the order you added them with your UseXY() calls. Here the MVC middleware needs the PathBase property filled but there should be no other middleware that requires an empty PathBase - that's why I wrote "add before any middleware". – Christoph Lütjen Sep 23 '18 at 15:24
0

You want to annotate your Ingress with nginx.ingress.kubernetes.io/rewrite-target. For example:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: mydnsname.westeurope.cloudapp.azure.com
    http:
      paths:
      - backend:
          serviceName: headless-cms-svc
          servicePort: 80
        path: /
      - backend:
          serviceName: blog-svc
          servicePort: 80
        path: /blog

Hope it helps!

Rico
  • 58,485
  • 12
  • 111
  • 141
0

Follow these steps to run your code:

  1. deployment: pass environment variable with path base in k8s-yaml-file
apiVersion: apps/v1
kind: Deployment
# ..
spec:
  # ..
  template:
    # ..
    spec:
      # ..
      containers:
        - name: test01
          image: test.io/test:dev
          # ...
          env:
            # define custom Path Base (it should be the same as 'path' in Ingress-service)
            - name: API_PATH_BASE # <---
              value: "blog"
  1. program: enable loading environment params in Program.cs
var builder = new WebHostBuilder()
    .UseContentRoot(Directory.GetCurrentDirectory())
    // ..
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        // ..  
        config.AddEnvironmentVariables(); // <---
        // ..
    })
    // ..
  1. startup: apply UsePathBaseMiddleware in Startup.cs
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    private readonly IConfiguration _configuration;

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        var pathBase = _configuration["API_PATH_BASE"]; // <---

        if (!string.IsNullOrWhiteSpace(pathBase))
        {
            app.UsePathBase($"/{pathBase.TrimStart('/')}");
        }

        app.UseStaticFiles(); // <-- StaticFilesMiddleware must follow UsePathBaseMiddleware

        // ..

        app.UseMvc();
    }

    // ..
}
vladimir
  • 13,428
  • 2
  • 44
  • 70