How to connect to mongodb running inside one container from golang app container

huangapple go评论83阅读模式
英文:

How to connect to mongodb running inside one container from golang app container

问题

func GetDatabase() (database *mongo.Database, ctx context.Context, err error) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://mongodb:27017"))
	if err != nil {
		log.Println("数据库连接错误", err)
		return nil, nil, err
	}

	err = client.Ping(context.TODO(), readpref.Primary())
	if err != nil {
		log.Println("错误", err)
		return
	}
	log.Println("连接成功并进行了 ping 操作。")

	dbName := GetDatabaseName()
	database = client.Database(dbName)

	log.Println(dbName, database.Name())
	return
}

这是一个用于检查数据库连接的函数。根据互联网上的一些建议,我尝试使用容器名称而不是 localhost。请提供有关 Dockerfile 或 docker-compose 文件的建议。

Dockerfile

FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
ENV CGO_ENABLED=0 
RUN go build -o main .

FROM alpine:latest
COPY --from=builder /app ./

EXPOSE 8080
ENTRYPOINT ["./main"]

docker-compose

version: '3.7'
services:
  db:
    image: mongo
    restart: always
    platform: linux/x86_64
    networks:
      - default
    ports:
      - "27017:27017"
    container_name: mongodb

  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    depends_on:
      - db

运行这两个容器后,应用容器将会报数据库连接错误。

英文:
func GetDatabase() (database *mongo.Database, ctx context.Context, err error) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://mongodb:27017"))
	if err != nil {
		log.Println("database connection error", err)
		return nil, nil, err
	}

	err = client.Ping(context.TODO(), readpref.Primary())
	if err != nil {
		log.Println("err", err)
		return
	}
	log.Println("Successfully connected and pinged.")

	dbName := GetDatabaseName()
	database = client.Database(dbName)

	log.Println(dbName, database.Name())
	return
}

This golang app is running on one container and mongodb on other.
Above is my function for checking the database connection. After reading some suggestions on internet I am trying to use container name instead of localhost.
Please provide your inputs on Dockerfile or docker-compose file.

Dockerfile

FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
ENV CGO_ENABLED=0 
RUN go build -o main .

FROM alpine:latest
COPY --from=builder /app ./

EXPOSE 8080
ENTRYPOINT ["./main"]

docker-compose

version: '3.7'
services:
  db:
    image: mongo
    restart: always
    platform: linux/x86_64
    networks:
      - default
    ports:
      - "27017:27017"
    container_name: mongodb

  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    depends_on:
      - db

on running these two containers, app container will give db connection error.

答案1

得分: 1

你有正确的服务名称。正如在这里所说的那样,你可以使用dbmongodb作为主机名。(你不需要设置用户名和密码...)

但是你的代码中有一个错误。你只是初始化了一个新的客户端,但没有连接到它。你可以使用Connect而不是NewClient,或者使用client.Connect
此外,你在使用context.Todo而不是带有超时的ctx(这不是什么大问题,但还是要注意)。

你应该这样做:

func GetDatabase() (database *mongo.Database, ctx context.Context, err error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
    if err != nil {
        log.Println("数据库连接错误", err)
        return nil, nil, err
    }

    err = client.Ping(ctx, readpref.Primary())
    if err != nil {
        log.Println("错误", err)
        return
    }
    log.Println("成功连接并进行了 ping 测试。")

    dbName := GetDatabaseName()
    database = client.Database(dbName)

    log.Println(dbName, database.Name())
    return
}
英文:

