Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Raft cluster mode supports address translation #7069

Merged
merged 13 commits into from
Dec 30, 2024

Conversation

PeppaO
Copy link
Contributor

@PeppaO PeppaO commented Dec 23, 2024

  • I have registered the PR changes.

Ⅰ. Describe what this PR did

现有raft sdk如果在k8s环境下,客户端集群外访问,无法通过域名方式链接raft node,造成无法通信。
支持外部访问的方案:
server: 在元数据Node中添加一个metadata.external数组,在部署时,传入SEATA_REGISTRY_METADATA_EXTERNAL变量,创建节点时解析设置,同步至主节点。
client:
通过api获取集群元数据,然后解析并选择通信的IP及地址。如果客户端配置了preferredNetworks,那么将从元数据Node.metadata.external中正则匹配选择,否则就按照原来方案,使用Node.control及Node.transaction。
客户端配置:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "raft"
  preferredNetworks = "192.168.*",
  raft {
    metadata-max-age-ms = 30000
    serverAddr = "10.10.92.71:30071,10.10.92.71:30072"

  }
 }

部署文件:

---
apiVersion: v1
kind: Service
metadata:
  name: s-0
  namespace: default
  labels:
    k8s-app: s-0
spec:
  type: NodePort
  ports:
    - port: 7091
      nodePort: 30071
      protocol: TCP
      name: http
    - port: 8091
      nodePort: 30091
      protocol: TCP
      name: tcp
  selector:
    statefulset.kubernetes.io/pod-name: v-0
---
apiVersion: v1
kind: Service
metadata:
  name: s-1
  namespace: default
  labels:
    k8s-app: s-1
spec:
  type: NodePort
  ports:
    - port: 7091
      nodePort: 30072
      protocol: TCP
      name: http
    - port: 8091
      nodePort: 30092
      protocol: TCP
      name: tcp
  selector:
    statefulset.kubernetes.io/pod-name: v-1
---
apiVersion: v1
kind: Service
metadata:
  name: s-2
  namespace: default
  annotations:
    my-custom-annotation: "value1"
    another-annotation: "value2"
  labels:
    k8s-app: s-2
    my-label: "30073"
spec:
  type: NodePort
  ports:
    - port: 7091
      nodePort: 30073
      protocol: TCP
      name: http
    - port: 8091
      nodePort: 30093
      protocol: TCP
      name: tcp
  selector:
    statefulset.kubernetes.io/pod-name: v-2
---
apiVersion: v1
kind: Service
metadata:
  name: svc-l
  namespace: default
  labels:
    k8s-app: svc-l
spec:
  type: ClusterIP
  clusterIP: None
  publishNotReadyAddresses: false
  ports:
    - name: http
      port: 7091
      targetPort: 7091
    - name: tcp
      port: 8091
      targetPort: 8091
  selector:
    k8s-app: v

---

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: v
  namespace: default
  labels:
    k8s-app: v
spec:
  replicas: 3
  serviceName: svc-l
  selector:
    matchLabels:
      k8s-app: v
  template:
    metadata:
      labels:
        k8s-app: v
    spec:
      initContainers:
        - name: cluster-init
          image: harbor.local:80/seata/seata-server:4.0
          imagePullPolicy: Always
          command: [
            "sh", "-c", "index=$(hostname | awk -F '-' '{print $NF}'); \
            case $index in \
              0) echo 30071:30091 > /etc/nodeport/port ;; \
              1) echo 30072:30092 > /etc/nodeport/port ;; \
              2) echo 30073:30093 > /etc/nodeport/port ;; \
              *) echo 'Invalid index' > /etc/nodeport/port ;; \
            esac"
          ]
          volumeMounts:
            - name: nodeport-volume
              mountPath: /etc/nodeport
      containers:
        - name: server
          image: harbor.local:80/seata/seata-server:4.0
          imagePullPolicy: Always
          command: ["bash", "-c", "
            export SEATA_REGISTRY_METADATA_EXTERNAL=${NODE_IP}:$(cat /etc/nodeport/port) && \
            echo 'Starting server with SEATA_REGISTRY_METADATA_EXTERNAL='$SEATA_REGISTRY_METADATA_EXTERNAL && \
            /seata-server/bin/seata-server.sh && \
            echo 'Server started successfully!' && \
            tail -f /dev/null"
          ]
          env:
            - name: SEATA_HOST_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: SEATA_IP
              value: "$(SEATA_HOST_NAME).svc-l.default.svc.cluster.local"
            - name: server.raftPort
              value: "9091"
            - name: server.raft.serverAddr
              value: "v-0.svc-l.default.svc.cluster.local:9091,v-1.svc-l.default.svc.cluster.local:9091,v-2.svc-l.default.svc.cluster.local:9091"
            - name: NODE_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
          ports:
            - name: http
              containerPort: 8091
              protocol: TCP
          volumeMounts:
            - name: nodeport-volume
              mountPath: /etc/nodeport
      volumes:
        - name: nodeport-volume
          emptyDir: {}
