背景与架构设计
网络中存在的挑战
- 挑战1:Kubernetes 的每个 Pod 拥有一个独立的 IP 地址。对外部访问则依赖 NodePort、LoadBalancer 或 Ingress,出于安全考虑不可能对每个 Pod 都暴露其 8849 端口 (NodePort) 或者使用 Ingress 配置大量的域名,这样无法连接到单独的某一个 Pod (通过Service)。
- 挑战2:网络环境是完全隔离的,用户无法通过 Pod IP 直接访问 Pod,所有的用户流量只能通过对应 IDC 的唯一入口进入。
- 挑战3:出于安全考虑不可能对每一个 Java 服务 (Pod) 在他的工作周期期间所有时间都暴露对应的 Jprofiler 端口,而仅仅想使用时可以连接,用后关闭。
JProfiler网络需求
JProfiler 可以通过加载 JVM 代理(libjprofilerti.so)与远程 GUI 通信,他的实现原理如下:
- JVM 启动时加载 JProfiler 代理,绑定到一个特定端口(如 8849)。
- 本地 JProfiler GUI 通过该端口连接到远程 JVM。
- 代理与 GUI 之间通过 TCP 传输性能数据。
在 Kubernetes 中,JProfiler 代理运行在 Pod 内部,但由于网络隔离和动态 IP,GUI 无法直接连接到 Pod。因此,我们需要一种机制将 JProfiler 的端口动态映射到可访问的外部端点。
需要实现的架构
下图是基于上面提到的问题从而设计的集群架构
上图可以看到,用户办公网络和 IDC 网络是完全隔离的,办公网络到 IDC 只有唯一入口,这里成为 LB,所有的流量都必须经由固定的 LB 进入,灰色背景的 Pod 是代理 Pod, 他可以动态的将流量转发到对应的用户真实想请求的 Pod 中。
解决方案
需要完成的步骤为
- 让业务Pod 接入JProfiler agent:使用 Init Container 将 JProfiler 所需库安装到 Pod 的共享卷中。
- 需要一个代理软件,用于转发 TCP 流量,让其可以作为一个唯一入口,但这个入口使用并不频繁。
- 可以实现转发的触发的工具。
综上所述,这里无法使用 ingress/gateway 资源作为映射了。
最终技术栈选择
软件版本 | 版本 | 下载地址 |
---|---|---|
Kubernetes集群 | 支持 1.16 以上的版本,并且不要对 Kubernetes 版本有依赖 | |
JProfiler | 14.0 | docker pull cylonchau/jprofiler-agent:14_0 [2] |
pod-proxier | 0.4 | docker pull cylonchau/pod-proxier:0.4 |
表中 pod-proxier 是自实现的一个控制器,主要功能完成 Pod 映射 (通过 HTTP API), 底层使用了 haproxy 实现。
实施步骤
安装pod-proxier
下面是 pod-proxier 的部署清单
|
|
配置service(可选)
如果是公有云环境,可以通过公有云 LB 来映射
|
|
Pod 启动 jprofiler agent 的方式
再次之前需要了解 Jprofiler 在 kubernetes 集群中运行的方法有:
- 方法1:打包至业务Pod容器内(缺点:需要侵入业务Pod内。不方便)。
- 方法2:使用 Init Container 将 jprofiler 安装复制到 InitContainer 和将在 Pod 中启动的其他容器之间共享卷。
- 方法3:使用 sidecar 方式共享业务 Pod 与 sidecar 共享名称空间。
- 缺点:
- 涉及到容器共享进程空间,与 jprofiler-agent 机制问题,所以需要共享 /tmp 目录。
- 需要手动触发启动 jprofiler。
- 缺点:
JProfiler finds JVMs via the “Attach API” that is part of the JDK. Have a look at the
$TMP/hsperfdata_$USER
directory, which is created by the hot spot JVM. It should contain PID files for all running JVMs. If not, delete the directory and restart all JVMs.
这里选择使用 “方法2” ;制作 initContainer [1] 方式如下:
|
|
使用 Init Container 为 Pod 注入 jprofiler agent
更改应用程序的部署配置如下
- 如果尚未定义,请在 “spec.template.spec” 下添加 “volumes” 部分并定义一个新卷,用于给 initContainer 和 mainContainer 提供初始化
|
|
如果尚未定义,请在 “spec.template.spec” 下添加 “initContainers”(Kubernetes 1.6+),并使用 JProfiler 的镜像定义 Init Container 将 Init container 中的文件复制到共享目录
|
|
将 jprofiler-agent 添加到 JVM 启动参数。
|
|
完整的Deployment 示例
|
|
一个完整的 Deployment 示例清单
|
|
Service 相关 (可选)
|
|
如何动态映射 Pod
需要连接 jprofiler-agent,在启动时作为进程启动,然后映射 jprofiler-agent 的 8849 端口。使用 pod-proixer,使用 haproxy 映射 Pod,并且提供 HTTP API,可以控制映射,与映射生效时间。
--jprofiler-port-name
,将会使用业务 Pod 配置的 Port name 来选择映射,而无需经过 ingress/gateway 。pod-proxier 本身有实现 Pod 映射的超时,所以不需要在去设计关闭映射部分了。
例如
|
|
交付
在可以完成所有的映射操作后,还需要可以把这部分交付给用户去使用,而不是让用户自己拼接 HTTP URL 来完成映射,这里思路可以接入到内部平台,每个 Pod 实例后增加一个按钮 “启用Jprofiler映射”;或者接入到工单系统,在审批后实现自动映射,超时自动关闭映射。可以参考 Apache Airflow, Stackstorm 之类作业平台来完成。
Reference
[1] How to Connect JProfiler to a JVM Running in Kubernetes Pod