Skip to content

Commit ab5c942

Browse files
committed
add resolution against node IPs
1 parent 8628192 commit ab5c942

6 files changed

Lines changed: 152 additions & 48 deletions

File tree

src/main/java/tech/stackable/hadoop/StackableTopologyProvider.java

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -119,24 +119,30 @@ private List<String> tryResolveFromCache(List<String> names) {
119119
private List<String> performFullResolution(List<String> names) {
120120
LOG.debug("Performing full topology resolution for: {}", names);
121121

122-
// Step 1: Gather all dataNodes
122+
// Pre-requisites : fetch all dataNodes and build label lookup maps from them
123123
List<Pod> dataNodes = fetchDataNodes();
124-
125-
// Step 2: Resolve listeners to actual datanode IPs
126-
List<String> resolvedNames = resolveListeners(names);
127-
128-
// Step 3: Build label lookup maps
129124
Map<String, Map<String, String>> podLabels = buildPodLabelMap(dataNodes);
130125
Map<String, Map<String, String>> nodeLabels = buildNodeLabelMap(dataNodes);
131126

132-
// Step 4: Build node-to-datanode map for O(1) colocated lookups
127+
// Build node-to-datanode map for O(1) colocated lookups
133128
Map<String, String> nodeToDatanodeIp = buildNodeToDatanodeMap(dataNodes);
134129

135-
// Step 5: Resolve client pods to co-located dataNodes
130+
// Resolve masqueraded IPs to nodes: do this before inspecting possible listener-
131+
// or other pod-IPs as we don't want to mistakenly treat a masqueraded IP as a
132+
// cache-miss
133+
List<String> resolvedNames = tryResolveNodes(names, nodeToDatanodeIp);
134+
135+
// Resolve dataNode listeners to datanode IPs
136+
resolvedNames = tryResolveListeners(resolvedNames);
137+
138+
// Step : Resolve client pods to co-located dataNodes
136139
List<String> datanodeIps =
137-
resolveClientPodsToDataNodes(resolvedNames, podLabels, nodeToDatanodeIp);
140+
tryResolveClientPodsToDataNodes(resolvedNames, podLabels, nodeToDatanodeIp);
138141

139-
// Step 6: Build topology strings and cache results
142+
// Step : Build topology strings and cache results
143+
// IP-masquerading can mean that the advertised IP is either a client Pod's node IP,
144+
// or an IP that is nto easily associated with a node (e.g. if the veth interface is
145+
// used).
140146
return buildAndCacheTopology(names, datanodeIps, podLabels, nodeLabels);
141147
}
142148

@@ -183,6 +189,25 @@ private List<Pod> fetchDataNodes() {
183189
return dataNodes;
184190
}
185191

192+
// ============================================================================
193+
// NODE RESOLUTION
194+
// ============================================================================
195+
196+
private List<String> tryResolveNodes(List<String> names, Map<String, String> nodeToDatanodeIp) {
197+
List<String> result = new ArrayList<>();
198+
199+
for (String name : names) {
200+
String dataNodeIp = nodeToDatanodeIp.get(name);
201+
if (dataNodeIp == null) {
202+
result.add(name);
203+
} else {
204+
LOG.debug("Returning dataNode {} for {}", name, dataNodeIp);
205+
result.add(dataNodeIp);
206+
}
207+
}
208+
return result;
209+
}
210+
186211
// ============================================================================
187212
// LISTENER RESOLUTION
188213
// ============================================================================
@@ -221,10 +246,10 @@ private String getListenerVersion() {
221246
}
222247
}
223248

