15

I have a stack with nginx and PHP to run on Docker Swarm Cluster.

In a moment in my PHP application, I need to get the remote_addr ($_SERVER['REMOTE_ADDR']) which contains the real IP from the client host accessing my webapp.

But the problem is that the IP informed for nginx by docker swarm cluster. It's showed an Internal IP like 10.255.0.2, but the real IP it's the external IP from the client Host (like 192.168.101.151).

How I can solve that?

My docker-compose file:

version: '3'

services:
  php:
    image: php:5.6
    volumes:
      - /var/www/:/var/www/
      - ./data/log/php:/var/log/php5
    networks:
      - backend
    deploy:
      replicas: 1
  web:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - /var/www/:/var/www/
      - ./data/log/nginx:/var/log/nginx
    networks:
      - backend
networks:
  backend:

My default.conf (vhost.conf) file:

server {
    listen          80;
    root            /var/www;
    index           index.html index.htm index.php;

    access_log  /var/log/nginx/access.log  main;
    error_log   /var/log/nginx/error.log error;

    location / {
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        try_files   $uri $uri/ /index.php;
    }

    location = /50x.html {
        root   /var/www;
    }

    # set expiration of assets to MAX for caching
    location ~* \.(js|css|gif|png|jp?g|pdf|xml|oga|ogg|m4a|ogv|mp4|m4v|webm|svg|svgz|eot|ttf|otf|woff|ico|webp|appcache|manifest|htc|crx|oex|xpi|safariextz|vcf)(\?[0-9]+)?$ {
            expires max;
            log_not_found off;
    }

    location ~ \.php$ {
        try_files                   $uri =404;
        fastcgi_index               index.php;
        fastcgi_split_path_info     ^(.+\.php)(/.+)$;
        fastcgi_pass                php:9000;
        include                     fastcgi_params;
        fastcgi_param               SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param               PATH_INFO       $fastcgi_path_info;
        fastcgi_read_timeout        300;
    }
}

My nginx config file:

user  nginx;
worker_processes    3;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

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

    keepalive_timeout   15;
    client_body_buffer_size     100K;
    client_header_buffer_size   1k;
    client_max_body_size        8m;
    large_client_header_buffers 2 1k;

    gzip             on;
    gzip_comp_level  2;
    gzip_min_length  1000;
    gzip_proxied     expired no-cache no-store private auth;
    gzip_types       text/plain application/x-javascript text/xml text/css application/xml;

    log_format  main  '$remote_addr - $remote_user [$time_local]  "$request_filename" "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    include /etc/nginx/conf.d/*.conf;
}
mayconfsbrito
  • 2,085
  • 4
  • 26
  • 45
  • I don't think the containers should know the IP address of the host where they're running. However you can do it with a different approach where you can set an `ENV` variable or even an `ARG` and read them from PHP or using Bash. Just thinking not the best solution – ReynierPM Mar 21 '18 at 20:10
  • @ReynierPM thank's for your attention. I belive that I explain wrong. I have an webapp that need to know the real IP from client, but when the request pass through swarm cluster, the real IP are lost. – mayconfsbrito Mar 21 '18 at 20:21

4 Answers4

23

for those don't want to read all the github thread ( https://github.com/moby/moby/issues/25526 ), the answer that was good for me was to change the config to this :

version: '3.7'
services:
  nginx:
    ports:
      - mode: host
        protocol: tcp
        published: 80
        target: 80
      - mode: host
        protocol: tcp
        published: 443
        target: 81

This still lets the internal overlay network work, but uses some tricks with iptables to forward those ports directly to the container, so the service inside the container see the correct source IP address of the packets.

There is no facility in iptables to allow balancing of ports between multiple containers, so you can only assign one port to one container (which includes multiple replicas of a container).

xrobau
  • 1,085
  • 9
  • 11
Oreste Viron
  • 3,592
  • 3
  • 22
  • 34
  • 3
    It works fine and gives the real client IP. But if I specify the replicas greater than 1, then it always create only 1 replica. Do you have any clue that what's actually I'm missing. Thanks – Abdul Jabbar Oct 11 '18 at 10:53
  • 5
    You can't have more than one replica in host mode: https://docs.docker.com/network/host/ – Grim... Dec 12 '18 at 15:24
  • 2
    Also, you completely disable swarm-routing-mesh for that service, which in most cases is a reverse-proxy behind a floating ip, which requires the routing mesh. so, NOT doing ingress is not a workaround if you need ingress. – sgohl Apr 09 '19 at 15:14
  • its not working for me, I am getting 502 bad gateway. – Sumit Jain Jan 24 '23 at 04:50
  • thanks, it saved my day ! – Ravi Soni Jun 30 '23 at 18:39
  • its not working for me, nginx container could not start - no errors. – user2573099 Jul 29 '23 at 03:22
3

You can't get this yet through an overlay network. If you scroll up from bottom on this long-running GitHub issue, you'll see some options for using bridge networks in Swarm with your proxies to get around this issue for now.

Bret Fisher
  • 8,164
  • 2
  • 31
  • 36
2

changing port binding mode to host worked for me

ports: 
  - mode: host 
    protocol: tcp 
    published: 8082 
    target: 80

however your web front end must listen on a specific host inside swarm cluster i.e.

deploy: 
  placement: 
    constraints: 
      [node.role == manager]
Faraz Partoei
  • 109
  • 1
  • 4
-5

X-Real-IP will be passthrough and you can use it to access client IP. You can look at http://dequn.github.io/2019/06/22/docker-web-get-real-client-ip/ for reference.

Dequn
  • 311
  • 3
  • 7