tunnel

A network tunnel to proxy network requests between cloud and edge.

Tunnel acts as the bridge between edge and cloud. It consists of tunnel-cloud and tunnel-edge, which is responsible for maintaining persistent cloud-to-edge network connection. It allows edge nodes with no public IP assigned to be managed by Kubernetes on the cloud to obtain unified and centralized operation and maintenance.

Architecture Diagram



Implementation

Node Registration

  • The tunnel-edge on the edge node actively connects to tunnel-cloud service, and tunnel-cloud service transfers the request to the tunnel-cloud pod according to the load balancing policy.
  • After tunnel-edge establishes a gRPC connection with tunnel-cloud, tunnel-cloud will write the mapping of its podIp and nodeName of the node where tunnel-edge is located into DNS。If the gRPC connection is disconnected, tunnel-cloud will delete the podIp and node name mapping.

Cloud Request Forwarding

  • When apiserver or other cloud applications access the kubelet or other applications on the edge node, the tunnel-dns uses DNS hijacking (resolving the node name in the host to the podIp of tunnel-cloud) to forward the request to the pod of the tunnel-cloud.
  • The tunnel-cloud forwards the request information to the gRPC connection established with the tunnel-edge according to the node name.
  • The tunnel-edge requests the application on the edge node according to the received request information.

Configuration File

The tunnel component includes tunnel-cloud and tunnel-edge. The tunnel-edge running on the edge node establishes a gRPC long-lived with the tunnel-cloud running on the cloud, which is used to forward the tunnel from the cloud to the edge node.

Tunnel-cloud

The tunnel-cloud contains three modules of stream, TCP and HTTPS. The stream module includes gRPC server and dns components. The gRPC server is used to receive gRPC long-lived requests from tunnel-edge, and the dns component is used to update the node name and IP mapping in the tunnel-cloud memory to the coredns hosts plug-in configmap.

Tunnel-cloud Configuration

tunnel-cloud-conf.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-cloud-conf
  namespace: edge-system
data:
  tunnel_cloud.toml: |
    [mode]
      [mode.cloud]
        [mode.cloud.stream]                             # stream module
          [mode.cloud.stream.server]                    # gRPC server component
            grpcport = 9000                             # listening port of gRPC server
            logport = 8000                              # The listening port of the http server for log and health check,use (curl -X PUT http://podip:logport/debug/flags/v -d "8") to set the log level
            channelzaddr = "0.0.0.0:6000"               # The listening address of the gRPC [channlez](https://grpc.io/blog/a-short-introduction-to-channelz/)server, used to obtain the debugging information of gRPC
            key = "../../conf/certs/cloud.key"          # The server-side private key of gRPC server
            cert = "../../conf/certs/cloud.crt"         # Server-side certificate of gRPC server
            tokenfile = "../../conf/token"              # The token list file (nodename: random string) is used to verify the token sent by the edge node tunneledge. If the verification fails according to the node name, the token corresponding to the default will be used to verify
          [mode.cloud.stream.dns]                       # DNS component
            configmap= "proxy-nodes"                    # configmap of the configuration file of the coredns hosts plugin
            hosts = "/etc/superedge/proxy/nodes/hosts"  # The path of the configmap of the configuration file of the coredns hosts plugin in the mount file of the tunnel-cloud pod
            service = "proxy-cloud-public"              # tunnel-cloud service name
            debug = true                                # DNS component switch, debug=true dns component is closed, the node name mapping in the tunnel-cloud memory will not be saved to the configmap of the coredns hosts plug-in configuration file, the default value is false
          [mode.cloud.tcp]                              # TCP module
            "0.0.0.0:6443" = "127.0.0.1:6443"           # The parameter format is "0.0.0.0:cloudPort": "EdgeServerIp:EdgeServerPort", cloudPort is the server listening port ofthe tunnel-cloud TCP module, EdgeServerIp and EdgeServerPort are the IP and port of the edge node server forwarded by the proxy
          [mode.cloud.https]                            # HTTPS module
            cert ="../../conf/certs/kubelet.crt"        # HTTPS module server certificate
            key = "../../conf/certs/kubelet.key"        # HTTPS module server private key
            [mode.cloud.https.addr]                     # The parameter format is "httpsServerPort": "EdgeHttpsServerIp: EdgeHttpsServerPort", httpsServerPort is the listening port of the HTTPS module server, EdgeHttpsServerIp:EdgeHttpsServerPort is the proxy forwarding the IP and port of the edge node HTTPS server, The server of the HTTPS module skips verifying the client certificate, so you can use (curl -k https://podip:httpsServerPort) to access the port monitored by the HTTPS module. The data type of the addr parameter is map, which can support monitoring multiple ports.
              "10250" = "101.206.162.213:10250"    

