Skip to content

Commit 6be0131

Browse files
Native image support for Config Server (#2361)
1 parent f9449a2 commit 6be0131

File tree

4 files changed

+132
-8
lines changed

4 files changed

+132
-8
lines changed

docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,29 @@
22
= AOT and Native Image Support
33
:page-section-summary-toc: 1
44

5-
Since `4.0.0`, Spring Cloud Config Server supports Spring AOT transformations. However, for the time being, GraalVM native images are not supported. Implementing native image support is blocked by https://github.com/oracle/graal/issues/5134[graal#5134] and will likely require the completion of the work on https://github.com/graalvm/taming-build-time-initialization[https://github.com/graalvm/taming-build-time-initialization] to be fixed.
5+
Since `4.0.0`, Spring Cloud Config Server supports Spring AOT transformations. As of `4.1.0` it also supports GraalVM native images, however it requires the user to add some workarounds for known GraalVM issues, as described below.
6+
7+
====
8+
9+
IMPORTANT::
10+
Due to [a bug](https://github.com/oracle/graal/issues/5134) in Graal's `FileSystemProvider` a configuration workaround needs to be added to allow the Config Server to run as a native image. You will need to add the following options to your GraalVM build plugin setup (please refer to https://www.graalvm.org/[GraalVM] Maven or Gradle plugin documentation for more details):
11+
12+
[source,indent=0]
13+
----
14+
-H:-AddAllFileSystemProviders
15+
--strict-image-heap
16+
--initialize-at-build-time=org.bouncycastle
17+
--initialize-at-build-time=net.i2p.crypto.eddsa.EdDSASecurityProvider
18+
--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$Default
19+
--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV
20+
----
21+
22+
NOTE:: Adding the additional build time initializations can affect performance, but it still may offer gains as compared to a regular JVM run. Make sure to measure and compare for your application.
23+
24+
====
25+
26+
TIP::
27+
If you are connecting with your config data backend over SSH, keep in mind that GraalVM requires https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/JCASecurityServices/#provider-registration[security provider registration using `java.security`]
28+
29+
WARNING: Refresh scope is not supported with native images. If you are going to run your config client application as a native image, make sure to set `spring.cloud.refresh.enabled` property to `false`.
630

spring-cloud-config-client-tls-tests/pom.xml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,8 @@
9898
</dependency>
9999
<dependency>
100100
<groupId>org.bouncycastle</groupId>
101-
<artifactId>bcpkix-jdk15on</artifactId>
102-
<scope>test</scope>
103-
</dependency>
104-
<dependency>
105-
<groupId>org.bouncycastle</groupId>
106-
<artifactId>bcpkix-jdk15on</artifactId>
107-
<version>1.67</version>
101+
<artifactId>bcpkix-jdk18on</artifactId>
102+
<version>1.74</version>
108103
<scope>test</scope>
109104
</dependency>
110105
</dependencies>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2018-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.config.server.config;
18+
19+
import java.security.KeyFactory;
20+
import java.security.KeyPairGenerator;
21+
import java.security.MessageDigest;
22+
import java.security.Signature;
23+
import java.util.Set;
24+
25+
import javax.crypto.KeyAgreement;
26+
import javax.crypto.Mac;
27+
28+
import org.apache.sshd.common.channel.ChannelListener;
29+
import org.apache.sshd.common.forward.PortForwardingEventListener;
30+
import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
31+
import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
32+
import org.apache.sshd.common.session.SessionListener;
33+
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar;
34+
import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar;
35+
import org.eclipse.jgit.api.FetchCommand;
36+
import org.eclipse.jgit.api.MergeCommand;
37+
import org.eclipse.jgit.internal.transport.sshd.SshdText;
38+
39+
import org.springframework.aot.hint.MemberCategory;
40+
import org.springframework.aot.hint.RuntimeHints;
41+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
42+
import org.springframework.aot.hint.TypeReference;
43+
import org.springframework.cloud.config.environment.PropertyValueDescriptor;
44+
import org.springframework.cloud.config.server.ssh.HostKeyAlgoSupportedValidator;
45+
import org.springframework.cloud.config.server.ssh.HostKeyAndAlgoBothExistValidator;
46+
import org.springframework.cloud.config.server.ssh.KnownHostsFileValidator;
47+
import org.springframework.cloud.config.server.ssh.PrivateKeyValidator;
48+
import org.springframework.cloud.config.server.ssh.SshPropertyValidator;
49+
import org.springframework.util.ClassUtils;
50+
51+
/**
52+
* A {@link RuntimeHintsRegistrar} implementation that makes types required by Config
53+
* Server available in constrained environments.
54+
*
55+
* @author Olga Maciaszek-Sharma
56+
* @since 4.1.0
57+
*/
58+
class ConfigServerRuntimeHints implements RuntimeHintsRegistrar {
59+
60+
@Override
61+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
62+
if (!ClassUtils.isPresent("org.springframework.cloud.config.server.config.ConfigServerConfiguration",
63+
classLoader)) {
64+
return;
65+
}
66+
hints.reflection().registerTypes(Set.of(TypeReference.of(HostKeyAndAlgoBothExistValidator.class),
67+
TypeReference.of(KnownHostsFileValidator.class), TypeReference.of(HostKeyAlgoSupportedValidator.class),
68+
TypeReference.of(PrivateKeyValidator.class), TypeReference.of(SshPropertyValidator.class),
69+
TypeReference.of(PropertyValueDescriptor.class)),
70+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
71+
hints.reflection().registerTypes(
72+
Set.of(TypeReference.of(PropertyValueDescriptor.class), TypeReference.of(Mac.class),
73+
TypeReference.of(KeyAgreement.class), TypeReference.of(KeyPairGenerator.class),
74+
TypeReference.of(KeyFactory.class), TypeReference.of(Signature.class),
75+
TypeReference.of(MessageDigest.class)),
76+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
77+
78+
// TODO: move over to GraalVM reachability metadata
79+
if (ClassUtils.isPresent("org.apache.sshd.common.SshConstants", classLoader)) {
80+
hints.reflection().registerTypes(Set.of(TypeReference.of(BouncyCastleSecurityProviderRegistrar.class),
81+
TypeReference.of(EdDSASecurityProviderRegistrar.class), TypeReference.of(Nio2ServiceFactory.class),
82+
TypeReference.of(Nio2ServiceFactoryFactory.class)),
83+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
84+
hints.reflection().registerTypes(Set.of(TypeReference.of(PortForwardingEventListener.class)),
85+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
86+
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
87+
hints.proxies().registerJdkProxy(TypeReference.of(ChannelListener.class),
88+
TypeReference.of(PortForwardingEventListener.class), TypeReference.of(SessionListener.class));
89+
}
90+
91+
// TODO: move over to GraalVM reachability metadata
92+
if (ClassUtils.isPresent("org.eclipse.jgit.api.Git", classLoader)) {
93+
hints.reflection()
94+
.registerTypes(Set.of(TypeReference.of(MergeCommand.FastForwardMode.Merge.class),
95+
TypeReference.of(MergeCommand.ConflictStyle.class),
96+
TypeReference.of(MergeCommand.FastForwardMode.class), TypeReference.of(FetchCommand.class)),
97+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
98+
hints.reflection().registerTypes(Set.of(TypeReference.of(SshdText.class)), hint -> hint
99+
.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS));
100+
}
101+
}
102+
103+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.aot.hint.RuntimeHintsRegistrar=\
2+
org.springframework.cloud.config.server.config.ConfigServerRuntimeHints

0 commit comments

Comments
 (0)