Py学习  »  docker

前端开发必会的Nginx知识及结合Docker的项目部署实战

奇舞精选 • 3 月前 • 73 次点击  

起因

截屏2025-03-14 10.00.12

事情的起因来自一封邮件,年前上线了一个项目官网,在过年期间,突然收到了一封来自安全部门邮件,说上线的官网存在以上安全风险,让尽快处理。现在看来解决这个问题很容易,只需要在Nginx上的server 块中配置 server_name为备案域名,再设置一个默认的server模块,匹配所有未明确指定 server_name 的请求并返回403就OK,但是当时对Nginx并不熟悉,导致走了些弯路。所以这篇文章主要是前端在项目部署中,针对常见的Nginx功能的介绍。基于目前前端项目部署方式大多采用Docker的CI/CD工作流来进行,所以也会涉及到Docker以及常见项目部署配置的相关知识。

Nginx介绍

Nginx是由伊戈尔·赛索耶夫在2002年创建的,最初是为了解决C10K问题,也就是同时处理上万个并发连接的问题。Apache服务器当时在处理大量并发时效率不高,所以Nginx采用了事件驱动的异步架构,性能更好,资源消耗更低。后来在2004年公开发布,凭借其轻量级、事件驱动、异步非阻塞的架构,迅速成为高性能 Web 服务器和反向代理的热门选择。

前端常用的Nginx功能

我们将从以下方面介绍前端开发与项目部署中常见的Nginx相关功能,主要涉及:基于Docker的Nginx安装、配置文件、静态资源托管、反向代理、负载均衡、HTTPS 配置(SSL 证书)等

基于Docker的Nginx安装

基于Mac电脑,假设你已经安装好看Docker,通过下面的一行命令就可以安装基于Docker容器的Nginx

docker run -d -p 80:80 --name my-nginx nginx

这段命令用于运行一个 Nginx Docker 容器,并将其端口映射到主机的端口。

  • docker run:这是 Docker 命令,用于创建并运行一个新的容器。

  • -d:这是一个选项,表示以分离模式(detached mode)运行容器。容器将在后台运行,而不会占用当前终端。

  • -p 80:80:这是一个端口映射选项,格式为 主机端口:容器端口, 80:80 表示将主机的端口 80 映射到容器的端口 80。这样,访问主机的端口 80 时,实际上是在访问容器的端口 80。

  • --name my-nginx:这是一个选项,用于为容器指定一个名称。在这里,容器的名称被指定为 my-nginx。 这样你可以通过这个名称来管理和引用这个容器,而不需要使用容器的 ID。

  • nginx:这是要运行的镜像名称。在这里,使用的是官方的 Nginx 镜像。 Docker 会从 Docker Hub 拉取这个镜像(如果本地没有的话),并基于这个镜像创建并运行一个新的容器。

当我们在浏览器访问: http://localhost,若看到欢迎页即安装成功。

截屏2025-03-12 19.46.42
配置文件

当我们在主机上运行my-nginx容器后,可以通过命令:

docker exec -it my-nginx /bin/sh 

来进入到容器中,查看和修改nginx的相关配置,nginx的主配置文件为: /etc/nginx/nginx.conf,执行 cat nginx.conf查看文件为:

user  nginx;
worker_processes  auto;

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


events {
    worker_connections  1024;
}


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

    log_format  main  '$remote_addr - $remote_user [$time_local] "$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;

    keepalive_timeout  65;

    #gzip  on;
    # 引入站点配置
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

这是 Nginx 的核心配置文件,定义了全局配置(如 Worker 进程数、日志格式、事件模型等)。通常不直接在这里配置具体的站点,而是通过 include 指令引入其他配置文件。我们一般不会修改这里的主配置文件,而是通过下面的/etc/nginx/conf.d/ 或 /etc/nginx/sites-available/来进行站点配置文件的配置。

