-2

I have an application deployed in a microk8s cluster. It has 3 components, frontend in angular, backend1 (port 5000) in flask and another set of backend2 (port 5001) services in another flask app. Angular invokes services in backend1. For few cases, backend1 will invoke services in backend2.

Initially there were three service load balancers with corresponding 3 services followed by deployments and pods. In order to make the application https, I switched off the 3 service LBs, and added the ingress add on in microk8s. I then added ingress objects in my application namespace.

One for each component. Backend1 is on a gunicorn/nginx and Backend2 is on a gunicorn. All of these are in docker containers that are deployed in the pod.

SSL works now without problem when I hit the application. I'm able to login and do a bunch of actions, which means the communication between frontend and backend1 is all set. Whereas in a particular flow where an action will invoke Backend1 will then invoke Backend2. This scenario does not work. It throws a 502 Bad Gateway Interface.

What did I try:

  • I added a path in Backend1's nginx.conf corresponding to port 5001 (a separate location section under server with proxy_pass)
  • I tried different combinations in my Backend2 ingress object
  • I removed port 5001 from Backend1's invoking code, but unsure as to where else to specify the port
  • I tried to see similar questions in SO, but none suits my scenario

Here are few links that I went through:

https://github.com/kubernetes/ingress-nginx/issues/1120

K8s Ingress rule for multiple paths in same backend service

How to configure ingress controller with multiple paths for the same service?

https://www.reddit.com/r/kubernetes/comments/rwlptd/creating_ingress_for_web_server_multiple_path/

Kubernetes access Service in other namespace via http request

How to make request to Kubernetes service?

All manifests and configuration files below.

ingress object for backend1:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-routes-backend1
  namespace: app-namespace
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  tls:
  - hosts:
    - domain-name
    secretName: tls-secret
  rules:
  - host: domain-name
    http:
      paths:
        - path: /api/v1/(.+)
          pathType: Prefix
          backend:
            service:
              name: backend1
              port:
                number: 5000

ingress object for backend2:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-routes-backend2
  namespace: app-namespace
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  tls:
  - hosts:
    - domain-name
    secretName: tls-secret
  rules:
  - host: domain-name
    http:
      paths:
        - path: /api/v1/data/(.*)
          pathType: Prefix
          backend:
            service:
              name: backend2
              port:
                number: 5001

Service file for backend1:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: app-name
    name: backend1
  name: backend1
  namespace: app-namespace
  clusterIP: 10.xxx.yyy.128
  clusterIPs:
  - 10.xxx.yyy.128
  externalTrafficPolicy: Cluster
  ports:
  - name: http
    nodePort: 31261
    port: 5000
    protocol: TCP
    targetPort: 5000
  selector:
    app: app-name
    name: backend1
  type: NodePort
status:
  loadBalancer: {}

Service file for backend2:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: app-name
    name: backend2
  name: backend2
  namespace: app-namespace
  clusterIP: 10.xxx.yyy.128
  clusterIPs:
  - 10.xxx.yyy.128
  externalTrafficPolicy: Cluster
  ports:
  - name: http
    nodePort: 31261
    port: 5001
    protocol: TCP
    targetPort: 5001
  selector:
    app: app-name
    name: backend1
  type: NodePort
status:
  loadBalancer: {}

A small example to show how the backend2 is being called from backend1:

@app.route('/api/v1/process-order/<customer_id>/customer/<order_id>/order/<bill_date>/billdate', methods=['GET'])
@jwt_required
def process_order(customer_id, order_id, bill_date):
    data = {
        "customer_id": customer_id,
        "order_id": order_id,
        "bill_date": bill_date,
    }
    response1 = requests.post(
        BASE_URL + ':5001/api/v1/data/load-order', headers=header1, data=json.dumps(data))
    result1 = response1.json()

BASE_URL is nothing but the domain name.

Below is the called method in backend2:

@app.route('/api/v1/data/load-order', methods=['POST'])
def load_order():
   pass

