需要帮助在 Docker 容器中的 VPS 上使用 nginx-proxy 代理 React 和 NodeJS 应用程序

Cat*_*ar4 5 vps nginx node.js docker reactjs

我想做的是在 VPS 上使用 Nestjs + React + MySQL + Nginx 堆栈部署一个 dockerized monorepo 项目(使用 NX 作为 monorepo 框架)。我希望nginx代理监听主机的88端口(因为另一个堆栈使用80端口,这是一个我不敢碰的旧堆栈)。VPS的操作系统是CentOS 7。

我会尝试省略构建(Dockerfile)的大部分细节,但知道构建可以工作,它都在我的本地环境中工作(主要是因为我不使用 nginx-proxy 进行本地开发)并且我知道这要么是我的 Docker 配置(我使用 docker-compose)的问题,要么是主机网络的问题。

这是堆栈的“鸟瞰图”:

  • React-frontend容器正在容器中的端口4200上运行react应用程序(使用nx服务react-frontend),将端口4200暴露给主机
  • backend-api 容器正在容器的端口 3333 上运行 Nodejs 应用程序(使用 Nodejs 入口点),将该端口暴露给主机
  • 一个 MySQL 容器,运行一个 mysql 服务器,该服务器运行在容器的端口 3306 上,暴露在主机的端口 3307 上
  • 使用 jwilder/nginx-proxy docker 镜像(我也尝试使用 nginxproxy/nginx-proxy docker 镜像)的 nginx-proxy 监听主机的 88 端口,并通过代理传递将请求重定向到react-frontend容器(这是部分我失败了)。

这是我的“compose-prod.yml”docker-compose 文件:

version: "3.7"

networks:
  corp:
    driver: bridge
  nginx-proxy:
    external:
      name: nginx-proxy

volumes:
  backend-db-volume:
    driver: local

services:
  nginx-proxy:
    image: jwilder/nginx-proxy # also tried nginxproxy/nginx-proxy image
    container_name: nginx-proxy
    networks:
      - corp
      - nginx-proxy
    environment:
      HTTP_PORT: 88
    ports:
      - "88:88" # also tried "88:80" but that gives me "connection refused" in the browser
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

  backend-db:
    image: backend-db
    hostname: backend-db
    restart: unless-stopped
    volumes:
      - backend-db-volume:/var/lib/mysql
    networks:
      - corp
    build:
      context: ./apps/backend-db
      dockerfile: ./Dockerfile
    ports:
      - 3307:3306
    expose:
      - 3306

  backend-api:
    container_name: backend-api
    depends_on:
      - backend-db
    build:
      context: ./
      cache_from:
        - base-image:nx-base
      dockerfile: ./apps/backend-api/Dockerfile
      args:
        NODE_ENV: "production"
        BUILD_FLAG: ""
    image: backend-api:nx-dev
    ports:
      - "3333:3333"
    environment:
      NODE_ENV: "production"
      PORT: 3333
      [... other env configs ommitted, like DB variables, etc.]
    networks:
      - corp
    restart: on-failure

  react-frontend:
    container_name: react-frontend
    build:
      context: ./
      dockerfile: ./apps/react-frontend/Dockerfile
      args:
        NODE_ENV: "production"
        BUILD_FLAG: ""
    image: react-frontend:nx-dev
    environment:
      VIRTUAL_HOST: react-frontend # note that my domain is react-frontend.com, obfuscated ofc ... which I also tried using in VIRTUAL_HOST config
      VIRTUAL_PORT: 4200
      NGINX_PROXY_CONTAINER: nginx-proxy
      NODE_ENV: "production"
      [...other env configs ommitted]
    ports:
      - "4200:4200"
    expose:
      - 4200
    networks:
      - nginx-proxy
      - corp
    restart: on-failure
Run Code Online (Sandbox Code Playgroud)

nginx-proxy 容器自动检测使用 VIRTUAL_HOST env 运行的容器。启用变量,为 compose-prod.yml 文件中的配置生成配置。现在,我使用“docker exec nginx-proxy cat /etc/nginx/conf.d/default.conf”命令生成的配置是这样的:

# nginx-proxy version : 1.0.1-6-gc4ad18f
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;
}

# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port {
  default $http_x_forwarded_port;
  ''      $server_port;
}

# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
  default upgrade;
  '' close;
}

# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparam
ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
# Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto
map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl {
  default off;
  https on;
}

gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent" '
                 '"$upstream_addr"';
access_log off;
                ssl_protocols TLSv1.2 TLSv1.3;
                ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
                ssl_prefer_server_ciphers off;
error_log /dev/stderr;
resolver 127.0.0.11;
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Original-URI $request_uri;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        server_tokens off;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        return 503;
}
        # react-frontend
upstream react-frontend {
        ## Can be connected with "react-frontend_corp" network
        # react-frontend
        server <IP of react-frontend container on Docker network>:4200;
        # Cannot connect to network 'nginx-proxy' of this container
        # Cannot connect to network 'react-frontend_corp' of this container
        ## Can be connected with "nginx-proxy" network
        # react-frontend
        server <IP of react-frontend container on Docker network>:4200;
}
server {
        server_name react-frontend;
        listen 88 ;
        access_log /var/log/nginx/access.log vhost;
        location / {
                proxy_pass http://react-frontend;
        }
}

Run Code Online (Sandbox Code Playgroud)

当我访问“example.com:88”时,我在浏览器中收到从 nginx 返回的“503 服务暂时不可用”页面,并且我在 nginx 的访问日志中看到了这一点:

nginx-proxy     | nginx.1     | example.com xx.yy.zz.ip - - [06/Jul/2022:16:48:12 +0000] "GET / HTTP/1.1" 503 592 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
nginx-proxy     | nginx.1     | example.com xx.yy.zz.ip - - [06/Jul/2022:16:48:12 +0000] "GET /favicon.ico HTTP/1.1" 503 592 "http://example.com:88/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"

Run Code Online (Sandbox Code Playgroud)

我省略了 Dockerfiles,因为 nginx-proxy 容器没有构建,它是从图像中获取的,并且所有构建都工作......这是部署给我带来了麻烦。

有人对我所缺少的有任何指示吗?我应该检查什么?这是一个个人项目,尽管我可以作为开发人员四处走动,但 Docker 网络/部署有时仍然让我感到困惑。

编辑:我在这里添加 VPS(主机)vhost 配置(nginx)...所以也许我可以使用此配置进行代理传递...我将如何修改此配置,以便我可以将请求代理传递到“例如“docker 容器暴露端口 4200(而不是 VPS 上的根目录)?

# configuration file /etc/nginx/conf.d/users/example.conf:
proxy_cache_path /var/cache/ea-nginx/proxy/example levels=1:2 keys_zone=example:10m inactive=60m;

