【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. 根据 nodePortCount nodePortAllocation 进行排序,端口占用多的节点在最前,得到一个slice,这也是 portAllocations 的雏形,也就是说 portAllocations  中index越小,其map value为true的越多(如上面表的例子所示,Node 0 相对 Node N,true的数量更多)
  5. nonReadyNodesPorts 记录的这些端口号都添加到 portAllocations 中,按照从前往后的顺序,只要第一个node中对应的端口没有被占用,就把它置为true,代表这个端口被占用了。最终得到了 portAllocations gameServerRegistry 。这样一来,既不会漏掉还未分配节点的hostport,也不会干扰按照节点端口占用多少顺序的逻辑。

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

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

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

相关文章
|
8月前
|
Python
Python网络编程基础(Socket编程)绑定地址和端口
【4月更文挑战第9天】在UDP服务器编程中,我们首先需要创建一个UDP套接字,然后绑定一个本地地址和端口,以便客户端可以通过这个地址和端口与我们的服务器进行通信。下面,我们将详细讲解如何绑定地址和端口。
|
前端开发 应用服务中间件 nginx
nginx中配置不输入端口(指定地址)访问项目的方法
nginx中配置不输入端口(指定地址)访问项目的方法
657 0
|
5月前
|
Docker 容器
【Azure 应用服务】App Server 部署后,Docker报错,找不到8080端口
【Azure 应用服务】App Server 部署后,Docker报错,找不到8080端口
|
6月前
|
关系型数据库 MySQL Java
软件开发2003 -Can·t to MySQL server on ‘xxxxxx‘(10038),宝塔初始化安装mysql,远程链接MySql注意事项,开始时服务器是没有放开端口的,宝塔也都开
软件开发2003 -Can·t to MySQL server on ‘xxxxxx‘(10038),宝塔初始化安装mysql,远程链接MySql注意事项,开始时服务器是没有放开端口的,宝塔也都开
|
8月前
|
网络协议
绑定地址和端口
【4月更文挑战第4天】创建socket对象后,需将其绑定到特定地址和端口。根据服务器需求,地址可选localhost(仅本机服务)、实际IP地址(公开服务)或空字符串(所有地址)。端口号应避开0-1023的保留范围。使用`bind()`方法绑定地址和端口,如`sock.bind(('', 12345))`。绑定可能遇到错误,需用异常处理机制捕获,确保程序稳定。
|
8月前
|
Python
Django项目如何通过修改manage.py指定运行的地址和端口号
Django项目如何通过修改manage.py指定运行的地址和端口号
167 2
|
8月前
|
SQL 弹性计算 分布式计算
Dataphin常见问题之执行 ODPS Sql 时抛出异常如何解决
Dataphin是阿里云提供的一站式数据处理服务,旨在帮助企业构建一体化的智能数据处理平台。Dataphin整合了数据建模、数据处理、数据开发、数据服务等多个功能,支持企业更高效地进行数据治理和分析。
|
8月前
|
前端开发 应用服务中间件 nginx
nginx中配置不输入端口(指定地址)访问项目的方法
nginx中配置不输入端口(指定地址)访问项目的方法
|
8月前
|
存储 安全 网络安全
HTTP与HTTPS的区别:安全性、协议地址和默认端口等比较
HTTP与HTTPS的区别:安全性、协议地址和默认端口等比较
457 0
|
网络协议 Ubuntu Linux
为公网SSH远程Ubuntu配置固定的公网TCP端口地址主图
为公网SSH远程Ubuntu配置固定的公网TCP端口地址主图
149 0

热门文章

最新文章