如何防止 github.com/ory/dockertest 将容器分配给随机端口?

Posted

技术标签:

【中文标题】如何防止 github.com/ory/dockertest 将容器分配给随机端口?【英文标题】:How to prevent github.com/ory/dockertest from assigning containers to random ports? 【发布时间】:2020-11-06 15:43:25 【问题描述】:

我正在尝试编写使用github.com/ory/dockertest 在本地运行和使用Docker executor 类型在CircleCI 环境(其中设置了“CI”环境变量)中运行的单元测试。在容器中,我想使用google/cloud-sdk 图像运行Google Pub/Sub emulator。

作为一个简化的例子,我编写了这个 Go 程序:

package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "net"
    "os"
    "time"

    "cloud.google.com/go/pubsub"
    "github.com/ory/dockertest"
    "github.com/ory/dockertest/docker"
    "google.golang.org/api/iterator"
)

var pubsubEmulatorHost string

func main() 
    flag.StringVar(&pubsubEmulatorHost, "pubsubEmulatorHost", "localhost:8085", "Google Pub/Sub emulator host")
    flag.Parse()

    if os.Getenv("CI") == "" 
        pool, err := dockertest.NewPool("")
        if err != nil 
            log.Fatalf("Could not connect to Docker: %v", err)
        

        opts := &dockertest.RunOptions
            Hostname:     "localhost",
            Repository:   "google/cloud-sdk",
            Cmd:          []string"gcloud", "beta", "emulators", "pubsub", "start", "--host-port", "127.0.0.1:8085",
            ExposedPorts: []string"8085",
            PortBindings: map[docker.Port][]docker.PortBinding
                "8085/tcp": HostIP: "127.0.0.1", HostPort: "8085/tcp",
            ,
        
        resource, err := pool.RunWithOptions(opts)
        if err != nil 
            log.Fatalf("Could not start resource: %v", err)
        

        pool.MaxWait = 10 * time.Second
        if err := pool.Retry(func() error 
            _, err := net.Dial("tcp", "localhost:8085")
            return err
        ); err != nil 
            log.Fatalf("Could not dial the Pub/Sub emulator: %v", err)
        

        defer func() 
            if err := pool.Purge(resource); err != nil 
                log.Fatalf("Could not purge resource: %v", err)
            
        ()
    

    os.Setenv("PUBSUB_EMULATOR_HOST", pubsubEmulatorHost)
    defer os.Unsetenv("PUBSUB_EMULATOR_HOST")

    client, err := pubsub.NewClient(context.Background(), "my-project")
    if err != nil 
        log.Fatalf("NewClient: %v", err)
    

    topic, err := client.CreateTopic(context.Background(), "my-topic")
    if err != nil 
        log.Fatalf("CreateTopic: %v", err)
    
    log.Println("Created topic:", topic)

    topicIterator := client.Topics(context.Background())
    for 
        topic, err := topicIterator.Next()
        if err == iterator.Done 
            break
        
        if err != nil 
            log.Fatalf("Next: %v", err)
        
        fmt.Printf("%s\n", topic)
    

首先,我验证了在从命令行运行容器后将CI 环境变量设置为非空值运行它会产生预期的结果:

> 
docker run -p "8085:8085" google/cloud-sdk gcloud beta emulators pubsub start --host-port=0.0.0.0:8085
Executing: /usr/lib/google-cloud-sdk/platform/pubsub-emulator/bin/cloud-pubsub-emulator --host=0.0.0.0 --port=8085
[pubsub] This is the Google Pub/Sub fake.
[pubsub] Implementation may be incomplete or differ from the real system.
[pubsub] Jul 16, 2020 9:21:33 PM com.google.cloud.pubsub.testing.v1.Main main
[pubsub] INFO: IAM integration is disabled. IAM policy methods and ACL checks are not supported
[pubsub] Jul 16, 2020 9:21:34 PM io.gapi.emulators.netty.NettyUtil applyJava7LongHostnameWorkaround
[pubsub] INFO: Applied Java 7 long hostname workaround.
[pubsub] Jul 16, 2020 9:21:34 PM com.google.cloud.pubsub.testing.v1.Main main
[pubsub] INFO: Server started, listening on 8085

紧随其后

> env CI=true go run main.go
2020/07/16 14:22:01 Created topic: projects/my-project/topics/my-topic
projects/my-project/topics/my-topic

注意此时容器上的8085端口如预期映射到主机上的8085端口:

> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
76724696f9d9        google/cloud-sdk    "gcloud beta emulato…"   55 seconds ago      Up 54 seconds       0.0.0.0:8085->8085/tcp   epic_ganguly

我不想在不设置CI 环境变量的情况下停止容器并运行程序,应该注意自动启动容器。然而,我观察到的是尝试建立连接时超时:

> go run main.go
2020/07/16 14:23:56 Could not dial the Pub/Sub emulator: dial tcp [::1]:8085: connect: connection refused
exit status 1

在检查容器时,它似乎映射到本地端口32778 而不是8085

> docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                     NAMES
0df07ac232d5        google/cloud-sdk:latest   "gcloud beta emulato…"   34 seconds ago      Up 33 seconds       0.0.0.0:32778->8085/tcp   wizardly_ptolemy

我认为像上面那样在RunOptions 中指定PortBindings 应该将容器上的端口8085 映射到主机上的端口8085,但似乎情况并非如此。有谁知道使该程序正常运行的正确运行选项?

【问题讨论】:

【参考方案1】:

Dockertest 允许您使用resource.GetPort() 检索容器的映射端口,您可以使用它来将pubsubEmulatorHost 设置为正确的值:

port := "8085"

if os.Getenv("CI") == "" 
  // ...
  pubsubEmulatorHost = opts.Hostname + resource.GetPort("8085/tcp")
  // pubsubEmulatorHost = "localhost:32778"
  // ...

【讨论】:

这种方法的问题是我必须让PUBSUB_EMULATOR_HOST 环境匹配随机生成的端口,而在 CircleCI 环境中它将是一个固定端口。这会使逻辑更加复杂;理想情况下,我想映射到一个固定端口,就像在 CircleCI Docker 执行器中一样。 我已经更新了我的答案以更好地回答你的问题,但如果这对你不起作用,那可能只是一个错误。但是,我不认为使用固定端口是你应该做的事情,最好是动态地使用健全的默认值,而不是静态的并且灾难性地失败。

以上是关于如何防止 github.com/ory/dockertest 将容器分配给随机端口?的主要内容,如果未能解决你的问题,请参考以下文章

如何防止超卖

在winform当中提交数据,如何防止重复提交?

PostgreSQL如何防止表太大?

如何彻底防止SQL注入?

如何防止VBS的错误提示

https如何防止会话劫持