spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

ServiceConnection for K3sContainer

Open MichaelDausmann opened this issue 7 months ago • 4 comments

I would like to use K3sContainer for local Development, I already have Kafka and Neo4j working with @ServiceConnection. This would be nice...

    @Bean
    @ServiceConnection
    K3sContainer k3s() {
        return new K3sContainer(DockerImageName.parse("rancher/k3s:v1.21.3-k3s1"))
                .withCommand("server", "--disable", "metrics-server") // we don't need metrics
                .withLogConsumer(new Slf4jLogConsumer(logger)
                );
    }

K3sContainer is here (modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java)

Looks like it would require work here (spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection) and here (spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure).

Workaround

Here is my workaround

    @Bean
    K3sContainer k3s() {
        K3sContainer k3sContainer = new K3sContainer(DockerImageName.parse("rancher/k3s:v1.21.3-k3s1"))
                .withCommand("server", "--disable", "metrics-server") // we don't need metrics
                .withLogConsumer(new Slf4jLogConsumer(logger)
                );

        // don't normally need to start the container in these @Bean methods but can't get the config unless its started
        k3sContainer.start();

        String kubeConfigYaml = k3sContainer.getKubeConfigYaml();
        Config config = Config.fromKubeconfig(kubeConfigYaml);  // requires io.fabric8:kubernetes-client:5.11.0 or higher

        // in the absence of @ServiceConnection integration for this testcontainer, Jack the test container URL into properties so it's picked up when I create a client in main app
        System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, config.getMasterUrl());
        System.setProperty(Config.KUBERNETES_CA_CERTIFICATE_DATA_SYSTEM_PROPERTY, config.getCaCertData());
        System.setProperty(Config.KUBERNETES_CLIENT_CERTIFICATE_DATA_SYSTEM_PROPERTY, config.getClientCertData());
        System.setProperty(Config.KUBERNETES_CLIENT_KEY_DATA_SYSTEM_PROPERTY, config.getClientKeyData());
        System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true");

        return k3sContainer;
    }

MichaelDausmann avatar May 28 '25 23:05 MichaelDausmann

@SpringFactories(ConnectionDetailsFactory.class)
class MinIOContainerConnectionDetailsFactory
		extends ContainerConnectionDetailsFactory<MinIOContainer, ConnectionDetails> {

	MinIOContainerConnectionDetailsFactory() {
		super(DockerImageNames.MINIO_IMAGE);
	}

	@Override
	protected ConnectionDetails getContainerConnectionDetails(ContainerConnectionSource<MinIOContainer> source) {
		return new MinIOContainerConnectionDetails(source);
	}

	private static final class MinIOContainerConnectionDetails extends ContainerConnectionDetails<MinIOContainer> {

		private MinIOContainerConnectionDetails(ContainerConnectionSource<MinIOContainer> source) {
			super(source);
		}

	}

}

You can handle K3sContainer like this, and you need to register this class in the spring.factories file.

livk-cloud avatar May 29 '25 03:05 livk-cloud

The @ServiceConnection annotation is designed to provide a bridge between something that Spring Boot can connect to and a container that provides an appropriate service. For example, JdbcConnectionDetails provides details for a JDBC connection and JdbcContainerConnectionDetailsFactory allows any JdbcDatabaseContainer to be the data source.

In the case of K3sContainer, I'm not sure what we'd connect it to.

@MichaelDausmann can you provide a bit more background about what you're trying to achieve? Perhaps you can share a sample application that uses your current workaround?

philwebb avatar May 29 '25 05:05 philwebb

@philwebb ah ok, good point. I'm specifically setting up a local dev environment with TestContainers for a Spring/Boot application that interacts directly with Kubernetes via the io.fabric8.kubernetes.client. so I guess to answer your question directly, I am connecting to the Kubernetes API server inside the K3sContainer.

the workaround lets me create a KubernetesClient bean with an active client

@Configuration
public class KubernetesClientConfig {

    @Bean
    public KubernetesClient kubernetesClient() {
        Config config = Config.autoConfigure(null);
        return new DefaultKubernetesClient(config);
    }
}

... because I have set the application properties, the autoConfigure finds what it needs and connects without issue. At deployment .autoConfigure doesn't need e.g. a host because it is deployed in the cluster and the default url is fine

MichaelDausmann avatar May 29 '25 06:05 MichaelDausmann

@livk-cloud I will give your approach a try

MichaelDausmann avatar May 29 '25 06:05 MichaelDausmann