Skip to content
横幅:容器时代的网络拓扑:当 127.0.0.1 不再指向“家”

容器时代的网络拓扑:当 127.0.0.1 不再指向“家” ​

你有没有遇到过这种情况?

你用 Docker 跑了一个 Redis 容器,端口映射到 6379。本地 .NET 应用用 127.0.0.1:6379 连得好好的,一切正常。

然后你启动了一个 RedisInsight 容器来管理 Redis,配置连接地址的时候,你自然而然地填了 127.0.0.1:6379——结果连不上。

你检查了容器状态,Redis 容器正常运行,端口映射也没问题。但 RedisInsight 容器就是死活连不上。

如果你经历过这个场景,恭喜你,你已经触及了容器时代一个非常核心但又容易被忽略的问题:容器的隔离性改变了“网络边界”的定义。

今天,我们就从这个案例切入,彻底搞清楚 127.0.0.1、host.docker.internal 和容器名之间的本质区别。

旧世界的秩序:一个地址管所有 ​

在传统物理机/虚拟机时代,所有进程都运行在同一个网络命名空间里。127.0.0.1 的含义是明确且单一的——它指向这台机器自身。

┌─────────────────────────────────────────────────────────────────┐
│                      一台物理机                                │
│  所有进程共享同一个网络命名空间                                 │
│  127.0.0.1 = 这台机器自己                                     │
│                                                                 │
│  ┌─────────────┐     ┌─────────────┐                          │
│  │  Redis 服务  │     │  .NET 应用  │                          │
│  │  监听 6379   │←────│  访问       │                          │
│  │             │     │  127.0.0.1:6379                        │
│  └─────────────┘     └─────────────┘                          │
│                                                                 │
│  访问方 .NET 应用 → 127.0.0.1 → 目标方 Redis 服务 ✅           │
└─────────────────────────────────────────────────────────────────┘

在这个世界里,规则很简单:所有程序都住在同一个“家”里,127.0.0.1 就是回家的路,谁都能用。

新世界的分裂:每个容器都有自己的“家” ​

容器技术的核心机制之一是网络命名空间隔离。每个容器拥有自己独立的网络栈——包括自己独立的 127.0.0.1。

这就出现了问题:一个容器里的 127.0.0.1,指向的是它自己,而不是宿主机的其他容器。

我们画一张图看清楚:

┌─────────────────────────────────────────────────────────────────┐
│                      宿主机 (你的电脑)                          │
│  宿主机网络命名空间 → 127.0.0.1 = 宿主机自己                   │
│                                                                 │
│  ┌───────────────────────┐   ┌───────────────────────────────┐│
│  │ 容器网络命名空间 A     │   │ 容器网络命名空间 B            ││
│  │ (Redis 容器)          │   │ (RedisInsight 容器)           ││
│  │ 127.0.0.1 = 容器A自己 │   │ 127.0.0.1 = 容器B自己        ││
│  │ 监听 6379             │   │                               ││
│  │                       │   │ 想访问 Redis                  ││
│  │                       │   │ 但 127.0.0.1 指向自己 ❌     ││
│  │                       │   │ 需要用 host.docker.internal ✓ ││
│  └───────────────────────┘   └───────────────────────────────┘│
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  宿主机进程 (.NET 应用, dotnet run)                     │   │
│  │  127.0.0.1 = 宿主机自己 ✅                              │   │
│  │  访问 127.0.0.1:6379 → 宿主机端口 → 容器映射端口 ✅    │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

现在再看那个 RedisInsight 连不上的问题,答案就很清楚了:

场景访问方位置127.0.0.1 指向谁?访问 Redis 的结果
.NET 本地开发宿主机宿主机自己✅ 通过端口映射访问
RedisInsight 容器容器内部容器自己❌ 找不到 Redis

根本原因:127.0.0.1 的含义从“这台机器”变成了“这个网络命名空间”。同样一个地址,在不同上下文中指向完全不同的实体。

