“10.0.1.50 是什么服务?”

老张盯着屏幕上的报错信息,眉头紧锁。

build failed: cannot find module "10.0.1.50/user-service":
    module lookup disabled by GOPROXY=off

他转头问旁边的新人小李:“你知道 10.0.1.50 是什么服务吗?”

小李一脸茫然。他才入职第三天。

这是老张所在公司——一家中型互联网金融公司——的日常。平台组有 50 多个 Go 微服务,全部用内网 IP 地址作为 import path。没有人觉得有什么不对——直到上周服务器搬迁。


背景:IP 地址做 import path 的"原始时代"

两年前,平台组刚开始用 Go 的时候,一切都很简单。5 个微服务,代码放在自建 GitLab 上,import path 直接写 IP:

import (
    "10.0.1.50/user-service"
    "10.0.1.50/order-service"
    "10.0.1.51/payment-gateway"
)

“能用就行”,当时没人多想。

但两年后,微服务从 5 个增长到了 50 多个。团队从 8 人扩张到 30 人。问题开始浮现:

问题一:IP 地址毫无可读性

// 这段代码你能看懂每个依赖是什么吗?
import (
    "10.0.1.50/user-service"    // ✅ 还行
    "10.0.1.51/pay-gw"          // ❓ 支付网关?
    "10.0.1.52/cfg-svc"         // ❓ 配置中心?
    "10.0.1.53/msg-queue"       // ❓ 消息队列封装?
)

新人入职第一天就要记住"IP 地址 ↔ 服务名"的映射表。老张自己都经常搞混 10.0.1.5210.0.1.53

问题二:服务器搬迁 = import path 大逃亡

上周,运维团队通知:机房要搬迁,GitLab 服务器的 IP 会从 10.0.1.50 段变成 10.0.2.50 段。

老张当时就觉得不对。果然——

$ grep -r "10.0.1.50" --include="*.go" | wc -l
347

347 处引用。 分布在 50 多个微服务中。每个服务的 go.mod、每个源文件的 import 语句,全都要改。

老张带着 4 个工程师,花了整整一周:

  1. 批量替换 import path
  2. 修改所有 go.mod 中的 require 指令
  3. 修改 CI/CD pipeline 中的 build 配置
  4. 全量回归测试
  5. 修复因为"漏改"导致的编译错误

一周的时间,全部浪费在"改地址"这件事上。

问题三:代码截图泄露内网信息

更让老张后怕的是——有一次,一个工程师在技术社区提问时贴了一段代码截图。截图里清清楚楚地写着:

import "10.0.1.50/internal-auth-service"

公司内网 IP 地址,就这样暴露在了公网上。

虽然没有造成安全事故,但信息安全团队找上门来:“你们能不能用个域名?不要用裸 IP。”


探索:调研各种方案

老张开始调研解决方案。

方案一:手写 go-import meta 标签

Go 官方的 vanity URL 机制很简单——在网页上放一个 meta 标签:

<meta name="go-import" content="go.company.com/user-service git http://gitlab.internal/user-service">

理论上可行。但 50 个服务要写 50 个 HTML 页面,还要配 Nginx、申请 HTTPS 证书、维护路由表。

“这不是在给自己找活干吗?“老张放弃了。

方案二:域名直接指向 GitLab

go.company.com 解析到 GitLab 服务器?不行——GitLab 不知道如何处理 ?go-get=1 查询参数。

方案三:使用开源工具

老张找到了几个开源方案,但要么已经停止维护,要么功能太简单不支持自定义域名。


发现:gvu

一个同事在 Hacker News 上看到了 gvu——一个专门做 Go Vanity URL 的托管服务。

老张一开始不太信任:“又一个托管服务?我们自己搭不行吗?”

但同事说:“试试呗,反正免费。不好用再自己搭。”

30 分钟后,老张的态度 180 度转变。


实施:30 分钟搞定 50 个模块

第一步:安装和注册

$ curl -fsSL gomodvanityurls.com/install.sh | bash
$ gvu auth signup

第二步:批量添加路由

老张写了一个简单的脚本,把 50 个微服务的路由批量添加:

#!/bin/bash
SERVICES=(
  "user-service"
  "order-service"
  "payment-gateway"
  "config-service"
  "msg-queue"
  # ... 45 more
)

for svc in "${SERVICES[@]}"; do
  gvu add "go.company.com/$svc" "http://gitlab.internal/platform/$svc.git"
done

因为有 Pro 订阅,50 个路由几分钟就全部添加完毕。

第三步:DNS 配置

在公司 DNS 管理后台添加一条 CNAME 记录:

go.company.com → gomodvanityurls.com

等了几分钟 DNS 生效。

第四步:修改代码

这一步最"痛苦”——要把所有 10.0.1.50/xxx 替换成 go.company.com/xxx。但这是最后一次了。

# 批量替换
find . -name "*.go" -exec sed -i 's|10.0.1.50/|go.company.com/|g' {} +
find . -name "*.go" -exec sed -i 's|10.0.1.51/|go.company.com/|g' {} +
find . -name "*.go" -exec sed -i 's|10.0.1.52/|go.company.com/|g' {} +

第五步:验证

$ go get go.company.com/user-service
go: downloading go.company.com/user-service v1.2.0
go: added go.company.com/user-service v1.2.0

✅ 成功。


效果:三个月后的今天

新人体验

小李入职后的第一个 PR 里写着:

import (
    "go.company.com/user-service"
    "go.company.com/order-service"
    "go.company.com/payment-gateway"
)

不需要查 IP 对照表了。import path 本身就是服务名。

服务器搬迁

上个月 GitLab 服务器又做了一次迁移(从物理机迁到 K8s)。这次老张只做了一个操作:

# 更新所有路由的 target repo
for svc in "${SERVICES[@]}"; do
  gvu remove "$(gvu list --oneline | grep $svc | awk '{print $1}')"
  gvu add "go.company.com/$svc" "http://gitlab.k8s.internal/platform/$svc.git"
done

代码零修改。 所有微服务的 import path 没变,编译不用动,CI/CD 不用动。

通配路由

后来老张发现了通配路由功能,一条规则替代了 50 条:

$ gvu add 'go.company.com/platform/*' 'http://gitlab.k8s.internal/platform/*.git'

新增微服务时,只要 repo 在 platform 组下,自动就能用 go.company.com/platform/xxx 来导入。零配置。


回顾

维度之前(IP 地址)之后(Vanity URL)
可读性10.0.1.50/cfg-svcgo.company.com/config-service
新人上手需要 IP 对照表import path 即服务名
服务器迁移347 处修改,一周工期改 DNS/路由表,代码零修改
安全内网 IP 裸奔只暴露品牌域名
品牌形象像内部实验像正式发布的产品

老张现在逢人就推荐 vanity URL。他最常说的话是:

“import path 是你的代码的门牌号。你搬家的时候,门牌号应该不变。”


如果你也在用 IP 地址或裸 GitHub 路径管理 Go 私有模块,试试 gvu。30 秒注册,30 分钟搞定全公司的 vanity URL。