Kubernetes 上 Java 应用的最佳实践
在本文中,您将了解在 Kubernetes 上运行 Java 应用程序的最佳实践。大多数这些建议也适用于其他语言。但是,我正在考虑 Java 特性范围内的所有规则,并且还展示了可用于基于 JVM 的应用程序的解决方案和工具。当使用最流行的 Java 框架(如 Spring Boot 或 Quarkus)时,这些 Kubernetes 建议中的一些是设计强制的。我将向您展示如何有效地利用它们来简化开发人员的生活。
1、不要将 Limit 设置得太低
2、首先考虑内存使用
3、适当的 liveness 和 readiness 探针
3.1 介绍
3.2 配置详情
management: endpoint: health: probes: enabled: truespring: application: name: sample-spring-boot-on-kubernetes data: mongodb: host: ${MONGO_URL} port: 27017 username: ${MONGO_USERNAME} password: ${MONGO_PASSWORD} database: ${MONGO_DATABASE} authentication-database: admin
management: endpoint.health: show-details: always group: readiness: include: mongo # (1) additional-path: server:/readiness # (2) probes: enabled: true server: port: 8081spring: application: name: sample-spring-kotlin-microservice datasource: url: jdbc:postgresql://postgres:5432/postgres username: postgres password: postgres123 hikari: connection-timeout: 2000 initialization-fail-timeout: 0 jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect rabbitmq: host: rabbitmq port: 5672 connection-timeout: 20004、选择合适的 JDK
apiVersion: skaffold/v2beta22kind: Configmetadata: name: sample-spring-boot-on-kubernetesbuild: artifacts: - image: piomin/sample-spring-boot-on-kubernetes buildpacks: builder: paketobuildpacks/builder:base buildpacks: - paketo-buildpacks/amazon-corretto - paketo-buildpacks/java env: - BP_JVM_VERSION=175、考虑迁移到原生编译
<profiles> <profile> <id>native</id> <activation> <property> <name>native</name> </property> </activation> <properties> <skipITs>false</skipITs> <quarkus.package.type>native</quarkus.package.type> </properties> </profile></profiles>$ mvn clean package -Pnative<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>build-info</goal> <goal>build-image</goal> </goals> </execution> </executions> <configuration> <image> <builder>paketobuildpacks/builder:tiny</builder> <env> <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE> <BP_NATIVE_IMAGE_BUILD_ARGUMENTS> --allow-incomplete-classpath </BP_NATIVE_IMAGE_BUILD_ARGUMENTS> </env> </image> </configuration></plugin>6、正确配置日志记录
<dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>7.2</version></dependency><configuration> <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"/> </appender> <logger name="jsonLogger" additivity="false" level="DEBUG"> <appender-ref ref="consoleAppender"/> </logger> <root level="INFO"> <appender-ref ref="consoleAppender"/> </root></configuration><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId></dependency><?xml version="1.0" encoding="UTF-8"?><configuration>
<springProperty name="destination" source="app.amqp.url" />
<appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender"> <layout> <pattern>{ "time": "%date{ISO8601}", "thread": "%thread", "level": "%level", "class": "%logger{36}", "message": "%message"} </pattern> </layout>
<addresses>${destination}</addresses> <applicationId>api-service</applicationId> <routingKeyPattern>logs</routingKeyPattern> <declareExchange>true</declareExchange> <exchangeName>ex_logstash</exchangeName>
</appender>
<root level="INFO"> <appender-ref ref="AMQP" /> </root>
</configuration>7、创建集成测试
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-container-image-jib</artifactId></dependency>@QuarkusIntegrationTestpublic class EmployeeControllerIT {
@TestHTTPEndpoint(EmployeeController.class) @TestHTTPResource URL url;
@Test void add() { EmployeeService service = RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Employee employee = new Employee(1L, 1L, "Josh Stevens", 23, "Developer"); employee = service.add(employee); assertNotNull(employee.getId()); }
@Test public void findAll() { EmployeeService service = RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Set<Employee> employees = service.findAll(); assertTrue(employees.size() >= 3); }
@Test public void findById() { EmployeeService service = RestClientBuilder.newBuilder() .baseUrl(url) .build(EmployeeService.class); Employee employee = service.findById(1L); assertNotNull(employee.getId()); }}@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@Testcontainers@TestMethodOrder(MethodOrderer.OrderAnnotation.class)public class PersonControllerTests {
@Autowired TestRestTemplate restTemplate;
@Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15.1") .withExposedPorts(5432);
@DynamicPropertySource static void registerMySQLProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); }
@Test @Order(1) void add() { Person person = Instancio.of(Person.class) .ignore(Select.field("id")) .create(); person = restTemplate.postForObject("/persons", person, Person.class); Assertions.assertNotNull(person); Assertions.assertNotNull(person.getId()); }
@Test @Order(2) void updateAndGet() { final Integer id = 1; Person person = Instancio.of(Person.class) .set(Select.field("id"), id) .create(); restTemplate.put("/persons", person); Person updated = restTemplate.getForObject("/persons/{id}", Person.class, id); Assertions.assertNotNull(updated); Assertions.assertNotNull(updated.getId()); Assertions.assertEquals(id, updated.getId()); }
}8、最后的想法
END
往期精彩还在只用 RedisTemplate 访问 Redis 吗?
Spring 中的 @Cacheable 缓存注解,你真的了解吗?
路透社:开源软件成为贸易战的重要环节
基于 SpringBoot + Vue 实现的可视化拖拽编辑的大屏项目
关注后端面试那些事,回复【2022面经】
获取最新大厂Java面经