You have the correct service name. As been said here, you can use both db or mongodb as the host name. (You don't need to set a user and pass...)

But you have a bug in the code. You only initialize a new client, you don't connect to it. You can use Connect instead of NewClient, or do client.Connect.
Also, you are using context.Todo instead of the ctx with the timeout (Not a biggy, but still).

You should do this:

func GetDatabase() (database *mongo.Database, ctx context.Context, err error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
    if err != nil {
        log.Println("database connection error", err)
        return nil, nil, err
    }

    err = client.Ping(ctx, readpref.Primary())
    if err != nil {
        log.Println("err", err)
        return
    }
    log.Println("Successfully connected and pinged.")

    dbName := GetDatabaseName()
    database = client.Database(dbName)

    log.Println(dbName, database.Name())
    return
}

答案2

得分: 0

如评论和oren的回答所指出的,你有两个问题。

  1. 你从未调用connect函数。以下是一些示例代码,基本上是从驱动程序自述文件中复制粘贴的:https://github.com/mongodb/mongo-go-driver
func main() {

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
	if err != nil {
		panic(err)
	}

	defer func() {
		if err = client.Disconnect(ctx); err != nil {
			panic(err)
		}
	}()

	ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	err = client.Ping(ctx, readpref.Primary())
	if err != nil {
		panic(err)
	}

	fmt.Println("Connected to MongoDB!")

}
  1. 在尝试连接时,数据库可能还没有准备好。你可以通过使用健康检查和depends_on中的条件来确保应用程序启动之前数据库已经准备好。
name: example

services:
  backend:
    image: backend
    build: ./
    depends_on:
      mongodb:
        condition: service_healthy

  mongodb:
    image: mongo
    healthcheck:
      test: mongo --norc --quiet --host=localhost:27017 --eval "db.getMongo()"
      interval: 30s
      timeout: 2s
      retries: 3
      start_period: 15s

你会看到应用程序报告已连接并关闭。

example-backend-1  | Connected to MongoDB!
example-backend-1 exited with code 0

可能有更好的方法来对数据库进行健康检查,我不是mongodb专家,所以你需要进行一些研究。


另一个想法是,最好在应用程序中构建一些重试逻辑和健康检查。这样它就可以重试连接到数据库,并报告当前是否没有连接。

这取决于你正在构建的服务的类型,这可能有意义,也可能没有意义。如果你有一个只需要运行一次任务的简单作业,等待数据库是有意义的。如果你有类似于REST API的东西,健康检查可能更有意义。

例如:

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
	if err != nil {
		panic(err)
	}

	defer func() {
		if err = client.Disconnect(ctx); err != nil {
			panic(err)
		}
	}()

	http.HandleFunc("/health/ready", func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel = context.WithTimeout(r.Context(), 2*time.Second)
		defer cancel()
		if err = client.Ping(ctx, readpref.Primary()); err != nil {
			log.Printf("ping error: %v", err)
			w.WriteHeader(http.StatusServiceUnavailable)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	http.ListenAndServe(":8080", nil)

}
英文:

As pointed out in the comment and the answer by oren. You have 2 issues.

  1. You never call connect. Here is some example code, more or less copy pasted from the driver readme: https://github.com/mongodb/mongo-go-driver
func main() {

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
	if err != nil {
		panic(err)
	}

	defer func() {
		if err = client.Disconnect(ctx); err != nil {
			panic(err)
		}
	}()

	ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	err = client.Ping(ctx, readpref.Primary())
	if err != nil {
		panic(err)
	}

	fmt.Println("Connected to MongoDB!")

}
  1. The db might not be ready by the time you try to connect. You could ensure that the db is ready bore the app starts by using a healthcheck and a condition in depends_on.
name: example

services:
  backend:
    image: backend
    build: ./
    depends_on:
      mongodb:
        condition: service_healthy

  mongodb:
    image: mongo
    healthcheck:
      test: mongo --norc --quiet --host=localhost:27017 --eval "db.getMongo()"
      interval: 30s
      timeout: 2s
      retries: 3
      start_period: 15s

You will see that the app reports that it has connected and the shuts down.

example-backend-1  | Connected to MongoDB!
example-backend-1 exited with code 0

There might be a better way to do health checks on the db, I am no mongodb expert, so you have to research if there is something.


Another thought is, that it may be better to build some kind of retry logic and health checks into your application. So that it retries to connect to the db and reports if there is currently no connection.

Depending on the kind of service you are building, this may or may not make sense. If you have a simple job that needs to run a one time task, it would make sense to wait for the db. If you have something like a rest API, health checks could make more sense.

For example:

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
	if err != nil {
		panic(err)
	}

	defer func() {
		if err = client.Disconnect(ctx); err != nil {
			panic(err)
		}
	}()

	http.HandleFunc("/health/ready", func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel = context.WithTimeout(r.Context(), 2*time.Second)
		defer cancel()
		if err = client.Ping(ctx, readpref.Primary()); err != nil {
			log.Printf("ping error: %v", err)
			w.WriteHeader(http.StatusServiceUnavailable)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	http.ListenAndServe(":8080", nil)

}

huangapple
  • 本文由 发表于 2022年4月16日 20:36:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/71893934.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定