当前位置: 首页 > 知识库问答 >
问题:

TestContainer中的Spring Boot云-连接到URL而不使用负载平衡器/服务发现

东郭弘
2023-03-14

我正在写一个集成测试。我有一个在Testcontainer中运行的Spring Boot 2.5应用程序。我还让StubRunnerExtension运行WireMock。

我需要Spring Boot应用程序连接到存根WireMock服务器。

发生错误是因为Spring认为host.testcontainers.internal是服务的名称。它不是-它是专门为这种情况提供的特殊Test容器主机名(从Test容器连接到主机)。

Wiremock存根肯定正在运行并可连接。如果我在运行时将docker exec-it放入容器中,我可以连接到它并使用curl获得有效响应http://host.testcontainers.internal

我已经尝试了很多很多类型的配置来禁用Spring Boot负载均衡器,无论是在application.ymlbootstrap.yml和环境变量中。这些肯定是由Spring Application加载的-但它们没有任何帮助。

  • 忽略接口-不会改变任何东西https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#ignore-network-interfaces
  • SimpleDiscoveryClient-我无法启用它
  • spring.cloud.discovery.client.simple.instances-没有效果

试图禁用发现客户端什么也做不了

eureka.client.enabled=false
eureka.cloud.discovery.enabled=false
spring.cloud.discovery.reactive.enabled=false
spring.cloud.discovery.blocking.enabled=false
spring.cloud.config.failFast=false

如何将Spring Boot应用程序配置为连接到URL?这必须是联调-我不能编辑应用程序的源代码。我根本不需要发现,如果我可以硬编码“service_name=http://host.testcontainers.internal”就可以了。

以下是rest配置:

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.web.client.RestTemplate;


@Configuration
public class RestConfig {

  @Bean
  @LoadBalanced
  public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
  }
}

@Test
fun integrationtest() {
    // my Spring Boot server
    val server = SpringBootAppTC()
    server.start()

    // verify the stubs are running
    stubRunnerExtension.findAllRunningStubs().allServicesNames.forEach {
      logger.info("stub [$it] is running")
    }
    
    assertTrue(server.isRunning)

   // assertions...
}

companion object {

    @JvmField
    @RegisterExtension
    val stubRunnerExtension: StubRunnerExtension = StubRunnerExtension()...

}

No servers available for service: host.testcontainers.internal
    at org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient.execute(BlockingLoadBalancerClient.java:79)
    at org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.schedule(DefaultReactiveExecutor.java:179)
    at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryTask.run(RedeliveryErrorHandler.java:712)
    at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryTask.doRun(RedeliveryErrorHandler.java:804)
    at org.apache.camel.component.bean.BeanProcessor.process(BeanProcessor.java:81)
    at org.apache.camel.component.bean.AbstractBeanProcessor.process(AbstractBeanProcessor.java:146)
    at org.apache.camel.component.bean.MethodInfo$1.proceed(MethodInfo.java:286)
    at org.apache.camel.component.bean.MethodInfo$1.doProceed(MethodInfo.java:316)
    at org.apache.camel.component.bean.MethodInfo.invoke(MethodInfo.java:494)
    at org.apache.camel.support.ObjectHelper.invokeMethodSafe(ObjectHelper.java:376)
    at java.base/java.lang.reflect.Method.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at my.application.RestApiService(RestApiService.java:39)
    at java.base/java.util.TimerThread.run(Unknown Source)
    at java.base/java.util.TimerThread.mainLoop(Unknown Source)
    at org.apache.camel.component.timer.TimerConsumer$1.run(TimerConsumer.java:76)
    at org.apache.camel.component.timer.TimerConsumer.sendTimerExchange(TimerConsumer.java:209)
    at org.apache.camel.impl.engine.CamelInternalProcessor.process(CamelInternalProcessor.java:398)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:184)
    at org.apache.camel.impl.engine.DefaultReactiveExecutor.scheduleMain(DefaultReactiveExecutor.java:64)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:651)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:751)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:776)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:77)
    at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:93)
    at org.springframework.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor.intercept(MetricsClientHttpRequestInterceptor.java:86)
    at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:93)
    at org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor.intercept(LoadBalancerInterceptor.java:56)

