如何防止 github.com/ory/dockertest 将容器分配到随机端口?
How to prevent github.com/ory/dockertest from assigning containers to random ports?
我正在尝试编写 运行 都在本地使用 github.com/ory/dockertest
and in a CircleCI environment (in which the "CI" environment variable is set) using a Docker executor type. In the container, I'd like to run the Google Pub/Sub emulator using the google/cloud-sdk
图片的单元测试。
作为一个简化的例子,我写了这个 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,但似乎不是案件。有谁知道使该程序运行的正确 运行 选项?
Dockertest 允许您使用 resource.GetPort()
检索容器的映射端口,您可以使用它来将 pubsubEmulatorHost
设置为正确的值:
port := "8085"
if os.Getenv("CI") == "" {
// ...
pubsubEmulatorHost = opts.Hostname + resource.GetPort("8085/tcp")
// pubsubEmulatorHost = "localhost:32778"
// ...
}
我正在尝试编写 运行 都在本地使用 github.com/ory/dockertest
and in a CircleCI environment (in which the "CI" environment variable is set) using a Docker executor type. In the container, I'd like to run the Google Pub/Sub emulator using the google/cloud-sdk
图片的单元测试。
作为一个简化的例子,我写了这个 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,但似乎不是案件。有谁知道使该程序运行的正确 运行 选项?
Dockertest 允许您使用 resource.GetPort()
检索容器的映射端口,您可以使用它来将 pubsubEmulatorHost
设置为正确的值:
port := "8085"
if os.Getenv("CI") == "" {
// ...
pubsubEmulatorHost = opts.Hostname + resource.GetPort("8085/tcp")
// pubsubEmulatorHost = "localhost:32778"
// ...
}