问题
Given the following docker containers:
- an
nginxservice that runs an unmodified official nginx:latest image- container name:
proxy
- container name:
- two applications running in separate containers based on modified official php:7.4.1-apache images
- container names:
app1andapp2
- container names:
- all containers
proxy,app1, andapp2are in the same Docker-created network with automatic DNS resolution
With the following example domain names:
local.web.testlocal1.web.testlocal2.web.test
I want to achieve the following behavior:
- serve
local.web.testfromnginxas the default server block - configure
nginxto proxy requests fromlocal1.web.testandlocal2.web.testtoapp1andapp2, respectively, both listening on port80 - configure
nginxto serve all three domain names using a wildcard SSL certificate
I experience two problems:
- I notice the following error in the
nginxlogs:2020/06/28 20:00:59 [crit] 27#27: *36 SSL_read() failed (SSL: error:14191044:SSL routines:tls1_enc:internal error) while waiting for request, client: 172.19.0.1, server: 0.0.0.0:443
- the
mod_rpafseems not to work properly (i.e., theipaddress in theapacheaccess logs is of thenginxserver [e.g.,172.19.0.2] instead of theipof the client that issues the request172.19.0.2 - - [28/Jun/2020:20:05:05 +0000] "GET /favicon.ico HTTP/1.0" 404 457 "http://local1.web.test/" "Mozilla/5.0 (Windows NTndows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36- the output of
phpinfo()for Apache Environment shows that:HTTP_X_REAL_IPlists the clientipSERVER_ADDRlists theapp1containerip(e.g.,172.19.0.4)REMOTE_ADDRshows theproxycontainerip(e.g.,172.19.0.2) instead of the clientip
To make this reproducible, this is how everything is set up. I tried this on my Windows machine so there are two preliminary steps.
Preliminary steps
a. in my
C:\Windows\System32\drivers\etc\hostsfile I added the following:127.0.0.1 local.web.test 127.0.0.1 local1.web.test 127.0.0.1 local2.web.testb. I generated a self-signed SSL certificate with the Common Name set to
*.local.testviaopenssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 365 -keyout localhost.key -out localhost.crtThe
proxyservice setupa. the
nginx.ymlfor thedocker-compose:version: "3.8" services: nginx: image: nginx:latest container_name: proxy ports: - "80:80" - "443:443" volumes: - ./nginx:/etc/nginx/conf.d - ./certs:/etc/ssl/nginx - ./static/local.web.test:/usr/share/nginx/html networks: - proxy networks: proxy: driver: bridgeb. within
./nginxthat is bind mounted at/etc/nginx/conf.dthere is a filedefault.confthat contains:server { listen 80 default_server; server_name local.web.test; location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name local.web.test; location / { root /usr/share/nginx/html; index index.html index.htm; } ssl_certificate /etc/ssl/nginx/localhost.crt; ssl_certificate_key /etc/ssl/nginx/localhost.key; }c. the
./certs:/etc/ssl/nginxbind mounts the folder containing the self-signed certificate and keyd. the
./static/local.web.test:/usr/share/nginx/htmlmakes available a fileindex.htmlthat contains<h1>local.web.test</h1>The
app1andapp2services setupa. the
apache.ymlfor thedocker-compose:version: "3.8" services: app1: build: context: . dockerfile: apache.dockerfile image: app1 container_name: app1 volumes: - ./static/local1.web.test:/var/www/html networks: - exp_proxy app2: build: context: . dockerfile: apache.dockerfile image: app2 container_name: app2 volumes: - ./static/local2.web.test:/var/www/html networks: - exp_proxy networks: # Note: the network is named `exp_proxy` because the root directory is `exp`. exp_proxy: external: trueb. the
apache.dockerfileimage looks like this:# Base image. FROM php:7.4.1-apache # Install dependencies. RUN apt-get update && apt-get install -y curl nano wget unzip build-essential apache2-dev # Clear cache. RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Change working directory, WORKDIR /root # Fetch mod_rpaf. RUN wget https://github.com/gnif/mod_rpaf/archive/stable.zip # Unzip. RUN unzip stable.zip # Change working directory, WORKDIR /root/mod_rpaf-stable # Compile and install. RUN make && make install # Register the module for load. RUN echo "LoadModule rpaf_module /usr/lib/apache2/modules/mod_rpaf.so" > /etc/apache2/mods-available/rpaf.load # Copy the configuration for mod_rpaf. COPY ./apache/mods/rpaf.conf /etc/apache2/mods-available/rpaf.conf # Enable the module. RUN a2enmod rpaf # Set working directory. WORKDIR /var/www/htmlc. the file
./apache/mods/rpaf.confcopied contains:<IfModule mod_rpaf.c> RPAF_Enable On RPAF_Header X-Real-Ip RPAF_ProxyIPs 127.0.0.1 RPAF_SetHostName On RPAF_SetHTTPS On RPAF_SetPort On </IfModule>d. the
./static/local1.web.test:/var/www/htmlbind mounts anindex.phpfile containing:<h1>local1.web.test</h1> <?php phpinfo(); ?>the same goes for
./static/local2.web.test:/var/www/htmle. the
000-default.confvirtual hosts inapp1andapp2are not modified:<VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>Starting the setup
a. start the
proxyserverdocker-compose -f nginx.yml up -d --buildb. start the
app1andapp2servicesdocker-compose -f apache.yml up -d --buildc. check containers to see if
mod_rpafis enableddocker-compose -f apache.yml exec app1 apachectl -t -D DUMP_MODULESd. add two files in
./nginxthat will be available on theproxycontainer at/etc/nginx/conf.dlocal1.web.test.confcontaining
upstream app-1 { server app1; } server { listen 80; server_name local1.web.test; location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name local1.web.test; location / { proxy_pass http://app-1; 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; } ssl_certificate /etc/ssl/nginx/localhost.crt; ssl_certificate_key /etc/ssl/nginx/localhost.key; }- the second file is
local2.web.test.confwith a similar setup (i.e., number1is replaced with2)
e. check the config and restart the
proxycontainer (or reload thenginxserver)docker-compose -f nginx.yml exec proxy nginx -t docker-compose -f nginx.yml exec proxy service nginx reload
The issues:
- when I run
docker logs proxy -fI notice the SSL internal error mentioned above:SSL_read() failed- someone faced a similar error (http2: SSL read failed while sending req in nginx) but in that case, the message more specifically points to the certificate authority
- if I run
docker logs app1 -fand visithttps://local1.web.test, theipin theGETrequest matches theipof theproxycontainer (i.e.,172.19.0.2) and not that of the remote client- I suspect the cuprit is this
RPAF_ProxyIPs 127.0.0.1, but I can't manually fix theipbecause I don't know whatipthe container will get in theexp_proxynetwork - also I can't use the hostname because
RPAF_ProxyIPsexpects anip docker inspect proxyshows"IPAddress": "172.19.0.2"docker inspect app1shows"IPAddress": "172.19.0.4"
- I suspect the cuprit is this
I can't seem to understand what goes wrong and would appreciate your help.
来源:https://stackoverflow.com/questions/62647604/how-to-correctly-use-nginx-as-reverse-proxy-for-multiple-apache-docker-container