Golang testcontainers与Azure DevOps流水线:容器是否会随机被终止?

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

Golang testcontainers vs Azure Devops pipelines: Containers killed randomly?

问题

在本地使用golang testcontainers实现和运行数据库集成测试非常顺利,但在Azure DevOps流水线中,我的测试似乎随机失败。

流水线日志显示如下:

2023/01/09 16:06:02 (...) Error: read tcp 127.0.0.1:52546->127.0.0.1:49161: read: connection reset by peer

在添加容器日志、改进等待条件、移除使用数据库容器配置文件(这样我就不必将文件复制到容器中)和禁用Ryuk之后,我想知道还需要做什么,或者是否初始化容器时出错了。

对于每个单元测试,testcontainers的启动方式如下:

func SetupTestDatabase(ctx context.Context) (testcontainers.Container, project.Repository, error) {
	containerReq := testcontainers.ContainerRequest{
		SkipReaper:   true,
		Image:        "postgres:11.18-alpine3.17",
		ExposedPorts: []string{"5432/tcp"},
		Cmd:          []string{"postgres", "-c", "fsync=off"},
		Env: map[string]string{
			"POSTGRES_DB":         "postgre",
			"POSTGRES_PASSWORD":   "postgres",
			"POSTGRES_USER":       "postgres",
			"PGUSER":              "postgres",
			"POSTGRES_EXTENSIONS": "uuid-ossp",
		},
	}

	containerReq.WaitingFor = wait.ForAll(
		wait.ForListeningPort("5432/tcp"),
		wait.ForExec([]string{"pg_isready", "-t 10", "-q"}), // postgre docs: https://www.postgresql.org/docs/9.4/app-pg-isready.html
	).WithDeadline(3 * time.Minute)

	dbContainer, err := testcontainers.GenericContainer(
		ctx,
		testcontainers.GenericContainerRequest{
			ContainerRequest: containerReq,
			Started:          true,
		})
	if err != nil {
		log.Fatalf("Database container could not be started. Error: %s", err)
		return nil, nil, errors.WithStack(err)
	}

	err = dbContainer.StartLogProducer(ctx)
	if err != nil {
		log.Fatalf("Logproducer could not be started. Error: %s", err)
		return nil, nil, errors.WithStack(err)
	}
	defer dbContainer.StopLogProducer()
	lc := logConsumer{}
	dbContainer.FollowOutput(&lc)

	port, err := dbContainer.MappedPort(ctx, "5432")
	if err != nil {
		log.Fatalf("Mapped port could not be retrieved. Error: %s", err)
		return nil, nil, errors.WithStack(err)
	}
	host, err := dbContainer.Host(ctx)
	if err != nil {
		log.Fatalf("Hostname could not be retrieved. Error: %s", err)
		return nil, nil, errors.WithStack(err)
	}

	global.Config.Postgres.Host = host
	global.Config.Postgres.Port = port.Port()
	global.Config.Postgres.User = "postgres"
	global.Config.Postgres.Password = "postgres"
	global.Config.Postgres.DbName = "postgre"
	global.Config.Local = true

	global.Logger = logger.NewNull(config.Config{
		Logging: config.Logging{
			Level: "debug",
		},
	})

	repository, err := New(ctx)
	if err != nil {
		log.Fatalf("Repository could not be setup. Error: %s", err)
		return nil, nil, errors.WithStack(err)
	}

	return dbContainer, repository, nil
}

...在repository, err := New(ctx)处创建仓库时,使用migrate来设置数据库,类似于我们的生产环境,并使用gorm进行数据库连接和处理等操作。

单元测试的基本模板如下:

func Test_pg_Has(t *testing.T) {
	ctx := context.TODO()
	dbContainer, repository, err := SetupTestDatabase(ctx)
	if err != nil {
		t.Errorf("error running testcontainers, error: %s", err)
	}
	t.Cleanup(func() {
		if err := dbContainer.Terminate(ctx); err != nil {
			t.Fatalf("failed to terminate container: %s", err)
		}
		time.Sleep(10 * time.Second)
	})
    
    ... 测试代码
}

对于Azure Pipeline,使用的是默认的Azure Agent Pools,GO版本为"1.18.0 x64"。

非常感谢您提供的任何提示。

英文:

while implemening and running my database integration tests with golang testcontainers works like a charm locally, it seems my tests are randomly not working in azure devops pipelines.

