微服务架构中的Docker Compose与Golang实践

微服务架构中的Docker Compose与Golang实践 关于微服务及其管理的主题。 由于缺乏好的资料,我在此寻求帮助。

我正在基于微服务架构构建一个应用程序,并且为了开发,我决定使用 docker compose 功能。我们目前有使用 Java Spring 编写的服务,但未来也会使用其他语言。

每个服务都位于不同的代码仓库中。 每个服务都有自己的 Dockerfiledocker-compose.yaml 文件。

当我开发某个特定服务时,我希望在 docker 环境 中运行其他服务,并通过 Kafka 或 gRPC 与它们通信。

我遇到了许多与环境变量以及网络/DNS 服务发现相关的问题。

  1. 我应该在 .env 文件中包含什么内容?
  2. 与特定服务相关的环境文件应该放在哪里(放在其代码仓库中,还是放在我启动它的代码仓库中?)
  3. 如何在 docker compose 中管理 DNS?当然,我可以提供服务的名称,它会被解析,但是如何为运行在 docker swarm 中的服务提供我正在开发的服务的 IP 和端口?

问题可能表述得不够清晰,我只是想听听关于设计/文件夹/项目结构的建议,以实现管理此类应用程序的便利性和灵活性。

假设我们在 产品服务仓库 中,我们有一个 docker-compose.yaml 文件来运行其他服务,例如 购物车服务

    cart-service:
        build:
            context: ../cart-service
            dockerfile: Dockerfile
        container_name: cart-service
        ports:
            - "${CART_SERVICE_PORT:?error}:8080"
        env_file:
            -
                path: ../cart-service/docker/app-env.txt
                required: false
        depends_on:
            cart-service-db:
                restart: true
                condition: service_healthy
            kafka:
                condition: service_healthy
    #

这是一个好的方法吗?需要改变什么?


更多关于微服务架构中的Docker Compose与Golang实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于微服务架构中的Docker Compose与Golang实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


针对你的微服务开发环境配置,以下是一些具体的实践方案:

1. 环境变量管理

.env 文件内容示例

# 全局环境变量
COMPOSE_PROJECT_NAME=product-service-dev
NETWORK_NAME=ms-network

# 服务端口
PRODUCT_SERVICE_PORT=8080
CART_SERVICE_PORT=8081
USER_SERVICE_PORT=8082

# Kafka配置
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
KAFKA_GROUP_ID=product-service-group

# 数据库配置
POSTGRES_HOST=postgres
POSTGRES_PORT=5432

环境文件位置建议

# 项目结构示例
product-service/
├── .env                    # 开发环境变量
├── docker-compose.yml
├── config/
│   ├── dev.env            # 开发专用配置
│   └── test.env           # 测试环境配置
├── internal/
│   └── config/
│       └── config.go      # Go配置结构
└── cmd/
    └── main.go

2. Go服务配置示例

// internal/config/config.go
package config

import (
    "os"
    "strconv"
)

type Config struct {
    ServiceName string
    Port        int
    Kafka       KafkaConfig
    Database    DatabaseConfig
}

type KafkaConfig struct {
    BootstrapServers string
    GroupID         string
    Topics          []string
}

type DatabaseConfig struct {
    Host     string
    Port     int
    Username string
    Password string
    Database string
}

