729 lines
18 KiB
Go
729 lines
18 KiB
Go
package docker
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/docker/docker/api/types"
|
||
"github.com/docker/docker/api/types/container"
|
||
"github.com/docker/docker/api/types/filters"
|
||
"github.com/docker/docker/api/types/network"
|
||
"github.com/docker/docker/client"
|
||
"github.com/docker/go-connections/nat"
|
||
|
||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||
)
|
||
|
||
// ContainerInfo 容器信息
|
||
type ContainerInfo struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Image string `json:"image"`
|
||
Status string `json:"status"`
|
||
Created int64 `json:"created"`
|
||
Labels map[string]string `json:"labels"`
|
||
}
|
||
|
||
// CreateContainer 创建容器
|
||
func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string, env []string, labels map[string]string, port int) (string, error) {
|
||
cli := GetClient()
|
||
|
||
// 检测是否在容器内运行
|
||
inContainer := isRunningInContainer()
|
||
|
||
// 如果在容器内运行,获取当前容器的网络设置
|
||
var currentNetworkName string
|
||
if inContainer {
|
||
currentNetworkName = getCurrentContainerNetwork(ctx, cli)
|
||
if currentNetworkName != "" {
|
||
// 使用发现的网络替代配置中的网络
|
||
cfg.Network = currentNetworkName
|
||
}
|
||
}
|
||
|
||
// 端口映射 - 将容器的9000端口映射到主机的端口
|
||
hostPort := nat.Port("9000/tcp")
|
||
exposedPorts := nat.PortSet{
|
||
hostPort: struct{}{},
|
||
}
|
||
|
||
// 设置主机端口映射
|
||
portBindings := nat.PortMap{}
|
||
|
||
// 只有在非容器环境下才进行端口映射
|
||
if !inContainer {
|
||
// 如果没有指定端口,则自动分配
|
||
if port <= 0 {
|
||
// 查找同镜像容器的最大端口号并加1
|
||
maxPort, err := findMaxPortForImage(ctx, cli, cfg.ImageName)
|
||
if err != nil {
|
||
// 如果出错,使用默认端口9001
|
||
port = 9001
|
||
} else {
|
||
port = maxPort + 1
|
||
}
|
||
}
|
||
|
||
// 添加端口映射
|
||
portBindings[hostPort] = []nat.PortBinding{
|
||
{
|
||
HostIP: "0.0.0.0",
|
||
HostPort: strconv.Itoa(port),
|
||
},
|
||
}
|
||
}
|
||
|
||
// 添加Redis环境变量
|
||
if cfg.Redis.Host != "" {
|
||
env = append(env, fmt.Sprintf("REDIS_HOST=%s", cfg.Redis.Host))
|
||
env = append(env, fmt.Sprintf("REDIS_PASSWORD=%s", cfg.Redis.Password))
|
||
env = append(env, fmt.Sprintf("REDIS_DB=%d", cfg.Redis.DB))
|
||
}
|
||
|
||
// 设置容器配置
|
||
containerConfig := &container.Config{
|
||
Image: cfg.ImageName,
|
||
Env: env,
|
||
ExposedPorts: exposedPorts,
|
||
Labels: labels,
|
||
}
|
||
|
||
// 设置主机配置
|
||
hostConfig := &container.HostConfig{
|
||
PortBindings: portBindings,
|
||
RestartPolicy: container.RestartPolicy{
|
||
Name: "unless-stopped",
|
||
},
|
||
}
|
||
|
||
// 设置网络配置
|
||
networkingConfig := &network.NetworkingConfig{}
|
||
if cfg.Network != "" {
|
||
// 首先检查网络类型,只在用户自定义网络上分配固定IP
|
||
isUserNetwork := false
|
||
if cfg.Network != "bridge" && cfg.Network != "host" && cfg.Network != "none" {
|
||
// 检查网络是否存在
|
||
_, err := cli.NetworkInspect(ctx, cfg.Network, types.NetworkInspectOptions{})
|
||
if err == nil {
|
||
isUserNetwork = true
|
||
}
|
||
}
|
||
|
||
endpointsConfig := make(map[string]*network.EndpointSettings)
|
||
endpointSettings := &network.EndpointSettings{}
|
||
|
||
// 只在用户自定义网络上尝试分配固定IP
|
||
if isUserNetwork {
|
||
// 自动为容器分配一个递增的IP地址
|
||
nextIP, err := getNextAvailableIPInNetwork(ctx, cli, cfg.Network)
|
||
if err == nil && nextIP != "" {
|
||
endpointSettings.IPAMConfig = &network.EndpointIPAMConfig{
|
||
IPv4Address: nextIP,
|
||
}
|
||
}
|
||
}
|
||
|
||
endpointsConfig[cfg.Network] = endpointSettings
|
||
networkingConfig.EndpointsConfig = endpointsConfig
|
||
}
|
||
|
||
// 创建容器
|
||
resp, err := cli.ContainerCreate(
|
||
ctx,
|
||
containerConfig,
|
||
hostConfig,
|
||
networkingConfig,
|
||
nil, // 平台
|
||
name,
|
||
)
|
||
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return resp.ID, nil
|
||
}
|
||
|
||
// getNextAvailableIPInNetwork 获取网络中下一个可用的IP地址
|
||
func getNextAvailableIPInNetwork(ctx context.Context, cli *client.Client, networkName string) (string, error) {
|
||
// 获取网络信息
|
||
networkResource, err := cli.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法检查网络: %w", err)
|
||
}
|
||
|
||
// 确认网络是否有配置子网
|
||
if len(networkResource.IPAM.Config) == 0 {
|
||
return "", fmt.Errorf("网络没有IPAM配置")
|
||
}
|
||
|
||
// 获取网络子网
|
||
subnet := networkResource.IPAM.Config[0].Subnet
|
||
if subnet == "" {
|
||
return "", fmt.Errorf("网络子网未定义,不能分配固定IP")
|
||
}
|
||
|
||
// 解析子网
|
||
_, ipNet, err := net.ParseCIDR(subnet)
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法解析子网CIDR: %w", err)
|
||
}
|
||
|
||
// 获取网络中的所有容器
|
||
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法列出容器: %w", err)
|
||
}
|
||
|
||
// 获取已使用的IP地址
|
||
usedIPs := make(map[string]bool)
|
||
for _, c := range containers {
|
||
for name, network := range c.NetworkSettings.Networks {
|
||
if name == networkName && network.IPAddress != "" {
|
||
usedIPs[network.IPAddress] = true
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取网络中的所有IP地址,查找最大IP
|
||
var maxIP net.IP
|
||
for ip := range usedIPs {
|
||
ipAddr := net.ParseIP(ip)
|
||
if ipAddr == nil {
|
||
continue
|
||
}
|
||
|
||
// 确保IP在子网内
|
||
if !ipNet.Contains(ipAddr) {
|
||
continue
|
||
}
|
||
|
||
// 更新最大IP
|
||
if maxIP == nil || compareIPs(ipAddr, maxIP) > 0 {
|
||
maxIP = ipAddr
|
||
}
|
||
}
|
||
|
||
// 如果网络中没有容器,使用网关地址的下一个IP
|
||
if maxIP == nil {
|
||
// 获取网关IP
|
||
gateway := networkResource.IPAM.Config[0].Gateway
|
||
if gateway == "" {
|
||
// 如果没有定义网关,使用子网的第一个可用IP
|
||
ip := ipNet.IP.To4()
|
||
if ip == nil {
|
||
return "", fmt.Errorf("无法获取IPv4地址")
|
||
}
|
||
|
||
// 通常第一个可用IP是网关后的地址(x.x.x.1 + 1 = x.x.x.2)
|
||
ip[3]++
|
||
maxIP = ip
|
||
} else {
|
||
gwIP := net.ParseIP(gateway)
|
||
if gwIP == nil {
|
||
return "", fmt.Errorf("无效的网关地址")
|
||
}
|
||
|
||
// 使用网关IP的下一个地址
|
||
maxIP = gwIP.To4()
|
||
if maxIP == nil {
|
||
return "", fmt.Errorf("无法获取IPv4网关地址")
|
||
}
|
||
maxIP[3]++
|
||
}
|
||
} else {
|
||
// 如果有容器,则使用最大IP + 1
|
||
maxIP = incrementIP(maxIP)
|
||
}
|
||
|
||
// 确保新IP仍然在子网内
|
||
if !ipNet.Contains(maxIP) {
|
||
return "", fmt.Errorf("无法分配下一个IP,已达到子网范围上限")
|
||
}
|
||
|
||
return maxIP.String(), nil
|
||
}
|
||
|
||
// compareIPs 比较两个IP地址的大小
|
||
func compareIPs(a, b net.IP) int {
|
||
// 转为IPv4格式
|
||
a4 := a.To4()
|
||
b4 := b.To4()
|
||
|
||
if a4 == nil || b4 == nil {
|
||
// 如果任一IP不是IPv4,则无法比较
|
||
return 0
|
||
}
|
||
|
||
// 逐字节比较
|
||
for i := 0; i < 4; i++ {
|
||
if a4[i] < b4[i] {
|
||
return -1
|
||
} else if a4[i] > b4[i] {
|
||
return 1
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// incrementIP 将IP地址递增1
|
||
func incrementIP(ip net.IP) net.IP {
|
||
// 创建IP的副本
|
||
newIP := make(net.IP, len(ip))
|
||
copy(newIP, ip)
|
||
|
||
// 确保使用IPv4格式
|
||
ipv4 := newIP.To4()
|
||
if ipv4 == nil {
|
||
return newIP
|
||
}
|
||
|
||
// 递增最后一个字节
|
||
for i := 3; i >= 0; i-- {
|
||
ipv4[i]++
|
||
if ipv4[i] > 0 {
|
||
// 如果没有溢出,退出循环
|
||
break
|
||
}
|
||
// 如果溢出(变成0),继续递增上一个字节
|
||
}
|
||
|
||
return ipv4
|
||
}
|
||
|
||
// getNextAvailableIP 获取网络中下一个可用的IP地址
|
||
func getNextAvailableIP(ctx context.Context, cli *client.Client, networkName string) (string, error) {
|
||
// 获取网络信息
|
||
networkResource, err := cli.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法检查网络: %w", err)
|
||
}
|
||
|
||
// 确认网络类型是否为bridge,并获取子网信息
|
||
if networkResource.IPAM.Config == nil || len(networkResource.IPAM.Config) == 0 {
|
||
return "", fmt.Errorf("网络没有IPAM配置")
|
||
}
|
||
|
||
// 获取网络子网
|
||
subnet := networkResource.IPAM.Config[0].Subnet
|
||
if subnet == "" {
|
||
return "", fmt.Errorf("网络子网未定义")
|
||
}
|
||
|
||
// 解析子网
|
||
_, ipNet, err := net.ParseCIDR(subnet)
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法解析子网CIDR: %w", err)
|
||
}
|
||
|
||
// 获取现有容器的IP地址
|
||
existingIPs := make(map[string]bool)
|
||
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法列出容器: %w", err)
|
||
}
|
||
|
||
// 收集所有容器在这个网络中使用的IP
|
||
for _, c := range containers {
|
||
if c.NetworkSettings != nil {
|
||
for name, network := range c.NetworkSettings.Networks {
|
||
if name == networkName && network.IPAddress != "" {
|
||
existingIPs[network.IPAddress] = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确定最大IP地址
|
||
var maxIP net.IP
|
||
for ipStr := range existingIPs {
|
||
ip := net.ParseIP(ipStr)
|
||
if ip == nil {
|
||
continue
|
||
}
|
||
|
||
if !ipNet.Contains(ip) {
|
||
continue // IP不在子网内
|
||
}
|
||
|
||
if maxIP == nil || compareIPs(ip, maxIP) > 0 {
|
||
maxIP = ip
|
||
}
|
||
}
|
||
|
||
// 如果没有找到最大IP,使用子网的第一个可用IP(通常是x.x.x.2,因为x.x.x.1通常是网关)
|
||
if maxIP == nil {
|
||
firstIP := getFirstUsableIP(ipNet)
|
||
if firstIP != nil {
|
||
return firstIP.String(), nil
|
||
}
|
||
return "", fmt.Errorf("无法确定子网的第一个可用IP")
|
||
}
|
||
|
||
// 递增最大IP地址
|
||
nextIP := getNextIP(maxIP)
|
||
|
||
// 确保新IP仍然在子网内
|
||
if !ipNet.Contains(nextIP) {
|
||
return "", fmt.Errorf("无法分配下一个IP,已达到子网范围上限")
|
||
}
|
||
|
||
return nextIP.String(), nil
|
||
}
|
||
|
||
// getNextIP 获取下一个IP地址
|
||
func getNextIP(ip net.IP) net.IP {
|
||
// 创建IP的副本
|
||
nextIP := make(net.IP, len(ip))
|
||
copy(nextIP, ip)
|
||
|
||
// 确保使用IPv4格式
|
||
nextIP = nextIP.To4()
|
||
if nextIP == nil {
|
||
return nil
|
||
}
|
||
|
||
// 从最后一个字节开始递增
|
||
for i := len(nextIP) - 1; i >= 0; i-- {
|
||
if nextIP[i] < 255 {
|
||
nextIP[i]++
|
||
break
|
||
}
|
||
nextIP[i] = 0 // 进位
|
||
}
|
||
|
||
return nextIP
|
||
}
|
||
|
||
// getFirstUsableIP 获取子网中的第一个可用IP
|
||
func getFirstUsableIP(ipNet *net.IPNet) net.IP {
|
||
// 子网的第一个地址通常是网络地址,不可用
|
||
// 第二个地址通常是网关地址,也可能不可用
|
||
// 因此我们从第三个地址开始(通常是x.x.x.2)
|
||
|
||
// 获取子网的起始IP
|
||
ip := ipNet.IP.To4()
|
||
if ip == nil {
|
||
return nil
|
||
}
|
||
|
||
// 创建副本
|
||
firstIP := make(net.IP, len(ip))
|
||
copy(firstIP, ip)
|
||
|
||
// 递增到第三个地址
|
||
thirdOctet := int(firstIP[2])
|
||
fourthOctet := int(firstIP[3]) + 2 // 通常是x.x.x.2
|
||
|
||
// 处理进位
|
||
if fourthOctet > 255 {
|
||
fourthOctet = fourthOctet % 256
|
||
thirdOctet++
|
||
if thirdOctet > 255 {
|
||
// 溢出处理(通常不会发生在正常子网中)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
firstIP[2] = byte(thirdOctet)
|
||
firstIP[3] = byte(fourthOctet)
|
||
|
||
return firstIP
|
||
}
|
||
|
||
// getCurrentContainerNetwork 获取当前容器所在的网络
|
||
func getCurrentContainerNetwork(ctx context.Context, cli *client.Client) string {
|
||
// 获取当前容器的ID
|
||
hostname, err := os.Hostname()
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
|
||
// 查询容器信息
|
||
containerInfo, err := cli.ContainerInspect(ctx, hostname)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
|
||
// 遍历容器的网络配置,获取第一个非空网络名称
|
||
for networkName := range containerInfo.NetworkSettings.Networks {
|
||
if networkName != "bridge" && networkName != "host" && networkName != "none" {
|
||
return networkName // 返回自定义网络名称
|
||
}
|
||
}
|
||
|
||
// 如果只有默认网络,则返回第一个网络名称
|
||
for networkName := range containerInfo.NetworkSettings.Networks {
|
||
return networkName // 返回第一个找到的网络名称
|
||
}
|
||
|
||
return ""
|
||
}
|
||
|
||
// isRunningInContainer 检测当前程序是否在容器内运行
|
||
func isRunningInContainer() bool {
|
||
// 检查环境变量 IS_DOCKER
|
||
isDockEnv := os.Getenv("IS_DOCKER")
|
||
if isDockEnv == "true" || isDockEnv == "1" || isDockEnv == "yes" {
|
||
return true
|
||
}
|
||
|
||
// 通过检查cgroup文件判断
|
||
if _, err := os.Stat("/.dockerenv"); err == nil {
|
||
return true
|
||
}
|
||
|
||
// 检查进程的cgroup信息
|
||
if data, err := os.ReadFile("/proc/1/cgroup"); err == nil {
|
||
return strings.Contains(string(data), "docker")
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// findMaxPortForImage 查找指定镜像的容器已使用的最大端口号
|
||
func findMaxPortForImage(ctx context.Context, cli *client.Client, imageName string) (int, error) {
|
||
// 默认起始端口
|
||
maxPort := 9000
|
||
|
||
// 获取所有容器
|
||
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
|
||
All: true,
|
||
})
|
||
if err != nil {
|
||
return maxPort, err
|
||
}
|
||
|
||
// 遍历容器,查找使用相同镜像且端口映射最大的容器
|
||
for _, c := range containers {
|
||
if c.Image == imageName || strings.HasPrefix(c.Image, imageName+":") {
|
||
for _, port := range c.Ports {
|
||
// 找到与内部9000端口映射的主机端口
|
||
if port.PrivatePort == 9000 && int(port.PublicPort) > maxPort {
|
||
maxPort = int(port.PublicPort)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return maxPort, nil
|
||
}
|
||
|
||
// StartContainer 启动容器
|
||
func StartContainer(ctx context.Context, containerID string) error {
|
||
cli := GetClient()
|
||
return cli.ContainerStart(ctx, containerID, types.ContainerStartOptions{})
|
||
}
|
||
|
||
// StopContainer 停止容器
|
||
func StopContainer(ctx context.Context, containerID string, timeout *time.Duration) error {
|
||
cli := GetClient()
|
||
t := int(timeout.Seconds())
|
||
return cli.ContainerStop(ctx, containerID, container.StopOptions{Timeout: &t})
|
||
}
|
||
|
||
// RemoveContainer 删除容器
|
||
func RemoveContainer(ctx context.Context, containerID string, force bool) error {
|
||
cli := GetClient()
|
||
return cli.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
|
||
Force: force,
|
||
})
|
||
}
|
||
|
||
// ListContainers 列出容器
|
||
func ListContainers(ctx context.Context, filterArgs map[string][]string) ([]ContainerInfo, error) {
|
||
cli := GetClient()
|
||
|
||
// 构建过滤器
|
||
filterSet := filters.NewArgs()
|
||
for k, vals := range filterArgs {
|
||
for _, v := range vals {
|
||
filterSet.Add(k, v)
|
||
}
|
||
}
|
||
|
||
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
|
||
All: true, // 包括未运行的容器
|
||
Filters: filterSet,
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var containerInfos []ContainerInfo
|
||
for _, c := range containers {
|
||
containerInfos = append(containerInfos, ContainerInfo{
|
||
ID: c.ID,
|
||
Name: c.Names[0][1:], // 去掉前面的/
|
||
Image: c.Image,
|
||
Status: c.Status,
|
||
Created: c.Created,
|
||
Labels: c.Labels,
|
||
})
|
||
}
|
||
|
||
return containerInfos, nil
|
||
}
|
||
|
||
// GetContainerLogs 获取容器日志
|
||
func GetContainerLogs(ctx context.Context, containerID string, tail string) (string, error) {
|
||
cli := GetClient()
|
||
|
||
options := types.ContainerLogsOptions{
|
||
ShowStdout: true,
|
||
ShowStderr: true,
|
||
Tail: tail,
|
||
}
|
||
|
||
logs, err := cli.ContainerLogs(ctx, containerID, options)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer logs.Close()
|
||
|
||
// 读取日志内容
|
||
logContent, err := io.ReadAll(logs)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return string(logContent), nil
|
||
}
|
||
|
||
// GetContainerStatus 获取容器状态
|
||
func GetContainerStatus(ctx context.Context, containerID string) (string, error) {
|
||
cli := GetClient()
|
||
|
||
inspect, err := cli.ContainerInspect(ctx, containerID)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
if inspect.State.Running {
|
||
return "running", nil
|
||
} else if inspect.State.Paused {
|
||
return "paused", nil
|
||
} else if inspect.State.Restarting {
|
||
return "restarting", nil
|
||
} else {
|
||
return "stopped", nil
|
||
}
|
||
}
|
||
|
||
// WaitForContainer 等待容器达到指定状态
|
||
func WaitForContainer(ctx context.Context, containerID string) (<-chan container.WaitResponse, <-chan error) {
|
||
cli := GetClient()
|
||
return cli.ContainerWait(ctx, containerID, container.WaitConditionNotRunning)
|
||
}
|
||
|
||
// GetContainerIP 获取容器的IP地址
|
||
func GetContainerIP(ctx context.Context, containerID string, cfg *config.DockerConfig) (string, error) {
|
||
// 如果在Docker环境中运行,需要获取容器真实IP
|
||
if isRunningInContainer() {
|
||
cli := GetClient()
|
||
|
||
inspect, err := cli.ContainerInspect(ctx, containerID)
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法检查容器: %w", err)
|
||
}
|
||
|
||
// 遍历网络配置找到IP地址
|
||
for networkName, network := range inspect.NetworkSettings.Networks {
|
||
if network.IPAddress != "" {
|
||
return network.IPAddress, nil
|
||
}
|
||
// 防止在没有获取到IP的情况下继续循环
|
||
fmt.Printf("Network %s has no IP address\n", networkName)
|
||
}
|
||
return "localhost", fmt.Errorf("未找到容器IP地址")
|
||
} else {
|
||
// 如果不是在Docker环境中运行,使用配置中的主机地址
|
||
return extractHostIP(cfg.Host), nil
|
||
}
|
||
}
|
||
|
||
// GetContainerHost 获取容器的访问地址(格式:ip:port)
|
||
func GetContainerHost(ctx context.Context, containerID string, cfg *config.DockerConfig) (string, error) {
|
||
// 如果在Docker环境中运行,需要获取容器真实IP或容器名
|
||
if isRunningInContainer() {
|
||
cli := GetClient()
|
||
|
||
inspect, err := cli.ContainerInspect(ctx, containerID)
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法检查容器: %w", err)
|
||
}
|
||
|
||
// 使用容器名作为主机名,用于内部网络通信
|
||
containerName := inspect.Name
|
||
if containerName != "" {
|
||
// 移除前导斜杠
|
||
if strings.HasPrefix(containerName, "/") {
|
||
containerName = containerName[1:]
|
||
}
|
||
// 使用容器名:9000作为内部访问地址
|
||
return containerName + ":9000", nil
|
||
}
|
||
|
||
// 备选方案:尝试使用容器IP
|
||
for _, network := range inspect.NetworkSettings.Networks {
|
||
if network.IPAddress != "" {
|
||
// 容器内部访问使用IP:9000格式
|
||
return network.IPAddress + ":9000", nil
|
||
}
|
||
}
|
||
|
||
return "localhost:9000", fmt.Errorf("未找到容器名或IP地址")
|
||
} else {
|
||
// 如果不是在Docker环境中运行,需要获取端口映射
|
||
cli := GetClient()
|
||
|
||
inspect, err := cli.ContainerInspect(ctx, containerID)
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法检查容器: %w", err)
|
||
}
|
||
|
||
hostIP := extractHostIP(cfg.Host)
|
||
|
||
// 查找9000端口的映射
|
||
for _, port := range inspect.NetworkSettings.Ports["9000/tcp"] {
|
||
if port.HostPort != "" {
|
||
// 主机访问使用hostIP:映射端口格式
|
||
return hostIP + ":" + port.HostPort, nil
|
||
}
|
||
}
|
||
|
||
// 如果找不到端口映射,返回默认的localhost:9000
|
||
return hostIP + ":9000", nil
|
||
}
|
||
}
|
||
|
||
// extractHostIP 从Docker主机地址中提取IP
|
||
func extractHostIP(host string) string {
|
||
// 处理http格式URL
|
||
if strings.HasPrefix(host, "http://") {
|
||
host = strings.TrimPrefix(host, "http://")
|
||
} else if strings.HasPrefix(host, "https://") {
|
||
host = strings.TrimPrefix(host, "https://")
|
||
} else if strings.HasPrefix(host, "tcp://") {
|
||
host = strings.TrimPrefix(host, "tcp://")
|
||
}
|
||
|
||
// 如果是unix socket或空值,返回localhost
|
||
if host == "" || strings.HasPrefix(host, "unix://") {
|
||
return "localhost"
|
||
}
|
||
|
||
// 分离端口号
|
||
if index := strings.LastIndex(host, ":"); index != -1 {
|
||
host = host[:index]
|
||
}
|
||
|
||
return host
|
||
}
|