概要

在构建现代化的分布式系统或微服务架构时,如何在多台服务器之间高效地管理流量入口,并实现基于域名的灵活访问?

传统的 Nginx 部署方式往往伴随着繁琐的环境配置和迁移成本,而 Docker 的引入则为这一过程带来了标准化的解决方案。

本文将深入探讨如何利用 Docker 容器化技术,在多台服务器上部署 Nginx 实例,并通过反向代理与 DNS 解析策略,构建一个高可用、易扩展的域名互联访问体系。

一、环境配置

1、软件资源

名称型号数量
Linux服务器Alibaba Cloud Linux 3.2104 LTS 64位2
Web 服务器jonasal/nginx-certbot:2.4.1-nginx1.21.0-alpine2
SpringBoot项目SpringBoot 3.x1
数据库mysql-81
缓存数据库redis:6.0.201
分布式对象存储系统rustfs:latest1
域名阿里云域名2

在这里插入图片描述

2、Docker安装

# 添加 Docker-ce 软件包源
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 修正仓库配置中的版本号(Alibaba Cloud Linux 4 对应 RHEL 8,因此使用 8)
sudo sed -i 's/$releasever/8/g' /etc/yum.repos.d/docker-ce.repo

# 清理并刷新缓存
sudo yum clean all
sudo yum makecache
sudo yum update

# 安装 Docker 社区版及相关组件
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

二、后端服务器

该服务器作为后端服务器:

  • 用于部署后端项目、数据库、缓存、对象存储等。
  • 域名:myserver2.com
  • Docker统一网络: pic-net

在 Linux 服务器上使用 Nginx 作为网关来统一管理 Docker 部署的 Spring Boot、 RustFS 项目,是一个非常经典的架构。

  • Nginx 可以通过路径前缀 或 域名,将请求精准路由到不同的后端服务。

1、针对MySQL 和 Redis:

  • 极致的安全性(零公网暴露):数据库和缓存属于核心内部服务,绝对不应该将端口映射到宿主机暴露给公网。
    • 加入 pic-net 后,可以去掉 -p 端口映射,Spring Boot 项目可以通过容器名(如 容器名称:3306)完美连接它们。
  • 高效的内部通信:容器之间通过 Docker 内置的 DNS 服务进行通信,应用只需将数据库主机名配置为容器名,无需硬编码不固定的内部 IP 地址。
  • 配置与代码分离:使用 Docker Compose 编排时,可以将数据库密码、用户名等敏感信息抽取到 .env 文件中,避免硬编码在代码或镜像里。

2、Docker Compose编排

  • 使用docker-comose.yml统一编排以上文件
services:
  # ==================== Nginx ====================
  nginx-c:
    container_name: nginx-c
    restart: unless-stopped
    image: jonasal/nginx-certbot:2.4.1-nginx1.21.0-alpine
    # network_mode: "host"
    ports:
      - "80:80"
      - "443:443"
    environment:
      CERTBOT_EMAIL: f15176258474@163.com
    volumes:
      - /root/dcs/nginx/nginx:/etc/nginx/user_conf.d
      - /root/dcs/nginx/letsencrypt:/etc/letsencrypt
      - /root/apps/frontend:/apps
    networks:
      - pic-net

  # ==================== Bao-Admin ====================
  bao-admin:
    image: openjdk:17-jdk
    working_dir: /app
    volumes:
      - /root/apps/backend/bao-admin.jar:/app/app.jar
    container_name: bao-admin
    depends_on:
      mysql:
        condition: service_healthy  # 等待 MySQL 健康
      redis:
        condition: service_healthy  # 等待 Redis 健康
    restart: always
    networks:
      - pic-net 
    command: >
      java
      -Xms256m -Xmx512m
      -XX:MaxMetaspaceSize=256m
      -Xss512k
      -XX:+UseSerialGC
      -Dspring.profiles.active=druid-prod
      -Djava.security.egd=file:/dev/./urandom
      -Duser.timezone=Asia/Shanghai
      -jar /app/app.jar
    # Docker 资源限制
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 768M
    ulimits:
      nofile:
        soft: 65536
        hard: 65536

  # ==================== RustFS ====================
  rustfs:
    image: rustfs/rustfs:latest
    container_name: rustfs
    user: "1000:1000"
    restart: always
    networks:
      - pic-net
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - /root/dcs/rustfs/data:/data
      - /root/dcs/rustfs/logs:/var/log/rustfs
    environment:
      - TZ=Asia/Shanghai
      - RUSTFS_ADDRESS=0.0.0.0:9000
      - RUSTFS_CONSOLE_ENABLE=true
      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
      - RUSTFS_ACCESS_KEY=username
      - RUSTFS_SECRET_KEY=password
      - RUSTFS_VOLUMES=/data
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '1.0'
        reservations:
          memory: 1G
          cpus: '0.5'
    healthcheck:
      # 使用 curl 检查根路径,如果返回 403 则视为健康(exit 0),否则视为不健康(exit 1)
      test: ["CMD-SHELL", "curl -sf -o /dev/null -w '%{http_code}' http://localhost:9000/ | grep -q '403' || exit 1"]
      interval: 30s       # 检查间隔
      timeout: 10s        # 超时时间
      retries: 3          # 失败重试次数
      start_period: 10s   # 容器启动后的宽限期

  # ==================== Redis ====================
  redis:
    image: redis:6.0.20
    container_name: redis-server
    networks:
      - pic-net
    ports:
      - "63790:6379"
    volumes:
      - /root/dcs/redis/redis.conf:/usr/local/redis/redis.conf
      - /root/dcs/redis/redis-data:/data
    command: redis-server /usr/local/redis/redis.conf
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
        reservations:
          cpus: '0.1'
          memory: 64M
    healthcheck:
      # 在 docker-compose 中,使用双 $$ 来转义环境变量,让容器内部能正确解析
      test: ["CMD", "redis-cli", "-a", "password", "ping"]
      interval: 5s        # 每 5 秒检查一次
      timeout: 3s         # 超时时间
      retries: 5          # 最多重试 5 次
      start_period: 5s    # 启动后预留 5 秒的初始化时间
    
     

  # ==================== MySQL ====================
  mysql:
    image: mysql:8.0.28
    container_name: mysql8
    restart: always
    networks:
      - pic-net
    ports:
      - "33060:3306"
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 'password'
    volumes:
      - /root/dcs/mysql/data:/var/lib/mysql
      - /root/dcs/mysql/conf:/etc/mysql/conf.d
      - /root/dcs/mysql/logs:/var/log/mysql
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 768M
    ulimits:
      nofile:
        soft: 65535
        hard: 65535