容器网络的三个层级 ​

要彻底解决“该填什么地址”的问题,需要建立分层认知。我们以 RedisInsight 和 .NET 应用作为典型访客,来梳理每一层的规律。

层级一:宿主机网络——我住家里,出门就是 ​

哪些程序住在这里? 直接在宿主机上运行的进程:dotnet run 启动的 .NET 应用、从官网下载的 RedisInsight 桌面版、系统自带的 Redis CLI。

规则是什么? 127.0.0.1 = 宿主机自己。访问容器的方式:通过 -p 映射到宿主机的端口。

实战案例:

bash
# 1. Redis 容器启动,将 6379 映射到宿主机 6379
docker run -d --name my-redis -p 6379:6379 redis:latest

# 2. 宿主机上的 .NET 应用访问 Redis
# appsettings.Development.json:
{
  "Redis": "127.0.0.1:6379,password=123456"
}

# 3. 宿主机上的 RedisInsight 桌面版
# Host 填写:127.0.0.1
┌─────────────────────────────────────────────────────────────────┐
│  层级一:宿主机网络 (Windows)                                  │
│  127.0.0.1 = 宿主机自己                                       │
│                                                                 │
│  ┌─────────────────────┐      ┌──────────────────────────┐    │
│  │  .NET 应用          │      │  RedisInsight 桌面版     │    │
│  │  (dotnet run)       │      │  (.exe 安装)             │    │
│  │  访问 127.0.0.1:6379│      │  Host = 127.0.0.1        │    │
│  └──────────┬──────────┘      └──────────┬───────────────┘    │
│             │                            │                     │
│             └──────────┬─────────────────┘                     │
│                        ↓                                       │
│             ┌─────────────────────┐                           │
│             │  宿主机端口 6379    │ ← 端口映射                 │
│             │  (暴露给宿主机)     │                            │
│             └──────────┬──────────┘                           │
│                        ↓ (Docker 网络转发)                     │
│             ┌─────────────────────┐                           │
│             │  Redis 容器         │                            │
│             │  监听 6379          │                            │
│             └─────────────────────┘                           │
└─────────────────────────────────────────────────────────────────┘

一句话总结:宿主机上的程序都住在“宿主机”这个层级,它们访问容器的统一入口是 127.0.0.1:映射端口。

层级二:容器网络——我住小区里,要找到回家的路 ​

哪些程序住在这里? 容器内部的进程:RedisInsight 容器版、在容器内执行的 Redis CLI。

规则是什么? 容器内的 127.0.0.1 = 容器自己(不是宿主机!)。访问宿主机的方式:host.docker.internal(Windows/Mac)。

实战案例:

bash
# 1. 启动 Redis(映射端口到宿主机)
docker run -d --name my-redis -p 6379:6379 redis:latest

# 2. 启动 RedisInsight 容器
docker run -d --name redisinsight -p 5540:5540 redis/redisinsight:latest

# 3. 在浏览器中打开 http://localhost:5540
# 4. RedisInsight 连接 Redis 时:
#    Host 填写:host.docker.internal  ← 注意!不是 127.0.0.1
#    Port:6379
#    Password:123456
┌─────────────────────────────────────────────────────────────────┐
│  层级二:容器网络 (RedisInsight 容器)                           │
│  127.0.0.1 = 容器自己 ❌ 找不到 Redis                          │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  RedisInsight 容器                                     │   │
│  │  容器内的 127.0.0.1 → 指向容器自己                     │   │
│  │  尝试访问 127.0.0.1:6379 → 自己没有 Redis ❌           │   │
│  │                                                         │   │
│  │  正确做法:host.docker.internal                         │   │
│  │  这个特殊域名 → Docker 自动解析为宿主机 IP             │   │
│  │  host.docker.internal:6379 → 宿主机端口 → Redis ✅     │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              ↓                                  │
│                    host.docker.internal                         │
│                              ↓                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  宿主机 (Windows)                                      │   │
│  │  宿主机端口 6379 ← 通过 -p 6379:6379 映射             │   │
│  │  ↓                                                     │   │
│  │  Redis 容器 (my-redis)                                 │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

