diff --git a/CHANGES.md b/CHANGES.md index fa31b77e969..e797333ee80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,5 +28,6 @@ Apollo 2.5.0 * [doc: Add rust apollo client link](https://github.com/apolloconfig/apollo/pull/5514) * [Perf: optimize namespace-related interface](https://github.com/apolloconfig/apollo/pull/5518) * [Perf: Replace synchronized multimap with concurrent hashmap in NotificationControllerV2 for better performance](https://github.com/apolloconfig/apollo/pull/5532) +* [Feature: Enable graceful shutdown for apollo-adminservice and apollo-configservice](https://github.com/apolloconfig/apollo/pull/5536) ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/16?closed=1) diff --git a/apollo-adminservice/src/main/resources/application.yml b/apollo-adminservice/src/main/resources/application.yml index e1b1595c351..dd9b8af4041 100644 --- a/apollo-adminservice/src/main/resources/application.yml +++ b/apollo-adminservice/src/main/resources/application.yml @@ -27,9 +27,12 @@ spring: properties: hibernate: metadata_builder_contributor: com.ctrip.framework.apollo.common.jpa.SqlFunctionsMetadataBuilderContributor + lifecycle: + timeout-per-shutdown-phase: ${GRACEFUL_SHUTDOWN_TIMEOUT:10s} server: port: 8090 + shutdown: graceful logging: file: diff --git a/apollo-adminservice/src/test/java/com/ctrip/framework/apollo/adminservice/GracefulShutdownConfigurationTest.java b/apollo-adminservice/src/test/java/com/ctrip/framework/apollo/adminservice/GracefulShutdownConfigurationTest.java new file mode 100644 index 00000000000..aa71560845a --- /dev/null +++ b/apollo-adminservice/src/test/java/com/ctrip/framework/apollo/adminservice/GracefulShutdownConfigurationTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.adminservice; + +import com.ctrip.framework.apollo.AdminServiceTestConfiguration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Configuration validation test for graceful shutdown feature. + * + * This test verifies that the graceful shutdown configuration is properly loaded + * from application.yml by checking ServerProperties and the web server lifecycle. + * + * Note: This test does NOT verify the actual behavior of graceful shutdown + * (e.g., waiting for in-flight requests). Full behavioral testing requires: + * - Integration tests with real HTTP requests during shutdown + * - Manual testing in staging/production environments + * - Monitoring of shutdown metrics and logs + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = AdminServiceTestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class GracefulShutdownConfigurationTest { + + @Autowired + private ServletWebServerApplicationContext webServerAppContext; + + @Autowired + private ServerProperties serverProperties; + + @Test + public void testGracefulShutdownIsConfigured() { + assertNotNull("WebServer should be available", webServerAppContext); + assertTrue("Server should be running", webServerAppContext.getWebServer().getPort() > 0); + + // Verify graceful shutdown is enabled in application.yml + assertEquals("Graceful shutdown should be enabled in application.yml", "graceful", + serverProperties.getShutdown().name().toLowerCase()); + + // Verify the lifecycle processor exists (indicates graceful shutdown is enabled) + assertNotNull("Lifecycle processor should be present for graceful shutdown", + webServerAppContext.getBean("lifecycleProcessor")); + } +} diff --git a/apollo-adminservice/src/test/resources/application.yml b/apollo-adminservice/src/test/resources/application.yml index d1f2e0bd8e9..6a668e8d3af 100644 --- a/apollo-adminservice/src/test/resources/application.yml +++ b/apollo-adminservice/src/test/resources/application.yml @@ -16,9 +16,12 @@ spring: application: name: apollo-adminservice + lifecycle: + timeout-per-shutdown-phase: ${GRACEFUL_SHUTDOWN_TIMEOUT:10s} server: port: ${port:8090} + shutdown: graceful eureka: instance: diff --git a/apollo-assembly/src/main/resources/application.yml b/apollo-assembly/src/main/resources/application.yml index e25af6fde1b..4f2a77e7b97 100644 --- a/apollo-assembly/src/main/resources/application.yml +++ b/apollo-assembly/src/main/resources/application.yml @@ -27,6 +27,8 @@ spring: properties: hibernate: metadata_builder_contributor: com.ctrip.framework.apollo.common.jpa.SqlFunctionsMetadataBuilderContributor + lifecycle: + timeout-per-shutdown-phase: ${GRACEFUL_SHUTDOWN_TIMEOUT:10s} logging: file: @@ -60,6 +62,7 @@ eureka: register-with-eureka: false server: + shutdown: graceful compression: enabled: true tomcat: diff --git a/apollo-configservice/src/main/resources/application.yml b/apollo-configservice/src/main/resources/application.yml index 986a8b91f38..77749a38243 100644 --- a/apollo-configservice/src/main/resources/application.yml +++ b/apollo-configservice/src/main/resources/application.yml @@ -27,9 +27,12 @@ spring: properties: hibernate: metadata_builder_contributor: com.ctrip.framework.apollo.common.jpa.SqlFunctionsMetadataBuilderContributor + lifecycle: + timeout-per-shutdown-phase: ${GRACEFUL_SHUTDOWN_TIMEOUT:10s} server: port: 8080 + shutdown: graceful logging: file: diff --git a/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/GracefulShutdownConfigurationTest.java b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/GracefulShutdownConfigurationTest.java new file mode 100644 index 00000000000..92dc9e57d16 --- /dev/null +++ b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/GracefulShutdownConfigurationTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.configservice; + +import com.ctrip.framework.apollo.ConfigServiceTestConfiguration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Configuration validation test for graceful shutdown feature. + * + * This test verifies that the graceful shutdown configuration is properly loaded + * from application.yml by checking ServerProperties and the web server lifecycle. + * + * Note: This test does NOT verify the actual behavior of graceful shutdown + * (e.g., waiting for in-flight requests). Full behavioral testing requires: + * - Integration tests with real HTTP requests during shutdown + * - Manual testing in staging/production environments + * - Monitoring of shutdown metrics and logs + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = ConfigServiceTestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class GracefulShutdownConfigurationTest { + + @Autowired + private ServletWebServerApplicationContext webServerAppContext; + + @Autowired + private ServerProperties serverProperties; + + @Test + public void testGracefulShutdownIsConfigured() { + assertNotNull("WebServer should be available", webServerAppContext); + assertTrue("Server should be running", webServerAppContext.getWebServer().getPort() > 0); + + // Verify graceful shutdown is enabled in application.yml + assertEquals("Graceful shutdown should be enabled in application.yml", "graceful", + serverProperties.getShutdown().name().toLowerCase()); + + // Verify the lifecycle processor exists (indicates graceful shutdown is enabled) + assertNotNull("Lifecycle processor should be present for graceful shutdown", + webServerAppContext.getBean("lifecycleProcessor")); + } +} diff --git a/apollo-configservice/src/test/resources/application.yml b/apollo-configservice/src/test/resources/application.yml index 0e8c9321db5..5e69a205bbd 100644 --- a/apollo-configservice/src/test/resources/application.yml +++ b/apollo-configservice/src/test/resources/application.yml @@ -16,9 +16,12 @@ spring: application: name: apollo-configservice + lifecycle: + timeout-per-shutdown-phase: ${GRACEFUL_SHUTDOWN_TIMEOUT:10s} server: port: ${port:8080} + shutdown: graceful eureka: instance: diff --git a/docs/en/deployment/distributed-deployment-guide.md b/docs/en/deployment/distributed-deployment-guide.md index a4997628ac6..ab610c568fe 100644 --- a/docs/en/deployment/distributed-deployment-guide.md +++ b/docs/en/deployment/distributed-deployment-guide.md @@ -513,7 +513,9 @@ export JAVA_OPTS="-server -Xms6144m -Xmx6144m -Xss256k -XX:MetaspaceSize=128m -X > Note 4: If the eureka.service.url of ApolloConfigDB.ServerConfig is only configured with the currently starting machine, the eureka registration failure information will be output in the log during the process of starting apollo-configservice, such as `com.sun.jersey .api.client.ClientHandlerException: java.net.ConnectException: Connection refused`. It should be noted that this is the expected situation, because apollo-configservice needs to register the service with the Meta Server (itself), but because it has not yet woken up during the startup process, it will report this error. The retry action will be performed later, so the registration will be normal after the service is up. -> Note 5: If you read this, I believe that you must be someone who reads the documentation carefully, and you are a little bit closer to success. Keep going, you should be able to complete the distributed deployment of Apollo soon! But do you feel that Apollo's distributed deployment steps are a bit cumbersome? Do you have any advice you would like to share with the author? If the answer is yes, please move to [#1424](https://github.com/apolloconfig/apollo/issues/1424) and look forward to your suggestions! +> Note 5: Starting from version 2.5.0, apollo-configservice supports graceful shutdown. When the service receives a stop signal, it will wait for in-flight requests to complete before shutting down, with a default timeout of 10 seconds. This feature is enabled via Spring Boot's `server.shutdown=graceful` and `spring.lifecycle.timeout-per-shutdown-phase=${GRACEFUL_SHUTDOWN_TIMEOUT:10s}` configuration. To adjust the timeout, you can set the `GRACEFUL_SHUTDOWN_TIMEOUT` environment variable (e.g., `30s`, `60s`, `2m`) or modify the settings in application.yml. In Kubernetes environments, ensure the Pod's `terminationGracePeriodSeconds` is greater than the configured timeout (recommend at least 10 seconds more). + +> Note 6: If you read this, I believe that you must be someone who reads the documentation carefully, and you are a little bit closer to success. Keep going, you should be able to complete the distributed deployment of Apollo soon! But do you feel that Apollo's distributed deployment steps are a bit cumbersome? Do you have any advice you would like to share with the author? If the answer is yes, please move to [#1424](https://github.com/apolloconfig/apollo/issues/1424) and look forward to your suggestions! #### 2.2.2.2 Deploy apollo-adminservice @@ -531,6 +533,8 @@ export JAVA_OPTS="-server -Xms2560m -Xmx2560m -Xss256k -XX:MetaspaceSize=128m -X > Note 3: To adjust the listening port of the service, you can modify the `SERVER_PORT` in scripts/startup.sh. +> Note 4: Starting from version 2.5.0, apollo-adminservice supports graceful shutdown. When the service receives a stop signal, it will wait for in-flight requests to complete before shutting down, with a default timeout of 10 seconds. This feature is enabled via Spring Boot's `server.shutdown=graceful` and `spring.lifecycle.timeout-per-shutdown-phase=${GRACEFUL_SHUTDOWN_TIMEOUT:10s}` configuration. To adjust the timeout, you can set the `GRACEFUL_SHUTDOWN_TIMEOUT` environment variable (e.g., `30s`, `60s`, `2m`) or modify the settings in application.yml. In Kubernetes environments, ensure the Pod's `terminationGracePeriodSeconds` is greater than the configured timeout (recommend at least 10 seconds more). + #### 2.2.2.3 Deploy apollo-portal Upload `apollo-portal-x.x.x-github.zip` to the server, unzip it and execute scripts/startup.sh. To stop the service, execute scripts/shutdown.sh. diff --git a/docs/zh/deployment/distributed-deployment-guide.md b/docs/zh/deployment/distributed-deployment-guide.md index dc617cc2041..a95cc4dfaa8 100644 --- a/docs/zh/deployment/distributed-deployment-guide.md +++ b/docs/zh/deployment/distributed-deployment-guide.md @@ -495,7 +495,9 @@ export JAVA_OPTS="-server -Xms6144m -Xmx6144m -Xss256k -XX:MetaspaceSize=128m -X > 注4:如果ApolloConfigDB.ServerConfig的eureka.service.url只配了当前正在启动的机器的话,在启动apollo-configservice的过程中会在日志中输出eureka注册失败的信息,如`com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused`。需要注意的是,这个是预期的情况,因为apollo-configservice需要向Meta Server(它自己)注册服务,但是因为在启动过程中,自己还没起来,所以会报这个错。后面会进行重试的动作,所以等自己服务起来后就会注册正常了。 -> 注5:如果你看到了这里,相信你一定是一个细心阅读文档的人,而且离成功就差一点点了,继续加油,应该很快就能完成Apollo的分布式部署了!不过你是否有感觉Apollo的分布式部署步骤有点繁琐?是否有啥建议想要和作者说?如果答案是肯定的话,请移步 [#1424](https://github.com/apolloconfig/apollo/issues/1424),期待你的建议! +> 注5:apollo-configservice从2.5.0版本开始支持优雅下线功能。当服务收到停止信号时,会等待正在处理的请求完成后再关闭,默认等待时间为10秒。此功能通过Spring Boot的`server.shutdown=graceful`和`spring.lifecycle.timeout-per-shutdown-phase=${GRACEFUL_SHUTDOWN_TIMEOUT:10s}`配置启用。如需调整超时时间,可以通过环境变量`GRACEFUL_SHUTDOWN_TIMEOUT`设置(如`30s`、`60s`、`2m`等),或直接修改application.yml中的配置。在Kubernetes环境中,请确保Pod的`terminationGracePeriodSeconds`大于配置的超时时间(建议至少多10秒)。 + +> 注6:如果你看到了这里,相信你一定是一个细心阅读文档的人,而且离成功就差一点点了,继续加油,应该很快就能完成Apollo的分布式部署了!不过你是否有感觉Apollo的分布式部署步骤有点繁琐?是否有啥建议想要和作者说?如果答案是肯定的话,请移步 [#1424](https://github.com/apolloconfig/apollo/issues/1424),期待你的建议! #### 2.2.2.2 部署apollo-adminservice 将对应环境的`apollo-adminservice-x.x.x-github.zip`上传到服务器上,解压后执行scripts/startup.sh即可。如需停止服务,执行scripts/shutdown.sh. @@ -511,6 +513,8 @@ export JAVA_OPTS="-server -Xms2560m -Xmx2560m -Xss256k -XX:MetaspaceSize=128m -X > 注3:如要调整服务的监听端口,可以修改scripts/startup.sh中的`SERVER_PORT`。 +> 注4:apollo-adminservice从2.5.0版本开始支持优雅下线功能。当服务收到停止信号时,会等待正在处理的请求完成后再关闭,默认等待时间为10秒。此功能通过Spring Boot的`server.shutdown=graceful`和`spring.lifecycle.timeout-per-shutdown-phase=${GRACEFUL_SHUTDOWN_TIMEOUT:10s}`配置启用。如需调整超时时间,可以通过环境变量`GRACEFUL_SHUTDOWN_TIMEOUT`设置(如`30s`、`60s`、`2m`等),或直接修改application.yml中的配置。在Kubernetes环境中,请确保Pod的`terminationGracePeriodSeconds`大于配置的超时时间(建议至少多10秒)。 + #### 2.2.2.3 部署apollo-portal 将`apollo-portal-x.x.x-github.zip`上传到服务器上,解压后执行scripts/startup.sh即可。如需停止服务,执行scripts/shutdown.sh.