Tunnel-edge

The tunnel-edge also contains three modules of stream, TCP and HTTPS. The stream module includes the gRPC client component, which is used to send gRPC long-lived requests to the tunnel-cloud.

Tunnel-edge Configuration

tunnel-edge-conf.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-edge-conf
  namespace: edge-system
data:
  tunnel_edge.toml: |
    [mode]
      [mode.edge]
        [mode.edge.stream]                              # stream module
          [mode.edge.stream.client]                     # gRPC client component
            token = "6ff2a1ea0f1611eb9896362096106d9d"  # Authentication token for access to tunnel-cloud
            cert = "../../conf/certs/ca.crt"            # The ca certificate of the server-side certificate of the gRPC server of tunnel-cloud is used to verify the server-side certificate
            dns = "localhost"                           # The IP or domain name signed by the gRPC server certificate of tunnel-cloud
            servername = "localhost:9000"               # The IP and port of gRPC server of tunnel-cloud
            logport = 7000                              # The listening port of the http server for log and health check,use (curl -X PUT http://podip:logport/debug/flags/v -d "8") to set the log level
            channelzaddr = "0.0.0.0:5000"               # The listening address of the gRPC channlez server, used to obtain the debugging information of gRPC
        [mode.edge.https]
          cert= "../../conf/certs/kubelet-client.crt"   # The client certificate of the HTTPS server forwarded by tunnel-cloud proxy
          key= "../../conf/certs/kubelet-client.key"    # The private key of the client side of the HTTPS server forwarded by the tunnel-cloud proxy    

Tunnel Forwarding Mode

Tunnel proxy supports either TCP or HTTPS request forwarding.

TCP Forwarding

The TCP module will forward the TCP request to the first edge node connected to the cloud. When there is only one tunnel-edge connected to the tunnel-cloud, The request will be forwarded to the node where the tunnel-edge is located

Tunnel-cloud Configuration

tunnel-cloud-conf.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-cloud-conf
  namespace: edge-system
data:
  tunnel_cloud.toml: |
    [mode]
      [mode.cloud]
        [mode.cloud.stream]
            [mode.cloud.stream.server]
                grpcport = 9000
                key = "/etc/superedge/tunnel/certs/tunnel-cloud-server.key"
                cert = "/etc/superedge/tunnel/certs/tunnel-cloud-server.crt"
                tokenfile = "/etc/superedge/tunnel/token/token"
                logport = 51000
            [mode.cloud.stream.dns]
                debug = true
        [mode.cloud.tcp]
            "0.0.0.0:6443" = "127.0.0.1:6443"
        [mode.cloud.https]    

The gRPC server of the tunnel-cloud listens on port 9000 and waits for the tunnel-edge to establish a gRPC long-lived. The 6443 request to access the tunnel-cloud will be forwarded to the server with the access address 127.0.0.1:6443 of the edge node.

tunnel-cloud.yaml


apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-cloud-conf
  namespace: edge-system
data:
  mode.toml: |
        {{tunnel-cloud-tcp.toml}}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-cloud-token
  namespace: edge-system
data:
  token: |
        default:{{.TunnelCloudEdgeToken}}
---
apiVersion: v1
data:
  tunnel-cloud-server.crt: '{{tunnel-cloud-server.crt}}'
  tunnel-cloud-server.key: '{{tunnel-cloud-server.key}}'
kind: Secret
metadata:
  name: tunnel-cloud-cert
  namespace: edge-system
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
  name: tunnel-cloud
  namespace: edge-system