Pipeline logs show this:

2023/01/09 16:06:02 (...) Error: read tcp 127.0.0.1:52546->127.0.0.1:49161: read: connection reset by peer

After adding container logging, improveing the waiting criteria, removing the usage of db container config files (so I dont have to copy files into the container) and disabling Ryuk, I am wondering what else needs to be done, or if I am initializing the containers incorrect.

For every unittest, the testcontainers are started like this:

func SetupTestDatabase(ctx context.Context) (testcontainers.Container, project.Repository, error) {
containerReq := testcontainers.ContainerRequest{
SkipReaper:   true,
Image:        "postgres:11.18-alpine3.17",
ExposedPorts: []string{"5432/tcp"},
Cmd:          []string{"postgres", "-c", "fsync=off"},
Env: map[string]string{
"POSTGRES_DB":         "postgre",
"POSTGRES_PASSWORD":   "postgres",
"POSTGRES_USER":       "postgres",
"PGUSER":              "postgres",
"POSTGRES_EXTENSIONS": "uuid-ossp",
},
}
containerReq.WaitingFor = wait.ForAll(
wait.ForListeningPort("5432/tcp"),
wait.ForExec([]string{"pg_isready", "-t 10", "-q"}), // postgre docs: https://www.postgresql.org/docs/9.4/app-pg-isready.html
).WithDeadline(3 * time.Minute)
dbContainer, err := testcontainers.GenericContainer(
ctx,
testcontainers.GenericContainerRequest{
ContainerRequest: containerReq,
Started:          true,
})
if err != nil {
log.Fatalf("Database container could not be started. Error: %s", err)
return nil, nil, errors.WithStack(err)
}
err = dbContainer.StartLogProducer(ctx)
if err != nil {
log.Fatalf("Logproducer could not be started. Error: %s", err)
return nil, nil, errors.WithStack(err)
}
defer dbContainer.StopLogProducer()
lc := logConsumer{}
dbContainer.FollowOutput(&lc)
port, err := dbContainer.MappedPort(ctx, "5432")
if err != nil {
log.Fatalf("Mapped port could not be retrieved. Error: %s", err)
return nil, nil, errors.WithStack(err)
}
host, err := dbContainer.Host(ctx)
if err != nil {
log.Fatalf("Hostname could not be retrieved. Error: %s", err)
return nil, nil, errors.WithStack(err)
}
global.Config.Postgres.Host = host
global.Config.Postgres.Port = port.Port()
global.Config.Postgres.User = "postgres"
global.Config.Postgres.Password = "postgres"
global.Config.Postgres.DbName = "postgre"
global.Config.Local = true
global.Logger = logger.NewNull(config.Config{
Logging: config.Logging{
Level: "debug",
},
})
repository, err := New(ctx)
if err != nil {
log.Fatalf("Repository could not be setup. Error: %s", err)
return nil, nil, errors.WithStack(err)
}
return dbContainer, repository, nil
}

... the creation of the repository with repository, err := New(ctx) towards the end uses migrate to setup the database analog to our productive ones and gorm for the db connection and handling et al.

The basic template for a unittest is:

func Test_pg_Has(t *testing.T) {
ctx := context.TODO()
dbContainer, repository, err := SetupTestDatabase(ctx)
if err != nil {
t.Errorf("error running testcontainers, error: %s", err)
}
t.Cleanup(func() {
if err := dbContainer.Terminate(ctx); err != nil {
t.Fatalf("failed to terminate container: %s", err)
}
time.Sleep(10 * time.Second)
})
... TEST_CODE
}

For the Azure Pipeline, stock Azure Agent Pools are used, GO in version "1.18.0 x64".

Any hint is appreciated, thank you in advance.

答案1

得分: 1

作为一个相当不令人满意的解决方案,我在创建容器后添加了5秒的延迟,在设置数据库连接之前。

这可能是Azure Devops的特定问题,因为我们发现即使在一个单独的容器中运行的单元测试也会随机失败。

接受这个响应也是对社区的一种挑战...

英文:

As a quite unsatisfing solution, I added a 5 seconds sleep after the container is created, just before I set up the db connection.

It might be a Azure Devops specific problems as we discovered that even unittests running in one single container randomly fail.

Accepting this as response is also kind of a challenge for the community...

huangapple
  • 本文由 发表于 2023年1月12日 00:50:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75086457.html
匿名

发表评论

匿名网友

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

确定