【问题标题】:Configuring Minio server for use with Testcontainers配置 Minio 服务器以与 Testcontainers 一起使用
【发布时间】:2019-08-19 12:28:39
【问题描述】:

我的应用程序使用 Minio 进行 S3 兼容的对象存储,我想通过 Testcontainers 在我的集成测试中使用 Minio docker 映像。

对于一些非常基本的测试,我使用minio/minio docker 映像运行 GenericContainer,除了MINIO_ACCESS_KEYMINIO_SECRET_KEY 之外没有任何配置。然后我的测试使用 Minio 的Java Client SDK。这些工作正常,表现与预期一样。

但是对于其他集成测试,我需要在 Mino 中设置单独的用户。据我所知,用户只能使用Admin API添加到Minio,没有Java客户端,只有minio/mc docker镜像(mc CLI在minio/minio docker中不可用用于服务器的图像)。

在命令行上,我可以像这样使用 Admin API:

$ docker run --interactive --tty --detach --entrypoint=/bin/sh --name minio_admin minio/mc

--interactive --tty 有点像保持容器运行的技巧,所以我以后可以运行类似这样的命令:

$ docker exec --interactive --tty minio_admin mc admin user add ...

使用 Testcontainers,我尝试这样做:

public void testAdminApi() throws Exception {
    GenericContainer mc = new GenericContainer("minio/mc")
            .withCommand("/bin/sh")
            .withCreateContainerCmdModifier(new Consumer<CreateContainerCmd>() {
                @Override
                public void accept(CreateContainerCmd cmd) {
                    cmd
                            .withAttachStdin(true)
                            .withStdinOpen(true)
                            .withTty(true);
                }
            });

    mc.start();
    log.info("mc is running: {}", mc.isRunning());

    String command = "mc";
    Container.ExecResult result = mc.execInContainer(command);
    log.info("Executing command '{}' returned exit code '{}' and stdout '{}'", command, result.getExitCode(), result.getStdout());

    assertEquals(0, result.getExitCode());
}

日志显示容器正在启动,但对其执行命令返回退出代码 126 并声称它处于停止状态:

[minio/mc:latest] - Starting container with ID: 4f96fc7583fe62290925472c4c6b329fbeb7a55b38a3c0ad41ee797db1431841
[minio/mc:latest] - Container minio/mc:latest is starting: 4f96fc7583fe62290925472c4c6b329fbeb7a55b38a3c0ad41ee797db1431841
[minio/mc:latest] - Container minio/mc:latest started
minio.MinioAdminTests - mc is running: true
org.testcontainers.containers.ExecInContainerPattern - /kind_volhard: Running "exec" command: mc
minio.MinioAdminTests - Executing command 'mc' returned exit code '126'
  and stdout 'cannot exec in a stopped state: unknown'

java.lang.AssertionError: Expected: 0, Actual: 126

在摆弄了几个小时之后,我的想法已经不多了。有人可以帮忙吗?

