在 Azure(容器实例)上使用 ReactJS 应用 运行 进行 Dockerized NGINX 配置

Dockerized NGINX Configuration with ReactJS App Running on Azure (Container Instances)

我有一个相当标准的 ReactJS 前端(使用端口 3000)应用程序,它由 NodeJS 后端服务器(使用端口 5000)提供服务。这两个应用程序都已 Docker 化并且我已经配置了 NGINX 以便代理从前端到服务器的请求。

Docker前端文件(NGINX“内置”):

FROM node:lts-alpine as build

WORKDIR /app

COPY ./package.json ./
COPY ./package-lock.json ./

RUN npm install
COPY . .
RUN npm run build

FROM nginx

EXPOSE 3000
EXPOSE 443
EXPOSE 80

COPY ./cert/app.crt /etc/nginx/
COPY ./cert/app.key /etc/nginx/

ENV HTTPS=true
ENV SSL_CRT_FILE=/etc/nginx/app.crt
ENV SSL_KEY_FILE=/etc/nginx/app.key

RUN rm /etc/nginx/conf.d/default.conf

COPY ./default.conf /etc/nginx/nginx.conf
COPY --from=build /app/build/ /usr/share/nginx/html

CMD ["nginx", "-g", "daemon off;"]

Docker服务器文件:

FROM node:lts-alpine as build

WORKDIR /app
EXPOSE 5000

ENV NODE_TLS_REJECT_UNAUTHORIZED=0
ENV DANGEROUSLY_DISABLE_HOST_CHECK=true
ENV NODE_CONFIG_DIR=./config/

COPY ./package.json ./
COPY ./package-lock.json ./

RUN npm install

COPY . .
CMD [ "npm", "start" ]

此设置的 docker-compose.yml 是

version: '3.8'
services:
  client:
    container_name: client
    depends_on:
      - server
    stdin_open: true
    environment:
      - CHOKIDAR_USEPOLLING=true
      - HTTPS=true
      - SSL_CRT_FILE=/etc/nginx/app.crt
      - SSL_KEY_FILE=/etc/nginx/app.key
    build:
      dockerfile: Dockerfile
      context: ./client
    expose:
      - "8000"
      - "3000"
    ports:
      - "3000:443"
      - "8000:80"
    volumes:
      - ./client:/app
      - /app/node_modules
      - /etc/nginx
    networks:
      - internal-network

  server:
    container_name: server
    build:
      dockerfile: Dockerfile
      context: "./server"
    expose:
      - "5000"
    ports:
      - "5000:5000"
    volumes:
      - /app/node_modules
      - ./server:/app
    networks:
      - internal-network

networks:
  internal-network:
    driver: bridge
    

而且至关重要的是,NGINX default.conf 是

worker_processes auto;

events {
  worker_connections 1024;
}

pid /var/run/nginx.pid;

http {

    include mime.types;

    upstream loadbalancer {
        server server:5000 weight=3;
    }

    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        port_in_redirect off;
        absolute_redirect off;

        return 301 https://$host$request_uri;
    }

    server {
        listen [::]:443 ssl;
        listen 443 ssl;

        server_name example.app* example.co* example.uksouth.azurecontainer.io* localhost*;
        error_page 497 https://$host:$server_port$request_uri;

        error_log /var/log/nginx/client-proxy-error.log;
        access_log /var/log/nginx/client-proxy-access.log;

        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
        ssl_prefer_server_ciphers  on;
        ssl_session_cache          shared:SSL:10m;
        ssl_session_timeout        24h;

        keepalive_timeout 300;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

        ssl_certificate     /etc/nginx/app.crt;
        ssl_certificate_key /etc/nginx/app.key;

        root /usr/share/nginx/html;
        index index.html index.htm index.nginx-debian.html;

         location / {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            try_files $uri $uri/ /index.html;
        }
        
        location /tours {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            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;
            proxy_pass http://loadbalancer;
        }
    }
}

使用此配置我有两个问题:

  1. 通过 运行 docker-compose up -d,此设置在本地构建和部署两个 Docker 容器。当我使用 https://localhost:3000/id 这有效并且数据被检索并正确显示在浏览器中 - 当我键入 http://localhost:3000/id 这被重定向到 http://localhost:443/id这是行不通的。我曾尝试使用 NGINX 命令 port_in_redirect off; absolute_redirect off; 但这没有帮助。如何确保重定向不会编辑端口号? (这在不使用端口号的生产中可能不会成为问题)。

  2. 更大的问题:部署到 Azure 是使用 docker context 和 运行 docker-compose -f ./docker-compose-azure.yml up。这将运行并创建两个 Docker 容器和一个 side-car 进程。 docker-compose-azure.yml 文件是

    版本:'3.8' 服务:

      client:
        image: dev.azurecr.io/example-client
        depends_on:
          - server
        stdin_open: true
        environment:
          - CHOKIDAR_USEPOLLING=true
          - HTTPS=true
          - SSL_CRT_FILE=/etc/nginx/app.crt
          - SSL_KEY_FILE=/etc/nginx/app.key
        restart: unless-stopped
        domainname: "example-dev"
        expose:
          - "3000"
        ports:
          - target: 3000
            #published: 3000
            protocol: tcp 
            mode: host
        networks:
          - internal-network
    
      server:
        image: dev.azurecr.io/example-server
        restart: unless-stopped
        ports:
          - "5000:5000"
        networks:
          - internal-network
    
    networks:
      internal-network:
        driver: bridge
    