image

这里之所以取名这么简单是测试的时候,发现service过长,会出现 #7082 的错误

元数据:

{
	"nodes": [{
		"control": {
			"host": "v-0.svc-l.default.svc.cluster.local",
			"port": 7091
		},
		"transaction": {
			"host": "v-0.svc-l.default.svc.cluster.local",
			"port": 8091
		},
		"internal": {
			"host": "v-0.svc-l.default.svc.cluster.local",
			"port": 9091
		},
		"group": "default",
		"role": "LEADER",
		"version": "2.3.0-SNAPSHOT",
		"metadata": {
			"external": [{
				"host": "192.168.105.7",
				"controlPort": 30071,
				"transactionPort": 30091
			}]
		}
	}, {
		"control": {
			"host": "v-2.svc-l.default.svc.cluster.local",
			"port": 7091
		},
		"transaction": {
			"host": "v-2.svc-l.default.svc.cluster.local",
			"port": 8091
		},
		"internal": {
			"host": "v-2.svc-l.default.svc.cluster.local",
			"port": 9091
		},
		"group": "default",
		"role": "FOLLOWER",
		"version": "2.3.0-SNAPSHOT",
		"metadata": {
			"external": [{
				"host": "192.168.105.7",
				"controlPort": 30073,
				"transactionPort": 30093
			}]
		}
	}, {
		"control": {
			"host": "v-1.svc-l.default.svc.cluster.local",
			"port": 7091
		},
		"transaction": {
			"host": "v-1.svc-l.default.svc.cluster.local",
			"port": 8091
		},
		"internal": {
			"host": "v-1.svc-l.default.svc.cluster.local",
			"port": 9091
		},
		"group": "default",
		"role": "FOLLOWER",
		"version": "2.3.0-SNAPSHOT",
		"metadata": {
			"external": [{
				"host": "192.168.105.7",
				"controlPort": 30072,
				"transactionPort": 30092
			}]
		}
	}],
	"storeMode": "raft",
	"term": 1
}

测试:
image

Ⅱ. Does this pull request fix one issue?

Ⅲ. Why don't you add test cases (unit test/integration test)?

Ⅳ. Describe how to verify it

Ⅴ. Special notes for reviews

@funky-eyes funky-eyes added this to the 2.4.0 milestone Dec 23, 2024
@funky-eyes funky-eyes added type: feature Category issues or prs related to feature request. module/discovery discovery module module/server server module store: raft labels Dec 23, 2024

private static InetSocketAddress selectEndpoint(String type, Node node) {
if (StringUtils.isBlank(PREFERRED_NETWORKS)) {
// 采取默认的方式,直接使用node.control node.transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 采取默认的方式,直接使用node.control node.transaction
// Use the default method, directly using node.control and node.transaction

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -366,7 +366,7 @@ public RaftClusterMetadata changeOrInitRaftClusterMetadata() {
Integer.parseInt(
((Environment)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT))
.getProperty("server.port", String.valueOf(7091))),
group, Collections.emptyMap());
group, new HashMap<>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change to new HashMap < > ()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为Collections.emptyMap()返回的集合使用了final修饰,导致我进行put操作时,报错,unsupoort...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不要直接put,而是构建一个新的map替换它,copyonwrite

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不要直接put,而是构建一个新的map替换它,copyonwrite

done

@@ -406,7 +406,7 @@ private void syncCurrentNodeInfo(PeerId leaderPeerId) {
Integer.parseInt(
((Environment)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT))
.getProperty("server.port", String.valueOf(7091))),
group, Collections.emptyMap());
group, new HashMap<>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

