Docs
Ingress ControllerManage traffic and APIsTraffic ManagementProxy TCP Traffic by Port

Proxy TCP Traffic by Port

Configure APISIX or API7 Ingress Controller to proxy TCP traffic by gateway port to a MySQL Service.

Applies to:API7 Ingress Controller (drives API7 Gateway)APISIX Ingress Controller (drives Apache APISIX)

Goal

Route TCP connections to a Kubernetes MySQL Service based on the incoming gateway port, using either a Gateway API TCPRoute or an APISIX CRD stream route.

Before you begin

  • Complete Set Up Ingress Controller and Gateway.
  • Install MySQL Shell so you can initiate TCP connections with the example MySQL server.
  • You have kubectl configured against the cluster used in the setup guide.
  • Choose a free local port for the port-forward used during validation. The examples use 9100 for the gateway stream listener and 9100 for the forwarded local port; change these if they conflict.

Set the variables this guide uses:

export MYSQL_ROOT_PASSWORD="my-secret-pw"
export GATEWAY_SERVICE_NAME="apisix-gateway"   # your gateway's Service name in namespace aic
export FORWARD_PORT="9100"

Steps

Step 1: Deploy the example MySQL upstream

Create a manifest file for an example MySQL upstream Service in the aic namespace:

mysql.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: aic
  labels:
    app: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:9.4
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "${MYSQL_ROOT_PASSWORD}"
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: aic
spec:
  selector:
    app: mysql
  ports:
    - name: mysql
      port: 3306
      targetPort: 3306

Apply the manifest:

kubectl apply -f mysql.yaml

Wait for the Pod to become ready:

kubectl wait --for=condition=ready pod -l app=mysql -n aic --timeout=120s

Step 2: Enable the gateway stream proxy

Upgrade the gateway Helm release to enable stream mode and expose TCP port 9100.

helm upgrade -n aic apisix apisix/apisix \
  --reuse-values \
  --set "service.stream.enabled=true" \
  --set "service.stream.tcp[0]=9100"

Step 3: Configure TCP routing

Create a route that listens for TCP traffic on port 9100.

Add a TCP listener to your existing Gateway manifest:

gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  namespace: aic
  name: apisix
spec:
  gatewayClassName: apisix
  listeners:
  - name: http
    protocol: HTTP
    port: 80
  - name: tcp
    protocol: TCP
    port: 9100
    allowedRoutes:
      kinds:
      - kind: TCPRoute
  infrastructure:
    parametersRef:
      group: apisix.apache.org
      kind: GatewayProxy
      name: apisix-config

Create a TCPRoute manifest:

tcp-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: TCPRoute
metadata:
  name: stream-route-mysql
  namespace: aic
spec:
  parentRefs:
  - name: apisix
    sectionName: tcp
  rules:
  - backendRefs:
    - name: mysql
      port: 3306

Apply both manifests:

kubectl apply -f gateway.yaml -f tcp-route.yaml

When listener_port_match_mode is explicit or auto, the sectionName: tcp reference adds a server_port match for 9100. The Gateway listener port must equal the physical gateway stream listener port. With the mode set to off, the controller does not add this match.

Step 4: Set the upstream transport scheme

Use tcp for a plain TCP backend or tls when the gateway must establish TLS to the backend. The L4 schemes apply only to stream routes.

Attach a BackendTrafficPolicy to the backend Service:

mysql-upstream-policy.yaml
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: mysql-transport
  namespace: aic
spec:
  targetRefs:
  - group: ""
    kind: Service
    name: mysql
    sectionName: mysql
  scheme: tcp

Apply the policy:

kubectl apply -f mysql-upstream-policy.yaml

To attach stream plugins to the TCPRoute, see Apply Plugins to L4 Routes.

Validate

Forward the gateway stream port to your local machine:

kubectl port-forward -n aic "svc/${GATEWAY_SERVICE_NAME}" "${FORWARD_PORT}:9100" &

Connect with the MySQL server as root. Enter the password stored in MYSQL_ROOT_PASSWORD when prompted:

mysqlsh --sqlc --host=127.0.0.1 --port="${FORWARD_PORT}" --user=root --password

Expected: MySQL Shell opens a classic protocol session and prints output similar to:

Creating a Classic session to 'root@127.0.0.1:9100'
Your MySQL connection id is 9
Server version: 9.4.0 MySQL Community Server - GPL

Roll back / Clean up

Delete the manifests created in this guide:

kubectl delete -f tcp-route.yaml
kubectl delete -f gateway.yaml
kubectl delete -f mysql.yaml
kubectl delete -f mysql-upstream-policy.yaml
kubectl delete -f mysql-upstream.yaml

Stop the local port-forward process if it is still running:

kill %1 2>/dev/null || true

Troubleshoot

SymptomCauseFix
mysqlsh cannot connect or times out.The gateway stream listener is not enabled, the TCPRoute is not accepted, or the port-forward targets the wrong Service.Confirm the Helm release has service.stream.enabled=true and service.stream.tcp[0]=9100 (or the API7 Gateway equivalents), the TCPRoute parentRefs resolve, and GATEWAY_SERVICE_NAME matches your gateway Service.
MySQL Shell reports an authentication failure.The root password in the MySQL Deployment does not match the password you entered.Verify MYSQL_ROOT_PASSWORD is the value you set in mysql.yaml.
The controller ignores the TCPRoute.The Gateway listener name or port does not match the TCPRoute parentRefs.Check that the Gateway listener is named tcp and exposes port 9100, and that sectionName: tcp is present in the TCPRoute.