Below is my nginx.conf file:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    client_max_body_size 100M;
    access_log /dev/stdout;
    error_log /dev/stdout;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    index   index.html index.htm;   
    
    server {
        listen       5000;
        listen       [::]:5000;
        root /var/www/html;
        server_name  localhost default_server;
          
        location / {
            proxy_read_timeout 360s;
            proxy_send_timeout 360s;
            proxy_connect_timeout 360s;

            uwsgi_read_timeout 360s;
            uwsgi_send_timeout 360s;
            uwsgi_connect_timeout 360s;
            include uwsgi_params;
            uwsgi_pass unix:/tmp/uwsgi.socket;
        }
    }
}

Below is backend1's docker file:

FROM python:3.6
COPY code/ app/
WORKDIR /app

RUN apt-get clean \
    && apt-get -y update \
    && apt-get -y install nginx \
    && apt-get -y install python3-dev \
    && apt-get -y install build-essential \
    && pip install -r requirements.txt
# Start nginx flask
COPY code/nginx.conf /etc/nginx
RUN chmod +x ./start.sh
CMD ["./start.sh"]

Below is the backend2's docker file:

FROM python:3.6
COPY ./ app/
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["gunicorn", "-w", "3", "-b", ":5001", "-t", "360", "--access-logfile", "-", "--error-logfile", "-", "--reload", "app:app"]

I'm literally out of ideas. I spent quite sometime on making SSL work, now this. Any help/directions will be really helpful.

  • Calls from `backend1` to `backend2` shouldn't go through the Ingress object, and the `BASE_URL` is probably the wrong host name. You should be using the Service name `backend2` and the Service's `port:` number 5001. (Consider making the Service port number the standard HTTP port 80 for simplicity, even if the code listens on some other port.) – David Maze Feb 07 '23 at 13:46
  • Okay. I checked the value of BASE_URL, it is the domain name only. When you say "You should be using the Service name backend2 and the Service's port: number 5001", where should I be using this? – incrediblegiant Feb 07 '23 at 13:50
  • As the URL in the `requests.post()` call. I'd suggest using an environment variable `os.getenv('BACKEND2_URL')` here, and then you can include a correct value in your Kubernetes Deployment's `env:` block (and use a different value for local development). – David Maze Feb 07 '23 at 13:56
  • Okay, but I'm missing to understand how it'd solve the problem. Also, I'm quite new to kubernetes. – incrediblegiant Feb 07 '23 at 14:07
  • Alright. I understood what needs to be done. I shall change my requests.post as how you suggested and update here. – incrediblegiant Feb 07 '23 at 16:24
  • I now invoke requests.post(https:backend2.app-namespace.svc.cluster.local:5001/api/v1/data/load_order from my backend1 code. It now doesn't throw 502 Bad Gateway, but keeps spinning for long and get a timeout error. When I check backend2's pod logs, no request has hit that. Not sure what am I missing. – incrediblegiant Feb 08 '23 at 08:05
  • @DavidMaze - It worked. Removing the ingress file, and invoking it through requests.post works. The mistake I made was to use https instead of http, which was why it was throwing Bad Gateway. How do I accept your comment as answer? – incrediblegiant Feb 09 '23 at 06:14

1 Answers1

1

An Ingress object is only used for requests coming from outside the cluster. For connections between Pods, you'd connect to the corresponding Service, using the Service's name as a host name.

If backend1 is calling backend2 then the call could look like

requests.post('http://backend2:5001/api/v1/data/load-order', ...)

One approach I've found very useful is to make the actual URL configurable, maybe via an environment variable. This can make it easier to run the program in different environments, at least including "in Kubernetes" and "with no containers at all on your local system".

backend2_url = os.environ.get('BACKEND2_URL', 'http://localhost:5001')
requests.post(backend2_url + '/api/v1/data/load-order', ...)
# backend1-deployment.yaml
env:
  - name: BACKEND2_URL
    value: http://backend2:5001

You probably need to use unencrypted HTTP for these calls. In comments you highlight that you were trying to use HTTP-over-TLS, which is normally a good practice. In your setup, the TLS termination is provided by the Ingress object, and you're not using that. Make sure to change https://... to http://....

If backend2 doesn't need to be reachable from outside the cluster, you can delete its Ingress object. You also may find it more convenient to change the Service's port: to the standard HTTP port 80, even if the targetPort: is something different, which will let you leave the port number off of the URLs for these cross-service calls.

David Maze
  • 130,717
  • 29
  • 175
  • 215