I have a .net core 6 web api service which I have deployed on AKS(Azure Kubernetes Services). The Program.cs looks like so;
var builder = WebApplication.CreateBuilder(args);
var swaggerConfig = builder.Configuration.GetSection(nameof(SwaggerConfig)).Get<SwaggerConfig>();
builder.Services.AddSwaggerGen(opts =>
{
opts.SchemaFilter<ExampleNashSchemaFilter>();
opts.SwaggerDoc(swaggerConfig.ApiVersion, new Microsoft.OpenApi.Models.OpenApiInfo { Title = swaggerConfig.ApiName, Version = swaggerConfig.ApiVersion });
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default.
// Clear that restriction because forwarders are enabled by explicit
// configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
builder.Services.AddCors(p => p.AddPolicy("devcors", builder =>
{
builder.WithOrigins("*").AllowAnyMethod().AllowAnyHeader();
}));
var app = builder.Build();
app.UseCors("devcors");
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/v1/swagger.json", swaggerConfig.ApiName);
c.RoutePrefix = "swagger";
});
app.UseAuthorization();
app.MapControllers();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Here is the swagger settings on appsettings.json
"SwaggerConfig": {
"ApiName": "Some Services",
"ApiVersion": "v1"
}
Here is my deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: servicetest
spec:
replicas: 1
selector:
matchLabels:
app: servicetest
template:
metadata:
labels:
app: servicetest
spec:
containers:
- name: servicetest
image: myprivatehub/servicetest:latest
imagePullPolicy: Always
ports:
- containerPort: 80
imagePullSecrets:
- name: regcred
---
apiVersion: v1
kind: Service
metadata:
name: servicetest
spec:
selector:
app: servicetest
ports:
- port: 80
protocol: TCP
targetPort: 80
type: LoadBalancer
For some weird reasons I can access the APIs running from my service on AKS but I can't access the swagger/index.html I get an error page can't be found. I replicated this deployment on my minikube and it works well. I'm curious to know why i can't access swaggerUI from AKS
Seems that you do not have an Ingress Controller deployed on your AKS. You will need that in order to get ingress to work and to expose the swagger-ui.
To access the swagger-ui for testing purposes you can do this:
kubectl port-forward service/servicetest 80:8080
Afterwards just access http://localhost:8080
But you should def. install an ingress-controller: Here is a Workflow from MS to install ingress-nginx as Ingress Controller on your Cluster.
To avoid that every service spawns a LoadBalancer and a PublicIP in Azure (to keep an eye on your costs), you will then only expose the ingress-controller to the internet and could also specify the loadBalancerIP statically if you created the PublicIP in advance:
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/azure-load-balancer-resource-group: myResourceGroup # only needed if the LB is in another RG
name: ingress-nginx-controller
spec:
loadBalancerIP: <YOUR_STATIC_IP>
type: LoadBalancer
The Ingress Controller then will route incoming traffic to your application with an Ingress resource:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
spec:
ingressClassName: nginx # ingress-nginx specifix
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: servicetest
port:
number: 80
Related
I have implemented a .Net Core API which is caching and accessing the data from Redis cache. I have created images for API and Redis and deployed both of them on 2 different pods. Currently I am using minikube as a Kubernetes cluster on my local machine.
.Net API code:
Controller method code:
[HttpGet]
public IActionResult Get()
{
var cacheKey = "weatherList";
var rng = new Random();
IEnumerable<WeatherForecast> weatherList = new List<WeatherForecast>();
var redisWeatherList = _distributedCache.Get(cacheKey);
if (redisWeatherList != null)
{
var serializedWeatherList = Encoding.UTF8.GetString(redisWeatherList);
weatherList = JsonConvert.DeserializeObject<List<WeatherForecast>>(serializedWeatherList);
return Ok(new Tuple<IEnumerable<WeatherForecast>, string>(weatherList, "Data fetched from cache"));
}
else
{
weatherList = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
var serializedWeatherList = JsonConvert.SerializeObject(weatherList);
redisWeatherList = Encoding.UTF8.GetBytes(serializedWeatherList);
var options = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(DateTime.Now.AddSeconds(5));
_distributedCache.Set(cacheKey, redisWeatherList, options);
return Ok(new Tuple<IEnumerable<WeatherForecast>>(weatherList));
}
}
Redis cache configuration in the Startup class
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = Configuration.GetSection("AppConfiguration").GetSection("RedisConfiguration").Value;
});
For deployment I have created the deployment and service yaml files for both of them. Since we have to access the API so its type is selected as LoadBalancer and Redis will be interacted by the API type is selected as default ClusterIP.
YML files:
API YML files:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
labels:
app: api-deployment
spec:
replicas: 1
selector:
matchLabels:
app: api-deployment
template:
metadata:
labels:
app: api-deployment
spec:
volumes:
- name: appsettings-volume
configMap:
name: appsettings-configmap
containers:
- name: api-deployment
image: testapiimage
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: appsettings-volume
mountPath: /app/appsettings.json
subPath: appsettings.json
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi
apiVersion: v1
kind: Service
metadata:
name: api-service
labels:
app: api-service
spec:
ports:
- port: 8080
targetPort: 80
type: LoadBalancer
selector:
app: api-deployment
Redis YML files:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deployment
labels:
app: redis-deployment
spec:
replicas: 1
selector:
matchLabels:
app: redis-deployment
template:
metadata:
labels:
app: redis-deployment
spec:
containers:
- name: redis-deployment
image: redis
ports:
- containerPort: 6379
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi
apiVersion: v1
kind: Service
metadata:
name: redis-service
labels:
app: redis-service
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis-deployment
Now the issue and confusion is how API will know how to connect to the ClusterIP of Redis pod. In the local code it is connecting through localhost. Therefore, while API container is running it is still trying to connect to the Redis container through localhost only just like in code.
Is there any way that the API container will automatically connected to the ClusterIP of the Redis container like Environment variables but in my case I have to depend on the value of Redis host which in the container environment is the ClusterIP which is not static and cant be injected through appsettings.
Please guide me here.
To reach other pods in the cluster that are not exposed publicly, you can use the fully qualified domain name (FQDN) assigned by the Kubernetes internal DNS. Kubernetes internal DNS will resolve this to the necessary ClusterIP value to reach the service.
The FQDN is in the format:
<service-name>.<namespace>.svc.<cluster-domain>
The default value of the cluster-domain is cluster.local and if no namespace was provided to redis, it will be running in the default namespace.
Assuming all defaults, the FQDN of the redis service would be:
redis-service.default.svc.cluster.local.
Kubernetes will automatically resolve this to the appropriate ClusterIP address.
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
I'm trying to expose a website inside my Kubernetes Cluster. Therefor I created an Ingress that links to my Service and so to my Pod. Till this point, everything works perfectly fine.
But now when I start navigating on my Page the URL changes, but the shown site stays the “Homepage”.
How is it possible to navigate on the page and access all the subpages properly?
My Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: wichteln-deployment
spec:
replicas: 1
selector:
matchLabels:
app: wichteln
template:
metadata:
labels:
app: wichteln
spec:
containers:
- image: jonasgoetz01/website:V1.0
name: wichteln-container
command: ["/bin/sh"]
args:
- -c
- >-
apt update
My Service:
apiVersion: v1
kind: Service
metadata:
name: wichteln-service
spec:
selector:
app: wichteln
ports:
- protocol: TCP
port: 5000
targetPort: 5000
My Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wichteln-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: public
rules:
- http:
paths:
- path: /wichteln/*
pathType: Prefix
backend:
service:
name: wichteln-service
port:
number: 5000
The Webpage is a NodeJS express Webpage.
Here is my routes.js file for reference (If you need more files, please let me know):
const express = require('express');
const router = express.Router();
const controller = require('../controllers/controller');
// Routes
router.get('/', controller.home);
router.get('/usermanagement/', controller.viewuser);
router.post('/usermanagement/', controller.finduser);
router.get('/usermanagement/adduser', controller.formuser);
router.post('/usermanagement/adduser', controller.createuser);
router.get('/usermanagement/edituser/:id', controller.edituser);
router.post('/usermanagement/edituser/:id', controller.updateuser);
router.get('/usermanagement/viewuser/:id', controller.viewalluser);
router.get('/usermanagement/:id',controller.deleteuser);
module.exports = router;
Every time I try to access the page, I start at the homepage. When I click on some buttons that normally would redirect me to a subpage, I stay at the homepage even if the URL changes correctly.
For example, the home URL is: www.mydomain.com/wichteln → I can see the homepage.
www.mydomain.com/wichteln/usermanagement → I can still see the homepage but want to see the subpage “usermanagement”.
Thank you for your help.
The issue is most likely coming from the rewrite that you have in your ingress manifest.
nginx.ingress.kubernetes.io/rewrite-target: /
this means that when the ingress receives the request from the client it takes the path portion of the url and rewrites it to be /, before passing it down to your backend, which in turn results in backend returning the root page of your website.
You should remove the rewrite annotation and use this one:
nginx.ingress.kubernetes.io/use-regex: "true"
And in your path configuration you can try something like that:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wichteln-ingress
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: public
rules:
- http:
paths:
- path: /wichteln/.*
pathType: Prefix
backend:
service:
name: wichteln-service
port:
number: 5000
This configuration creates the following NGINX location block:
location ~* "^/wichteln/.*" {
...
}
Path Rewrite + Capture Groups
You can use capture groups to capture the part after the path prefix and use it in the rewrite rule see the docs.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wichteln-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: public
rules:
- http:
paths:
- path: /wichteln/(/|$)(.*)
pathType: Prefix
backend:
service:
name: wichteln-service
port:
number: 5000
Context Path
Alternatively, and not uncommon, is it to inject a context path into your application. Then you don't need any path rewrite on your ingress manifest.
...
const ctx = process.env.CONTEXT_PATH || ''
router.get(`${ctx}/`, controller.home);
router.get(`${ctx}/usermanagement/`, controller.viewuser);
...
To inject it, add it to the pod spec.
...
spec:
containers:
- image: jonasgoetz01/website:V1.0
env:
- name: CONTEXT_PATH
value: /wichteln
...
I am using Kubernetes v1.21.5 on docker. The following is my ingress YAML file
kind: Ingress
metadata:
name: ingress-service
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
rules:
- host: triver.dev
http:
paths:
- path: /service/account/?(.*)
pathType: Prefix
backend:
service:
name: auth-srv
port:
number: 3000
I am running an auth srv image and the following is its YAML file
kind: Deployment
metadata:
name: auth-depl
spec:
replicas: 1
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
spec:
containers:
- name: auth
image: triver/auth
env:
- name: MONGO_URI
value: 'mongodb://mongo-srv:27017/auth'
- name: JWT_KEY
valueFrom:
secretKeyRef:
name: jwt-secret
key: JWT_KEY
---
apiVersion: v1
kind: Service
metadata:
name: auth-srv
spec:
selector:
app: auth
ports:
- name: auth
protocol: TCP
port: 3000
targetPort: 3000
but when I try to send a request to any route that I created in auth service for example I created a triver.dev/service/account/signup post express router for a user to signup. When I try to send a post request to the route through Postman it gives an error (404) of ECONNRefused. Couldn't send a request. Why is it happening? (My postman is working fine. It's not an issue on the postman end.)
What am I doing wrong
The app works but I just can't access the route. It's definitely an ingress issue. Can someone help me, please? This is a very important project.
This is what show up when I use the command 'kubectl get ingress'
Everything works fine when I run the application using skaffold dev.
it's due to you have not mentioned the hostname into the ingress, also make sure your ingress controller is running
example ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host: hello-world.info
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 8080
host: hello-world.info
Reference : https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/
you can also checkout : https://kubernetes.io/docs/concepts/services-networking/ingress/
if you have set default backend set in ingress and host is not issue
make sure you are sending the request on HTTP instead of HTTPS
nginx.ingress.kubernetes.io/ssl-redirect: "false"
as you are not using the certificate SSL/TLS so please try the URL with HTTP
I'm working on a simple project to learn more about microservices.
I have a very simple .net core web with a single GET endpoint.
I have added a ApiGateway web app with Ocelot, and everything seems to be working fine except when I deploy to a local Kubernetes cluster.
Those are the yaml files I'm using for the deployment:
ApiGateway.yaml
kind: Pod
apiVersion: v1
metadata:
name: api-gateway
labels:
app: api-gateway
spec:
containers:
- name: api-gateway
image: apigateway:dev
---
kind: Service
apiVersion: v1
metadata:
name: api-gateway-service
spec:
selector:
app: api-gateway
ports:
- port: 80
TestService.yaml
kind: Pod
apiVersion: v1
metadata:
name: testservice
labels:
app: testservice
spec:
containers:
- name: testservice
image: testbuild:latest
---
kind: Service
apiVersion: v1
metadata:
name: testservice-service
spec:
selector:
app: testservice
ports:
- port: 80
ocelot.json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/endpoint",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "testservice-service",
"Port": 80
}
],
"UpstreamPathTemplate": "/test",
"UpstreamHttpMethod": [ "Get" ]
}
]
}
If I try to make a request with cUrl directly from the ApiGateway pod to the TestService service it works with no issue. But when I try to request it from Ocelot, it returns a 500 error, saying:
warn: Ocelot.Responder.Middleware.ResponderMiddleware[0]
requestId: 0HLUEDTNVVN26:00000002, previousRequestId: no previous request id, message: Error Code: UnableToCompleteRequestError Message: Error making http request, exception: System.Net.Http.HttpRequestException: Name or service not known
---> System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known
Also, I tried with this https://ocelot.readthedocs.io/en/latest/features/kubernetes.html but honestly the doc is not really clear and I haven't had any success so far.
Any idea what the problem might be?
(I'm also using an Ingress resource for exposing the services, but that works with no problem so I will keep it out of this thread for now)
Middlewares are not being detected and therefore paths are not being stripped resulting in 404s in the backend api.
Middleware exists in k8s apps namespace
$ kubectl get -n apps middlewares
NAME AGE
traefik-middlewares-backend-users-service 1d
configuration for middleware and ingress route
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
annotations:
kubernetes.io/ingress.class: traefik
name: apps-services
namespace: apps
spec:
entryPoints:
- web
routes:
- kind: Rule
match: Host(`example.com`) && PathPrefix(`/users/`)
middlewares:
- name: traefik-middlewares-backend-users-service
priority: 0
services:
- name: backend-users-service
port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: traefik-middlewares-backend-users-service
namespace: apps
spec:
stripPrefix:
prefixes:
- /users
Static configuration
global:
checkNewVersion: true
sendAnonymousUsage: true
entryPoints:
http:
address: :80
traefik:
address: :8080
providers:
providersThrottleDuration: 2s
kubernetesIngress: {}
api:
# TODO: make this secure later
insecure: true
ping:
entryPoint: http
log: {}
Traefik dasboard has no middlewares
Spring Boot 404 page. Route is on example.com/actuator/health
The /users is not being stripped. This worked for me in traefik v1 perfectly fine.
Note: actual domain has been replaced with example.com and domain.com in the examples.
To get this working, I had to:
Add the Kubernetes CRD provider with the namespaces where the custom k8s CRDs for traefik v2 exist
Add TLSOption resource definition
Update cluster role for traefik to have permissions for listing and watching new v2 resources
Make sure all namespaces with new resources are configured
Traefik Static Configuration File
providers:
providersThrottleDuration: 2s
kubernetesCRD:
namespaces:
- apps
- traefik
TLSOption CRD
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: tlsoptions.traefik.containo.us
spec:
group: traefik.containo.us
version: v1alpha1
names:
kind: TLSOption
plural: tlsoptions
singular: tlsoption
scope: Namespaced
Updated Static Configuration for Traefik
global:
checkNewVersion: true
sendAnonymousUsage: true
entryPoints:
http:
address: :80
traefik:
address: :8080
providers:
providersThrottleDuration: 2s
kubernetesCRD:
namespaces:
- apps
- traefik
api:
# TODO: make this secure later
insecure: true
ping:
entryPoint: http
log: {}