seanmorley15 / AdventureLog

Self-hostable travel tracker and trip planner.
https://adventurelog.app
Other
592 stars 15 forks source link

[BUG] Images and profile picture not displayed/found in kubernetes environment using Traefik #345

Open dhop90 opened 1 month ago

dhop90 commented 1 month ago

Describe the bug Images and profile picture are not displayed when accessing application. I've reviewed https://github.com/seanmorley15/AdventureLog/issues/288 as well as post on reddit https://www.reddit.com/r/selfhosted/comments/1eswkgd/adventurelog_self_hosted_travel_tracker_and/ but have not been able to get this to work.

To Reproduce Steps to reproduce the behavior:

  1. This is a kubernetes/k8s environment using traefik
  2. Configure adventurelog per instructions, able to access application @ https://adventurelog.domain.duckdns.org 1.b - secrets for database are defined in config, all are working as expected
  3. Server config
      env:
            - name: CSRF_TRUSTED_ORIGINS
              value: >-
                https://adventurelog.domain.duckdns.org,
                https://server.domain.duckdns.org, http://server.domain.duckdns.org,
                http://server.adventurelog.svc.cluster.local,
                http://web.adventurelog.svc.cluster.local
                http://server.adventurelog.svc.cluster.local:8000,
                http://web.adventurelog.svc.cluster.local, http://web,
                http://server
            - name: DEBUG
              value: 'true'
            - name: DJANGO_ADMIN_EMAIL
              value: domain@gmail.com
            - name: DJANGO_ADMIN_USERNAME
              value: admin
            - name: FRONTEND_URL
              value: http://web.adventurelog.svc.cluster.local
            - name: PGDATABASE
              value: adventure
            - name: PGHOST
              value: db.adventurelog.svc.cluster.local
            - name: PGUSER
              value: adventure
            - name: PUBLIC_URL
              value: http://server.domain.duckdns.org
            - name: PUBLIC_SERVER_URL
              value: http://server.domain.duckdns.org
  4. Web config
         env:
            - name: BODY_SIZE_LIMIT
              value: '100000'
            - name: DEBUG
              value: 'true'
            - name: PUBLIC_SERVER_URL
              value: http://server.domain.duckdns.org
            - name: ORIGIN
              value: https://adventurelog.domain.duckdns.org
  5. Ingress config

server -

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: server
  namespace: adventurelog
  labels:
    io.kompose.service: server
    k8slens-edit-resource-version: v1
  annotations:
    app.kubernetes.io/component: server
    app.kubernetes.io/instance: adventurelog
    app.kubernetes.io/managed-by: Self
    app.kubernetes.io/name: adventurelog
    app.kubernetes.io/version: latest
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/router.entrypoints: http
  selfLink: /apis/networking.k8s.io/v1/namespaces/adventurelog/ingresses/server
status:
  loadBalancer: {}
spec:
  ingressClassName: traefik
  rules:
    - host: server.domain.duckdns.org
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: server
                port:
                  number: 8000

web -

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: adventurelog
  labels:
    io.kompose.service: web
    k8slens-edit-resource-version: v1
  annotations:
    app.kubernetes.io/component: web
    app.kubernetes.io/instance: adventurelog
    app.kubernetes.io/managed-by: Self
    app.kubernetes.io/name: adventurelog
    app.kubernetes.io/version: latest
    cert-manager.io/cluster-issuer: cert-manager-webhook-duckdns-production
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/router.entrypoints: websecure, http
    traefik.ingress.kubernetes.io/router.middlewares: authentik-forward@kubernetescrd
    traefik.ingress.kubernetes.io/router.tls: 'true'
  selfLink: /apis/networking.k8s.io/v1/namespaces/adventurelog/ingresses/web
status:
  loadBalancer: {}
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - adventurelog.domain.duckdns.org
      secretName: adventurelog-tls-duckdns-secret-production
  rules:
    - host: adventurelog.domain.duckdns.org
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Expected behavior World Travel map images are displayed as well as uploaded profile picture

Screenshots If applicable, add screenshots to help explain your problem.

Docker Compose See configs above

Additional context Server Logs: System check identified no issues (0 silenced). October 19, 2024 - 15:55:11 Django version 5.0.8, using settings 'main.settings' Starting development server at http://0.0.0.0:8000/ Quit the server with CONTROL-C. "GET /auth/user/ HTTP/1.1" 200 297 "GET /auth/user/ HTTP/1.1" 200 297 "GET /api/countries/ HTTP/1.1" 200 40732 "GET /auth/user/ HTTP/1.1" 200 297 "GET /api/adventures/filtered?types=all&order_by=updated_at&order_direction=asc&include_collections=false&page=1 HTTP/1.1" 301 0 Ordering by: -updated_at "GET /api/adventures/filtered/?types=all&order_by=updated_at&order_direction=asc&include_collections=false&page=1 HTTP/1.1" 200 52 "GET /auth/user/ HTTP/1.1" 200 297 "GET /api/adventures/filtered?types=all&order_by=updated_at&order_direction=asc&include_collections=false&page=1 HTTP/1.1" 301 0 Ordering by: -updated_at "GET /api/adventures/filtered/?types=all&order_by=updated_at&order_direction=asc&include_collections=false&page=1 HTTP/1.1" 200 52 "GET /auth/user/ HTTP/1.1" 200 297 "GET /api/collections/?order_by=updated_at HTTP/1.1" 200 828