#### main domain for example ##
server {
    server_name example.com www.example.com mail.example.com;
    listen 80;
    listen [::]:80;

    include conf.d/includes-optional/cloudflare.conf;

    set $CPANEL_APACHE_PROXY_PASS $scheme://apache_backend_${scheme}_51_222_24_216;

    # For includes:
    set $CPANEL_APACHE_PROXY_IP 51.222.24.216;
    set $CPANEL_APACHE_PROXY_SSL_IP 51.222.24.216;

    set $CPANEL_PROXY_CACHE example;
    set $CPANEL_SKIP_PROXY_CACHING 0;

    listen 443 ssl;
    listen [::]:443 ssl;

    ssl_certificate /var/cpanel/ssl/apache_tls/example.com/combined;
    ssl_certificate_key /var/cpanel/ssl/apache_tls/example.com/combined;

    ssl_protocols TLSv1.2 TLSv1.3;
    proxy_ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
    proxy_ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;

    root /home/example/public_html;

    location /cpanelwebcall {
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass http://127.0.0.1:2082/cpanelwebcall;
    }

    location /Microsoft-Server-ActiveSync {
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass http://127.0.0.1:2090/Microsoft-Server-ActiveSync;
    }

    location = /favicon.ico {
        allow all;
        log_not_found off;
        access_log off;
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }

    location / {
        proxy_cache $CPANEL_PROXY_CACHE;
        proxy_no_cache $CPANEL_SKIP_PROXY_CACHING;
        proxy_cache_bypass $CPANEL_SKIP_PROXY_CACHING;

        proxy_cache_valid 200 301 302 60m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout http_429 http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 1;
        proxy_cache_lock on;

        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }


    include conf.d/server-includes/*.conf;
    include conf.d/users/example/*.conf;
    include conf.d/users/example/example.com/*.conf;
}
server {
    listen 80;
    listen [::]:80;

    listen 443 ssl;
    listen [::]:443 ssl;

    ssl_certificate /var/cpanel/ssl/apache_tls/example.com/combined;
    ssl_certificate_key /var/cpanel/ssl/apache_tls/example.com/combined;

    server_name  cpanel.example.com cpcalendars.example.com cpcontacts.example.com webdisk.example.com webmail.example.com;

    include conf.d/includes-optional/cloudflare.conf;

    set $CPANEL_APACHE_PROXY_PASS $scheme://apache_backend_${scheme}_51_222_24_216;

    # For includes:
    set $CPANEL_APACHE_PROXY_IP 51.222.24.216;
    set $CPANEL_APACHE_PROXY_SSL_IP 51.222.24.216;

    location /.well-known/cpanel-dcv {
        root /home/example/public_html;
        disable_symlinks if_not_owner;
    }

    location /.well-known/pki-validation {
        root /home/example/public_html;
        disable_symlinks if_not_owner;
    }

    location /.well-known/acme-challenge {
        root /home/example/public_html;
        disable_symlinks if_not_owner;
    }

    location / {
        # Force https for service subdomains
        if ($scheme = http) {
            return 301 https://$host$request_uri;
        }

        # no cache
        proxy_cache off;
        proxy_no_cache 1;
        proxy_cache_bypass 1;

        # pass to Apache
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }
}

Run Code Online (Sandbox Code Playgroud)

Sil*_*ity 1

如果您想使用访问您的应用程序,example.com或者www.example.com您必须设置server_name example.com *.example.com;. 您也可以在使用 docker-compose 时使用 DNS 访问 docker 容器,根据您的情况backend-api:3333react-frontend:4200. 您的配置中几乎没有更正。

server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        server_tokens off;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        return 503;
}
        # react-frontend
upstream frontends {
        server react-frontend:4200;
        server react-frontend:4200;
}
upstream backends {
        server backend-api:3333;
        server backend-api:3333;
}
server {
        server_name 127.0.0.1 example.com *.example.com;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        location / {
                proxy_pass http://frontends;
        }
        # if required , only then use it otherwise remove it [for direct api calls]
        location /api/v1 {
                proxy_pass http://backends;
        }
}
Run Code Online (Sandbox Code Playgroud)

人们可以根据需要添加更多选项或配置。503由于server_name _上面代码片段中的配置,我们看到了默认页面。如果需要,可以对其进行配置。(如下面的片段)

server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        server_tokens off;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        return 403 "..Ops";
}
Run Code Online (Sandbox Code Playgroud)

不确定,如果您在一个撰写文件中运行所有内容,为什么需要两个网络。如果需要两个网络,下面是 docker-compose 文件和nginx.conf.

docker-compose.yaml

version: "3.7"
networks:
  corp:
    driver: bridge
  nginx-proxy:
    external:
      name: nginx-proxy

services:
  nginx-proxy:
    container_name: nginx-proxy
    image: nginx
    ports:
      - 3210:80
    networks:
      - nginx-proxy
      - corp
    volumes:
    - another-nginx.conf:/etc/nginx/conf.d/another-nginx.conf

  react-frontend:
    container_name: react-frontend
    image: httpd
    ports:
      - 3211:80
    networks:
      - nginx-proxy
      - corp
  
  backend-x:
    container_name: backend
    image: nginx
    ports:
      - 3212:80
    networks:
      - corp
  
  db-x:
    container_name: db
    image: httpd
    ports:
      - 3213:80
    networks:
      - corp
Run Code Online (Sandbox Code Playgroud)

another-nginx.conf

server {
    server_name _; # This is just an invalid value which will never trigger on a real hostname.
    server_tokens off;
    listen 88;
    access_log /var/log/nginx/access.log;
    return 503;
}
    # react-frontend
upstream frontends {
    server react-frontend:80;
}
upstream backends {
    server backend:80;
}
server {
    server_name 127.0.0.1 localhost example.com *.example.com;
    listen 80;
    access_log /var/log/nginx/access.log;
    location / {
        proxy_pass http://frontends;
    }
    # if required , only then use it otherwise remove it [for direct api calls]
    location /api/v1 {
        proxy_pass http://backends;
    }
}
Run Code Online (Sandbox Code Playgroud)

nginx-proxy如果不存在则创建网络。

docker network create nginx-proxy