func LoadConfig() *Config {
    port, _ := strconv.Atoi(getEnv("PORT", "8080"))
    
    return &Config{
        ServiceName: getEnv("SERVICE_NAME", "product-service"),
        Port:        port,
        Kafka: KafkaConfig{
            BootstrapServers: getEnv("KAFKA_BOOTSTRAP_SERVERS", "localhost:9092"),
            GroupID:         getEnv("KAFKA_GROUP_ID", "default-group"),
            Topics:          []string{"products", "orders"},
        },
        Database: DatabaseConfig{
            Host:     getEnv("DB_HOST", "localhost"),
            Port:     5432,
            Username: getEnv("DB_USER", "postgres"),
            Password: getEnv("DB_PASSWORD", ""),
            Database: getEnv("DB_NAME", "products"),
        },
    }
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

3. Docker Compose网络配置

# docker-compose.yml
version: '3.8'

networks:
  ms-network:
    driver: bridge
    name: ${NETWORK_NAME:-ms-network}

services:
  product-service:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: ${COMPOSE_PROJECT_NAME:-product-service}
    ports:
      - "${PRODUCT_SERVICE_PORT:-8080}:8080"
    environment:
      - SERVICE_NAME=product-service
      - PORT=8080
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
      - CART_SERVICE_URL=http://cart-service:8081
      - USER_SERVICE_URL=http://user-service:8082
    env_file:
      - .env
      - ./config/dev.env
    networks:
      - ms-network
    depends_on:
      kafka:
        condition: service_healthy
      postgres:
        condition: service_healthy

  cart-service:
    image: cart-service:latest  # 或使用build指向本地
    container_name: cart-service
    ports:
      - "${CART_SERVICE_PORT:-8081}:8081"
    environment:
      - SERVICE_NAME=cart-service
      - PORT=8081
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
    networks:
      - ms-network

  kafka:
    image: confluentinc/cp-kafka:latest
    container_name: kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    networks:
      - ms-network
    healthcheck:
      test: ["CMD", "kafka-topics", "--list", "--bootstrap-server", "localhost:9092"]
      interval: 30s
      timeout: 10s
      retries: 3

4. Go服务发现示例

// internal/client/service_discovery.go
package client

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

type ServiceClient struct {
    baseURL    string
    httpClient *http.Client
}

func NewServiceClient(serviceName string, port string) *ServiceClient {
    // Docker Compose中直接使用服务名作为主机名
    baseURL := fmt.Sprintf("http://%s:%s", serviceName, port)
    
    return &ServiceClient{
        baseURL: baseURL,
        httpClient: &http.Client{
            Timeout: 30 * time.Second,
        },
    }
}

func (c *ServiceClient) GetCart(ctx context.Context, cartID string) ([]byte, error) {
    url := fmt.Sprintf("%s/api/carts/%s", c.baseURL, cartID)
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    
    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    // 处理响应...
    return nil, nil
}

5. 多仓库开发配置

# 开发环境docker-compose.override.yml
version: '3.8'

services:
  product-service:
    build:
      context: .
      dockerfile: Dockerfile.dev  # 开发专用Dockerfile
    volumes:
      - .:/app
      - /app/vendor
    command: air  # 使用air实现热重载

  cart-service:
    build:
      context: ../cart-service  # 指向其他仓库
      dockerfile: Dockerfile.dev
    volumes:
      - ../cart-service:/app
    environment:
      - DEBUG=true

6. 健康检查与依赖管理

// cmd/healthcheck/main.go
package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "time"
)

func main() {
    services := map[string]string{
        "cart-service":  "http://cart-service:8081/health",
        "user-service":  "http://user-service:8082/health",
        "kafka":         "http://kafka:9092",
    }
    
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
    defer cancel()
    
    for name, url := range services {
        if err := checkService(ctx, name, url); err != nil {
            fmt.Printf("Service %s not ready: %v\n", name, err)
            os.Exit(1)
        }
    }
    
    fmt.Println("All services are ready")
}

func checkService(ctx context.Context, name, url string) error {
    client := &http.Client{Timeout: 5 * time.Second}
    
    for {
        select {
        case <-ctx.Done():
            return fmt.Errorf("timeout waiting for %s", name)
        default:
            req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
            if err != nil {
                return err
            }
            
            resp, err := client.Do(req)
            if err == nil && resp.StatusCode == http.StatusOK {
                resp.Body.Close()
                return nil
            }
            
            time.Sleep(2 * time.Second)
        }
    }
}

这个配置方案确保了在Docker Compose环境中,各服务可以通过服务名直接通信,环境变量分层管理,并且支持多仓库开发。

回到顶部