Web Logs: user: { pk: 2, profile_pic: 'http://server.domain.duckdns.org/media/profile-pics/user.webp', uuid: 'b387387f-6e35-4fb7-af9b-764a9862cbf9', public_profile: true, username: 'user', email: 'user@gmail.com', first_name: 'First', last_name: 'Last', date_joined: '2024-10-17T01:03:15Z', is_staff: true }, props: { countries: [ [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], ... 150 more items ] } }

profile-pics ~ % curl http://server.domain.duckdns.org/media/profile-pics/user.webp

<!doctype html>
<html lang="en">
<head>
  <title>Not Found</title>
</head>
<body>
  <h1>Not Found</h1><p>The requested resource was not found on this server.</p>
</body>
</html>

~ % ping server.domain.duckdns.org PING server.domain.duckdns.org (192.168.86.19): 56 data bytes 64 bytes from 192.168.86.19: icmp_seq=0 ttl=64 time=3.970 ms 64 bytes from 192.168.86.19: icmp_seq=1 ttl=64 time=1.168 ms ^C --- server.domain.duckdns.org ping statistics --- 2 packets transmitted, 2 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 1.168/2.569/3.970/1.401 ms

From the server container, wget works for index.html:

root@server-7d456ffbcb-kzlmz:/code/media/images# wget http://server:8000
--2024-10-19 19:45:22--  http://server:8000/
Resolving server (server)... 10.103.206.158
Connecting to server (server)|10.103.206.158|:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5219 (5.1K) [text/html]
Saving to: ‘index.html’

index.html                                                                      100%[=======================================================================================================================================================================================================>]   5.10K  --.-KB/s    in 0s      

2024-10-19 19:45:22 (188 MB/s) - ‘index.html’ saved [5219/5219]

but trying to get an image, does not:


root@server-7d456ffbcb-kzlmz:/code/media/images# wget http://server:8000/media/images/tech-stack.webp
--2024-10-19 19:45:09--  http://server:8000/media/images/tech-stack.webp
Resolving server (server)... 10.103.206.158
Connecting to server (server)|10.103.206.158|:8000... connected.
HTTP request sent, awaiting response... 404 Not Found
2024-10-19 19:45:09 ERROR 404: Not Found.

file exists:

root@server-7d456ffbcb-kzlmz:~# ls /code/media/images/
tech-stack.webp
root@server-7d456ffbcb-kzlmz:~# 
lovwyr commented 2 days ago

I ran into an similar Issue with external Traefik, the Backend Container needs to expose the /media PathPrefix.

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.adventurelog.entrypoints=websecure"
      - "traefik.http.routers.adventurelog.rule=Host(`yourdomain.com`) && PathPrefix(`/media`)" # Replace with your domain
      - "traefik.http.routers.adventurelog.tls=true"
      - "traefik.http.routers.adventurelog.tls.certresolver=letsencrypt" 

before 0.7.1 this was done by the separate nginx, in the current example Config nothing is providing this Path.

seanmorley15 commented 2 days ago

Thanks @lovwyr! I am unfamiliar with how Traefik works but do you recommend I update the docker compose Traefik version with these changes you provided? Thanks!

lovwyr commented 2 days ago

@seanmorley15 yes please :)

web Container

 labels:
      - "traefik.enable=true"
      - "traefik.http.routers.adventurelogweb.entrypoints=websecure"
      - "traefik.http.routers.adventurelogweb.rule=Host(`yourdomain.com`) && !PathPrefix(`/media`)" # Replace with your domain
      - "traefik.http.routers.adventurelogweb.tls=true"
      - "traefik.http.routers.adventurelogweb.tls.certresolver=letsencrypt" 

server Container

 labels:
      - "traefik.enable=true"
      - "traefik.http.routers.adventurelogserver.entrypoints=websecure"
      - "traefik.http.routers.adventurelogserver.rule=Host(`yourdomain.com`) && PathPrefix(`/media`)" # Replace with your domain
      - "traefik.http.routers.adventurelogserver.tls=true"
      - "traefik.http.routers.adventurelogserver.tls.certresolver=letsencrypt" 

maybe it would be an Idea to move the serving of the Images from the Backend to the Frontend Container?

dhop90 commented 2 days ago

Thanks @lovwyr, but can't seem to get this to work for my setup. I was able to create an ingress rule for /media routed to the server:8000 and can access https://adventure.domain.duckdns.org/media/profile-pics/dhop-avatar.webp along with flags in the /media/maps directory. I'm wondering if I need to replace /media with /app/media since that is what the nginx proxy was doing. Not 100% how to do that.

lovwyr commented 2 days ago

@dhop90 i think the ingress route/port should be 80, the 8000 is the internal django

    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"      
      - "traefik.http.routers.adventurelogserver.entrypoints=websecure"
      - "traefik.http.routers.adventurelogserver.rule=Host(`adventurelog.${DOMAIN}`) && PathPrefix(`/media`)"
      - "traefik.http.services.adventurelogserver.loadbalancer.server.port=80"
      - "traefik.http.routers.adventurelogserver.service=adventurelogserver"

this is my current docker only config which works. unfortunately i had no k8s available to test it.


mhh after reading again :)

the nginx serves the media files from /code/media, i only had an bind mount for this

dhop90 commented 2 days ago

@lovwyr when I access both port 80 and 8000 it takes me to the internal django API Server, and both ports when configured for the ingress route allows me to access https://adventure.domain.duckdns.org/media/profile-pics/dhop-avatar.webp

dhop90 commented 2 days ago

Got it working, I'll provide details tomorrow