@@ -55,6 +58,10 @@ public Node createNode(String host, int txPort, int internalPort, int controlPor
node.setGroup(group);
node.setVersion(Version.getCurrent());
node.setInternal(node.createEndpoint(host, internalPort, "raft"));
String serverRegistryMetadataExternalValue = System.getProperty("SERVER_REGISTRY_METADATA_EXTERNAL_VALUE");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不应该这样读取,如果有用户直接使用properties配置文件去配置怎么办?应该通过下面这样的方式读取
It should not be read like this. What if a user directly uses the properties configuration file to configure it? It should be read in the following way

ConfigurableEnvironment environment=ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT);
environment.resolvePlaceholders("${seata.registry.metadata.external:${registry.metadata.external:}}")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

不要直接put,而是构建一个新的map替换它,copyonwrite

Copy link

codecov bot commented Dec 28, 2024

Codecov Report

Attention: Patch coverage is 25.83333% with 89 lines in your changes missing coverage. Please review.

Project coverage is 52.59%. Comparing base (c0a2e92) to head (ff5ff02).
Report is 1 commits behind head on 2.x.

Files with missing lines Patch % Lines
...in/java/org/apache/seata/common/metadata/Node.java 0.00% 41 Missing ⚠️
...scovery/registry/raft/RaftRegistryServiceImpl.java 47.05% 20 Missing and 7 partials ⚠️
...seata/common/exception/ParseEndpointException.java 0.00% 10 Missing ⚠️
...cluster/raft/sync/msg/dto/RaftClusterMetadata.java 37.50% 3 Missing and 2 partials ⚠️
...ain/java/org/apache/seata/common/util/NetUtil.java 40.00% 3 Missing ⚠️
...roperties/registry/RegistryMetadataProperties.java 25.00% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##                2.x    #7069      +/-   ##
============================================
- Coverage     52.65%   52.59%   -0.07%     
- Complexity     6666     6680      +14     
============================================
  Files          1129     1131       +2     
  Lines         40168    40277     +109     
  Branches       4708     4721      +13     
============================================
+ Hits          21152    21185      +33     
- Misses        16999    17067      +68     
- Partials       2017     2025       +8     
Files with missing lines Coverage Δ
...ava/org/apache/seata/common/ConfigurationKeys.java 0.00% <ø> (ø)
...toconfigure/SeataCoreEnvironmentPostProcessor.java 100.00% <100.00%> (ø)
...ta/spring/boot/autoconfigure/StarterConstants.java 100.00% <ø> (ø)
...ain/java/org/apache/seata/common/util/NetUtil.java 52.98% <40.00%> (-0.51%) ⬇️
...roperties/registry/RegistryMetadataProperties.java 25.00% <25.00%> (ø)
...cluster/raft/sync/msg/dto/RaftClusterMetadata.java 77.14% <37.50%> (-12.15%) ⬇️
...seata/common/exception/ParseEndpointException.java 0.00% <0.00%> (ø)
...scovery/registry/raft/RaftRegistryServiceImpl.java 23.70% <47.05%> (+6.23%) ⬆️
...in/java/org/apache/seata/common/metadata/Node.java 38.09% <0.00%> (-24.41%) ⬇️

... and 3 files with indirect coverage changes

throw new ParseEndpointException("Node metadata is empty.");
}

Object external = metadata.get("external");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里要判空,高版本客户端对接低版本server是不会有这个字段的

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

低版本不支持这个功能,不会走到这个逻辑,会直接走这里
image

@@ -4,7 +4,7 @@

### feature:

- [[#PR_NO](https://github.com/seata/seata/pull/PR_NO)] 支持XXX
- [[#7069](https://github.com/apache/incubator-seata/pull/7069)] Raft模式支持集群外访问
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该是 raft集群模式支持地址转换

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@PeppaO PeppaO changed the title feature: Raft mode supports access outside the cluster feature: Raft cluster mode supports address translation Dec 30, 2024
Copy link
Contributor

@funky-eyes funky-eyes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@funky-eyes funky-eyes merged commit dc08160 into apache:2.x Dec 30, 2024
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module/discovery discovery module module/server server module store: raft type: feature Category issues or prs related to feature request.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants