KT-Connect 是一个用于 Kubernetes 环境下的网络代理工具,旨在简化开发和测试过程中对 Kubernetes 集群内服务的访问。它通过创建一个或多个代理,使得本地开发环境可以直接与 Kubernetes 集群内的服务进行通信,而无需复杂的端口映射或修改服务配置。这种方式有助于开发者在本地开发、调试容器化应用时,如同直接操作本地服务一样便捷。

KT-Connect 的主要功能和特点包括:

  1. 零配置代理:用户无需手动配置端口映射或修改服务定义,就能将集群内服务的流量透明地转发到本地开发环境。
  2. 双向通信:不仅支持从本地到集群的服务请求,也支持集群内服务调用本地服务,实现了真正的双向通信。
  3. 环境隔离:通过为每个开发者创建独立的命名空间或环境,保持开发环境的隔离性,避免开发间互相干扰。
  4. 服务模拟:允许开发者模拟集群内的服务,便于在没有依赖服务就绪的情况下进行开发和测试。
  5. 一键式操作:提供命令行工具,使得启动、停止代理、查看状态等操作变得简单快捷。
  6. 安全接入:通过 TLS 加密和其他安全机制确保通信的安全性,保护集群资源不被非法访问。

使用 KT-Connect 可以显著提升 Kubernetes 应用的开发效率,尤其是对于需要频繁调试、集成测试的场景,它提供了一个灵活且高效的本地开发解决方案。

KT-Connect实践

要想运行KT-Connect,需要确保两个镜像能在你的Kubernetes集群中正常拉取:

  • registry.cn-hangzhou.aliyuncs.com/rdc-incubator/kt-connect-router:v0.3.7
  • registry.cn-hangzhou.aliyuncs.com/rdc-incubator/kt-connect-shadow:v0.3.7

如果你的网络受限,不能拉取以上镜像,需要你提前将镜像导入到每个Kubernetes节点。

部署实例应用

为了便于展示结果,首先在集群中部署一个nginx服务并创建一个默认首页:

$ kubectl create deployment nginx --image=nginx:alpine --port=80
deployment.apps/nginx created

$ kubectl expose deployment nginx --port=80 --target-port=80
service/nginx exposed

$ kubectl exec deploy/nginx -- /bin/sh -c 'echo "kt-connect cluster nginx service" > /usr/share/nginx/html/index.html'

连接集群网络

使用ktctl connect命令建立从本地到集群的网络通道,注意该命令需要管理员权限。

$ sudo ktctl connect

将集群流量转发到本地

为了验证集群访问本地服务的场景,我们在本地也启动一个nginx的容器,并为其创建一个内容不同的首页。

$ docker run -d --name nginx -p 80:80 nginx:alpine
$ docker exec nginx /bin/sh -c 'echo "kt-connect local nginx service" > /usr/share/nginx/html/index.html'

KtConnect提供了两种能够让集群流量重定向到本地服务的命令,在使用场景上稍有不同。

  • Exchange:将集群指定服务的所有流量转向本地
  • Mesh:将集群指定服务的部分流量(按Header或Label规则)转向本地

Exchange命令

将集群里访问指定服务的所有请求拦截并转发到本地的指定端口。通常用于调试在测试环境里,调试位于业务调用链中间环节的特定服务。

┌──────────┐     ┌─ ── ── ──     ┌──────────┐
│ ServiceA ├─┬─►x│ ServiceB │ ┌─►│ ServiceC │
└──────────┘ │    ── ── ── ─┘ │  └──────────┘
         exchange             │
             │   ┌──────────┐ │
             └──►│ ServiceB'├─┘ (本地服务实例)
                 └──────────┘

使用ktctl exchange命令将先前部署到集群中的nginx服务流量全部转到本地80端口:

$ ktctl exchange nginx --expose 80
00:00AM INF KtConnect start at <PID>
... ...
---------------------------------------------------------------
 Now all request to service 'nginx' will be redirected to local
---------------------------------------------------------------

在本地或者集群中访问示例开始时部署到集群的nginx服务,查看输出结果:

$ curl http://nginx:80
kt-connect local nginx service

可以看到,访问集群里nginx服务的请求被路由到了本地的nginx实例,现在就可以直接在本地调试这个服务了。

Mesh命令

将集群里访问指定服务的部分请求拦截并转发到本地的指定端口。通常用于团队协作时,需要定向调试调用链中间位置的服务,又不希望影响其他开发者正常使用测试环境的场景。

┌──────────┐     ┌──────────┐    ┌──────────┐
│ ServiceA ├─┬──►│ ServiceB │─┬─►│ ServiceC │
└──────────┘ │   └──────────┘ │  └──────────┘
            mesh              │
             │   ┌──────────┐ │
             └──►│ ServiceB'├─┘ (本地服务实例)
                 └──────────┘

Mesh命令有两种运行模式,默认的auto模式不需要额外的服务网格组件,能够直接实现HTTP请求的自动按需路由。

通过ktctl mesh命令创建代理Pod:

$ ktctl mesh nginx --expose 80
00:00AM INF KtConnect start at <PID>
... ...
--------------------------------------------------------------
 Now you can access your service by header 'VERSION: feo3x'
--------------------------------------------------------------

在命令日志的末尾,输出了一个特定的Header值。此时,直接访问集群里的nginx服务,流量将正常进入集群的服务实例:

$ curl http://nginx:80
kt-connect cluster nginx service

若请求包含Mesh命令输出的Header,则流量将自动被本地的服务实例接收。

$ curl -H 'VERSION: feo3x' http://nginx:80
kt-connect local nginx service

在实际使用时,可结合ModHeader插件,使得只有开发者从自己浏览器发出的请求会访问其本地的服务进程。

除此以外,还有一种可灵活配置路由规则的manual模式,该模式下KtConnect不会自动创建路由,在Mesh命令运行后,访问指定服务的流量将随机访问集群服务和本地实例。您可以自行使用任何服务网格组件(譬如Istio)创建基于version标签的路由规则,将特定流量转发到本地。详见Manual Mesh文档。

ktctl exchangektctl mesh命令的最大区别在于,前者会将原应用实例流量全部替换为由本地服务接收,而后者仅将包含指定Header的流量导流到本地,同时保证测试环境正常链路始终可用。

将本地服务提供给其他开发者

除了已经部署到集群的服务,在开发过程中,也可以利用KtConnect将本地服务快速"放"到集群,变成一个临时的服务,供其他开发者或集群中的其他服务使用。

  • Preview:将本地服务注册为集群里的Service
  • Forward:将集群服务映射到本地,结合Preview命令可实现开发者之间跨主机使用Localhost地址互访

Preview命令

将本地运行的服务实例注册到集群上。主要用于将本地开发中的服务提供给其他开发者进行联调和预览。

使用ktctl preview命令将运行在本地80端口的服务注册到测试集群,命名为nginx-v2

$ ktctl preview nginx-v2 --expose 80
00:00AM INF KtConnect start at <PID>
... ...
---------------------------------------------------------------
 Now you can access your local service in cluster by name 'nginx-v2'
---------------------------------------------------------------

现在集群里的服务就可以通过nginx-v2名称来访问本地注册的服务实例了,其他开发者也可以在执行ktctl connect后,直接通过nginx-v2服务名称来预览该服务的实时情况:

$ curl http://nginx-v2:80
kt-connect local nginx service

Forward命令

将任意IP或集群中的服务映射到本地的指定端口。用于在测试时,使用localhost地址便捷的访问集群中的特定IP或服务,典型场景是是访问其他开发者通过Preview命令注册的本地服务。

         ┌─────────────────────────────┐
      forward           |           preview
         │              |              │
┌────────┴───────┐      |      ┌───────▼──────┐
│ localhost:8080 │      |      │ local nginx  │
└────────────────┘      |      └──────────────┘
      开发者 B           |           开发者 A

例如当一个开发者A运行了前述的Preview命令后,另一个开发者B可以使用ktctl forward命令将它映射到自己本地的6060端口。

$ ktctl forward nginx-v2 6060:80
00:00AM INF KtConnect start at <PID>
... ...
---------------------------------------------------------------
 Now you can access port 80 of service 'nginx-v2' via 'localhost:6060'
---------------------------------------------------------------

现在开发者B就可以使用localhost:6060地址访问到开发者A本地运行的Tomcat服务了。

当映射的流量源是集群中的服务名时,其效果与kubectl port-forward命令相似,只是额外增加了断网自动重连的能力。

结合SpringCloud

Nacos注册/发现服务路由问题

如果是使用Nacos作为注册/发现服务时,默认情况下是将Pod IP注册到Nacos的实例,导致服务之间通讯是Pod IP,通过Pod IP通讯不经过kt-connect,也就没办法路由到正确的实例。

解决方法:注册Nacos时将IP参数设置为服务名,通过服务名通讯,就可以通过kt-connect实现精细化控制

spring:
  application:
    name: my-app
  cloud:
    nacos:
      server-addr: http://127.0.0.1:8848
      discovery:
        namespace: default
        ip: ${spring.application.name}

OpenFeign调用问题

SpringCloud微服务之间相互调用通常是使用OpenFeign,默认情况下,OpenFeign不会透传请求头到下游服务,而ktctl mesh是通过请求头实现的分流。

解决方法:配置OpenFeign拦截器,透传请求头

@Configuration
public class FeignConfig {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                // 原始请求
                HttpServletRequest request = attributes.getRequest();
                String version = request.getHeader("VERSION");

                // Feign请求增加头
                requestTemplate.header("VERSION", version);
            }
        };
    }
}

标签: none

添加新评论