@@ -135,11 +135,16 @@ public class KubernetesBackend extends AbstractContainerBackend {
135135 private static final String PROPERTY_IMG_PULL_SECRETS = "image-pull-secrets" ;
136136 private static final String PROPERTY_IMG_PULL_SECRET = "image-pull-secret" ;
137137 private static final String PROPERTY_NODE_SELECTOR = "node-selector" ;
138+ private static final String PROPERTY_CLUSTER_DOMAIN = "cluster-domain" ;
138139
139140 private static final String DEFAULT_NAMESPACE = "default" ;
140141 private static final String DEFAULT_API_VERSION = "v1" ;
142+ private static final String DEFAULT_CLUSTER_DOMAIN = "cluster.local" ;
141143
142144 private static final String SECRET_KEY_REF = "secretKeyRef" ;
145+ private static final String HEADLESS_SERVICE_NAME = "sp-headless-service" ;
146+ private static final String HEADLESS_SERVICE_LABEL = "openanalytics.eu/sp-headless-service" ;
147+ private static final String HEADLESS_SERVICE_LABEL_VALUE = "true" ;
143148
144149 private static final String ANNOTATION_MANIFEST_POLICY = "openanalytics.eu/sp-additional-manifest-policy" ;
145150 private final ObjectMapper writer = new ObjectMapper (new YAMLFactory ());
@@ -158,6 +163,7 @@ public class KubernetesBackend extends AbstractContainerBackend {
158163 private String kubeNamespace ;
159164 private String apiVersion ;
160165 private String imagePullPolicy ;
166+ private String clusterDomain ;
161167
162168 @ PostConstruct
163169 public void initialize () {
@@ -197,6 +203,7 @@ public void initialize(KubernetesClient client) {
197203 nodeSelectorString = getProperty (PROPERTY_NODE_SELECTOR );
198204 apiVersion = getProperty (PROPERTY_API_VERSION , DEFAULT_API_VERSION );
199205 imagePullPolicy = getProperty (PROPERTY_IMG_PULL_POLICY );
206+ clusterDomain = getProperty (PROPERTY_CLUSTER_DOMAIN , DEFAULT_CLUSTER_DOMAIN );
200207
201208 String imagePullSecret = getProperty (PROPERTY_IMG_PULL_SECRET );
202209 if (imagePullSecret != null ) {
@@ -284,6 +291,7 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
284291 Map <String , String > serviceLabels = new HashMap <>();
285292 Map <String , String > podLabels = new HashMap <>();
286293 podLabels .put ("app" , containerId );
294+ podLabels .put (HEADLESS_SERVICE_LABEL , HEADLESS_SERVICE_LABEL_VALUE );
287295
288296 String podName = StringUtils .left (spec .getResourceName ().getValueOrDefault ("sp-pod-" + proxy .getId () + "-" + initialContainer .getIndex ()), 63 );
289297
@@ -325,12 +333,13 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
325333 podSpec .setVolumes (volumes );
326334 podSpec .setImagePullSecrets (imagePullSecrets );
327335 podSpec .setRestartPolicy ("Never" );
336+ podSpec .setHostname (podName );
337+ podSpec .setSubdomain (HEADLESS_SERVICE_NAME );
328338
329339 if (nodeSelectorString != null ) {
330340 podSpec .setNodeSelector (Splitter .on ("," ).withKeyValueSeparator ("=" ).split (nodeSelectorString ));
331341 }
332342
333-
334343 Pod startupPod = podBuilder .withSpec (podSpec ).build ();
335344 Pod patchedPod = applyPodPatches (user , proxySpec , specExtension , proxy , startupPod , initialContainer );
336345 final String effectiveKubeNamespace = patchedPod .getMetadata ().getNamespace (); // use the namespace of the patched Pod, in case the patch changes the namespace.
@@ -377,27 +386,30 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
377386 Service service ;
378387 Map <Integer , Integer > portBindings = new HashMap <>();
379388 if (isUseInternalNetwork ()) {
380- // If SP runs inside the cluster, it can access pods directly and doesn't need any port publishing service.
389+ // If SP runs inside the cluster, it re-uses an existing headless service
390+ createHeadlessService (effectiveKubeNamespace );
381391 } else {
382392 List <ServicePort > servicePorts = spec .getPortMapping ().stream ()
383393 .map (p -> new ServicePortBuilder ().withPort (p .getPort ()).build ())
384394 .toList ();
385395
396+ // @formatter:off
386397 Service startupService = kubeClient .services ().inNamespace (effectiveKubeNamespace )
387398 .resource (new ServiceBuilder ()
388399 .withApiVersion (apiVersion )
389400 .withKind ("Service" )
390401 .withNewMetadata ()
391- .withName (getServiceName (proxy , initialContainer ))
392- .withLabels (serviceLabels )
402+ .withName (getServiceName (proxy , initialContainer ))
403+ .withLabels (serviceLabels )
393404 .endMetadata ()
394405 .withNewSpec ()
395- .addToSelector ("app" , containerId )
396- .withType ("NodePort" )
397- .withPorts (servicePorts )
406+ .addToSelector ("app" , containerId )
407+ .withType ("NodePort" )
408+ .withPorts (servicePorts )
398409 .endSpec ()
399410 .build ())
400- .create ();
411+ .create ();
412+ // @formatter:on
401413
402414 // Workaround: waitUntilReady appears to be buggy.
403415 Retrying .retry ((currentAttempt , maxAttempts ) -> new Retrying .Result (isServiceReady (kubeClient .resource (startupService ).get ())), 60_000 );
@@ -636,14 +648,44 @@ private boolean isServiceReady(Service service) {
636648 return service .getStatus ().getLoadBalancer () != null ;
637649 }
638650
651+ private void createHeadlessService (String effectiveKubeNamespace ) {
652+ if (kubeClient .services ().inNamespace (effectiveKubeNamespace ).withName (HEADLESS_SERVICE_NAME ).get () == null ) {
653+ // @formatter:off
654+ kubeClient .services ().inNamespace (effectiveKubeNamespace )
655+ .resource (new ServiceBuilder ()
656+ .withApiVersion (apiVersion )
657+ .withKind ("Service" )
658+ .withNewMetadata ()
659+ .withName (HEADLESS_SERVICE_NAME )
660+ .endMetadata ()
661+ .withNewSpec ()
662+ .addToSelector (HEADLESS_SERVICE_LABEL , HEADLESS_SERVICE_LABEL_VALUE )
663+ .withClusterIP ("None" )
664+ .endSpec ()
665+ .build ())
666+ .create ();
667+ // @formatter:on
668+ }
669+ }
670+
671+ private String getPodFqdn (Pod pod ) {
672+ return String .format (
673+ "%s.%s.%s.svc.%s" ,
674+ pod .getSpec ().getHostname (),
675+ pod .getSpec ().getSubdomain (),
676+ pod .getMetadata ().getNamespace (),
677+ clusterDomain );
678+
679+ }
680+
639681 protected URI calculateTarget (Container container , PortMappings .PortMappingEntry portMapping , Integer servicePort ) throws Exception {
640682 String targetHostName ;
641683 int targetPort ;
642684
643685 Pod pod = getPod (container ).orElseThrow (() -> new ContainerFailedToStartException ("Pod not found while calculating target" , null , container ));
644686
645687 if (isUseInternalNetwork ()) {
646- targetHostName = pod . getStatus (). getPodIP ( );
688+ targetHostName = getPodFqdn ( pod );
647689 targetPort = portMapping .getPort ();
648690 } else {
649691 targetHostName = pod .getStatus ().getHostIP ();
0 commit comments