下面是我如何在Testcontainer的EnvironmentVariables中配置SimpleDiscoveryClient的:

"SPRING_APPLICATION_JSON" to """
{
  "spring": {
    "cloud": {
      "discovery": {
        "client": {
          "simple": {
            "instances": {
              "contract-service": [
                {
                  "uri": "http://host.testcontainers.internal:60104"
                }
              ]
            }
          }
        }
      }
    }
  }
}
  • Spring防尘套。版本2.5.0

共有1个答案

邓令雪
2023-03-14

我已经解决了这个问题

  1. 设置
  • <代码>Spring。云发现客户易于理解的实例。合同服务[0]。uri=http://host.testcontainers.internal:Testcontainer中的$PORTSPRING\u APPLICATION\u JSON(文档)

编辑:我还发现,即使我已经禁用了Eureka,我仍然需要这个依赖项:org.springframework.cloud: spring-cloud d-starter-netfex-eureka-Client。没有它,发现被完全禁用,Spring Boot应用程序可以直接连接到host.testcontainers.internal-不需要大惊小怪。

简化代码以演示:

import kotlin.test.assertEquals
import kotlin.test.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import org.slf4j.LoggerFactory
import org.springframework.cloud.contract.stubrunner.junit.StubRunnerExtension
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.output.OutputFrame
import org.testcontainers.containers.output.WaitingConsumer
import org.testcontainers.containers.wait.strategy.Wait
import org.testcontainers.junit.jupiter.Testcontainers


@Testcontainers
class MyIntegrationTest {

  init {
    // see https://www.testcontainers.org/features/networking/#exposing-host-ports-to-the-container
    org.testcontainers.Testcontainers.exposeHostPorts(contractServicePort)
  }

  @Test
  fun `test Contract Service stub`() {

    // verify that the contract-service stub is running, and the hardcoded port is correct
    val contractServiceUrl = stubRunnerExtension.findStubUrl("contract-service")
    assertEquals(contractServicePort, contractServiceUrl.port)

    // manually construct the URL for contract-service
    val contractServiceDiscoverableName = "http://contract-service:$contractServicePort"
    val contractServiceUri = "http://host.testcontainers.internal:$contractServicePort"
    // (see https://www.testcontainers.org/features/networking/#exposing-host-ports-to-the-container)

    // now we have all the pieces, we can create the my SB app test container
    val mySpringBootApp =
      MySpringBootApplicationTestContainer(contractServiceDiscoverableName, contractServiceUri)
    // manually start container
    mySpringBootApp.start()
    assertTrue(mySpringBootApp.isRunning)

    // verify the stubs are running
    stubRunnerExtension.findAllRunningStubs().allServicesNames.forEach {
      logger.info("Stub '$it' is running ")
    }

    val logConsumer = WaitingConsumer()
    mySpringBootApp.followOutput(logConsumer, OutputFrame.OutputType.STDOUT)
    logConsumer.waitUntil { frame ->
      frame.utf8String.contains("just putting something here so the test doesn't quit immediately and I can investigate")
    }

    // todo - verification of output
  }

  companion object {

    private val logger = LoggerFactory.getLogger(MyIntegrationTest::class.java)

    /** Hardcode a port for the contract-service mock */
    private const val contractServicePort: Int = 60104

    /** Download stubs from maven */
    @JvmField
    @RegisterExtension
    val stubRunnerExtension: StubRunnerExtension =
      StubRunnerExtension()
        .stubsMode(StubRunnerProperties.StubsMode.LOCAL)
        .failOnNoStubs(true)
        .downloadStub("my.company:contract-service:4.3.1")
        .withPort(contractServicePort)
  }
}
class MySpringBootApplicationTestContainer(
  private val contractServiceDiscoverableName: String,
  private val contractServiceUri: String,
  imageName: String = "my.project/my-spring-boot-application"
) : GenericContainer<MySpringBootApplicationTestContainer>(imageName) {

  init {
    waitingFor(
      Wait.forLogMessage(
        ".*Started MySpringBootApplication.*".toRegex(RegexOption.IGNORE_CASE).pattern, 1
      )
    )
  }

  override fun configure() {
    super.configure()

    // set Spring Boot environment variables
    withEnv(
      mapOf(
        "spring.cloud.discovery.enabled" to "true",
        "eureka.client.enabled" to "false",

        // https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#features-stub-runner-cloud-stubbing-profiles
        "spring.cloud.config.failFast" to "false",
        "SPRING_APPLICATION_JSON" to
            """
              {
                "spring": {
                  "cloud": {
                    "discovery": {
                      "client": {
                        "simple": {
                          "instances": {
                            "contract-service": [
                              {
                                "uri": "$contractServiceUri"
                              }
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            """.trimIndent(),

        // set the test containers
        "endpoint.contract.base" to contractServiceDiscoverableName,
      )

      // (note I'm setting environment variables in configure() because there are
      // other test containers that MySpringBootApplicationTestContainer depends on,
      // and I need to wait for them to start before fetching their URIs
    )
  }
}

如果我在Spring Boot启动时记录所有应用程序属性(使用此),相关设置为:

endpoint.contract.base: http://contract-service:60104
eureka.client.enabled: false
java.vm.version: 11.0.11+9-LTS
jdk.debug: release
line.separator: 

loadbalancer.client.name: contract-service

spring.cloud.client.hostname: 574557a76be5
spring.cloud.client.ip-address: 172.17.0.5
spring.cloud.config.failFast: false
spring.cloud.discovery.client.simple.instances.contract-service[0].uri: http://host.testcontainers.internal:60104
spring.cloud.discovery.enabled: true

我不知道loadbalancer.client.name:合同服务来自哪里或正在设置。我不知道spring.cloud.client.*道具是否重要或相关。

 类似资料:
  • 我是微服务的新手。(学习阶段)。我有一个问题。我们在云中部署微服务。(例如 AWS)。云已经提供了负载平衡和日志。我们还在Spring Boot中实现了负载平衡(功能区)和日志(Rabbit MQ和Zipkin)。这两种实现有什么区别?我们两者都需要吗?有些人可以回答这些问题吗? 提前感谢。

  • 在将SOA部署到像AWS这样的云上的大型系统时,有两种方法可用于服务交互。 > 让每个服务群集位于内部elb后面。客户端使用相应的elb创建连接池,elb执行循环平衡。 采用netflix eureka等服务发现方法。 目前,我们正在使用第1种方法,其中每个服务集群位于内部elb后面,客户机通过elb进行通信,因此每个客户机实例只需维护一个池,即与elbendpoint进行通信。 我对第二部分有以

  • 试图自学如何使用库伯内特斯,但有一些问题。 我的下一步是尝试使用LoadBalancer类型的服务来访问nginx。 我建立了一个新的集群并部署了nginx映像。 然后,我为LoadBalancer设置服务 设置完成后,我尝试使用LoadBalancer入口(我在描述LoadBalancer服务时发现)访问nginx。我收到一个此页面无法工作的错误。 不太确定我哪里出错了。 kubectl获得sv

  • 我们有两个Kafka节点,出于本问题范围之外的原因,我们希望设置一个负载平衡器来终止生产者(客户端)的SSL。负载平衡器托管的SSL证书将由客户端本机应信任的受信任/根CA签名。 所以连接看起来像: 这是否可行,或者Kafka是否以某种方式要求直接在Kafka服务器上设置SSL? 谢谢

  • 当我将服务与ClusterIP类型和2个POD一起使用时,流量分布在2个POD上。 我找到了另一种服务类型LoadBalancer。这两种服务的区别是什么?LoadBalancer与ClusterIP有何不同? 谢谢

  • 如何将aws应用型负载均衡器和网络负载均衡器直接与自动缩放组(ASG)连接?在AWS控制台中,只有经典负载均衡器可用。我想要的是,每当在自动缩放组中启动实例时,它将开始直接向应用程序/网络负载均衡器报告,而不是手动输入目标组中的每个实例。