【Agones系列】Game Server的地址与端口

简介: 本文介绍Agones的网络模式,如何分配服务地址与端口

在上篇文章《【Agones系列】Agones初体验》中,我们部署了一个game server,gs自动获取了一个ADDRESS和PORT,这个地址和端口是从哪来的,是如何分配的呢?在这篇文章中我们来揭晓Agones game server的网络模式。

 

首先,Agones使用的是主机IP+端口转发的网络模式。之所以不使用负载均衡器是为了减少转发、降低网络延迟。同时,Agones为了pod具备独立的网络空间进而有能力运行sidecar容器,也没有使用hostnetwork,而是主机端口转发。Agones会分配一个主机端口给game server,game server的容器端口也会通过containerPort字段暴露,主机端口到容器端口的路由通常由主机的iptables或者ipvs进行,这具体取决于容器网络模型。

知道了Agones的网络原理,接下来我们来看地址与端口的分配逻辑。

Address

如上文提到,game server使用主机IP,它会从game server所在节点的k8s node对象中拿到对应的IP信息,具体逻辑如下:

// agones/pkg/gameservers/gameservers.go
func address(node *corev1.Node) (string, error) {

	externalDNS := runtime.FeatureEnabled(runtime.NodeExternalDNS)

	if externalDNS {
		for _, a := range node.Status.Addresses {
			if a.Type == corev1.NodeExternalDNS {
				return a.Address, nil
			}
		}
	}

	for _, a := range node.Status.Addresses {
		if a.Type == corev1.NodeExternalIP && net.ParseIP(a.Address) != nil {
			return a.Address, nil
		}
	}

	// There might not be a public DNS/IP, so fall back to the private DNS/IP
	if externalDNS {
		for _, a := range node.Status.Addresses {
			if a.Type == corev1.NodeInternalDNS {
				return a.Address, nil
			}
		}
	}

	for _, a := range node.Status.Addresses {
		if a.Type == corev1.NodeInternalIP && net.ParseIP(a.Address) != nil {
			return a.Address, nil
		}
	}

	return "", errors.Errorf("Could not find an address for Node: %s", node.ObjectMeta.Name)
}

agones依次找node.Status.Address中type为ExternalDNSExternalIPInternalDNSInternalIP 的IP。在我们上篇文章的例子中使用的是节点的ExternalIP,即120.27.21.131

Port

我们重点来看一下端口是如何分配的。Agones使用pod所在主机的端口暴露给用户连接,所以我们看到gs的status字段中端口叫做HostPort。HostPort的分配有三种方式:1)Static,由用户定义暴露端口; 2)Dynamic,Agones会为gs选择一个开放的端口;3)Passthrough,将containerPort设置为HostPort。Agones使用一个PortAllocator进行端口分配。下面着重介绍一下PortAllocator

PortAllocator 是围绕缓存数据构建的分配逻辑,该缓存叫做portAllocations ,数据结构如下

portAllocations    []map[int32]bool

它记录了集群node以及每个node对应端口是否被占用的情况。用下面一张表来解释会比较容易理解,Node 0 为该slice的第一个元素,包含了其开放的各个端口是否被gs占用。值得注意的是,这里的Node实际上是一个“逻辑节点”,并不能真正对应上集群中某一个节点,只是方便分配/回收端口而设置而已。因此,该缓存是一个slice,而并非记录了nodeName的map

True / False

Node 0

...

Node N

Min Port Num

...

x

...

...

Max Port Num

...

x

在缓存这块,除了portAllocations之外,还有一个gameServerRegistry,它是map类型,key为gs的id,value为bool类型,表示该gs是否已经被登记过占用。

