Skip to content

Commit

Permalink
Native image support for Config Server (#2361)
Browse files Browse the repository at this point in the history
  • Loading branch information
OlgaMaciaszek authored Dec 5, 2023
1 parent f9449a2 commit 6be0131
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 8 deletions.
26 changes: 25 additions & 1 deletion docs/modules/ROOT/pages/server/aot-and-native-image-support.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,29 @@
= AOT and Native Image Support
:page-section-summary-toc: 1

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.
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.

====
IMPORTANT::
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):
[source,indent=0]
----
-H:-AddAllFileSystemProviders
--strict-image-heap
--initialize-at-build-time=org.bouncycastle
--initialize-at-build-time=net.i2p.crypto.eddsa.EdDSASecurityProvider
--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$Default
--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV
----
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.
====

TIP::
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`]

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`.

9 changes: 2 additions & 7 deletions spring-cloud-config-client-tls-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,8 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.67</version>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.74</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2018-2023 the original author or 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
*
* https://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 org.springframework.cloud.config.server.config;

import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.Signature;
import java.util.Set;

import javax.crypto.KeyAgreement;
import javax.crypto.Mac;

import org.apache.sshd.common.channel.ChannelListener;
import org.apache.sshd.common.forward.PortForwardingEventListener;
import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
import org.apache.sshd.common.session.SessionListener;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar;
import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.MergeCommand;
import org.eclipse.jgit.internal.transport.sshd.SshdText;

import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.cloud.config.environment.PropertyValueDescriptor;
import org.springframework.cloud.config.server.ssh.HostKeyAlgoSupportedValidator;
import org.springframework.cloud.config.server.ssh.HostKeyAndAlgoBothExistValidator;
import org.springframework.cloud.config.server.ssh.KnownHostsFileValidator;
import org.springframework.cloud.config.server.ssh.PrivateKeyValidator;
import org.springframework.cloud.config.server.ssh.SshPropertyValidator;
import org.springframework.util.ClassUtils;

/**
* A {@link RuntimeHintsRegistrar} implementation that makes types required by Config
* Server available in constrained environments.
*
* @author Olga Maciaszek-Sharma
* @since 4.1.0
*/
class ConfigServerRuntimeHints implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (!ClassUtils.isPresent("org.springframework.cloud.config.server.config.ConfigServerConfiguration",
classLoader)) {
return;
}
hints.reflection().registerTypes(Set.of(TypeReference.of(HostKeyAndAlgoBothExistValidator.class),
TypeReference.of(KnownHostsFileValidator.class), TypeReference.of(HostKeyAlgoSupportedValidator.class),
TypeReference.of(PrivateKeyValidator.class), TypeReference.of(SshPropertyValidator.class),
TypeReference.of(PropertyValueDescriptor.class)),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
hints.reflection().registerTypes(
Set.of(TypeReference.of(PropertyValueDescriptor.class), TypeReference.of(Mac.class),
TypeReference.of(KeyAgreement.class), TypeReference.of(KeyPairGenerator.class),
TypeReference.of(KeyFactory.class), TypeReference.of(Signature.class),
TypeReference.of(MessageDigest.class)),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));

// TODO: move over to GraalVM reachability metadata
if (ClassUtils.isPresent("org.apache.sshd.common.SshConstants", classLoader)) {
hints.reflection().registerTypes(Set.of(TypeReference.of(BouncyCastleSecurityProviderRegistrar.class),
TypeReference.of(EdDSASecurityProviderRegistrar.class), TypeReference.of(Nio2ServiceFactory.class),
TypeReference.of(Nio2ServiceFactoryFactory.class)),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
hints.reflection().registerTypes(Set.of(TypeReference.of(PortForwardingEventListener.class)),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
hints.proxies().registerJdkProxy(TypeReference.of(ChannelListener.class),
TypeReference.of(PortForwardingEventListener.class), TypeReference.of(SessionListener.class));
}

// TODO: move over to GraalVM reachability metadata
if (ClassUtils.isPresent("org.eclipse.jgit.api.Git", classLoader)) {
hints.reflection()
.registerTypes(Set.of(TypeReference.of(MergeCommand.FastForwardMode.Merge.class),
TypeReference.of(MergeCommand.ConflictStyle.class),
TypeReference.of(MergeCommand.FastForwardMode.class), TypeReference.of(FetchCommand.class)),
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
hints.reflection().registerTypes(Set.of(TypeReference.of(SshdText.class)), hint -> hint
.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.cloud.config.server.config.ConfigServerRuntimeHints

0 comments on commit 6be0131

Please sign in to comment.