spec:
  ports:
    - name: proxycloud
      port: 9000
      protocol: TCP
      targetPort: 9000
  selector:
    app: tunnel-cloud
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: tunnel-cloud
  name: tunnel-cloud
  namespace: edge-system
spec:
  selector:
    matchLabels:
      app: tunnel-cloud
  template:
    metadata:
      labels:
        app: tunnel-cloud
    spec:
      serviceAccount: tunnel-cloud
      serviceAccountName: tunnel-cloud
      containers:
        - name: tunnel-cloud
          image: superedge/tunnel:v0.2.0
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /cloud/healthz
              port: 51010
            initialDelaySeconds: 10
            periodSeconds: 60
            timeoutSeconds: 3
            successThreshold: 1
            failureThreshold: 1
          command:
            - /usr/local/bin/tunnel
          args:
            - --m=cloud
            - --c=/etc/superedge/tunnel/conf/mode.toml
            - --log-dir=/var/log/tunnel
            - --alsologtostderr
          volumeMounts:
            - name: token
              mountPath: /etc/superedge/tunnel/token
            - name: certs
              mountPath: /etc/superedge/tunnel/certs
            - name: conf
              mountPath: /etc/superedge/tunnel/conf
          ports:
            - containerPort: 9000
              name: tunnel
              protocol: TCP
            - containerPort: 6443
              name: apiserver
              protocol: TCP
          resources:
            limits:
              cpu: 50m
              memory: 100Mi
            requests:
              cpu: 10m
              memory: 20Mi
      volumes:
        - name: token
          configMap:
            name: tunnel-cloud-token
        - name: certs
          secret:
            secretName: tunnel-cloud-cert
        - name: conf
          configMap:
            name: tunnel-cloud-conf
      nodeSelector:
        node-role.kubernetes.io/master: ""
      tolerations:
        - key: "node-role.kubernetes.io/master"
          operator: "Exists"
          effect: "NoSchedule"

The TunnelCloudEdgeToken in the configmap of tunnel-cloud-token is a random string used to verify the tunnel-edge; The server-side certificate and private key of gRPC server corresponding to the secret of tunnel-cloud-cert.

Tunnel-edge Configuration

apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-edge-conf
  namespace: edge-system
data:
  tunnel_edge.toml: |
    [mode]
      [mode.edge]
        [mode.edge.stream]
            [mode.edge.stream.client]
                token = "{{.TunnelCloudEdgeToken}}"
                cert = "/etc/superedge/tunnel/certs/tunnel-ca.crt"
                dns = "{{ServerName}}"
                servername = "{{.MasterIP}}:9000"
                logport = 51000    

The tunnel-edge uses MasterIP:9000 to access the tunnel-cloud, uses TunnelCloudEdgeToken as the verification token,and sends it to the cloud for verification. The token is the TunnelCloudEdgeToken in the configmap of the tunnel-cloud-token of the deployment of the tunnel-cloud; DNS is the domain name or IP signed by the gRPC server certificate of the tunnel-cloud; MasterIP is the IP of the node where the tunnel-cloud is located, and 9000 is the tunnel-cloud service NodePort.