224-
private List<String> resolveListeners(List<String> names) {
249+
private List<String> tryResolveListeners(List<String> names) {
225250
refreshListenerCacheIfNeeded(names);
226251

227-
return names.stream().map(this::resolveListenerToDatanode).collect(Collectors.toList());
252+
return names.stream().map(this::tryResolveListenerToDatanode).collect(Collectors.toList());
228253
}
229254

230255
private void refreshListenerCacheIfNeeded(List<String> names) {
@@ -268,7 +293,7 @@ private void cacheListenerByNameAndAddresses(GenericKubernetesResource listener)
268293
* listener
269294
* @return either the name (for non-listener) or the dataNode IP to which this listener resolves
270295
*/
271-
private String resolveListenerToDatanode(String name) {
296+
private String tryResolveListenerToDatanode(String name) {
272297
GenericKubernetesResource listener = cache.getListener(name);
273298
if (listener == null) {
274299
LOG.debug("Not a listener: {}", name);
@@ -314,7 +339,7 @@ private GenericKubernetesResourceList fetchListeners(String listenerVersion) {
314339
// CLIENT POD RESOLUTION
315340
// ============================================================================
316341

317-
private List<String> resolveClientPodsToDataNodes(
342+
private List<String> tryResolveClientPodsToDataNodes(
318343
List<String> names,
319344
Map<String, Map<String, String>> podLabels,
320345
Map<String, String> nodeToDatanodeIp) {
@@ -408,7 +433,8 @@ private String resolveToIpAddress(String hostname) {
408433

409434
/**
410435
* Build a map from Kubernetes node name to datanode IP. This enables O(1) lookup when finding
411-
* co-located dataNodes for client pods.
436+
* co-located dataNodes for client pods. This map will contain as keys both the node name and all
437+
* its addresses (as the address may be used by pods with IP masquerading).
412438
*
413439
* <p>Note: If multiple dataNodes run on the same node, the last one wins. This is acceptable
414440
* because all dataNodes on the same node have the same topology.
@@ -421,11 +447,17 @@ private Map<String, String> buildNodeToDatanodeMap(List<Pod> dataNodes) {
421447
String dataNodeIp = dataNode.getStatus().getPodIP();
422448

423449
if (nodeName != null && dataNodeIp != null) {
450+
LOG.debug("Assigned to node-name [{}/{}]", nodeName, dataNodeIp);
424451
nodeToDatanode.put(nodeName, dataNodeIp);
452+
Node node = getOrFetchNode(nodeName);
453+
for (NodeAddress nodeAddress : node.getStatus().getAddresses()) {
454+
LOG.debug("Assigned to node-address [{}/{}]", nodeAddress.getAddress(), dataNodeIp);
455+
nodeToDatanode.put(nodeAddress.getAddress(), dataNodeIp);
456+
}
425457
}
426458
}
427459

428-
LOG.debug("Built node-to-datanode map with {} entries", nodeToDatanode.size());
460+
LOG.debug("Built node-to-datanode map {}", nodeToDatanode);
429461
return nodeToDatanode;
430462
}
431463

@@ -474,27 +506,32 @@ private String extractLabelValue(
474506
* Given a list of dataNodes this function will resolve which dataNodes run on which node as well
475507
* as all the ips assigned to a dataNodes. It will then return a mapping of every ip address to
476508
* the labels that are attached to the "physical" node running the dataNodes that this ip belongs
477-
* to.
509+
* to. It will also do this for the node addresses as calling pods may masquerade as node IPs.
478510
*
479511
* @param dataNodes List of all in-scope dataNodes (datanode pods in this namespace)
480512
* @return Map of ip addresses to labels of the node running the pod that the ip address belongs
481513
* to
482514
*/
483515
private Map<String, Map<String, String>> buildNodeLabelMap(List<Pod> dataNodes) {
484516
Map<String, Map<String, String>> result = new HashMap<>();
485-
for (Pod pod : dataNodes) {
486-
String nodeName = pod.getSpec().getNodeName();
517+
for (Pod dataNode : dataNodes) {
518+
String nodeName = dataNode.getSpec().getNodeName();
487519

488520
if (nodeName == null) {
489-
LOG.warn("Pod [{}] not yet assigned to node, retrying", pod.getMetadata().getName());
521+
LOG.warn("Pod [{}] not yet assigned to node, retrying", dataNode.getMetadata().getName());
490522
return result;
491523
}
492524

493525
Node node = getOrFetchNode(nodeName);
494526
Map<String, String> nodeLabels = node.getMetadata().getLabels();
495527
LOG.debug("Labels for node [{}]:[{}]....", nodeName, nodeLabels);
496528

497-
for (PodIP podIp : pod.getStatus().getPodIPs()) {
529+
for (NodeAddress nodeAddress : node.getStatus().getAddresses()) {
530+
LOG.debug("...assigned to node address [{}]", nodeAddress.getAddress());
531+
result.put(nodeAddress.getAddress(), nodeLabels);
532+
}
533+
534+
for (PodIP podIp : dataNode.getStatus().getPodIPs()) {
498535
LOG.debug("...assigned to IP [{}]", podIp.getIp());
499536
result.put(podIp.getIp(), nodeLabels);
500537
}

test/topology-provider/stack/01-install-krb5-kdc.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ spec:
3131
spec:
3232
initContainers:
3333
- name: init
34-
image: oci.stackable.tech/sdp/krb5:1.18.2-stackable0.0.0-dev
34+
image: oci.stackable.tech/sdp/krb5:1.21.1-stackable0.0.0-dev
3535
args:
3636
- sh
3737
- -euo
@@ -52,7 +52,7 @@ spec:
5252
name: data
5353
containers:
5454
- name: kdc
55-
image: oci.stackable.tech/sdp/krb5:1.18.2-stackable0.0.0-dev
55+
image: oci.stackable.tech/sdp/krb5:1.21.1-stackable0.0.0-dev
5656
args:
5757
- krb5kdc
5858
- -n
@@ -65,7 +65,7 @@ spec:
6565
- mountPath: /var/kerberos/krb5kdc
6666
name: data
6767
- name: kadmind
68-
image: oci.stackable.tech/sdp/krb5:1.18.2-stackable0.0.0-dev
68+
image: oci.stackable.tech/sdp/krb5:1.21.1-stackable0.0.0-dev
6969
args:
7070
- kadmind
7171
- -nofork
@@ -78,7 +78,7 @@ spec:
7878
- mountPath: /var/kerberos/krb5kdc
7979
name: data
8080
- name: client
81-
image: oci.stackable.tech/sdp/krb5:1.18.2-stackable0.0.0-dev
81+
image: oci.stackable.tech/sdp/krb5:1.21.1-stackable0.0.0-dev
8282
tty: true
8383
stdin: true
8484
env:

test/topology-provider/stack/03-hdfs.yaml

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,55 @@ metadata:
55
name: simple-zk
66
spec:
77
image:
8-
productVersion: 3.8.3
8+
productVersion: 3.9.4
9+
pullPolicy: IfNotPresent
910
servers:
1011
roleGroups:
1112
default:
1213
replicas: 1
1314
---
14-
apiVersion: zookeeper.stackable.tech/v1alpha1
15-
kind: ZookeeperZnode
16-
metadata:
17-
name: simple-hdfs-znode
18-
spec:
19-
clusterRef:
20-
name: simple-zk
21-
---
2215
apiVersion: hdfs.stackable.tech/v1alpha1
2316
kind: HdfsCluster
2417
metadata:
2518
name: simple-hdfs
2619
spec:
2720
image:
28-
productVersion: 3.4.0
29-
custom: hdfs # updated by tilt
21+
productVersion: 3.4.2
22+
custom: oci.stackable.tech/sandbox/andrew/hadoop:3.4.2-stackable0.0.0-topprov
3023
pullPolicy: IfNotPresent
3124
clusterConfig:
3225
dfsReplication: 1
33-
zookeeperConfigMapName: simple-hdfs-znode
26+
zookeeperConfigMapName: simple-zk
3427
rackAwareness:
35-
- labelType: node
36-
labelName: kubernetes.io/hostname
37-
- labelType: pod
38-
labelName: app.kubernetes.io/role-group
28+
- nodeLabel: kubernetes.io/hostname
29+
- podLabel: app.kubernetes.io/role-group
3930
authentication:
4031
tlsSecretClass: tls
4132
kerberos:
4233
secretClass: kerberos-default
4334
nameNodes:
4435
config:
45-
listenerClass: external-stable # We want to access the Web UI
36+
listenerClass: external-stable
37+
logging:
38+
enableVectorAgent: false
39+
containers:
40+
hdfs:
41+
console:
42+
level: DEBUG
43+
file:
44+
level: DEBUG
45+
loggers:
46+
ROOT:
47+
level: DEBUG
4648
configOverrides: &configOverrides
4749
core-site.xml:
4850
hadoop.user.group.static.mapping.overrides: "dr.who=;nn=;nm=;jn=;testuser=supergroup;"
4951
roleGroups:
5052
default:
5153
replicas: 2
5254
dataNodes:
55+
config:
56+
listenerClass: external-stable
5357
configOverrides: *configOverrides
5458
roleGroups:
5559
default:

test/topology-provider/stack/07-spark.yaml renamed to test/topology-provider/stack/04-spark.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ metadata:
55
name: spark-teragen
66
spec:
77
sparkImage:
8-
custom: docker.stackable.tech/stackable/spark-k8s-with-teragen:3.5.0-stackable0.0.0-dev
9-
productVersion: 3.5.0
8+
custom: oci.stackable.tech/sandbox/spark-k8s:3.5.6-stackable0.0.0-terasort
9+
productVersion: 3.5.6
1010
pullPolicy: IfNotPresent
1111
mode: cluster
1212
mainApplicationFile: local:///tmp/spark-terasort-1.2-SNAPSHOT.jar
1313
mainClass: com.github.ehiggs.spark.terasort.TeraGen
1414
args:
15-
- "10M"
15+
- "100M"
1616
- "hdfs://simple-hdfs/user/stackable/teragen_output"
1717
sparkConf:
1818
"spark.driver.extraClassPath": "/etc/hadoop/conf/:/stackable/spark/extra-jars/*"
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
apiVersion: batch/v1
3+
kind: Job
4+
metadata:
5+
name: access-hdfs
6+
spec:
7+
template:
8+
spec:
9+
containers:
10+
- name: access-hdfs
11+
image: oci.stackable.tech/sandbox/andrew/hadoop:3.4.2-stackable0.0.0-topprov
12+
imagePullPolicy: IfNotPresent
13+
env:
14+
- name: HADOOP_CONF_DIR
15+
value: /stackable/conf/hdfs
16+
- name: KRB5_CONFIG
17+
value: /stackable/kerberos/krb5.conf
18+
- name: HADOOP_OPTS
19+
value: -Djava.security.krb5.conf=/stackable/kerberos/krb5.conf
20+
command:
21+
- /bin/bash
22+
- -c
23+
- |
24+
set -ex
25+
klist -k /stackable/kerberos/keytab
26+
kinit -kt /stackable/kerberos/keytab testuser/access-hdfs.default.svc.cluster.local
27+
klist
28+
29+
#bin/hdfs dfs -mkdir /stackable
30+
#bin/hdfs dfs -chown -R access-hive /stackable
31+
#bin/hdfs dfs -mkdir /access-hive
32+
#bin/hdfs dfs -chown -R access-hive /access-hive
33+
bin/hdfs dfs -ls /
34+
#bin/hdfs dfs -ls -d /
35+
36+
sleep infinity
37+
volumeMounts:
38+
- name: hdfs-config
39+
mountPath: /stackable/conf/hdfs
40+
- name: kerberos
41+
mountPath: /stackable/kerberos
42+
volumes:
43+
- name: hdfs-config
44+
configMap:
45+
name: simple-hdfs
46+
- name: kerberos
47+
ephemeral:
48+
volumeClaimTemplate:
49+
metadata:
50+
annotations:
51+
secrets.stackable.tech/class: kerberos-default
52+
secrets.stackable.tech/scope: service=access-hdfs
53+
secrets.stackable.tech/kerberos.service.names: testuser
54+
spec:
55+
storageClassName: secrets.stackable.tech
56+
accessModes:
57+
- ReadWriteOnce
58+
resources:
59+
requests:
60+
storage: "1"
61+
securityContext:
62+
fsGroup: 1000
63+
restartPolicy: OnFailure
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#
22
# cd test/stack/teragen
3-
# docker build . -t docker.stackable.tech/stackable/spark-k8s-with-teragen:3.5.0-stackable0.0.0-dev
3+
# docker build . -t oci.stackable.tech/sandbox/spark-k8s:3.5.6-stackable0.0.0-terasort
44
#
55

6-
FROM oci.stackable.tech/sdp/spark-k8s:3.5.1-stackable0.0.0-dev
6+
FROM oci.stackable.tech/sdp/spark-k8s:3.5.6-stackable0.0.0-dev
77

88
# this .jar is compiled from the code here: https://github.com/ehiggs/spark-terasort
9-
COPY --chown=stackable:stackable ./spark-terasort-1.2-SNAPSHOT.jar /tmp/
9+
COPY --chown=stackable:stackable ./spark-terasort-1.2-SNAPSHOT.jar /tmp/

0 commit comments

Comments
 (0)