首先,在PortAllocator启动后会新进行初始化(func (pa *PortAllocator) syncAll() error)其主要目的是通过遍历node与gameserver,实现对portAllocationsgameServerRegistry 两种数据结构的初始构建。构建过程如下:

  1. 通过lister得到存量所有的node与gameserver
  2. 根据存量的node初始化一个 nodePortAllocation,即map版的 portAllocations ,key为nodename;对应还有一个 nodePortCount ,记录每个node已经被占用多少个端口
  3. 遍历所有gameserver及其Ports字段,忽略type为Static的类型,将 gsRegistry对应gs记为true,若gs存在对应nodename,则更新 nodePortAllocation,将node对应端口记为true。同时该node的 nodePortCount加一;若gs hostport已经存在但没有对应nodename,则用 nonReadyNodesPorts记录这些端口号
  4. 根据 nodePortCountnodePortAllocation进行排序,端口占用多的节点在最前,得到一个slice,这也是 portAllocations的雏形,也就是说 portAllocations 中index越小,其map value为true的越多(如上面表的例子所示,Node 0 相对 Node N,true的数量更多)
  5. nonReadyNodesPorts记录的这些端口号都添加到 portAllocations中,按照从前往后的顺序,只要第一个node中对应的端口没有被占用,就把它置为true,代表这个端口被占用了。最终得到了 portAllocationsgameServerRegistry。这样一来,既不会漏掉还未分配节点的hostport,也不会干扰按照节点端口占用多少顺序的逻辑。

构建了这两个缓存portAllocationsgameServerRegistry,再来看分配逻辑非常简单:按照即将要被分配的gs的ports数量,从portAllocations中按顺序找到对应数目的还未分配的端口赋值给gs对应的字段。与此同时将gameServerRegistry对应的gs置为true。

最后再看一下回收的逻辑:遍历gs中的hostPort,与分配一样,按照portAllocations顺序找到对应的口端号,将其改为false即可,最后将gameServerRegistry 对应gs一项删除。

整体看下来,我们可以从代码中窥看到PortAllocator的分配思想,1)它其实并不关注gs要分配的端口在哪个节点,只要知道它占用与否。所以没有必要用map增加检索的复杂度,从分配端口最多的“逻辑节点”上拿/放就可以了。 只要确保集群中某个具体数字的端口开放数量和表中对应端口为True数量一致即可。2)尽管与调度无关,端口分配依然会打散分配的端口号,尽量在集群中不出现过多数字一样开放端口。

相关文章
|
前端开发 应用服务中间件 nginx
nginx中配置不输入端口(指定地址)访问项目的方法
nginx中配置不输入端口(指定地址)访问项目的方法
380 0
|
1月前
|
前端开发 应用服务中间件 nginx
nginx中配置不输入端口(指定地址)访问项目的方法
nginx中配置不输入端口(指定地址)访问项目的方法
27 0
Java中使用HttpRequest获取用户真实IP地址端口
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/81357594 import javax.
1816 0
|
3月前
|
存储 安全 网络安全
HTTP与HTTPS的区别:安全性、协议地址和默认端口等比较
HTTP与HTTPS的区别:安全性、协议地址和默认端口等比较
163 0
|
8月前
|
网络协议 Ubuntu Linux
为公网SSH远程Ubuntu配置固定的公网TCP端口地址主图
为公网SSH远程Ubuntu配置固定的公网TCP端口地址主图
85 0
|
11月前
|
云安全 Kubernetes 安全
云安全之Kubernetes API Server 8080端口未授权
在Kubernetes中,API Server是与集群通信的核心组件之一。默认情况下,Kubernetes API Server会在端口8080上侦听请求,如果Kubernetes API Server在8080端口上启用了未授权访问,那么攻击者可以通过该端口访问API Server并获取敏感信息或执行攻击。这可能会影响任何使用未经身份验证的HTTP协议连接的版本,包括Kubernetes的早期版本和未经修补的漏洞版本。
1303 0
|
11月前
|
关系型数据库 MySQL Apache
Service Apache can not start. Reason:(OS 10048)通常每个套接字地址(协议/网络地址/端口)只允许使用一-次。: AH00072: make_ sock
Service Apache can not start. Reason:(OS 10048)通常每个套接字地址(协议/网络地址/端口)只允许使用一-次。: AH00072: make_ sock
146 0
|
Dubbo Java 应用服务中间件
Springboot 服务 禁止设置启动server端口使用
Springboot 服务 禁止设置启动server端口使用
275 0
Springboot 服务 禁止设置启动server端口使用
|
弹性计算 C语言 云计算
在阿里云ESC的Windows Server 2012 R2 完成注册云服务器与为一个新网站配置网络端口的使用体验
为完成外教的作业,使用阿里云ESC的经历体验,和对未来发展的想法。
227 0
在阿里云ESC的Windows Server 2012 R2 完成注册云服务器与为一个新网站配置网络端口的使用体验