tunnel-edge.yaml

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: tunnel-edge
rules:
  - apiGroups: [ "" ]
    resources: [ "configmaps" ]
    verbs: [ "get" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tunnel-edge
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tunnel-edge
subjects:
  - kind: ServiceAccount
    name: tunnel-edge
    namespace: edge-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tunnel-edge
  namespace: edge-system
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-edge-conf
  namespace: edge-system
data:
  mode.toml: |
        {{tunnel-edge-conf}}
---
apiVersion: v1
data:
  tunnel-ca.crt: '{{.tunnel-ca.crt}}'
kind: Secret
metadata:
  name: tunnel-edge-cert
  namespace: edge-system
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tunnel-edge
  namespace: edge-system
spec:
  selector:
    matchLabels:
      app: tunnel-edge
  template:
    metadata:
      labels:
        app: tunnel-edge
    spec:
      hostNetwork: true
      containers:
        - name: tunnel-edge
          image: superedge/tunnel:v0.2.0
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /edge/healthz
              port: 51010
            initialDelaySeconds: 10
            periodSeconds: 180
            timeoutSeconds: 3
            successThreshold: 1
            failureThreshold: 3
          resources:
            limits:
              cpu: 20m
              memory: 20Mi
            requests:
              cpu: 10m
              memory: 10Mi
          command:
            - /usr/local/bin/tunnel
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: spec.nodeName
          args:
            - --m=edge
            - --c=/etc/superedge/tunnel/conf/tunnel_edge.toml
            - --log-dir=/var/log/tunnel
            - --alsologtostderr
          volumeMounts:
            - name: certs
              mountPath: /etc/superedge/tunnel/certs
            - name: conf
              mountPath: /etc/superedge/tunnel/conf
      volumes:
        - secret:
            secretName: tunnel-edge-cert
          name: certs
        - configMap:
            name: tunnel-edge-conf
          name: conf

The ca certificate corresponding to the secret of tunnel-edge-cert to verify the gRPC server certificate; tunnel-edge is deployed in the form of deployment, and the number of copies is 1 , Tcp forwarding now only supports forwarding to a single node.

HTTPS Forwarding

To forward cloud requests to edge nodes through tunnel, you need to use the edge node name as the domain name of the HTTPS request host. Domain name resolution can reuse tunnel-coredns ,To use HTTPS forwarding, you need to deploy tunnel-cloud, tunnel-edge and tunnel-coredns three modules .

Tunnel-cloud Configuration

tunnel-cloud-conf.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-cloud-conf
  namespace: edge-system
data:
  tunnel_cloud.toml: |
    [mode]
      [mode.cloud]
        [mode.cloud.stream]
            [mode.cloud.stream.server]
                grpcport = 9000
                logport = 51010
                key = "/etc/superedge/tunnel/certs/tunnel-cloud-server.key"
                cert = "/etc/superedge/tunnel/certs/tunnel-cloud-server.crt"
                tokenfile = "/etc/superedge/tunnel/token/token"
            [mode.cloud.stream.dns]
                configmap = "tunnel-nodes"
                hosts = "/etc/superedge/tunnel/nodes/hosts"
                service = "tunnel-cloud"
            [mode.cloud.https]
                cert = "/etc/superedge/tunnel/certs/apiserver-kubelet-server.crt"
                key = "/etc/superedge/tunnel/certs/apiserver-kubelet-server.key"
                [mode.cloud.https.addr]
                    "10250" = "127.0.0.1:10250"    

The gRPC server of the tunnel-cloud listens on port 9000 and waits for the tunnel-edge to establish a gRPC long-lived. The request to access tunnel-cloud 10250 will be forwarded to the server with the access address 127.0.0.1:10250 of the edge node.

tunnel-cloud.yaml

tunnel-cloud.yaml

Tunnel-edge Configuration

tunnel-edge-conf.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: tunnel-edge-conf
  namespace: edge-system
data:
  tunnel_edge.toml: |
    [mode]
      [mode.edge]
        [mode.edge.stream]
            [mode.edge.stream.client]
                token = "{{.TunnelCloudEdgeToken}}"
                cert = "/etc/superedge/tunnel/certs/cluster-ca.crt"
                dns = "tunnel.cloud.io"
                servername = "{{.MasterIP}}:9000"
                logport = 51000
        [mode.edge.https]
            cert = "/etc/superedge/tunnel/certs/apiserver-kubelet-client.crt"
            key = "/etc/superedge/tunnel/certs/apiserver-kubelet-client.key"    

The certificate and private key of the HTTPS module are the client certificate corresponding to the server-side certificate of the server of the edge node forwarded by the tunnel-cloud. For example, when tunnel-cloud forwards the request from apiserver to kubelet, it is necessary to configure the client certificate and private key corresponding to the kubelet port 10250 server certificate.

tunnel-edge.yaml

tunnel-edge.yaml

Local Debugging

Tunnel supports HTTPS (HTTPS module) and TCP protocol (TCP module). The data of the protocol module is transmitted through gRPC long-lived (stream module ), so it can be divided into modules for local debugging. Local debugging can use go’s testing framework. The configuration file can be generated by calling config_test test method Test_Config (where the constant variable config_path is the path of the generated configuration file relative to the path of the config_test go file, and main_path is the configuration file relative to the test file Path), such as the local debugging of the stream module: config_path = “../../../conf” (The generated configuration file is in the conf folder under the root directory of the project), then main_path="../../../../conf"(the path of stream_test relative to conf), the configuration file is generated to support the configuration of ca.crt and ca.key (when configpath/certs/ca.crt and configpath/certs/ca.key exist, the specified ca is used to issue the certificate).

stream module debugging

start of the stream server

func Test_StreamServer(t *testing.T) {
	err := conf.InitConf(util.CLOUD, "../../../../conf/cloud_mode.toml")
	if err != nil {
		t.Errorf("failed to initialize stream server configuration file err = %v", err)
		return
	}
	model.InitModules(util.CLOUD)
	InitStream(util.CLOUD)
	model.LoadModules(util.CLOUD)
	context.GetContext().RegisterHandler(util.MODULE_DEBUG, util.STREAM, StreamDebugHandler)
	model.ShutDown()

}
Load configuration file(conf.InitConf)->Initialize the module(model.InitMoudule)->Initialize the stream module(InitStream)->Load the initialized module->Register a custom handler (StreamDebugHandler)->Shut down the module (model.ShutDown)

StreamDebugHandler is a custom handler for debugging cloud side messaging

start of the stream client

func Test_StreamClient(t *testing.T) {
	os.Setenv(util.NODE_NAME_ENV, "node1")
	err := conf.InitConf(util.EDGE, "../../../../conf/edge_mode.toml")
	if err != nil {
		t.Errorf("failed to initialize stream client configuration file err = %v", err)
		return
	}
	model.InitModules(util.EDGE)
	InitStream(util.EDGE)
	model.LoadModules(util.EDGE)
	context.GetContext().RegisterHandler(util.MODULE_DEBUG, util.STREAM, StreamDebugHandler)
	go func() {
		running := true
		for running {
			node := context.GetContext().GetNode(os.Getenv(util.NODE_NAME_ENV))
			if node != nil {
				node.Send2Node(&proto.StreamMsg{
					Node:     os.Getenv(util.NODE_NAME_ENV),
					Category: util.STREAM,
					Type:     util.MODULE_DEBUG,
					Topic:    uuid.NewV4().String(),
					Data:     []byte{'c'},
				})
			}
			time.Sleep(10 * time.Second)
		}
	}()
	model.ShutDown()

}
Set the node name environment variable->Load configuration file (conf.InitConf)->Initialization module (model.InitMoudule)->Initialize the stream module (InitStream)->Load the initialized module->Register a custom handler (StreamDebugHandler)->Shut down the module (model.ShutDown)

The node name is loaded through the environment variable of NODE_NAME

TCP module debugging

TCP server debugging

func Test_TcpServer(t *testing.T) {
	err := conf.InitConf(util.CLOUD, "../../../../conf/cloud_mode.toml")
	if err != nil {
		t.Errorf("failed to initialize stream server configuration file err = %v", err)
		return
	}
	model.InitModules(util.CLOUD)
	InitTcp()
	stream.InitStream(util.CLOUD)
	model.LoadModules(util.CLOUD)
	model.ShutDown()
}

Need to initialize the TCP module (InitTcp) and the stream module (stream.InitStream) at the same time

TCP client debugging

func Test_TcpClient(t *testing.T) {
	os.Setenv(util.NODE_NAME_ENV, "node1")
	err := conf.InitConf(util.EDGE, "../../../../conf/edge_mode.toml")
	if err != nil {
		t.Errorf("failed to initialize stream client configuration file err = %v", err)
		return
	}
	model.InitModules(util.EDGE)
	InitTcp()
	stream.InitStream(util.EDGE)
	model.LoadModules(util.EDGE)
	model.ShutDown()
}

HTTPS module debugging

Similar to TCP module debugging, HTTPS module and stream module need to be loaded at the same time.

Debugging of tunnel main() function

In the main test file of the tunnel tunnel_test, you need to use init() set the parameters, at the same time you need to use TestMain to parse the parameters and call the test method


Last modified June 16, 2021 : Update Tunnel description (6bb9132)