# ==================== Networks ====================
networks:
  pic-net:
    driver: bridge

后端基础设施就形成了一个高内聚、高安全的内网闭环。

  • 外部流量只能通过 Nginx 网关安全地进入。

3、Nginx配置文件

# ==========================================
    # 1. 定义上游服务(使用 Docker 容器名)
    # ==========================================
    upstream springboot_backend {
        server bao-admin:8080;  # 对应你之前配置的 Spring Boot 容器
    }

    upstream rustfs_api {
        server rustfs:9000; # RustFS S3 API 端口 (请根据实际端口修改)
    }

    upstream rustfs_console {
        server rustfs:9001; # RustFS 控制台端口
    }

    server {
        listen 80;
        server_name myserver2.com; # 替换为你的实际域名或公网IP

        # ==========================================
        # 2. Spring Boot 业务接口
        # ==========================================
        location /api/ {
            proxy_redirect off;
            proxy_pass http://springboot_backend/;

            proxy_http_version 1.1;
            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_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;

        }

                # 5. RustFS 对象存储 API
        location /rustfs-api/ {
            proxy_pass http://rustfs_api/;
            proxy_buffering off;
            proxy_request_buffering off;
            client_max_body_size 10240M;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Origin '';
        }

        # 6. RustFS Web 控制台
        location /rustfs-console/ {
            proxy_pass http://rustfs_console/;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header Origin '';
            proxy_buffering off;
        }
    }

三、门户服务器

该服务器用于部署前端项目:

  • 用于部署前端项目,用于反向代理
  • 域名:myserver1.com

1、nginx.conf

# 1. 将所有 HTTP 请求重定向到 HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name myserver1.com;

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

# 2. 配置 HTTPS 服务 (合并了所有 location)
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name alxuan.fun;

    # --- SSL 证书配置 ---
    ssl_certificate /etc/letsencrypt/live/myserver1.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myserver1.com/privkey.pem;

    # --- 推荐的 SSL 安全配置 ---
    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;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=31536000" always;

    client_max_body_size 500m;


    # --- 2. 前端静态资源配置 ---
    location / {
        alias /apps/media_bao/;
        index index.html;
        try_files $uri $uri/ /index.html;
    }

    # --- 3. API 接口代理 --
    location /rustfs/ {
        proxy_pass http://myserver2.com/rustfs-api/;
       
        proxy_buffering off;
   	    proxy_request_buffering off;
	      client_max_body_size 10240M;
    
   	   # 必须传递 Host 头,否则服务器 2 的 Nginx 可能因为 server_name 不匹配而拒绝请求
   	    proxy_set_header Host myserver2.com; 
    
 	      proxy_set_header X-Real-IP $remote_addr;
  	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 	    proxy_set_header Origin '';
     }
 

    location /prod-api/ {

        proxy_pass http://myserver2.com/api/;

        proxy_set_header Host myserver2.com;
        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;
    }
}

四、访问流程图

在这里插入图片描述

常见问题

1、问题1:数据库连接地址

在 Docker 网络中,Spring Boot 的数据库连接 URL 绝对不能使用 localhost。localhost 会指向 Spring Boot 容器自身,导致连接失败(Communications link failure)。

解决方法

  • 使用 MySQL 的容器名(如 mysql_server)作为主机名。
  • SpringBoot项目配置文件改为:
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://mysql8:3306/database-name?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai
                username: username
                password: password

2、问题2:请求重定向

 location /prod-api/ {

        proxy_pass http://myserver2.com/api/;

        proxy_set_header Host myserver2.com;
        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_set_header Host myserver2.com; 这行配置的作用是:
    • 确保了后端服务器能正确地识别出请求的目标是它托管的 myserver2.com 这个站点,从而返回正确的数据。

总结

为了确保多服务器间的域名能够顺利解析和互联

  • 配置域名解析到对应 IP 地址
  • 需要检查云服务商的安全组规则以及服务器内部的防火墙设置,确保 Nginx 容器暴露的端口(80 和 443)对外部流量开放,同时内部服务端口仅对受信任的内网 IP 开放。

在这里插入图片描述

通过这种分层网络策略,既实现了基于域名的公网访问,又保障了后端服务集群的私密性与安全性。

各位再见!这里是 鳄鱼杆的空间,钓……鳄鱼的杆儿!

期待下次再会!

愿你的每一次垂钓之旅都能满载而归。

在这里插入图片描述

更多推荐