为什么 RedisInsight 容器不能用 127.0.0.1?

错误理解正确理解
“容器也是运行在宿主机上的,所以 127.0.0.1 应该指向宿主机”❌ 容器有独立的网络命名空间,它的 127.0.0.1 指向容器自己
“127.0.0.1:6379 应该能访问到 Redis”❌ 除非 Redis 也运行在同一个容器内,否则 127.0.0.1 找不到它
“host.docker.internal 看起来很复杂,应该不需要”✅ 这是 Docker 专门为容器访问宿主机设计的“桥”,必须用

Linux 用户的额外配置:

host.docker.internal 在 Docker Desktop(Windows/Mac)上默认支持。但在 Linux 原生 Docker 环境下,需要手动添加:

bash
docker run -d --name redisinsight -p 5540:5540 \
  --add-host=host.docker.internal:host-gateway \
  redis/redisinsight:latest

层级三:容器间网络——我们都住同一个小区,直接喊名字 ​

哪些程序住在这里? 同一个 Docker 网络内的多个容器:容器化的 .NET 应用、Redis 容器。

规则是什么? 同一 Docker 网络内的容器可以通过容器名互相访问。Docker 提供内置 DNS 服务,自动解析容器名到 IP。

实战案例:

yaml
# docker-compose.yml
version: '3.8'

services:
  redis:
    image: redis:latest
    container_name: my-redis
    command: redis-server --requirepass 123456

  myapp:
    image: myapp:latest
    build: .
    ports:
      - "8080:8080"
    environment:
      - Redis__ConnectionString=redis:6379,password=123456
    # ↑ 关键:用服务名 "redis" 作为主机名
┌─────────────────────────────────────────────────────────────────┐
│  层级三:容器间网络 (Docker Compose 内部网络)                   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │                    Docker 内部网络                         ││
│  │              (Docker 内置 DNS 服务)                        ││
│  │                                                             ││
│  │  ┌──────────────────┐     ┌──────────────────────────┐    ││
│  │  │  Redis 容器      │     │  .NET 应用容器            │    ││
│  │  │  (my-redis)      │     │  (myapp)                  │    ││
│  │  │  监听 6379       │←────│  连接地址:redis:6379    │    ││
│  │  │                  │     │  环境变量注入             │    ││
│  │  └──────────────────┘     └──────────────────────────┘    ││
│  │                                                             ││
│  │  为什么用 "redis"?                                         ││
│  │  → Docker DNS 将服务名 "redis" 解析为容器 IP 172.17.0.x   ││
│  │  → 容器间通信不需要端口映射(内部直接通信)                 ││
│  └─────────────────────────────────────────────────────────────┘│
│                                                                 │
│  外部访问 (宿主机 → 应用)                                       │
│  ┌──────────────┐    ┌──────────────────────────────────┐     │
│  │  宿主机浏览器  │───→│  localhost:8080 (端口映射)      │     │
│  └──────────────┘    │  → 进入 .NET 应用容器            │     │
│                       └──────────────────────────────────┘     │
└─────────────────────────────────────────────────────────────────┘

一句话总结:容器之间“直接喊名字”,Docker 帮你自动翻译成 IP。

三个层级的速查对比 ​

层级代表访客访问 Redis 的地址一句话解释
层级一(宿主机).NET 本地开发、RedisInsight 桌面版127.0.0.1:6379“我住家里,出门就是”
层级二(容器)RedisInsight 容器版host.docker.internal:6379“我住小区里,要找到回家的路”
层级三(容器间).NET 应用容器redis:6379(容器名)“我们都住同一个小区,直接喊名字”

一套通用的思考框架 ​