【问题讨论】:

    标签: docker minio testcontainers


    【解决方案1】:

    感谢@glebsts 和@bsideup,我能够让我的集成测试正常工作。下面是一个如何添加用户的最小示例:

    public class MinioIntegrationTest {
    
        private static final String ADMIN_ACCESS_KEY = "admin";
        private static final String ADMIN_SECRET_KEY = "12345678";
        private static final String USER_ACCESS_KEY = "bob";
        private static final String USER_SECRET_KEY = "87654321";
    
        private static GenericContainer minioServer;
        private static String minioServerUrl;
    
        @BeforeAll
        static void setUp() throws Exception {
            int port = 9000;
            minioServer = new GenericContainer("minio/minio")
                    .withEnv("MINIO_ACCESS_KEY", ADMIN_ACCESS_KEY)
                    .withEnv("MINIO_SECRET_KEY", ADMIN_SECRET_KEY)
                    .withCommand("server /data")
                    .withExposedPorts(port)
                    .waitingFor(new HttpWaitStrategy()
                            .forPath("/minio/health/ready")
                            .forPort(port)
                            .withStartupTimeout(Duration.ofSeconds(10)));
            minioServer.start();
    
            Integer mappedPort = minioServer.getFirstMappedPort();
            Testcontainers.exposeHostPorts(mappedPort);
            minioServerUrl = String.format("http://%s:%s", minioServer.getContainerIpAddress(), mappedPort);
    
            // Minio Java SDK uses s3v4 protocol by default, need to specify explicitly for mc
            String cmdTpl = "mc config host add myminio http://host.testcontainers.internal:%s %s %s --api s3v4 && "
                    + "mc admin user add myminio %s %s readwrite";
            String cmd = String.format(cmdTpl, mappedPort, ADMIN_ACCESS_KEY, ADMIN_SECRET_KEY, USER_ACCESS_KEY, USER_SECRET_KEY);
    
            GenericContainer mcContainer = new GenericContainer<>("minio/mc")
                    .withStartupCheckStrategy(new OneShotStartupCheckStrategy())
                    .withCreateContainerCmdModifier(containerCommand -> containerCommand
                            .withTty(true)
                            .withEntrypoint("/bin/sh", "-c", cmd));
            mcContainer.start();
        }
    
        @Test
        public void canCreateBucketWithAdminUser() throws Exception {
            MinioClient client = new MinioClient(minioServerUrl, ADMIN_ACCESS_KEY, ADMIN_SECRET_KEY);
            client.ignoreCertCheck();
    
            String bucketName = "foo";
            client.makeBucket(bucketName);
            assertTrue(client.bucketExists(bucketName));
        }
    
        @Test
        public void canCreateBucketWithNonAdminUser() throws Exception {
            MinioClient client = new MinioClient(minioServerUrl, USER_ACCESS_KEY, USER_SECRET_KEY);
            client.ignoreCertCheck();
    
            String bucketName = "bar";
            client.makeBucket(bucketName);
            assertTrue(client.bucketExists(bucketName));
        }
    
        @AfterAll
        static void shutDown() {
            if (minioServer.isRunning()) {
                minioServer.stop();
            }
        }
    }
    

    【讨论】:

    • * @BeforeAll/!AfterAll 没有意义,因为 testcontainers 可以很好地与 @ClassRule 一起工作,并且您不需要手动清理 * 覆盖入口点 - 值得怀疑,所有这些都是因为将巨大的超字符串粘合在一起而完成的; * 从 genericContainer 复制硬编码常量是值得怀疑的
    • * 为什么你真的需要暴露端口? Networking documentation 描述了您可能需要它的情况,但在这里您有 miniomc 在容器中运行,将它们放入同一个网络会更好地隔离它们,而你不需要当您也可以拥有内部 IP 时,不需要依赖文本 url; * 你的 minio 没有在测试之间清理,环境很脏。这可能会导致测试相互依赖,或者太痛苦,即添加不需要添加用户的测试
    【解决方案2】:

    正如@bsideup 建议的那样,您可以使用一次性策略,例如here。 UPD:添加了工作测试。了解这一点很重要

    容器启动时,执行入口点+命令(一般是Docker,与Testcontainers无关)。 Source from TC github

    public class TempTest {
        @Rule
        public Network network = Network.newNetwork();
    
        private String runMcCommand(String cmd) throws TimeoutException {
            GenericContainer container = new GenericContainer<>("minio/mc")
                    .withCommand(cmd)
                    .withNetwork(network)
                    .withStartupCheckStrategy(new OneShotStartupCheckStrategy())
                    .withCreateContainerCmdModifier(command -> command.withTty(true));
            container.start();
            WaitingConsumer waitingConsumer = new WaitingConsumer();
            ToStringConsumer toStringConsumer = new ToStringConsumer();
            Consumer<OutputFrame> composedConsumer = toStringConsumer.andThen(waitingConsumer);
            container.followOutput(composedConsumer);
            waitingConsumer.waitUntilEnd(4, TimeUnit.SECONDS);
            return toStringConsumer.toUtf8String();
        }
    
        private void showCommandOutput(String cmd) throws TimeoutException {
            String res = runMcCommand(cmd);
            System.out.printf("Cmd '%s' result:\n----\n%s\n----%n", cmd, res);
        }
    
        @Test
        public void testAdminApi() throws Exception {
            showCommandOutput("ls");
            showCommandOutput("version");
        }
    }
    

    另一种选择是使用minio/mc的dockerfile的内容,它很小,修改执行的命令(默认为一次性“mc”),每次测试运行一次自己的容器,与一个相比-关闭容器,如果您需要执行多个命令,将节省一些时间:

    @Rule
    public Network network = Network.newNetwork();
    
    @Rule
    public GenericContainer mc = new GenericContainer(new ImageFromDockerfile()
      .withDockerfileFromBuilder(builder ->
        builder
          .from("alpine:3.7")
          .run("apk add --no-cache ca-certificates && apk add --no-cache --virtual .build-deps curl && curl https://dl.minio.io/client/mc/release/linux-amd64/mc > /usr/bin/mc && chmod +x /usr/bin/mc && apk del .build-deps")
          .cmd("/bin/sh", "-c", "while sleep 3600; do :; done")
          .build())
        )
      .withNetwork(network);
    
    public void myTest() {
      mc.execInContainer("mc blah");
      mc.execInContainer("mc foo");
    }
    

    基本上,它运行安装了 mc 的映像,并休眠 1 小时,这对于您的测试来说已经足够了。在它运行时,您可以执行命令等。完成后,它会被杀死。 你的 minio 容器可以在同一个网络中。

    【讨论】:

    • 基本上这个“睡眠”和你让容器分离并运行一样。
    • 添加了一次性容器的示例。在我的机器上工作(tm)
    【解决方案3】:

    您可以运行带有mcwithCommand("your command") 的一次性容器(使用OneShotStartupCheckStrategy),连接到与您正在运行的minio 服务器相同的网络(请参阅Networking)。

    【讨论】:

      【解决方案4】:

      对于那些正在寻找 s3 与 minio 对象服务器集成测试的人。 上面的大部分答案都没有 docker-compose 实现。

      docker-compose 文件:

      version: '3.7'
      services:
        minio-service:
          image: quay.io/minio/minio
          command: minio server /data
          ports:
            - "9000:9000"
          environment:
            MINIO_ROOT_USER: minio
            MINIO_ROOT_PASSWORD: minio123
      

      实际的 IntegrationTest 类:

      import com.amazonaws.auth.AWSStaticCredentialsProvider;
      import com.amazonaws.auth.BasicAWSCredentials;
      import com.amazonaws.client.builder.AwsClientBuilder;
      import com.amazonaws.regions.Regions;
      import com.amazonaws.services.s3.AmazonS3;
      import com.amazonaws.services.s3.AmazonS3ClientBuilder;
      import com.amazonaws.services.s3.model.S3Object;
      import org.junit.jupiter.api.*;
      import org.testcontainers.containers.DockerComposeContainer;
      
      import java.io.File;
      
      @TestInstance(TestInstance.Lifecycle.PER_CLASS)
      class MinioIntegrationTest {
      
          private static final DockerComposeContainer minioContainer = new DockerComposeContainer<>(new File("src/test/resources/docker-compose.yml"))
                  .withExposedService("minio-service", 9000);
          private static final String MINIO_ENDPOINT = "http://localhost:9000";
          private static final String ACCESS_KEY = "minio";
          private static final String SECRET_KEY = "minio123";
          private AmazonS3 s3Client;
      
          @BeforeAll
          void setupMinio() {
              minioContainer.start();
              initializeS3Client();
          }
      
          @AfterAll
          void closeMinio() {
              minioContainer.close();
          }
      
          private void initializeS3Client() {
              String name = Regions.US_EAST_1.getName();
              AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration(MINIO_ENDPOINT, name);
               s3Client = AmazonS3ClientBuilder.standard()
                      .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY)))
                      .withEndpointConfiguration(endpoint)
                      .withPathStyleAccessEnabled(true)
                      .build();
          }
      
          @Test
          void shouldReturnActualContentBasedOnBucketName() throws Exception{
              String bucketName = "test-bucket";
              String key = "s3-test";
              String content = "Minio Integration test";
              s3Client.createBucket(bucketName);
              s3Client.putObject(bucketName, key, content);
              S3Object object = s3Client.getObject(bucketName, key);
              byte[] actualContent = new byte[22];
              object.getObjectContent().read(actualContent);
              Assertions.assertEquals(content, new String(actualContent));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-02-26
        • 2022-10-20
        • 2013-10-31
        • 1970-01-01
        • 2021-09-20
        • 1970-01-01
        • 2011-03-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多