站点配置目录: /etc/nginx/conf.d/ 或 /etc/nginx/sites-available/为什么有两个目录?

  • /etc/nginx/conf.d/:这是一个简单的配置目录,通常用于存放单个站点的配置文件。每个文件通常对应一个站点或服务,文件名以 .conf 结尾。适合小型项目或简单场景。也是我们前端开发最常用的目录。

  • /etc/nginx/sites-available/ 和 /etc/nginx/sites-enabled/:这是一个更灵活的配置管理方式,适合复杂的多站点场景。

    /sites-available/: 存放所有站点的配置文件(相当于“配置仓库”), /sites-enabled/: 存放当前启用的站点配置,这种方式可以方便地启用或禁用站点,而无需删除配置文件。

执行cat /etc/nginx/conf.d/default.conf 查看默认的配置文件

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    root   /usr/share/nginx/html;
    index  index.html index.htm;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        try_files $uri $uri/ /index.html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

我们部署前端项目,通过Nginx实现的相关功能,都是在这个文件中进行相关字段的配置而实现的。

  • server : 用于定义一个虚拟主机(Virtual Host),它决定了 Nginx 如何处理来自不同域名或 IP 的请求。

  • listen 80:监听 80 端口(HTTP 默认端口)。

  • server_name localhost:指定匹配的域名,用于虚拟主机识别,当请求的 Host 头匹配 localhost 时,该服务器块会处理请求。

  • root /usr/share/nginx/html;:指定服务器的根目录,用于查找静态文件。

  • index index.html:设置默认的首页文件: 用于指定默认的索引文件,当请求的路径是目录时,Nginx 会尝试返回 index.html 或 index.htm

  • location 块: 根据请求的 URL 路径定义不同的处理规则。

  • error_page:定义错误页面的处理规则。当服务器返回 500、502、503 或 504 错误时,返回 /50x.html

  • location = /50x.html : 精确匹配 /50x.html 路径,并指定其根目录, 也就是/50x.html 文件会从 /usr/share/nginx/html 目录中查找

根据这些字段的配置就可以解决上面邮件中提到的问题了。只需要在Nginx上的server 块中配置 server_name为备案域名,再设置一个默认的server快,匹配所有未明确指定 server_name 的请求并返回403就可以了。



    
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

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

    location / {
        root   /usr/share/nginx/dist;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

# 默认 server 块,拒绝非法请求
server {
    listen 80 default_server;
    server_name _;
    return 403;
}

上面我们定义了两个server块,只有当来自host为localhost的请求才会匹配第一个server块,第二个是默认的server,匹配任意的host,我们直接返回403状态码。

注意:server_name 是必须的吗?不是必须的,但强烈建议配置。如果 server_name 未指定,Nginx 可能会匹配默认服务器,导致:潜在的安全问题:恶意用户可以通过 IP 直接访问 你的 Nginx 服务器,可能会暴露本应受限的服务。错误的路由: 多个站点可能会被错误地解析到默认的 server 块,导致访问到错误的网站或泄露敏感信息。

静态资源托管

Nginx 采用事件驱动架构,能够高效处理大量并发请求,适合托管静态资源(如 HTML、CSS、JavaScript、图片等),最长见的方式是托管一个单页应用(SPA)。我们将前端打包好的dist文件直接拷贝到Nginx的静态资源目录,并进行Nginx的相关配置就可以实现SPA应用的部署。

  • 在启动的my-nginx docker容器中执行



    
docker cp ./Desktop/dist my-nginx:/usr/share/nginx/

将本地的dist文件拷贝到容器的/usr/share/nginx/目录下,你可以通过docker  exec -it my-nginx /bin/bash 进入容器,查看是否拷贝成功。

  • 在本地修改default.conf 配置文件,然后将其拷贝到Nginx主配置文件下进行覆盖

    server {
        listen       80;
        listen  [::]:80;
        server_name  localhost;

        #access_log  /var/log/nginx/host.access.log  main;
        # 这个静态资源目录进行了修改
         root   /usr/share/nginx/dist;
         index  index.html index.htm;
        location / {
            try_files $uri $uri/ /index.html;
        }
        #error_page  404              /404.html;
        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

         # 设置缓存时间 
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            expires 1y;
            add_header Cache-Control "public";
        }
    }

    # 默认 server 块,拒绝非法请求
    server {
        listen 80 default_server;
        server_name _;
        return 403;
    }

    注意将原来的静态资源目录修改为 root /usr/share/nginx/dist;  这个是刚才我们拷贝的打包资源的目录。

  • 执行覆盖命令

    docker cp ./Desktop/default.conf my-nginx:/etc/nginx/conf.d/default.conf

    执行完成后,重新启动 Docker 容器:docker restart my-nginx, 浏览器访问http://localhost/就可以看到项目部署成功了。

  • try_files 指令的设置为:**try_files $uri $uri/ /index.html;** 当访问指定路径时,$uri:尝试查找请求的文件, $uri/:尝试查找请求的目录,/index.html:如果未找到文件或目录,则返回index.html。SPA 只有一个 index.html,前端路由由 JavaScript 处理,所有它最终都返回 index.html,由前端来解析路径。这适用于 Vue Router、React Router、Angular Router 的 history 模式,避免 404 的问题。