当你下次再遇到“容器里连不上另一个服务”的问题时,按这个框架一步步拆解:

Step 1:访问方在哪里?

  • 在宿主机上(dotnet run、桌面应用)→ 层级一
  • 在容器内(docker run 启动的)→ 层级二或三

Step 2:目标方在哪里?

  • 映射到了宿主机端口(-p)→ 可通过宿主机访问
  • 在同一 Docker 网络内 → 可通过容器名访问

Step 3:选择正确的地址

  • 访问方在宿主机 → 127.0.0.1:端口
  • 访问方在容器,目标方在宿主机 → host.docker.internal:端口
  • 访问方在容器,目标方在同网络容器 → 容器名:端口

把 RedisInsight 容器的案例套进这个框架:

步骤分析结论
访问方在哪?docker run 启动的在容器内
目标方在哪?Redis 通过 -p 6379:6379 映射到宿主机通过宿主机端口访问
用什么地址?访问方在容器,目标方在宿主机host.docker.internal:6379

容器的本质:一种新的“位置”概念 ​

容器技术重新定义了软件部署中的“位置”。在这个新世界里,我们需要建立三个核心认知:

1. 每个容器都是一个独立的“位置”

容器不是“运行在宿主机上的一个程序”,而是“运行在一个独立的微型系统中”。它的网络环境、文件系统、进程空间都被隔离。127.0.0.1 指向这个微型系统自身,而不是宿主机。

2. 位置之间的通信需要“翻译”

通信路径翻译机制
宿主机 → 容器端口映射(-p)
容器 → 宿主机host.docker.internal
容器 → 容器Docker 网络 + DNS

3. 环境一致性不是绝对的

常说“容器保证环境一致性”,但网络环境的一致性是一个特例。开发环境(宿主机访问容器)和生产环境(容器间访问)的网络拓扑不同,连接地址自然不同。

最佳实践:将连接字符串抽象为环境变量或配置文件:

csharp
// appsettings.Development.json(开发环境)
{
  "Redis": {
    "ConnectionString": "127.0.0.1:6379,password=123456"
  }
}

// appsettings.Production.json(生产环境 - 容器化部署)
{
  "Redis": {
    "ConnectionString": "redis:6379,password=${REDIS_PASSWORD}"
  }
}
环境Redis 地址注入方式
本地开发(dotnet run)127.0.0.1:6379appsettings.Development.json
Docker Composeredis:6379环境变量覆盖
Kubernetesredis-service:6379ConfigMap / Secret

一个思考题 ​

如果有一天,你把 .NET 应用从本地 dotnet run 迁移到 Kubernetes 集群中,Redis 的连接地址该如何变化?为什么?

阶段环境Redis 连接地址原因
阶段一本地开发(dotnet run)127.0.0.1:6379访问方在宿主机,通过端口映射访问容器
阶段二Docker Composeredis:6379访问方在容器内,通过 Docker DNS 解析服务名
阶段三Kubernetesredis-service:6379访问方在 Pod 内,通过 K8s DNS 解析 Service 名

核心规律:随着“位置”的变化,访问地址不断变化,但“谁在访问、从哪访问”的思维框架始终适用。掌握这套思维模型,比记住某个具体地址更有价值。

总结 ​

回到开头的那个问题:为什么同样的 Redis 服务,.NET 应用用 127.0.0.1 而 RedisInsight 容器要用 host.docker.internal?

答案其实很清晰:

  • .NET 应用在宿主机上,127.0.0.1 指向宿主机自己,通过端口映射就能访问 Redis 容器
  • RedisInsight 在容器内,127.0.0.1 指向容器自己,必须用 host.docker.internal 找到“回家的路”

容器改写了“网络边界”的定义。理解谁在访问、访问谁、从哪访问——比记住一个具体的 IP 地址重要得多。这套思维模型,会在你深入云原生的路上反复派上用场。

Released under the MIT License.