Kubernetes 下 Pod IP 变更 (对 ES 的影响)

StatefulSet DNS 问题


用过 StatefulSet 的童鞋应该知道, 重启之后 Pod Name 是不变的, 但会被分配新的 Pod IP。
举个例子, 假如你部署了 3 个 ES 实例, 分别是 es-0/es-1/es-2, 其中 es-2 的 IP 是 10.2.16.15, 这是如果 es-2 服务异常退出, 接着被 k8s 重新拉起之后, es-2 将会有新的 IP, 比如是 10.2.16.16.

这就可能导致一个很大的问题, 就是客户端的实现能不能处理这个变化。也即是说, 如果客户端没有 dns 缓存刷新的支持, 或者对 dns 解析的处理不当, 就会造成服务的不可用.

例子: ES TransportClient


比如 ES 的 TransportClient 实现( 5.4.0 )就不能应对这种情况。

如果我们使用 org.elasticsearch.client.transport.TransportClient.addTransportAddress(),
首先需要构造一个 InetSocketTransportAddress, 之后会被保存在 org.elasticsearch.cluster.node.DiscoveryNode.address 中.

然而, ES 并不会重新解析新地址, ES 使用了 netty4, 连接使用的是:

io.netty.bootstrap.Bootstrap#connect(java.net.SocketAddress)

public ChannelFuture connect(SocketAddress remoteAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}

validate();
return doResolveAndConnect(remoteAddress, config.localAddress());
}

上面的 doResolveAndConnect() 方法内部主要调用了 doResolveAndConnect0() :

io.netty.bootstrap.Bootstrap#doResolveAndConnect0()

if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
// Resolver has no idea about what to do with the specified remote address or it's resolved already.
doConnect(remoteAddress, localAddress, promise);
return promise;
}

如上, 有个判断条件, resolver.isResolved(remoteAddress) , 其实现最后通过 java.net.InetSocketAddress#isUnresolved 来判断是否已经解析过:

public final boolean isUnresolved() {
return holder.isUnresolved();
}

private boolean isUnresolved() {
return addr == null;
}

如上, 只是简单判断 java.net.InetSocketAddress.InetSocketAddressHolder.addr 是否为 null. 因此需要我们手工来处理 DNS 变更的问题.

简单的 Fix


如果 ES 是非嗅探模式, 需要加上定时检测、清理和重连.

比如通过 TransportClient.connectedNodes()TransportClient.listedNodes() 获取 DNS IP 已变化的 node, 把它们移除并重新加入新的地址.

要注意的是在 remove 时 TransportAddress.equals() 的实现, 它最终的逻辑是在 java.net.InetSocketAddress.InetSocketAddressHolder.equals() :

@Override
public final boolean equals(Object obj) {
if (obj == null || !(obj instanceof InetSocketAddressHolder))
return false;
InetSocketAddressHolder that = (InetSocketAddressHolder)obj;
boolean sameIP;
if (addr != null)
sameIP = addr.equals(that.addr);
else if (hostname != null)
sameIP = (that.addr == null) &&
hostname.equalsIgnoreCase(that.hostname);
else
sameIP = (that.addr == null) && (that.hostname == null);
return sameIP && (port == that.port);
}

最后


使用 StatefulSet 带来的 dns IP 变化问题, 将会是一个普遍的问题, 而不仅仅出现在 ES Client 上.
这就要求我们对依赖的库实现了解比较清楚, 同时在开发时也要考虑和处理这种异常, 而且必须进行完善的异常测试, 以保证自己程序的可用性.