反向代理与负载均衡

为什么前端要了解Nginx的反向代理?

  • 解决跨域问题:对于前后端分离的项目比如:如前端部署在 http://frontend.com,后端在 http://api.example.com),通过 Nginx 将前后端请求统一到同一域名下(如前端 http://example.com,API http://example.com/api),浏览器认为这是同源请求,避免跨域限制。
  • 隐藏后端服务:另一方面可以避免直接暴露后端服务的 IP 或端口存在安全风险,通过反向代理方案,后端服务仅对 Nginx 可见,外部请求通过 Nginx 转发,隐藏真实后端地址。
  • 高并发场景下的负载均衡:高并发场景下,单台后端服务器可能无法处理所有请求。通过反向代理,Nginx 将流量分发到多个后端实例,提升系统吞吐量和容错能力。

如何实现反向代理:

还是基于上面的例子,假如我们有个后端的服务地址为:http://example.com/api, 而前端是本地的localhost,可以通过如下配置实现反向代理:

server {
    listen 80;
    server_name localhost;

    # 前端静态资源
    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;  # 支持 SPA 路由
    }

    # 反向代理到后端 API
    location /api/ {
        proxy_pass http://example.com;  # 后端服务地址
        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_pass:将匹配的请求转发到后端服务。这里需要注意location后面的路径与proxy_pass 的匹配规则,当前的设置会匹配以 /api/ 开头的请求(例如 /api/user/api/data),** proxy_pass**末尾不带/,则会将客户端请求的 URI 完整追加到目标地址后,

    实际的代理路径为:http://example.com/api/user 与http://example.com/api/data, 若 proxy_pass 包含路径且以 / 结尾,Nginx 会替换 location 匹配部分,实际路径为:http://example.com/user,http://example.com/data

  • proxy_set_header:传递客户端真实 IP 和域名信息到服务端。方便后端记录和分析。后端可以通过 X-Real-IP 读取用户 IP,用于日志记录、IP 限制、用户追踪等操作。

负载均衡

要配置 Nginx 实现负载均衡,你需要在 Nginx 配置文件中定义一个 upstream 块,并在服务器块中使用 proxy_pass 指令将请求转发到定义的上游服务器组。以下是一个示例配置,展示了如何配置 Nginx 实现负载均衡:

upstream backend {
    server backend1.example.com weight=3;
    server backend2.example.com;
    server backend3.example.com;
}

server {
    listen       80;
    server_name  example.com;

    # 反向代理到上游服务器组
    location / {
        proxy_pass http://backend;
        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;
    }
}
  • 上游服务器组:使用 upstream 指令定义一个上游服务器组,包含多个后端服务器。
  • 负载均衡策略:默认情况下,Nginx 使用轮询(round-robin)策略进行负载均衡。你可以通过设置权重(weight)来调整服务器的负载分配,权重用于控制请求的分配比例:
    • 权重越高:分配到的请求越多。
    • 默认权重:如果未指定权重,默认为 1。
  • 反向代理:在服务器块中使用 proxy_pass 指令将请求转发到上游服务器组。
HTTPS 配置(SSL 证书)

通过 Nginx 配置网站的 HTTPS 协议访问是一个常见且重要的任务,首先我们准备好SSL/TLS 证书,一般是从权威证书颁发机构(CA)来获取,证书通常包括以下两个文件:

  • 证书文件(如 example.com.crt )
  • 私钥文件(如  example.com.key )

将证书文件拷贝到Nginx服务的目录下一般放在:/etc/nginx/ssl/目录下

进行Nginx的配置:

server {
    listen 443 ssl;
    server_name example.com; 

    # SSL 证书和私钥路径 
    ssl_certificate /etc/nginx/ssl/example.com.crt; 
    ssl_certificate_key /etc/nginx/ssl/example.com.key; 

    # SSL 配置 
    ssl_protocols TLSv1.2 TLSv1.3;  # 推荐使用 TLS 1.2 和 1.3 
    ssl_ciphers HIGH:!aNULL:!MD5;    # 推荐使用安全的加密套件 
    ssl_prefer_server_ciphers on;

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

    # 处理请求 
    location / {
        try_files $uri $uri/ =404;
    }
}

监听443端口,并且通过certificatessl_certificate_key设置对应的证书与key的路径

为了确保所有访问都通过 HTTPS,可以配置 HTTP 请求自动重定向到 HTTPS:

server {
    listen 80;
    server_name example.com; 

    # 重定向所有 HTTP 请求到 HTTPS 
    return 301 https://$host$request_uri;
}

以上就是前端常见的Nginx 的配置方式,下面我们结合docker来实际部署我们项目。

部署SPA项目

假设你已经打包好了你的项目,并且生成了dist文件,下面是文件目录结构:

my-nginx-spa/
├─ dist/
│  ├─ index.html 
│  └─ styles.css 
├─ default.conf
└─ Dockerfile

default.conf

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

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

    location / {
        root   /usr/share/nginx/dist;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

# 默认 server 块,拒绝非法请求
server {
    listen 80 default_server;
    server_name _;
    return 403;
}

Dockerfile

# 使用官方 Nginx 镜像作为基础镜像
FROM nginx:1.25-alpine 

# 将本地的 default.conf 复制到容器中
COPY default.conf /etc/nginx/conf.d/

#将本地的 dist 目录复制到容器中的 Nginx 默认静态文件目录
COPY dist /usr/share/nginx/dist

# 暴露 80 端口
EXPOSE 80

# 启动 Nginx
CMD ["nginx""-g""daemon off;"]

执行docker构建命令打包镜像

docker build -t nginx-spa-image .

基于镜像运行docker容器

docker run --name nginx-spa-container -d -p 80:80 nginx-spa-image

运行后浏览器访问: http://localhost 就可以看到我们部署的项目了。

部署nuxtjs的SSR项目

相比SPA项目,SSR项目的部署要麻烦一点,因为不仅需要一个Nginx服务来托管我们的静态资源,还需要一个Node服务来进行服务端的渲染逻辑。

这里介绍两种部署方案:由于需要两个容器来分别进行node服务与Nginx服务的启动,我们需要使用docker-compose 来进行容器的管理与编排,这就是方案1的思路,当然也可以不使用docker-compose,而是将node与Nginx服务放在一个容器中来简化部署,这就是方案2的部署思路。

方案1:
my-nginx-ssr/
├─ .output/
│  ├─ public/ 
│  └─ server/ 
├─ node/
│  └─ Dockerfile
├─ nginx/
│  └─ nginx.conf 
├─ docker-compose.yml
└─ package.json

nginx.conf

server {
    listen 80;
    server_name localhost;

    # 静态资源处理(由 Nginx 直接提供)
    location /_nuxt/ {
        alias /usr/share/nginx/html/_nuxt/; # 静态资源目录(需与 Docker 挂载路径一致)
        expires 1d;
        add_header Cache-Control "public";
        try_files $uri $uri/ =404;
    }

    # 反向代理到 Node 服务(处理 SSR 请求)
    location / {
        proxy_pass http://node-service:3000; # 使用 Docker 服务名通信
        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;
    }
}

# 默认 server 块,拒绝非法请求
server {
    listen 80 default_server;
    server_name _;
    return 403;
}

Dockerfile

FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 1. 复制依赖清单文件(利用 Docker 层缓存加速构建)
COPY package*.json ./

# 2. 安装生产依赖
RUN npm install 

# 3. 复制构建产物(.output 目录)
COPY .output/ ./.output/

# 4. 暴露服务端口(与 Nuxt 配置的端口一致,默认 3000)
EXPOSE3000

# 5. 启动命令(直接运行 Node 服务)
CMD ["node"".output/server/index.mjs"]

docker-compose.yml

version: '3.8'

services:
# Node.js 服务
node-service:
    build:
      context:.          # 上下文路径为项目根目录
      dockerfile:node/Dockerfile# 指定 Dockerfile 路径
    container_name:nuxt-node
    environment:
      -NODE_ENV=production
    networks:
      -nuxt-network

# Nginx 服务
nginx:
    image:nginx:alpine   # 使用官方 Nginx Alpine 镜像
    container_name:nuxt-nginx
    ports:
      -"80:80"           # 映射宿主机 80 端口到容器 80 端口
    volumes:
      # 挂载 Nginx 配置文件(覆盖默认配置)
      -./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      # 挂载静态资源目录(Nginx 直接提供 _nuxt 内容)
      -./.output/public/_nuxt:/usr/share/nginx/html/_nuxt
    depends_on:
      -node-service      # 确保 Node 服务先启动
    networks:
      -nuxt-network

# 定义共享网络(容器间通过服务名通信)
networks:
nuxt-network:
    driver:bridge

为了减轻node服务的压力,实现静态资源的高效访问,这里利用了Nginx的静态资源托管的能力,将Nuxt的静态资源通过docker-compose的volumes来挂载到Nginx对应的目录上,静态资源处理由 Nginx 直接提供。

需要运行docker-compose命令来进行镜像的打包与容器的启动

#构建镜像并启动容器(后台运行)
docker-compose up -d --build
方案2:
my-nginx-node-ssr/
├─ .output/
│  ├─ public/ 
│  └─ server/ 
├─ node/
│  └─ Dockerfile
├─ nginx/
│  └─ nginx.conf 
└─ package.json

不使用docker-compose,而是将node与Nginx服务放在一个容器中来简化部署的方案:

nginx.conf

server {
    listen 80;
    server_name localhost;

    location /_nuxt/ {
        alias /usr/share/nginx/html/_nuxt/;
        expires 1d;
        add_header Cache-Control "public";
        try_files $uri $uri/ =404;
    }

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}

server {
    listen 80 default_server;
    server_name _;
    return 403;
}

Dockerfile

# 使用 Node.js 官方镜像(Alpine 版本体积更小)
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 1. 复制依赖清单文件(利用 Docker 层缓存加速构建)
COPY package*.json ./

# 2. 安装生产依赖
RUN npm install

# 3. 复制构建产物(.output 目录)
COPY .output/ ./.output/

# 4. 安装 Nginx
RUN apk add --no-cache nginx

# 5. 复制 Nginx 配置文件
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf

# 6. 确保 Nginx 主配置文件包含必要的基本结构
RUN echo "user  nginx; \
worker_processes  auto; \
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; \
    log_format  main  '\$remote_addr - \$remote_user [\$time_local] \"\$request\" ' \
                      '\$status \$body_bytes_sent \"\$http_referer\" ' \
                      '\"\$http_user_agent\" \"\$http_x_forwarded_for\"'; \
    access_log  /var/log/nginx/access.log  main; \
    sendfile        on; \
    keepalive_timeout  65; \
    include /etc/nginx/conf.d/*.conf; \
}"
 > /etc/nginx/nginx.conf


# 7. 检查 Nginx 配置语法
RUN nginx -t

# 8. 复制静态资源到 Nginx 目录
COPY .output/public/_nuxt /usr/share/nginx/html/_nuxt

# 9. 暴露端口
EXPOSE80

# 10. 启动命令
CMD sh -c "npm run start & nginx -g 'daemon off;'"

同样的先运行docker镜像构建命令然后运行docker命令

# 构建镜像
docker build -t nginx-node-image -f node/Dockerfile .

# 基于镜像启动容器
docker run -d -p 80:80 --name nginx-node-container nginx-node-image

总结

通过掌握上述Nginx核心功能及Docker化部署技巧,前端开发者可独立完成从本地开发到生产上线的全链路工作,构建高效、安全、易维护的Web应用。



-END -

如果您关注前端+AI 相关领域可以扫码进群交流



添加小编微信进群😊


关于奇舞团

奇舞团是 360 集团最大的大前端团队,非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/181337
 
73 次点击