如果我不使用 HTTPS 和简单的反向代理 - 上面列出的两个问题就会消失。但是使用上面的配置,调用 Azure FQDN/URL 失败; HTTPS 请求超时“ERR_CONNECTION_TIMED_OUT”,对于 HTTP,找不到站点。我在这里做错了什么?

谢谢你的时间。

我认为您需要 check/update 正确配置 Nginx 文件并确保 SSL 证书文件可用

# http block would be
server {
        listen 80 default_server;
        return 301 https://$server_name$request_uri;
}

并且在 https 服务器块中,您需要更新 location

location /tours {
        proxy_pass http://server:5000;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
}
        
location / {
        try_files $uri $uri/ /index.html;
}

已更新

你的 Nginx 配置文件是

worker_processes auto;

events {
  worker_connections 1024;
}

pid /var/run/nginx.pid;

http {

    include mime.types;

    server {
        listen [::]:443 ssl;
        listen 443 ssl;

        server_name my-redirected-domain.com my-azure-domain.io localhost;

        access_log /var/log/nginx/client-proxy.log;

        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
        ssl_prefer_server_ciphers  on;
        ssl_session_cache          shared:SSL:10m;
        ssl_session_timeout        24h;

        keepalive_timeout 300;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

        ssl_certificate     /etc/nginx/viewform.app.crt;
        ssl_certificate_key /etc/nginx/viewform.app.key;

        root /usr/share/nginx/html;
        index index.html index.htm index.nginx-debian.html;

        location / {
            try_files $uri $uri/ /index.html;
        }
        
        location /tours {
            proxy_pass http://server:5000;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
        }
    }

    server {
        listen 80 default_server;
        return 301 https://$server_name$request_uri;
    }
}

到处使用端口 443 以避免与端口重新映射(可以是预先设置)混淆:

1.) 将 client 容器定义为端口 443 上的 运行:

version: '3.8'
services:
  client:
...
    ports:
      - port: 443
        protocol: TCP

2.) 将 Nginx 定义为 运行 在端口 443 上,使用正确的 TLS 设置,就像您在更新的 nginx.conf

中所做的那样

部署并打开https://<public IP>(您很可能需要在浏览器中添加sec.exception)。

顺便说一句:Azure 有一篇关于使用 TLS 的 Nginx 的非常好的文章(但使用了更高级的设置): https://docs.microsoft.com/en-us/azure/container-instances/container-instances-container-group-ssl

恕我直言,从 http 到 https 的更好重定向是:

server {
    listen         [::]:80;
    return 301 https://$host$request_uri;
}

我认为 Jan Garaj 的回答触及了所有重要的部分。以上是我的看法,力求有针对性的回答。

HTTP 到 HTTPS 重定向

目前 return 301 语句正在使用 $host 变量,该变量仅包含主机名而不是端口信息。要捕获两者,您可以改用 $http_host 变量。

server {
    listen         [::]:80;

    #//307 to preserve POST data
    return 307 https://$http_host$request_uri; 
}

Azure 配置有问题

在 Azure 配置中,你有这个位:

    ports:
      - target: 3000
        #published: 3000
        protocol: tcp 
        mode: host

将 3000 标识为侦听请求的内部客户端端口。但是您必须记住,您内部有一个 NGINX 代理,它只侦听端口 80 或 443(Nginx 配置中的 server 块)。所以这就是您收到 ERR_CONNECTION_TIMED_OUT 错误的原因,因为请求被发送到没有任何监听的端口 3000。

如果要进行 HTTPS 部署,可以将其设置为 443,Nginx 将处理请求。

在 Azure 上启用 HTTP 重定向

最后一点是配置 Azure 部署,这样当向您的 URL 发出 HTTP 请求时,它应该被重定向到对应的 HTTPS。我们已经有了端口 80 的 NGINX 重定向块。

但是,这无济于事。当我们将目标指定为容器内的 443 时,HTTP 请求将尝试命中 443 并被拒绝。This文章最后也提到了相同的内容

Use your browser to navigate to the public IP address of the container group. The IP address shown in this example is 52.157.22.76, so the URL is https://52.157.22.76. You must use HTTPS to see the running application, because of the Nginx server configuration. Attempts to connect over HTTP fail.

如果可以向 Azure 配置添加另一个端口,即端口 80,则可以解决此问题。

     ports:
      - port: 443
        protocol: TCP
      - port: 80
        protocol: TCP

我不确定 Azure 是否允许这样做,但如果允许,那就是最终解决方案。