Release OKG v0.9 Documents (#201)

Signed-off-by: ChrisLiu <chrisliu1995@163.com>
This commit is contained in:
ChrisLiu 2024-08-23 08:14:49 +08:00 committed by GitHub
parent 233cc50499
commit 679403f69b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 6437 additions and 9 deletions

View File

@ -51,8 +51,8 @@ OpenKruiseGameOKG具有如下核心能力
<table>
<tr style={{"border":0}}>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/hypergryph-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/lilith-logo.png').default} width="120" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/hypergryph-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/bilibili-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/shangyou-logo.jpeg').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/xingzhe-logo.png').default} width="125" /></center></td>

View File

@ -31,6 +31,9 @@ type GameServerSetSpec struct {
// 游戏服接入层网络设置
Network *Network `json:"network,omitempty"`
// 用户自定义设置生命周期钩子
Lifecycle *appspub.Lifecycle `json:"lifecycle,omitempty"`
}
```
@ -149,6 +152,10 @@ type ServiceQualityAction struct {
// 动作为更改GameServerSpec中的字段
GameServerSpec `json:",inline"`
// 动作为更改GameServer的Annotations和Labels
Annotations map[string]string `json:"annotations,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
```
@ -174,6 +181,24 @@ type KVParams struct {
}
```
#### Lifecycle
```
// Lifecycle contains the hooks for Pod lifecycle.
type Lifecycle struct {
// 设置在pod删除前卡点
PreDelete *LifecycleHook `json:"preDelete,omitempty"`
// 设置在pod原地升级前卡点
InPlaceUpdate *LifecycleHook `json:"inPlaceUpdate,omitempty"`
}
type LifecycleHook struct {
LabelsHandler map[string]string `json:"labelsHandler,omitempty"`
FinalizersHandler []string `json:"finalizersHandler,omitempty"`
MarkPodNotReady bool `json:"markPodNotReady,omitempty"`
}
```
### GameServerSetStatus
```

View File

@ -172,11 +172,11 @@ PING minecraft-2.minecraft.default.svc.cluster.local (172.16.0.12): 56 data byte
可以发现accessor访问minecraft-2成功DNS成功解析到对应的内网IP地址。在这里的DNS访问规则如下{pod-name}.{gss-name}.{namespace-name}.svc.cluster.local
## GameServer 与 Pod 注释同步
## GameServer 与 Pod 标签/注释同步
如上所述,通过 DownwardAPI 可以将 pod annotation的信息下沉至容器中。我们有时希望将 GameServer 的 annotation 可以同步到 Pod 上以完成GameServer元数据信息的下沉动作。
如上所述,通过 DownwardAPI 可以将 pod label/annotation的信息下沉至容器中。我们有时希望将 GameServer 的 label/annotation 可以同步到 Pod 上以完成GameServer元数据信息的下沉动作。
OKG 支持以 "gs-sync/" 开头的 annotation 从 GameServer 同步到 Pod 之上,如下所示:
OKG 支持以 "gs-sync/" 开头的 label/annotation 从 GameServer 同步到 Pod 之上,如下所示:
```bash
kubectl patch gs minecraft-0 --type='merge' -p '{"metadata":{"annotations":{"gs-sync/test-key":"some-value"}}}'

View File

@ -0,0 +1,116 @@
# 自定义生命周期管理
游戏服务器强状态的关键特性使它们对于优雅的下线操作有很高的需求。
一个游戏服务器通常需要等待数据被完全持久化到磁盘上并确保安全后,才能进行彻底的移除。
虽然Kubernetes原生提供了preStop钩子允许容器在即将关闭前执行特定操作但存在一个局限性一旦超出了预设的时间限制容器将不得不被强制终止不管数据处理是否完成。在某些情况下这种方法缺乏真正的优雅性。
我们需要一个更灵活的机制来确保游戏服务器能够在保护了所有关键状态的前提下平滑地退出。
OpenKruise 引入了 Lifecycle Hook 功能,为游戏服务器提供了在关键生命周期节点上的精确控制和等待机制。
这使得服务器能失在满足特定条件后,方才执行真正的删除或更新操作。
通过提供可配置的 Lifecycle 字段并结合自定义服务质量的能力OKG 能够确保游戏服务器的下线过程既优雅又可靠。
借助这一进阶特性,维护者可以确保所有必要的数据持久化和内部状态同步在安全无误地完成后,服务器才会被平稳地移除或更新。
## 使用示例
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
lifecycle:
preDelete:
labelsHandler:
gs-sync/delete-block: "true"
gameServerTemplate:
metadata:
labels:
gs-sync/delete-block: "true"
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/chrisliu95/minecraft-demo:probe-v0
name: minecraft
volumeMounts:
- name: gsState
mountPath: /etc/gsinfo
volumes:
- name: gsinfo
downwardAPI:
items:
- path: "state"
fieldRef:
fieldPath: metadata.labels['game.kruise.io/gs-state']
serviceQualities:
- name: healthy
containerName: minecraft
permanent: false
exec:
command: ["bash", "./probe.sh"]
serviceQualityAction:
- state: true
result: done
labels:
gs-sync/delete-block: "false"
- state: true
result: WaitToBeDeleted
opsState: WaitToBeDeleted
- state: false
opsState: None
```
对应的脚本如下。该脚本做了以下动作:
- 从 /etc/gsinfo/state 中拿到当前gs的状态并判断其是否为“PreDelete”
- 若是PreDelete则说明当前gs应处于下线阶段。判断数据落盘是否完成这个示例中通过判断文件以文件存在表示数据落盘完成
- 若数据落盘未完成,则执行落盘动作(这个示例是创建一个文件)
- 若数据落盘完成则输出“done”并以1退出。
- 若不是PreDelete则说明该gs没有未进入下线阶段。以游戏服人数判断当前是否应该下线。
- 若游戏服人数等于0则输出“WaitToBeDeleted”以1退出。
- 若游戏服人数不为0则以0退出。
```
#!/bin/bash
file_path="/etc/gsinfo/state"
data_flushed_file="/etc/gsinfo/data_flushed"
if [[ ! -f "$file_path" ]]; then
exit 0
fi
state_content=$(cat "$file_path")
if [[ "$state_content" == "PreDelete" ]]; then
if [[ -f "$data_flushed_file" ]]; then
echo "done"
exit 1
else
touch "$data_flushed_file"
echo "WaitToBeDeleted"
exit 1
fi
else
people_count_file="/etc/gsinfo/people_count"
people_count=$(cat "$people_count_file")
if [[ "$people_count" -eq 0 ]]; then
echo "WaitToBeDeleted"
exit 1
else
exit 0
fi
fi
```
![grace-deletion.png](/img/kruisegame/user-manuals/gs-lifecycle-delete.png)
优雅下线的过程如下:
1. 游戏服正常运行玩家数量不为0
2. 当玩家数量为0通过自定义服务质量设置opsState为WaitToBeDeleted
3. 通过自动缩容策略OKG将该GameServer删除。由于配置了lifecycle hookdelete-block 标签为 truegs不会真正被删除而进入PreDelete状态并通过自定义服务质量触发数据落盘过程。
4. 当数据完成落盘通过自定义服质量将delete-block标签设为false卡点解除。
5. 卡点解除后PreDelete阶段将进入Delete阶段。gs真正被删除。

View File

@ -19,6 +19,7 @@ OKG 会集成不同云提供商的不同网络插件用户可通过GameServer
- AlibabaCloud-SLB-SharedPort
- AlibabaCloud-NLB-SharedPort
- Volcengine-CLB
- AmazonWebServices-NLB
---
### Kubernetes-HostPort
@ -535,6 +536,72 @@ AllowNotReadyContainers
- 格式:{containerName_0},{containerName_1},... 例如sidecar
- 是否支持变更:在原地升级过程中不可变更。
LBHealthCheckSwitch
- 含义:是否开启健康检查
- 格式“on”代表开启“off”代表关闭。默认为on
- 是否支持变更:支持
LBHealthCheckFlag
- 含义是否开启http类型健康检查
- 格式“on”代表开启“off”代表关闭。默认为off
- 是否支持变更:支持
LBHealthCheckType
- 含义:健康检查协议
- 格式:填写 “tcp” 或者 “http”默认为tcp
- 是否支持变更:支持
LBHealthCheckConnectTimeout
- 含义:健康检查响应的最大超时时间。
- 格式:单位:秒。取值范围[1, 300]。默认值为“5”
- 是否支持变更:支持
LBHealthyThreshold
- 含义:健康检查连续成功多少次后,将服务器的健康检查状态由失败判定为成功。
- 格式:取值范围[2, 10]。默认值为“2”
- 是否支持变更:支持
LBUnhealthyThreshold
- 含义:健康检查连续失败多少次后,将服务器的健康检查状态由成功判定为失败。
- 格式:取值范围[2, 10]。默认值为“2”
- 是否支持变更:支持
LBHealthCheckInterval
- 含义:健康检查的时间间隔。
- 格式:单位:秒。取值范围[1, 50]。默认值为“10”
- 是否支持变更:支持
LBHealthCheckProtocolPort
- 含义http类型健康检查的协议及端口。
- 格式:多个值之间用英文半角逗号(,分隔。如https:443,http:80
- 是否支持变更:支持
LBHealthCheckUri
- 含义健康检查类型为HTTP时对应的检查路径。
- 格式长度为1~80个字符只能使用字母、数字、字符。 必须以正斜线(/)开头。
- 是否支持变更:支持
LBHealthCheckDomain
- 含义健康检查类型为HTTP时对应的域名。
- 格式特定域名长度限制1~80个字符只能使用小写字母、数字、短划线-)、半角句号(.)。
- 是否支持变更:支持
LBHealthCheckMethod
- 含义健康检查类型为HTTP时对应的方法。
- 格式“GET” 或者 “HEAD”
- 是否支持变更:支持
#### 插件配置
```
[alibabacloud]
@ -588,6 +655,66 @@ AllowNotReadyContainers
- 格式:{containerName_0},{containerName_1},... 例如sidecar
- 是否支持变更:在原地升级过程中不可变更。
LBHealthCheckFlag
- 含义:是否开启健康检查
- 格式“on”代表开启“off”代表关闭。默认为on
- 是否支持变更:支持
LBHealthCheckType
- 含义:健康检查协议
- 格式:填写 “tcp” 或者 “http”默认为tcp
- 是否支持变更:支持
LBHealthCheckConnectPort
- 含义:健康检查的服务器端口。
- 格式:取值范围[0, 65535]。默认值为“0”
- 是否支持变更:支持
LBHealthCheckConnectTimeout
- 含义:健康检查响应的最大超时时间。
- 格式:单位:秒。取值范围[1, 300]。默认值为“5”
- 是否支持变更:支持
LBHealthyThreshold
- 含义:健康检查连续成功多少次后,将服务器的健康检查状态由失败判定为成功。
- 格式:取值范围[2, 10]。默认值为“2”
- 是否支持变更:支持
LBUnhealthyThreshold
- 含义:健康检查连续失败多少次后,将服务器的健康检查状态由成功判定为失败。
- 格式:取值范围[2, 10]。默认值为“2”
- 是否支持变更:支持
LBHealthCheckInterval
- 含义:健康检查的时间间隔。
- 格式:单位:秒。取值范围[1, 50]。默认值为“10”
- 是否支持变更:支持
LBHealthCheckUri
- 含义健康检查类型为HTTP时对应的检查路径。
- 格式长度为1~80个字符只能使用字母、数字、字符。 必须以正斜线(/)开头。
- 是否支持变更:支持
LBHealthCheckDomain
- 含义健康检查类型为HTTP时对应的域名。
- 格式特定域名长度限制1~80个字符只能使用小写字母、数字、短划线-)、半角句号(.)。
- 是否支持变更:支持
LBHealthCheckMethod
- 含义健康检查类型为HTTP时对应的方法。
- 格式“GET” 或者 “HEAD”
- 是否支持变更:支持
#### 插件配置
```
[alibabacloud]
@ -799,7 +926,7 @@ status:
此外生成的EIP资源在阿里云控制台中会以{pod namespace}/{pod name}命名,与每一个游戏服一一对应。
---
#### 插件名称
### AlibabaCloud-SLB-SharedPort
`AlibabaCloud-SLB-SharedPort`
@ -1085,6 +1212,219 @@ networkStatus:
---
---
### AmazonWebServices-NLB
#### 插件名称
`AmazonWebServices-NLB`
#### Cloud Provider
AmazonWebServices
#### 插件说明
- 对于在AWS EKS集群中使用OKG的游戏业务通过网络负载均衡将流量直接路由到Pod端口是实现高性能实时服务发现的基础。利用NLB进行动态端口映射简化了转发链路规避了Kubernetes kube-proxy负载均衡带来的性能损耗。这些特性对于处理副本战斗类型的游戏服务器尤为关键。对于指定了网络类型为AmazonWebServices-NLB的GameServerSetAmazonWebServices-NLB网络插件将会调度一个NLB自动分配端口创建侦听器和目标组并通过TargetGroupBinding CRD将目标组与Kubernetes服务进行关联。如果集群配置了VPC-CNI那么此时流量将自动转发到Pod的IP地址否则将通过ClusterIP转发。观察到GameServer的网络处于Ready状态时该过程即执行成功。
- 是否支持网络隔离:否
#### 前提准备
由于AWS的设计有所区别要实现NLB端口与Pod端口映射需要创建三类CRD资源Listener/TargetGroup/TargetGroupBinding
##### 部署elbv2-controller
Listener/TargetGroup的CRD定义及控制器https://github.com/aws-controllers-k8s/elbv2-controller 该项目联动了k8s资源与AWS云资源chart下载https://gallery.ecr.aws/aws-controllers-k8s/elbv2-chart value.yaml示例
```yaml
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::xxxxxxxxx:role/test"
aws:
region: "us-east-1"
endpoint_url: "https://elasticloadbalancing.us-east-1.amazonaws.com"
```
部署该项目最关键的在于授权k8s ServiceAccount访问NLB SDK推荐通过IAM角色的方式
###### 步骤 1为 EKS 集群启用 OIDC 提供者
1. 登录到 AWS 管理控制台。
2. 导航到 EKS 控制台https://console.aws.amazon.com/eks/
3. 选择您的集群。
4. 在集群详细信息页面上,确保 OIDC 提供者已启用。获取 EKS 集群的 OIDC 提供者 URL。在集群详细信息页面的 “Configuration” 部分,找到 “OpenID Connect provider URL”。
###### 步骤 2配置 IAM 角色信任策略
1. 在 IAM 控制台中,创建一个新的身份提供商,并选择 “OpenID Connect”
- 提供商URL填写EKS 集群的 OIDC 提供者 URL
- 受众填写:`sts.amazonaws.com`
2. 在 IAM 控制台中,创建一个新的 IAM 角色,并选择 “Custom trust policy”。
- 使用以下信任策略,允许 EKS 使用这个角色:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:<NAMESPACE>:ack-elbv2-controller",
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
}
}
}
]
}
```
- 将 `<AWS_ACCOUNT_ID>`、`<REGION>`、`<OIDC_ID>`、`<NAMESPACE>` 和 `<SERVICE_ACCOUNT_NAME>` 替换为您的实际值。
- 添加权限 `ElasticLoadBalancingFullAccess`
##### 部署AWS Load Balancer Controller
TargetGroupBinding的CRD及控制器https://github.com/kubernetes-sigs/aws-load-balancer-controller/
官方部署文档https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html 其本质也是通过授权k8s ServiceAccount一个IAM角色的方式。
#### 网络参数
NlbARNs
- 含义填写nlb的arn可填写多个需要现在【AWS】中创建好nlb。
- 填写格式各个nlbARN用,分割。例如arn:aws:elasticloadbalancing:us-east-1:888888888888:loadbalancer/net/aaa/3b332e6841f23870,arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/net/bbb/5fe74944d794d27e
- 是否支持变更:是
NlbVPCId
- 含义填写nlb所在的vpcid创建AWS目标组需要。
- 填写格式字符串。例如vpc-0bbc9f9f0ffexxxxx
- 是否支持变更:是
NlbHealthCheck
- 含义填写nlb目标组的健康检查参数可不填使用默认值。
- 填写格式:各个配置用,分割。例如:"healthCheckEnabled:true,healthCheckIntervalSeconds:30,healthCheckPath:/health,healthCheckPort:8081,healthCheckProtocol:HTTP,healthCheckTimeoutSeconds:10,healthyThresholdCount:5,unhealthyThresholdCount:2"
- 是否支持变更:是
- 参数解释:
- **healthCheckEnabled**指示是否启用了健康检查。如果目标类型是lambda默认情况下健康检查是禁用的但可以启用。如果目标类型是instance、ip或alb健康检查总是启用的且不能禁用。
- **healthCheckIntervalSeconds**:每个目标之间健康检查的时间间隔(以秒为单位)。 取值范围为5-300秒。如果目标组协议是TCP、TLS、UDP、TCP_UDP、HTTP或HTTPS默认值为30秒。 如果目标组协议是GENEVE默认值为10秒。如果目标类型是lambda默认值为35秒。
- **healthCheckPath**[HTTP/HTTPS健康检查] 目标健康检查的路径。 [HTTP1或HTTP2协议版本] ping路径。默认值为/。 [GRPC协议版本] 自定义健康检查方法的路径,格式为/package.service/method。默认值为/Amazon Web Services.ALB/healthcheck。
- **healthCheckPort**:负载均衡器在对目标执行健康检查时使用的端口。 如果协议是HTTP、HTTPS、TCP、TLS、UDP或TCP_UDP默认值为traffic-port这是每个目标接收负载均衡器流量的端口。 如果协议是GENEVE默认值为端口80。
- **healthCheckProtocol**:负载均衡器在对目标执行健康检查时使用的协议。 对于应用负载均衡器默认协议是HTTP。对于网络负载均衡器和网关负载均衡器默认协议是TCP。 如果目标组协议是HTTP或HTTPS则不支持TCP协议进行健康检查。GENEVE、TLS、UDP和TCP_UDP协议不支持健康检查。
- **healthCheckTimeoutSeconds**在目标没有响应的情况下认为健康检查失败的时间以秒为单位。取值范围为2-120秒。对于HTTP协议的目标组默认值为6秒。对于TCP、TLS或HTTPS协议的目标组默认值为10秒。对于GENEVE协议的目标组默认值为5秒。如果目标类型是lambda默认值为30秒。
- **healthyThresholdCount**在将目标标记为健康之前所需的连续健康检查成功次数。取值范围为2-10。如果目标组协议是TCP、TCP_UDP、UDP、TLS、HTTP或HTTPS默认值为5。 对于GENEVE协议的目标组默认值为5。如果目标类型是lambda默认值为5。
- **unhealthyThresholdCount**指定在将目标标记为不健康之前所需的连续健康检查失败次数。取值范围为2-10。如果目标组的协议是TCP、TCP_UDP、UDP、TLS、HTTP或HTTPS默认值为2。如果目标组的协议是GENEVE默认值为2。如果目标类型是lambda默认值为5。
PortProtocols
- 含义pod暴露的端口及协议支持填写多个端口/协议
- 填写格式port1/protocol1,port2/protocol2,...(协议需大写)
- 是否支持变更:是
Fixed
- 含义是否固定访问端口。若是即使pod删除重建网络内外映射关系不会改变
- 填写格式false / true
- 是否支持变更:是
AllowNotReadyContainers
- 含义:在容器原地升级时允许不断流的对应容器名称,可填写多个
- 填写格式:{containerName_0},{containerName_1},... 例如sidecar
- 是否支持变更:在原地升级过程中不可变更
Annotations
- 含义添加在service上的anno可填写多个
- 填写格式key1:value1,key2:value2...
- 是否支持变更:是
#### 插件配置
```toml
[aws]
enable = true
[aws.nlb]
# 填写nlb可使用的空闲端口段用于为pod分配外部接入端口范围最大为50(闭区间)
# 50限制来自AWS对侦听器数量的限制参考https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-limits.html
max_port = 32050
min_port = 32001
```
#### 示例说明
```shell
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gs-demo
namespace: default
spec:
replicas: 1
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: AmazonWebServices-NLB
networkConf:
- name: NlbARNs
value: "arn:aws:elasticloadbalancing:us-east-1:xxxxxxxxxxxx:loadbalancer/net/okg-test/yyyyyyyyyyyyyyyy"
- name: NlbVPCId
value: "vpc-0bbc9f9f0ffexxxxx"
- name: PortProtocols
value: "80/TCP"
- name: NlbHealthCheck
value: "healthCheckIntervalSeconds:15"
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/gs-demo/gameserver:network
name: gameserver
EOF
```
检查GameServer中的网络状态
```
networkStatus:
createTime: "2024-05-30T03:34:14Z"
currentNetworkState: Ready
desiredNetworkState: Ready
externalAddresses:
- endPoint: okg-test-yyyyyyyyyyyyyyyy.elb.us-east-1.amazonaws.com
ip: ""
ports:
- name: "80"
port: 32034
protocol: TCP
internalAddresses:
- ip: 10.10.7.154
ports:
- name: "80"
port: 80
protocol: TCP
lastTransitionTime: "2024-05-30T03:34:14Z"
networkType: AmazonWebServices-NLB
```
## 网络隔离
考虑以下场景,如:
- 游戏服发生重大异常,需要避免玩家连入
- 版本更新前,切断玩家网络连接
- 其他临时有关服的需求,待运维处理后再次开服
相对直接将游戏服Pod删除的方式在接入层进行网络隔离是更简便、轻量的操作可以保留Pod与GameServer元数据信息无需重建提高再开服效率。
### 使用方法
GameServer.Spec 具有 NetworkDisabled 字段。 GameServerSet部署生成的GameServers其默认的 NetworkDisabled 为 false意味着游戏服部署时都不会设置网络隔离。
当游戏服具有网络隔离的需求时,可以通过 手动(kubectl/K8s API) / 自定义服务质量 的方式设置 GameServer.Spec.NetworkDisabled 为 true则开始进行网络隔离置为false时再恢复网络。
需要注意的是,网络隔离功能由各个网络插件提供,需要用户在使用网络插件的时候查阅对应插件是否支持网络隔离功能。
## 获取网络信息

View File

@ -5,8 +5,216 @@
然而,每个进程重要性却有所不同,对于"轻量级进程"错误的情况用户并不希望将整个pod删除重建像k8s原生的liveness probe并不能很好地满足这种需求过于僵化的模式与游戏场景并不适配。
OKG 认为游戏服的服务质量水平应该交由游戏开发者定义,开发者可以根据不同游戏服状态去设置对应的处理动作。自定义服务质量功能是探测+动作的组合,通过这种方式帮助用户自动化地处理各类游戏服状态问题。
## 使用说明
通过 `GameServerSet.Spec.ServiceQualities` 使用自定义服务质量功能。其详细的数据结构如下:
```
type GameServerSetSpec struct {
// ...
ServiceQualities []ServiceQuality `json:"serviceQualities,omitempty"`
// ...
}
type ServiceQuality struct {
corev1.Probe `json:",inline"`
Name string `json:"name"`
ContainerName string `json:"containerName,omitempty"`
// Whether to make GameServerSpec not change after the ServiceQualityAction is executed.
// When Permanent is true, regardless of the detection results, ServiceQualityAction will only be executed once.
// When Permanent is false, ServiceQualityAction can be executed again even though ServiceQualityAction has been executed.
Permanent bool `json:"permanent"`
ServiceQualityAction []ServiceQualityAction `json:"serviceQualityAction,omitempty"`
}
type ServiceQualityAction struct {
State bool `json:"state"`
// Result indicate the probe message returned by the script.
// When Result is defined, it would exec action only when the according Result is actually returns.
Result string `json:"result,omitempty"`
GameServerSpec `json:",inline"`
Annotations map[string]string `json:"annotations,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
```
用户通过实现一个探测脚本,将容器中的业务/运维状态透出至Kubernetes GameServer对象上。
支持多结果输出脚本中退出码0 对应 ServiceQualityAction 的 State 为 true脚本中退出码1 对应 ServiceQualityAction 的 State 为 false脚本中 echo 的字符串对应 ServiceQualityAction 的 Result 值。
当 State 与 Result 同时满足时GameServer 的 GameServerSpec/Annotations/Labels 将按照用户填写的参数设置。其中GameServerSpec包括 OpsState/NetworkDisabled等具体字段如下
```
type GameServerSpec struct {
OpsState OpsState `json:"opsState,omitempty"`
UpdatePriority *intstr.IntOrString `json:"updatePriority,omitempty"`
DeletionPriority *intstr.IntOrString `json:"deletionPriority,omitempty"`
NetworkDisabled bool `json:"networkDisabled,omitempty"`
// Containers can be used to make the corresponding GameServer container fields
// different from the fields defined by GameServerTemplate in GameServerSetSpec.
Containers []GameServerContainer `json:"containers,omitempty"`
}
```
## 使用示例
我们来通过一个示例看下如何通过一个探测脚本实现游戏服多种状态感知。probe.sh 是业务容器中探测脚本将被OKG周期性调用其脚本代码如下
```shell
#!/bin/bash
gate=$(ps -ef | grep gate | grep -v grep | wc -l)
data=$(ps -ef | grep data | grep -v grep | wc -l)
if [ $gate != 1 ]
then
echo "gate"
exit 0
fi
if [ $data != 1 ]
then
echo "data"
exit 0
fi
exit 1
```
而对应的GameServerSet的yaml如下所示
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
maxUnavailable: 100%
gameServerTemplate:
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/chrisliu95/minecraft-demo:probe-v0
name: minecraft
serviceQualities:
- name: healthy
containerName: minecraft
permanent: false
exec:
command: ["bash", "./probe.sh"]
serviceQualityAction:
- state: true
result: gate
opsState: GateMaintaining
- state: true
result: data
opsState: DataMaintaining
- state: false
opsState: None
```
部署完成后生成3个Pod与GameServer
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 14s
minecraft-1 Ready None 0 0 14s
minecraft-2 Ready None 0 0 14s
kubectl get po
NAME READY STATUS RESTARTS AGE
minecraft-0 1/1 Running 0 15s
minecraft-1 1/1 Running 0 15s
minecraft-2 1/1 Running 0 15s
```
进入minecraft-0容器中模拟gate进程故障将其对应的进程号kil
```bash
kubectl exec -it minecraft-0 /bin/bash
/data# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:00 ? 00:00:00 /bin/bash ./start.sh
root 7 1 0 03:00 ? 00:00:00 /bin/bash ./gate.sh
root 8 1 0 03:00 ? 00:00:00 /bin/bash ./data.sh
root 9 1 99 03:00 ? 00:00:24 java -jar /minecraft_server.
...
/data# kill -9 7
/data# exit
```
获取当前gs的opsState已经变为GateMaintaining
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready GateMaintaining 0 0 2m14s
minecraft-1 Ready None 0 0 2m14s
minecraft-2 Ready None 0 0 2m14s
```
进入minecraft-1容器中模拟data进程故障将其对应的进程号kil
```bash
kubectl exec -it minecraft-1 /bin/bash
/data# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:00 ? 00:00:00 /bin/bash ./start.sh
root 7 1 0 03:00 ? 00:00:00 /bin/bash ./gate.sh
root 8 1 0 03:00 ? 00:00:00 /bin/bash ./data.sh
root 9 1 99 03:00 ? 00:00:24 java -jar /minecraft_server.
...
/data# kill -9 8
/data# exit
```
获取当前gs的opsState已经变为DataMaintaining
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready GateMaintaining 0 0 3m10s
minecraft-1 Ready DataMaintaining 0 0 3m10s
minecraft-2 Ready None 0 0 3m10s
```
分别进入minecraft-0minecraft-1手动拉起挂掉的进程
```bash
kubectl exec -it minecraft-0 /bin/bash
/data# bash ./gate.sh &
/data# exit
kubectl exec -it minecraft-1 /bin/bash
/data# bash ./data.sh &
/data# exit
```
此时gs的运维状态已经都恢复为None
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 5m6s
minecraft-1 Ready None 0 0 5m6s
minecraft-2 Ready None 0 0 5m6s
```
## 使用场景
### 游戏服空闲设置即将下线
部署一个带有自定义服务质量的GameServerSet

View File

@ -0,0 +1,192 @@
# 游戏服敏捷交付与运维管理最佳实践
在传统的运维模式中,游戏服务器的部署不可避免地导致业务与底层基础设施的紧密耦合。这种面向过程的交付方式,由于自动化程度不高和缺乏有效的批量管理能力,常常导致部署效率低下并且一旦出现问题,排障变得异常困难。
相较之下,云原生技术以其声明式和一致性交付的特性,为游戏服务器的部署与运维提供了显著的效率提升。然而,在实际的应用过程中,我们观察到,由于游戏服务器具有状态性的特质,其交付逻辑与传统的无状态服务存在显著的差别。本文旨在阐明这些差异,并提出最佳实践方案,旨在为云原生环境下游戏服务器的敏捷部署和运维管理开辟新的思路。
## 使用ArgoCD进行游戏服交付
在执行示例具体的发布操作之前,我们一起认识下云原生的交付思想 —— 声明式而非面向过程这也就意味着云原生式的应用交付关注的并不是应用的部署过程而是对应用的定义。而应用的定义就是Yaml它通过配置参数化的方式描述这个应用该是什么样子。因此一切有关应用的变更和发布实际上都是对应用描述Yaml的更改。至此我们发现我们需要一个仓库将Yaml落盘记录当前对应用的描述并且能够追溯和审计过去Yaml的变更。说到这里我相信大家会发现git repo天然符合该特点运维工程师可以通过提交Commit和Merge Request的方式将Yaml上传并落盘权限管理、审计都遵循git规范。在理想状态下我们只需要维护一套对游戏服描述的Yaml一键触发全球多地域的游戏服发布无需面向过程一一操作集群去执行部署动作。这就是GitOps的思想。
在GitOps落地过程中最富有挑战的事情实际上是对游戏服应用的描述抽象。GameServerSet是一批同属性的游戏服集合属于Kubernetes中的面向游戏服管理的工作负载因此每个GameServerSet是无法跨Kubernetes集群部署的。因此在某些情境比如多集群的场景下每一个集群需要至少一个GameServerSet且每个GameServerSet的描述或多或少存在着一些差异似乎难以通过一个Yaml将所有游戏服都概括。举个例子考虑全球开服的场景 —— 计划在上海、东京、法兰克福三地开服因此我们需要这三个地区的基础设施资源。在上海我希望发布10个游戏区服而在法兰克福我只希望发布3个区服这样一来一个Yaml因为replicas字段的差异就无法描述不同地域的游戏服。难道我们需要为每一个地域维护一个Yaml吗这样也非合理的做法当进行非差异性字段变更时例如为所有地域的游戏服打上一个标签我们需要重复地执行多个Yaml的更改集群数量多的时候容易造成遗漏或者错误这并不符合云原生交付思想。
实际上我们可以通过Helm Chart进行游戏服应用的进一步抽象将差异性的部分作为Value提取出来。在我们本次的示例中(https://github.com/AliyunContainerService/gitops-demo/tree/main/manifests/helm/open-game),
我们抽象出这样几个差异性字段:
* 主镜像 —— 每个地域/集群的主镜像可能存在差异
* sidecar镜像 —— 每个地域/集群的sidecar镜像可能存在差异
* 副本数 —— 每个地域/集群的发布的游戏服数量可能存在差异
* 是否自动伸缩 —— 每个地域/集群对于自动伸缩的要求可能存在差异
除此之外的其他字段都保持一致,意味着不存在地域差异性影响。
ArgoCD(https://argo-cd.readthedocs.io/en/stable/),
其很好地继承了GitOps的思想本文将利用ArgoCD进行游戏服交付的实操。接下来我们展开具体的操作
### 连接Git仓库
我们需要将描述了游戏服应用的Git仓库连接起来。操作步骤如下
1. 在ArgoCD UI左侧导航栏选择Settings然后选择Repositories > + Connect Repo
2. 在弹出的面板中配置以下信息然后单击CONNECT添加连接
| 区域 | 参数 | 参数值 |
|------------------------------------|--------------------------------|------------------------------------------------------------------|
| Choose your connection method | | VIA HTTPS |
| CONNECT REPO USING HTTPS | | git |
| | Project | default |
| | Repository URL | https://github.com/AliyunContainerService/gitops-demo.git |
| | Skip server verification | 勾选 |
<img src={require('/static/img/kruisegame/best-practices/git1.png').default} style={{ width: '800px'}} />
<img src={require('/static/img/kruisegame/best-practices/git2.png').default} style={{ width: '800px'}} />
### PvE类型游戏发
PvE 类型游戏通常存在区服概念,大多情况下由运维工程师手动控制各地域的开服数量。关于 PvE 游戏云原生化最佳实践可参考 OKG PvE 游戏最佳实践文档https://openkruise.io/zh/kruisegame/best-practices/pve-game)
在初次尝试ArgoCD时我们可以使用白屏控制台为每个地域的集群分别创建Application
1. 在ArgoCD UI左侧导航栏选择Applications然后单击+ NEW APP
2. 在弹出的面板配置以下信息然后单击CREATE进行创建。以opengame-demo-shanghai-dev为例
| 区域 | 参数 | 参数值 |
|------------------------------|--------------------------|---------------------------------------------------------------------------------|
| GENERAL | Application Name | opengame-demo-shanghai-dev |
| | Project Name | default |
| | SYNC POLICY | 在下拉列表中选择Automatic。参数取值如下 |
| Manual手动同步Git仓库变化 | | |
| Automatic自动同步Git仓库变化间隔3min | | |
| | SYNC OPTIONS | 勾选AUTO-CREATE NAMESPACE |
| SOURCE | Repository URL | 在下拉列表选择已有Git Repo此处选择https://github.com/AliyunContainerService/gitops-demo.git |
| | Revision | HEAD |
| | Path | manifests/helm/open-game |
| DESTINATION | Cluster URL/Cluster Name | 在下拉列表中选择目标集群 |
| | Namespace | opengame |
| HELM | VALUES FILES | values.yaml |
| PARAMETERS | replicas | 3 #发布三个游戏服 |
| | scaled.enabled | false # 不开启自动弹性伸缩 |
3. 创建完成后在Application页面即可看到opengame-demo-shanghai-dev的应用状态。如果SYNC POLICY选择的是Manual方式需要手动点击SYNC将应用同步部署至目标集群。应用的Status为Healthy和Synced表示已经成功同步。
<img src={require('/static/img/kruisegame/best-practices/argo.png').default} style={{ width: '850px'}} />
4. 单击opengame-demo-shanghai-dev应用名称即可查看应用详情展示应用相关的Kubernetes资源的拓扑结构及相应状态。
对ArgoCD有所熟悉了之后我们也可以通过ApplicationSet对象来一键发布游戏服。各个集群的差异性通过elements抽象出来例如下面Yaml中以集群维度抽象出三个字段cluster集群名称用于区分Application名称url用于区分目标集群地址replicas用于区别不同集群发布的游戏服数量。
编写完成该ApplicationSet Yaml 后将其部署到ACK One舰队集群即可自动创建出四个Application。
```bash
kubectl apply -f pve.yaml -n argocd
# pve.yaml 内容如下:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: minecraft
spec:
generators:
- list:
elements:
- cluster: shanghai-dev
url: <https://47.100.237.xxx:6443>
replicas: '1'
- cluster: shanghai-prod
url: <https://47.101.214.xxx:6443>
replicas: '3'
- cluster: frankfurt-prod
url: <https://8.209.103.xxx:6443>
replicas: '2'
- cluster: japan-prod
url: <https://10.0.0.xxx:6443>
replicas: '2'
template:
metadata:
name: '{{cluster}}-minecraft'
spec:
project: default
source:
repoURL: '<https://github.com/AliyunContainerService/gitops-demo.git>'
targetRevision: HEAD
path: manifests/helm/open-game
helm:
valueFiles:
- values.yaml
parameters: #对应helm chart中提取的value参数
- name: replicas
value: '{{replicas}}'
- name: scaled.enabled
value: 'false'
destination:
server: '{{url}}'
namespace: game-server #部署到对应集群的game-server命名空间下
syncPolicy:
syncOptions:
- CreateNamespace=true #若集群中命名空间不存在则自动创建
```
在该Yaml中所有的镜像版本都一致若希望各集群镜像版本出现差异可以仿照replicas的方式添加新的parameters参数。
### PvP类型游戏发布
对于 PvP 类型的游戏,房间服的数量由自身伸缩器调配,而非运维工程师手动指定。有关 PvP 类型游戏的云原生化最佳实践可参考 OKG PvP 游戏最佳实践文档https://openkruise.io/zh/kruisegame/best-practices/session-based-game)
在 OKG 中我们通过为 GameServerSet 配置 ScaledObject 对象来实现房间服的弹性伸缩。因此Helm Chart Value中的scaled.enabled 在此场景下需要开启。此外,房间服的副本数有 ArgoCD 和 OKG 2 个控制者而冲突,可以通过让 ArgoCD 忽略 GameServerSet 资源的副本数变化来解决,具体在 spec.ignoreDifferences 设置相应字段即可。考虑以上情况,该 pvp.yaml 如下所示:
```bash
kubectl apply -f pvp.yaml -n argocd
# pvp.yaml 内容如下:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: pvp
spec:
generators:
- list:
elements:
- cluster: shanghai-dev
url: <https://47.100.237.xxx:6443>
- cluster: shanghai-prod
url: <https://47.101.214.xxx:6443>
- cluster: frankfurt-prod
url: <https://8.209.103.xxx:6443>
- cluster: japan-prod
url: <https://10.0.0.xxx:6443>
template:
metadata:
name: '{{cluster}}-pvp'
spec:
project: defaultminecraft
ignoreDifferences: # 设置 GameServerSet minecraft副本数目由集群自控制
- group: game.kruise.io
kind: GameServerSet
name: minecraft
namespace: game
jsonPointers:
- /spec/replicas
source:
repoURL: '<https://github.com/AliyunContainerService/gitops-demo.git>'
targetRevision: HEAD
path: manifests/helm/open-game
helm:
valueFiles:
- values.yaml
destination:
server: '{{url}}'
namespace: pvp-server
syncPolicy:
syncOptions:
- CreateNamespace=true
```
在该Yaml中所有的镜像版本都一致若希望各集群镜像版本出现差异可以仿照replicas的方式添加新的parameters参数。
### 经验总结
通过上面的示例我们会发现做好应用的抽象是游戏服敏捷交付的关键之处。我们需要尽量保持GameServerSet大多字段一致将有差异性的字段提取出来这样只需要针对不同环境更改维护特定的相应字段即可真正做到敏捷交付。
## 游戏服运维管理
即使是同个工作负载GameServerSet游戏服之间的状态也是存在差异性的。在这种情况下交付后的游戏服也需要持续地进行定向运维管理这是与无状态业务最大的不同。
### OKG Dashboard 白屏化主动运维
通常,我们需要主动运维游戏服 —— 统计和查询游戏服状态定向更改游戏服版本、资源规格、运维状态等。通过OKG Dashboard可以实现游戏服的主动运维
* 有关OKG Dashboard使用说明可参考https://openkruise.io/zh/kruisegame/user-manuals/game-dashboard
* 对OKG Dashboard的更多需求可在issue下评论https://github.com/openkruise/kruise-game/issues/139
### 建设监控告警机制,加强游戏服稳定性
除了主动运维以外,我们需要建立稳定性问题订阅机制。当游戏服非预期运行时,运维工程师能够及时响应并处理。
OKG提供了自定义服务质量的功能灵活运用此功能可以实现定向游戏服异常状态透出并告警。可阅读文档
* https://openkruise.io/zh/kruisegame/user-manuals/service-qualities#%E6%B8%B8%E6%88%8F%E6%9C%8D%E7%8A%B6%E6%80%81%E5%BC%82%E5%B8%B8%E8%AE%BE%E7%BD%AE%E7%BB%B4%E6%8A%A4%E4%B8%AD
* https://openkruise.io/zh/kruisegame/best-practices/pve-game#%E6%B8%B8%E6%88%8F%E6%9C%8D%E8%87%AA%E5%AE%9A%E4%B9%89%E6%9C%8D%E5%8A%A1%E8%B4%A8%E9%87%8F
此外若希望游戏服通过监控指标来实现定向告警也可以通过在自定义服务质量脚本中调用prometheus APIpod name可利用DownwardAPI获取对比指标阈值来决定GameServer OpsState的值。

View File

@ -0,0 +1,440 @@
# 传统区服类游戏PvE最佳实践
## PvE区服类游戏落地Kubernetes的挑战
首先PvE区服类游戏有以下特点
1. 单个区服运行时间较长,应尽量避免停服操作,利于玩家游戏体验
2. 开服时(或)存在配置差异
3. 单区服容器中(或)存在多进程,区服服务质量需由用户定义
4. 随着时间推移,各区服状态存在差异,需定向管理,如更改资源规格、镜像版本、定向合服等
该类游戏在落地Kubernetes通常遇到**左右为难**的困境:
- 若使用Kubernetes原生workload则无法进行游戏服精细化管理具体地
- 若使用Deployment管理
- 生成的pod没有类似序号的状态标识导致1无法基于序号进行有状态的服务发现了2无法区别游戏服之间状态差异性3异常重启时状态丢失配置/存储等无法自动重定向。
- 若使用StatefulSet管理
- 生成的pod虽然有序号作为状态标识但是1只能从序号大到小进行更新或删除无法定向管理游戏服2无法感知游戏服之间的状态差异特性。
- 若不使用Kubernetes原生workload则无法利用上K8s的编排能力
- 若使用脚本程序批量开服:
- 属于面向过程的方式,参数无法落盘,出错率高。
- 若使用gitops管理
- 区服数量较多时需要维护大量有着相同字段的yaml文件有时甚至超过文件长度限制批量发布时也十分复杂。
- 若通过自建PaaS平台管理
- 需要引入大量开发工作,且与业务属性耦合较重,导致后续迭代复杂
本篇最佳实践将介绍如何利用OKG管理区服类游戏服务。
## 游戏服热更新
OKG提供的原地升级热更新是一种更加云原生的游戏服热更落地方式通过该方式可以实现游戏服热更文件的版本化管理、灰度更新、更新状态感知、以及故障恢复后热更版本一致性。
具体实现方式可参考相应文档 https://openkruise.io/zh/kruisegame/user-manuals/hot-update
## 游戏服配置管理
GameServerSet管理的GameServer具有序号属性其名称是固定不变的这一点与StatefulSet相同。因此「GameServerSet名称+序号」可作为游戏服的唯一标识,联动分布式存储或配置管理系统进行配置的差异化管理。
### 挂载对象存储
通过对象存储将不同游戏服的不同配置分别放在以游戏服名称命名的路径下保证bucket的路径与游戏服一一对应以PVC在GameServerSet上声明。该方式示意图如下
<img src={require('/static/img/kruisegame/best-practices/gss-oss-config.png').default} style={{ width: '500px'}} />
GameServerSet Yaml示例如下
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gameserver
namespace: default
spec:
replicas: 2
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
name: minecraft
env:
- name: POD_NAME #把pod name作为环境变量传入
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
volumeMounts:
- name: pvc-oss #挂载oss对应pvc
mountPath: "/app/sgame.config" #容器中的目录及文件
subPathExpr: $(POD_NAME)/sgame.config #对应oss目录及文件
volumes:
- name: pvc-oss
persistentVolumeClaim:
claimName: pvc-oss
```
这样一来开服前只需准备好游戏服对应配置并上传到bucket对应路径中再部署GameServerSet或调整Replicas即可。
如图所示gameserver-0 与 gameserver-1两个目录下的文件内容不同将分别挂载到对应的游戏服gameserver-0 与 gameserver-1上
<img src={require('/static/img/kruisegame/best-practices/oss-bucket-dir.jpg').default} style={{ width: '500px'}} />
### 动态拉取
如果业务存在配置中心服务如Nacos可以通过游戏服名称是固定且唯一的特性在游戏服容器启动时将自身的名称作为请求参数向配置中心发生请求拉取对应配置。
容器自身名称的获取方式与挂载对象存储中类似,通过 DownwardAPI将其作为环境变量传入。
## 游戏服自定义服务质量
### 背景与概念
传统区服类游戏容器化落地时往往以“富容器”的形态存在,也就是一个容器中存在着多种进程,每个进程负责单个区服的不同功能。此时单个游戏服的状态错综复杂。而原生 Kubernetes 对业务状态管理停留在容器层面,无法精细化感知容器中特定进程状态,造成故障或异常难以定位处理。
OKG 认为游戏服的服务质量应由用户定义,用户可根据业务针对性地设置游戏服所处的状态,并精细化地进行相应处理。通过 OKG 的”自定义服务质量“探测到具体进程异常状态,并将其透出至 Kubernetes 侧,再利用 kube-event 等事件通知组件将异常告警至运维群中,帮助运维工程师快速发现问题,实现秒级故障定位,分钟级的故障处理。
下图是自定义服务质量功能示意图,通过 probe.sh 脚本的返回结果对应更改GameServer的运维状态实现故障/异常的快速定位:
<img src={require('/static/img/kruisegame/best-practices/service-quality.png').default} style={{ width: '500px'}} />
### 示例
我们来通过一个示例看下如何通过一个探测脚本实现游戏服多种状态感知。
在制作容器镜像时,编写探测容器状态的脚本。该示例脚本 probe.sh 将探测gate进程、data进程是否存在。
当gate进程不存在则输出“gate”并正常退出当data进程不存在则输出“data”并正常退出当不存在异常以退出码1退出。
probe.sh 是业务容器中探测脚本将被OKG周期性调用原理类似于Kubernetes原生的liveness/readiness探针。在上述场景下其代码如下
```shell
#!/bin/bash
gate=$(ps -ef | grep gate | grep -v grep | wc -l)
data=$(ps -ef | grep data | grep -v grep | wc -l)
if [ $gate != 1 ]
then
echo "gate"
exit 0
fi
if [ $data != 1 ]
then
echo "data"
exit 0
fi
exit 1
```
而对应的GameServerSet的yaml如下所示
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
maxUnavailable: 100%
gameServerTemplate:
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/chrisliu95/minecraft-demo:probe-v0
name: minecraft
serviceQualities:
- name: healthy
containerName: minecraft
permanent: false
exec:
command: ["bash", "./probe.sh"]
serviceQualityAction:
- state: true
result: gate
opsState: GateMaintaining
- state: true
result: data
opsState: DataMaintaining
- state: false
opsState: None
```
部署完成后生成3个Pod与GameServer
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 14s
minecraft-1 Ready None 0 0 14s
minecraft-2 Ready None 0 0 14s
kubectl get po
NAME READY STATUS RESTARTS AGE
minecraft-0 1/1 Running 0 15s
minecraft-1 1/1 Running 0 15s
minecraft-2 1/1 Running 0 15s
```
进入minecraft-0容器中模拟gate进程故障将其对应的进程号kil
```bash
kubectl exec -it minecraft-0 /bin/bash
/data# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:00 ? 00:00:00 /bin/bash ./start.sh
root 7 1 0 03:00 ? 00:00:00 /bin/bash ./gate.sh
root 8 1 0 03:00 ? 00:00:00 /bin/bash ./data.sh
root 9 1 99 03:00 ? 00:00:24 java -jar /minecraft_server.
...
/data# kill -9 7
/data# exit
```
获取当前gs的opsState已经变为GateMaintaining
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready GateMaintaining 0 0 2m14s
minecraft-1 Ready None 0 0 2m14s
minecraft-2 Ready None 0 0 2m14s
```
进入minecraft-1容器中模拟data进程故障将其对应的进程号kil
```bash
kubectl exec -it minecraft-1 /bin/bash
/data# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:00 ? 00:00:00 /bin/bash ./start.sh
root 7 1 0 03:00 ? 00:00:00 /bin/bash ./gate.sh
root 8 1 0 03:00 ? 00:00:00 /bin/bash ./data.sh
root 9 1 99 03:00 ? 00:00:24 java -jar /minecraft_server.
...
/data# kill -9 8
/data# exit
```
获取当前gs的opsState已经变为DataMaintaining
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready GateMaintaining 0 0 3m10s
minecraft-1 Ready DataMaintaining 0 0 3m10s
minecraft-2 Ready None 0 0 3m10s
```
分别进入minecraft-0minecraft-1手动拉起挂掉的进程
```bash
kubectl exec -it minecraft-0 /bin/bash
/data# bash ./gate.sh &
/data# exit
kubectl exec -it minecraft-1 /bin/bash
/data# bash ./data.sh &
/data# exit
```
此时gs的运维状态已经都恢复为None
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 5m6s
minecraft-1 Ready None 0 0 5m6s
minecraft-2 Ready None 0 0 5m6s
```
## 游戏服定向管理
### 设置GameServer回收策略
GameServer存在两种生命周期回收策略 —— Cascade 与 Delete在GameServerSet.Spec.GameServerTemplate.ReclaimPolicy设置。
- CascadeGameServer在pod删除时被回收与pod生命周期保持一致。Cascade为ReclaimPolicy的默认值。
- DeleteGameServer在GameServerSet副本数缩小时被回收。当对应的pod被手动删除、更新重建、被驱逐时GameServer都不会被删除。
`Cascade`策略适合短生命周期游戏服存在频繁启动删除而状态需要及时清空的情景如大部分的PvP会话类游戏。
而`Delete`策略更适合传统区服类PvE游戏游戏服的状态需要长期被记录在GameServer上避免状态丢失。只有用户执行合服/删服操作时才会将其回收。
在创建GameServerSet时显式声明GameServer回收策略为Delete能够更好地实现区服类游戏的定向管理功能。
### 定向更新游戏服镜像与资源规格
存在对特定游戏服存在定向更新镜像的场景,例如:
- 在灰度或测试环境中,不同区对应着不同的镜像版本;
- SLG类型游戏存在玩法副本的概念不同区的玩法可能不尽相同对应着不同的镜像。
针对这种一个GameServerSet下可能存在多个版本的镜像游戏服可以通过设置GameServer.Spec.Containers中image字段来指定更新特定游戏服镜像版本。
存在对特定游戏服存在定向更新镜像的场景,例如:
- 随着时间增长,出现玩家增加过多、或者流失的情况,某些区服的计算资源无法满足当前需求。
针对这种一个GameServerSet下可能存在多种资源规格游戏服可以通过设置GameServer.Spec.Containers中resources字段来指定游戏服特定资源规格。
定向更新游戏服镜像与资源规格的示意图如下:
<img src={require('/static/img/kruisegame/best-practices/gs-update-images-resources.png').default} style={{ width: '500px'}} />
#### 示例说明
接下来通过一个示例来展示如何进行定向更新游戏服的镜像与资源规格。
首先部署一个3副本的GameServerSet
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
spec:
replicas: 3
gameServerTemplate:
reclaimPolicy: Delete
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
name: minecraft
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
maxUnavailable: 100%
```
定向更新minecraft-0的镜像将其改为 registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
```yaml
kubectl edit gs minecraft-0
...
spec:
deletionPriority: 0
opsState: None
updatePriority: 0
# 新增containers
containers:
- name: minecraft
image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
...
```
保存退出后过一段时间过后pod完成更新由于指定原地升级策略故容器重启次数+1
```bash
kubectl get po
NAME READY STATUS RESTARTS AGE
minecraft-0 1/1 Running 1 (13s ago) 3m28s
```
此时假如pod故障/或被手动删除生成的pod镜像会以GameServer声明的spec为准例如
```bash
# delete pod
kubectl delete po minecraft-0
# pod state is Terminating, gs state is Deleting
kubectl get gs minecraft-0
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Deleting None 0 0 8m19s
kubectl get po minecraft-0
NAME READY STATUS RESTARTS AGE
minecraft-0 1/1 Terminating 1 (5m12s ago) 8m19s
# after a while
# pod running againage of GameServer is different from age of pod
kubectl get po minecraft-0
NAME READY STATUS RESTARTS AGE
minecraft-0 1/1 Running 0 28s
kubectl get gs minecraft-0
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 9m18s
```
由于设置了gs回收策略为Delete所以游戏服的设置的状态不会因为pod的消失而消失。
当前游戏服的镜像依然是更新后的registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
接下来定向更新游戏服minecraft-1的资源规格将requests调整为cpu: 500m
```yaml
kubectl edit gs minecraft-1
...
spec:
deletionPriority: 0
opsState: None
updatePriority: 0
# 新增containers
containers:
- name: minecraft
resources:
requests:
cpu: 500m
...
```
资源配置不会立即原地更新。等待停服维护时运维工程师手动使pod重建新的资源规格即可生效例如
```bash
kubectl delete po minecraft-1
# after a while
# gs won't be deleted
kubectl get gs minecraft-1
NAME STATE OPSSTATE DP UP AGE
minecraft-1 Ready None 0 0 15m
# pod recreated
kubectl get po minecraft-1
NAME READY STATUS RESTARTS AGE
minecraft-1 1/1 Running 0 11s
```
此时pod的资源规格requests调整为cpu: 500m
### 游戏服合服
当某一区服游戏玩家流失到一定程度,需要进行合服操作,此时可以定向将游戏服进行删除。
- 通过ReserveGameServerIds与replicas设置可以实现批量合服动作例如存在5个游戏服id分别为0、1、2、3、4希望删除游戏服2、3则设置ReserveGameServerIds为2和3同时replicas调整为3即可。详情可参考文档[游戏服伸缩文档/游戏服 id reserve](https://openkruise.io/zh/kruisegame/user-manuals/gameservers-scale#%E6%B8%B8%E6%88%8F%E6%9C%8D-id-reserve)
- 而通过设置gs的opsState为Kill也可以实现快速对一个游戏服进行删除操作。详情可参考文档[游戏服伸缩文档/游戏服Kill](https://openkruise.io/zh/kruisegame/user-manuals/gameservers-scale#%E6%B8%B8%E6%88%8F%E6%9C%8D-kill)
## OKG管理PvE游戏服常见问题
### Q如何决定GameServerSet的纳管范围
首先需要明确的是GameServerSet(简称gss)是集群维度的资源不可跨集群声明。其次PvE类型游戏通常会涉及到zone、group等概念维度较多。这样一来同个zone使用一个gss还是同个group使用一个gss呢实际上判断条件主要取决于这些游戏服初始的差异性如何。
差异性的考量一般可以从两个方面来看:
1. 配置差异性。如上文配置管理所述可以通过游戏服GameServer集群命名空间内唯一名称的特性屏蔽掉内容的差异。故可以使用同一个GameServerSet管理。
2. 资源规格差异性与镜像版本差异性。这类差异性存在两种情况1初始时一致随着时间拉长开始出现差异性。2初始就不一致。对于情况1使用使用同一个GameServerSet管理游戏服再利用上文提到的OKG定向更新功能即可。对于情况2实际上也可以使用同一个GameServerSet管理游戏服只不过在开新服时做的操作就更加复杂一些不仅需要调整副本数目还需要调整对应的GameServer的Spec使其拥有独立的镜像或资源配置。这种方式比较适合测试环境或区服数目较少的生产环境。在区服数目规模较大时建议进行规格限定使同种镜像同种资源规格的游戏服用同一个GameServerSet管理。
### Q如何进行开新服
新开服的步骤如下:
1. 确保集群中部署了相应的GameServerSet初始副本数目或为0
2. 若存在差异配置则提前将配置文件准备好并上传至oss或用户自定义配置中心若不存在差异性配置则使用创建GameServerSet时配置configmap即可
3. 找到对应的gss调整副本数目使增加相应开服的数量
4. 若开新服的镜像版本/资源规格与GameServerSet中声明的不一致则可以更改对应的GameServerSpec的containers字段的镜像或资源。需要注意的是如果要调整资源规格需要手动删除pod使其完成一次重建。

View File

@ -0,0 +1,319 @@
# 会话类游戏PvP开房间最佳实践
会话类session based游戏是指在有限的时间内将玩家汇聚到特定游戏场景下的游戏类型。在通常意义下会话等同对局一局结束后玩家间的游戏关系也在此结束该会话也同时结束。因此在业界也会将会话类游戏通俗的理解为“开房间游戏”一个房间承载了对应的游戏会话。这类游戏往往存在着以下特点
- 游戏时间非连续,存在明显的起停时间节点
- 常见于MOBA、FPS类游戏对时延要求较高
- 会话中至少存在2个及以上的玩家相互战斗、交互
- 业务波峰与波谷时,对局数量差距明显
根据以上特点,一个理想的会话类游戏云原生架构图如下所示
<img src={require('/static/img/kruisegame/best-practices/session-based-game-arch.png').default} style={{ width: '500px'}} />
它应该具备以下能力:
- 提供网络直连功能,为每个房间提供独立的公网访问地址,玩家客户端可直接访问。
- 提供游戏匹配功能,为玩家找到合适的队友与对手组成会话对局,并为其分配合适的游戏房间。
- 提供状态管理功能,自动化管理游戏房间的业务状态与生命周期。
- 提供弹性伸缩功能,根据业务波峰与波谷自动申请和释放基础设施资源,控制成本。
- 可高效地进行游戏交付及运维管理,自动化水平高。
## 网络直连
会话类游戏需要网络直连,通常有以下考虑:
- 降低游戏时延,增加玩家游戏体验
- 去掉不必要的网关或代理,节约资源成本的同时简化技术架构
### 选择OpenKruiseGame网络模型
在传统游戏运维时代游戏服业务与基础设施较为耦合往往开发服务端程序时需要设计额外的端口分配管理器来避免同个机器上不同房间的端口冲突问题。理想状态在云原生化后游戏业务无需再关注游戏房间的端口分配问题房间服可水平扩展因此需要每个房间服拥有独自的公网访问地址然而Kubernetes原生的service负载均衡模型却无法满足该需求。
OpenKruiseGame(OKG)提供了多种网络模型,自动化管理(创建&回收房间服的公网地址EIP+端口自此以后游戏开发者无需关注基础设施的网络配置而游戏运维者只需填写简单的参数就可以高效部署并自动化管理房间服网络。在OKG的模式下**每个房间服对应一个pod**针对会话类游戏当前可使用的网络模型包括Kubernetes-HostPort、AlibabaCloud-NATGW、 AlibabaCloud-SLB、AlibabaCloud-EIP。每种网络模型特点不同适用于不同的场景。
**Kubernetes-HostPort**
利用宿主机EIP + 端口作为公网地址这种模式适合节点pod高密部署的情况。当房间服较小且数量很多时调度到每个node上的pod数就会很多此时充分利用该node上EIP的带宽能够最大程度地节约EIP资源成本。
**AlibabaCloud-SLB**
将同个SLB的不同端口映射到对应不同的房间服上以实现房间服具有独立公网地址的效果。该模式配置最为简单同时使用EIP的数量非常少。但注意SLB受限于后端最大实例数限制每个SLB最多关联200个房间服当GameServerSet下房间服的数量即将超过200时需要新增SLB实例以满足扩容需求。
**AlibabaCloud-NATGW**
将自动化管理房间服相关联的Dnat映射规则用户需要安装ack-extend-network-controller组件配置NAT网关相关参数。NATGW模型相对SLB模型可扩展性更强、更灵活但同时配置起来也更加复杂。
**AlibabaCloud-EIP**
为每个房间服pod分配独立的EIP。这种模式下消耗的EIP将比较多适合于刚做容器化迁移时游戏服存在端口管理器的情况。容器暴露的端口段即为被访问的端口段不存在映射行为。
详细说明与示例可参考[OKG网络模型文档](https://openkruise.io/zh/kruisegame/user-manuals/network)
### 获取房间服网络信息
游戏房间拥有了独立的公网访问地址剩下的问题就是如何将该地址提供给玩家客户端。一般来说会话类游戏会存在匹配服务这样的角色而匹配服务又通常存在两种方式感知房间服的网络地址1主动获取2房间服自注册上报。
**主动获取**
匹配服务会主动获取到当前集群中可用的房间服并获取到对应公网地址选择合适的房间服返回给客户端。这时匹配服务需要调用Kubernetes API来获取GameServer对象中NetworkStatus。对于GameServer的CURD操作可以参考Kruise-Game官方仓库的[e2e用例](https://github.com/openkruise/kruise-game/tree/master/test/e2e)
**注册上报**
当然,也存在着房间服业务上报网络信息的情况。此时,利用[Kubernetes的DownwardAPI机制](https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/downward-api/)可将网络信息下沉至容器内被业务感知到,业务程序解析到对应地址后再上报即可。具体示例可参考[文档](https://openkruise.io/zh/kruisegame/user-manuals/network#downwardapi)
## 游戏匹配
会话类游戏匹配的过程大致分为两个阶段 —— 1玩家寻找队友/对手形成对局2为对局分配合适的房间服并将网络地址返回给玩家。
### 基于OpenMatch实现游戏匹配服务
在开源社区中有像Open Match这样的游戏匹配框架用户只需按照框架标准实现匹配逻辑即可。OKG基于Open Match提供了 [kruise-game-open-match-director](https://github.com/CloudNativeGame/kruise-game-open-match-director) 组件主要帮助实现上述匹配过程的第二阶段——为对局找到游戏服并返回地址。这样一来用户只需关注第一阶段的匹配逻辑即可。有关基于Open Match 与 OKG 的匹配服务开发指南可以观看[云原生游戏系列课程](https://edu.aliyun.com/course/316831/lesson/19791?spm=a2cwt.28120015.316831.13.577431dbdxbMkD)也可以加入云原生游戏社区群钉钉群ID44862615参与讨论或提问。
### 自研游戏匹配服务
当然,如果存在自研的匹配框架/系统也可以通过简单的二次开发接入OKG。如上文中获取网络信息一节中所提到的匹配服务联动房间服有两种方式一种是主动获取房间服状态及网络信息另一种是房间服自注册上报。但对于第二种方式值得注意的是真正执行分配地址给玩家客户端前需要确认一次对应房间服的状态因为网络地址的获取和分配是异步的中间过程中存在房间服不可用的情况。而对于第一种方式推荐的做法是基于Kubernetes Informer机制监听GameServer对象在存在为对局分配房间服需求时获取当前可用的GameServer并将对应的网络信息返回此时的网络信息的获取和分配是同步的具体实现方式可以参考[kruise-game-open-match-director的allocator代码](https://github.com/CloudNativeGame/kruise-game-open-match-director/blob/main/pkg/allocator.go)。
## 状态管理
### 会话类游戏状态设置
在上一节游戏匹配中我们提到匹配服务在为形成对局的各个玩家客户端分配房间服地址时需要获取房间服的状态来保证给玩家分配的房间服是可用的。那么房间服状态可用应如何界定呢在OKG的设计思想里一个可用的游戏服应该 = 基础设施运行时状态可用State Ready + 基础设施网络可用Network Ready + 业务状态可用。
何为业务状态可用?这涉及到游戏业务对于房间服状态的定义。我们推荐房间服业务至少包含以下几种状态:
- None(不存在任何异常或特殊的状态,表明可用,也是房间服初始化启动后的默认状态)
- Allocated(已被分配,表明正在或即将有玩家进行游戏)
- WaitToBeDeleted(即将被删除等待OKG回收pod)
以上三种状态可用GameServer Spec中的OpsState记录。OKG提供两种方式进行状态标记
- 调用Kubernetes API 直接更改GameServer.Spec.OpsState 通常为匹配系统分配完房间服后将其标记为Allocated
- 通过[自定义服务质量](https://openkruise.io/zh/kruisegame/user-manuals/service-qualities)将容器中的业务状态暴露并转化为对应的GameServer.Spec.OpsState
最简单的状态转化模式如图所示:
1. 房间服被拉起后的默认状态可用此时OpsState为**None**
2. 当匹配需求产生时匹配服务查找可用的基础设施Ready & OpsState为None房间服分配后将其OpsState置为**Allocated** 通过Kubernetes API进行设置可参考[kruise-game-open-match-director的allocator代码](https://github.com/CloudNativeGame/kruise-game-open-match-director/blob/main/pkg/allocator.go)。若使用OKG + Open Match则无需设置Director已经做了上述工作
3. 当对局结束,游戏业务通过[自定义服务质量](https://openkruise.io/zh/kruisegame/user-manuals/service-qualities)将OpsState置为**WaitToBeDeleted**。这样对应的pod将被OKG自动进行回收删除后续弹性伸缩部分将展开介绍。
<img src={require('/static/img/kruisegame/best-practices/session-based-game-state-1.png').default} style={{ width: '150px'}} />
当然如果希望不频繁的起停pod在对局结束后也可以更改OpsState为None。整体状态转化模式如图所示
1. 同上房间服被拉起后的默认状态可用此时OpsState为**None**
2. 同上,分配房间服后,匹配系统将其设置为**Allocated**
3. 当对局结束,通过[自定义服务质量](https://openkruise.io/zh/kruisegame/user-manuals/service-qualities)将OpsState置为**None**
4. 通过协程判断房间服状态长期处于None状态时将通过[自定义服务质量](https://openkruise.io/zh/kruisegame/user-manuals/service-qualities)将OpsState置为**WaitToBeDeleted。**
<img src={require('/static/img/kruisegame/best-practices/session-based-game-state-2.png').default} style={{ width: '200px'}} />
### 通过服务质量透出房间服状态
在完成房间服状态流转设计后我们会发现有些状态是有房间服业务决定的而这些状态同时也需要透出到Kubernetes层面这样才能联动自动伸缩器、匹配系统等。因此需要一种机制将业务状态标志到Kubernetes对象上也就是GameServer上而这就是**自定义服务质量**功能。
自定义服务质量通过**执行探测脚本**的结果,以及用户设置的**探测结果对应状态**来自动化地将房间服状态标记到GameServer上。
下面是一个状态探测脚本名为waitToBeDeleted.sh探测容器中GS_STATE环境变量的值是否为WaitToBeDeleted
```bash
#!/bin/bash
if [ -z "$GS_STATE" ]; then
exit 1
elif [ "$GS_STATE" = "WaitToBeDeleted" ]; then
echo "$GS_STATE"
else
exit 1
fi
```
对应的GameServerSet yaml应该如下
```bash
...
spec:
...
serviceQualities:
- name: waitToBeDeleted
containerName: battle #探测容器名为battle的容器
permanent: false
exec:
#OKG将周期性执行battle容器中./waitToBeDeleted.sh脚本需要注意将脚本放置到对应的路径下
command: ["bash", "./waitToBeDeleted.sh"]
serviceQualityAction:
#当探测结果为true也就是脚本执行结果为正常退出退出码为0标记GameServer的opsState为WaitToBeDeleted
- state: true
opsState: WaitToBeDeleted
```
当然自定义服务质量可以有多个比如当房间服需要将自身None状态透出时名为none.sh的脚本如下
```bash
#!/bin/bash
if [ -z "$GS_STATE" ]; then
exit 1
elif [ "$GS_STATE" = "None" ]; then
echo "$GS_STATE"
else
exit 1
fi
```
对应的GameServerSet yaml应该如下
```bash
...
spec:
...
serviceQualities:
- name: waitToBeDeleted
containerName: battle #探测容器名为battle的容器
permanent: false
exec:
#OKG将周期性执行battle容器中./waitToBeDeleted.sh脚本需要注意将脚本放置到对应的路径下
command: ["bash", "./waitToBeDeleted.sh"]
serviceQualityAction:
#当探测结果为true也就是脚本执行结果为正常退出退出码为0标记GameServer的opsState为WaitToBeDeleted
- state: true
opsState: WaitToBeDeleted
- name: none
containerName: battle #探测容器名为battle的容器
permanent: false
exec:
#OKG将周期性执行battle容器中./none.sh脚本需要注意将脚本放置到对应的路径下
command: ["bash", "./none.sh"]
serviceQualityAction:
#当探测结果为true也就是脚本执行结果为正常退出退出码为0标记GameServer的opsState为None
- state: true
opsState: None
```
至此我们发现房间服业务程序只需要在合适的时刻节点设置对应的GS_STATE的环境变量值即可。比如
- 当房间服刚刚拉起设置GS_STATE=None
- 当房间服有玩家进入设置GS_STATE=Allocated尽管不需要透出到opsState但依然可以做状态变迁避免自身状态与在Kubernetes显示的不一致
- 当房间服对局结束再设置GS_STATE=None
- 当房间服长时间空闲设置GS_STATE=WaitToBeDeleted。
## 弹性伸缩
在上一节中我们设计了三种房间服状态None / Allocated / WaitToBeDeleted。在本节我们将根据以上房间服状态进行相应的弹性伸缩配置。
对于会话类游戏弹性伸缩的理想状态就是在业务高峰期房间服数量足够多可以让玩家秒级接入而业务低峰期的时候减少房间服的数量节约资源成本。OKG提供了自动伸缩器可以感知房间服状态来自动调节GameServerSet的replicas值从而实现根据游戏业务状态伸缩的理想效果。
### 房间服自动减少
在状态管理一节中我们也有所提到opsState为WaitToBeDeleted的GameServer将会自动被OKG回收。这样一来只要业务决定了自身不再提供服务了通过自定义服务质量设置WaitToBeDeleted即可。关于缩容策略的具体的配置可以参考 [https://openkruise.io/zh/kruisegame/user-manuals/gameservers-scale#使用示例](https://openkruise.io/zh/kruisegame/user-manuals/gameservers-scale#%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B)
### 房间服自动增加
OKG提供自动扩容的核心策略就是保证房间服存在可用且充足的数量。这个数量等同于buffer是由用户决定的在OKG中这个参数叫做 **minAvailable**
在当前所有opsState为None的游戏服数量少于设置**minAvailable**值时OKG将自动扩容出新的游戏服使opsState为None的游戏服数量满足设置的最小个数。
### 资源自动伸缩
Kubernetes的弹性伸缩涵盖两个层面应用层弹性 与 资源层弹性。其中OKG提供了房间服应用层弹性的能力自动地调节房间服对应pod的数量。而仅调节pod的数量是无法实现资源成本的节约需要自动地调节节点数量这正是Kubernetes cluster-autoscaler实现资源层弹性的方式。cluster-autoscaler核心原理是
- 当pod由于资源不足而处于pending状态时自动弹出节点。
- 当节点利用率过低/节点空闲时,自动回收节点。
对于游戏场景,自动伸缩最佳实践建议如下:
- 根据节点规格设置OKG **minAvailable** 大小。节点的启动是需要时间的所以需要提前准备好空闲可用的房间服供玩家连接。空闲的房间服本质上将节点水平扩容的触发时间前置了弥补了启动机器的时间差。这样一来节点规格和minAvailable就关联密切了举个例子集群中使用的节点规格为8核16G而运行在其上的房间服pod需要1核2G的资源这样理论上该节点可以运行7个房间服节点会有保留资源所以不是8个。在这种情况下minAvailable 设置为7以上则集群中会一直存在着一个“空闲”节点这里的空闲指的不是没有pod而是没有玩家设置14以上则集群中会一直存在着两个“空闲”节点这样也就将**房间服备用数量**转化为了**节点备用数量**,用户可以按照业务场景、成本控制的角度具体设置。
- 设置节点完全空闲时才使cluster autoscaler自动回收节点保障游戏运行正常。根据资源阈值的方式缩容对游戏并不友好由于游戏是有状态的服务存在极大的可能性遇到节点上资源负载较小但玩家依然正在游戏的情况不能轻易删除。
## 房间服热更新
为了平滑地进行房间服升级,同时简化运维操作,往往存在着需求:希望一键更新房间服版本,同时不影响玩家的游戏体验,不进行停服维护。这一过程也叫做房间服的热更新。
房间服的热更新与传统PvE类型游戏需要的原地升级不同由于单局时间短往往在开启游戏对局之后不会更改该房间的服务逻辑而是一键更新发布后新开启的房间使用最新的版本存量的房间服不做任何改动同时配合匹配系统将新进入的玩家导入新版本的房间再利用自动伸缩机制使旧版本的房间数目随着人数流失而不断减少新版本的房间数随着人数增加不断增加最终完成版本切换。
整个过程只需更改房间服容器镜像即可,自动化不停服更新,大幅度减少运维复杂度。
### 不更新存量房间服
使用GameServerSet工作负载时可以选择“OnDelete”更新类型实现不更新存量房间服而新的房间服使用新版本的效果。
例如起始部署一个3副本的游戏服集合镜像tag全部为1.12.2
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
updateStrategy:
type: OnDelete
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
name: minecraft
```
此时进行镜像更新镜像tag改为1.12.2-new
```bash
kubectl edit gss minecraft
...
spec:
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
name: minecraft
...
```
可以看到存量的游戏服并没有进行更新镜像版本依然为1.12.2
```bash
kubectl get po -oyaml | grep minecraft-demo
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
imageID: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo@sha256:8aa4177a19b15d7336162c6ca4d833a74c3cb23d85eab2ef2a63f7a2a682b8fb
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
imageID: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo@sha256:8aa4177a19b15d7336162c6ca4d833a74c3cb23d85eab2ef2a63f7a2a682b8fb
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
imageID: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo@sha256:8aa4177a19b15d7336162c6ca4d833a74c3cb23d85eab2ef2a63f7a2a682b8fb
```
此时进行扩容新创建一个游戏服minecraft-3发现其镜像版本为1.12.2-new
```bash
kubectl scale gss minecraft --replicas=4
gameserverset.game.kruise.io/minecraft scaled
kubectl get po minecraft-3 -oyaml | grep minecraft-demo
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
imageID: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo@sha256:f68fd7d5e6133c511b374a38f7dbc35acedce1d177dd78fba1d62d6264d5cba0
```
### 与匹配系统的联动
当存GameServerSet下存在新版本的镜像时匹配系统需要决定玩家进入哪一个版本的房间服。
匹配系统有两种方式感知到当前工作负载下的版本:
1. 房间服启动时将自身版本主动注册上报,删除时反注册析构
2. 匹配系统匹配时调用Kubernetes API查询当前合适该玩家的版本
这两种方式与前文提到的如何获取房间服地址的方式是类似的,与其实现方式保持一致即可。
如按方式1房间服启动时将访问地址信息上报的同时一并将版本信息上报如按方式2则在查找合适房间服时额外增加筛选房间服版本的逻辑。
需要注意的是,即使匹配系统感知到最新版本,但此时并不能使玩家只进入到最新版本中。由于刚刚完成版本更新,新版本的房间数量并不充足,需要存在一定时间的过渡阶段,让找不到最新版本房间服的玩家依然可以进入旧版本游戏。
### 通过自动伸缩完成平滑升级
当GameServerSet镜像版本已经更新且匹配系统可感知到最新版本时我们希望新版本的房间服越来越多而旧版本的房间服越来越少而这恰好通过自动伸缩来实现。
首先在版本切换之初GameServerSet下只存在旧版本的房间服且该版本的房间服存在以下几种状态Allocated、WaitToBeDeleted、None。
若Allocated的房间服在对局结束后直接变为WaitToBeDeleted则旧版本的None在玩家不激增的情况下会先变为Allocated、再变为WaitToBeDeleted进而整体会随着时间的推移逐渐减少而新的房间服数量会因为设置了minAvailable参数而不断增加
但如果Allocated的房间服在对局结束后被再次利用重新变为None在玩家不激增的情况下会存在很长一段时间新版本的房间服无法扩容出来所以建议在更新GameServerSet镜像后手动调大其副本数量直接扩容出最新版本的房间服。旧版本的处于None状态的房间服会由于长时间等待不到玩家进入而进入WaitToBeDeleted状态最终被删除。

View File

@ -0,0 +1,227 @@
# 游戏服共享内存最佳实践
## 背景说明
内存敏感型游戏服务特指对于内存资源需求较大的游戏服在启动时往往需要加载众多资源到内存之中以提升玩家游戏交互体验。但也正因于此带来了1游戏服启动速度慢、版本更新时效率低2游戏服之间存在相同内存数据但无法被服用节点内存资源被过度浪费的问题。
游戏开发者往往使用共享内存技术来解决上述问题以提升游戏服启动效率和内存资源效率。通常存在一个init进程执行游戏服初始化加载的功能将数据写入共享内存在此之后在该机器上新创建的所有游戏服都无需重复执行该过程只需读取同一块地址对应的内存数据即可启动速度提升了且内存资源被复用不会造成资源浪费。
本文将关注容器化游戏服共享内存使用方案,提供最佳实践。
## 方案介绍
方案涉及到两种类型的进程如上文中提到的init进程进行写内存gs进程在启动时进行读内存。
<img src={require('/static/img/kruisegame/best-practices/shm-arch.png').default} style={{ width: '500px'}} />
架构图若上所示init进程使用DaemonSet进行管理每个游戏服节点部署一个init而gs进程使用GameServerSet进行管理每个节点可以有多个gs。当DaemonSet部署完成并且对应的init pod成功执行后进行GameServerSet部署开启游戏服。此时gs将快速启动并且同一个节点上的gs会初始时复用同一块内存空间。
## 实践示例
### 1. init进程程序示例写内存
使用下面代码创建一块共享内存然后每隔一秒向内存写入id数据id每秒递增加一
```cpp
/* study.cpp 写端代码 */
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/shm.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/* 用于传递消息的结构体 */
typedef struct _msg
{
int id;
char str[64];
}MSG;
int main()
{
MSG* msg;
/* 获取键值 */
key_t key = ftok("./",2015);
if(key == -1)
{
perror("ftok");
exit(-1);
}
/* 创建或打开一块共享内存,并返回内存标识符 */
int shd = shmget(key,sizeof(MSG),IPC_CREAT | 0666);
if(shd == -1)
{
perror("shmget");
exit(-1);
}
/* 映射内存地址到当前进程,并返回内存块的地址 */
msg = (MSG*)shmat(shd,NULL,0);
if(msg == (MSG*)-1)
{
perror("shmat");
exit(-1);
}
/* 改变内存地址的数据 */
memset((void*)msg,0,sizeof(MSG));
for(int i = 0;i < 100000;i++)
{
msg->id = i;
printf("msg->id = %d\n",msg->id);
sleep(1);
}
/* 查看系统中的共享内存 */
system("ipcs -m");
return 0;
}
```
### 2. gs进程程序示例读内存
以下为读取共享内存的代码, 代码获取shm_id为0的共享内存数据循环打印id数据。
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main() {
int shm_id;
void *shared_memory;
// 获取共享内存标识符
shm_id = 0;
printf("shm_id: %d\n", shm_id);
// if (shm_id == -1) {
// perror("shmget failed");
// exit(1);
// }
// 连接共享内存
shared_memory = shmat(shm_id, NULL, 0);
if (shared_memory == (void *) -1) {
perror("shmat failed");
exit(1);
}
// 读取共享内存数据
while(1) {
printf("Value from shared memory: %d\n", *((int *)shared_memory));
}
// 断开与共享内存的连接
if (shmdt(shared_memory) == -1) {
perror("shmdt failed");
exit(1);
}
return 0;
}
```
### 3. 制作镜像
gs的Dockerfile如下init 与之类似):
```docker
FROM gcc:latest
WORKDIR /usr/src/myapp
COPY . .
RUN gcc -o read read.c
USER root
RUN chmod 777 /usr/src/myapp/read
EntryPoint ["/usr/src/myapp/read"]
CMD ["sleep 300000"]
```
### 4. 部署init进程
```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: shm-daemonset
namespace: default
spec:
selector:
matchLabels:
name: init
template:
metadata:
labels:
name: init
spec:
hostIPC: true #设置 hostIPC: true 时Pod将使用宿主机的IPC命名空间,使用宿主IPC的Pod可以访问宿主机上的共享内存段
nodeSelector:
app: shared-mem #通过标签控制共享内存作用的节点范围
containers:
- name: init
image: registry.cn-hangzhou.aliyuncs.com/skkk/testc:write27_v2
volumeMounts:
- name: shm
mountPath: /dev/shm
volumes:
- name: shm
hostPath:
path: /dev/shm
type: Directory
```
创建ds后可以在宿主机上看到创建的共享内存
<img src={require('/static/img/kruisegame/best-practices/shm-host-ipcs.png').default} style={{ width: '500px'}} />
可以在容器日志中看到每隔一秒修改id的值
<img src={require('/static/img/kruisegame/best-practices/shm-init-log.png').default} style={{ width: '200px'}} />
### 5. 部署gs进程
创建gs从共享内存中读取id的值
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gameserver
namespace: default
spec:
replicas: 2
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
gameServerTemplate:
spec:
hostIPC: true
nodeSelector:
app: shared-mem
containers:
- image: registry.cn-hangzhou.aliyuncs.com/skkk/testc:readtest
imagePullPolicy: Always
name: gs
volumeMounts:
- name: shm
mountPath: /dev/shm
volumes:
- hostPath:
path: /dev/shm
type: Directory
name: shm
```
部署成功后在容器日志中可以看到已经获取到共享内存中的id值
<img src={require('/static/img/kruisegame/best-practices/shm-gs-log.png').default} style={{ width: '200px'}} />

View File

@ -0,0 +1,215 @@
# 游戏运维工作流最佳实践
## 背景
由于游戏服务器有状态的特性对于游戏运维而言通常需要根据当前业务状态进行相应处理以避免玩家体验受损。OKG提供了游戏服务器状态感知和定向管理的能力具备根据不同状态进行不同处理的前提条件。在实际生产过程中一个运维流程是多个运维动作的组合运维工程师往往需要进行“游戏服状态确认” → “游戏服操作”的往复动作,这也导致游戏服云原生化后依然存在一定的操作复杂度。
本文将结合“房间服无损发布”这一实际场景向读者展示如何通过Argo Workflow将对GameServerSet和GameServer的运维动作有效组合起来从而构筑一套既流畅又高效的运维工作流。
## 示例
### 场景说明
“房间服无损发布” 需要满足以下特点:
- 正在游戏的旧版本房间不受影响,而空闲的旧版本房间服需要被回收清理
- 存在一定数量的新版本房间,可让新连接的玩家随时进入
为在新版本发布保证“房间无损”,房间服在交付时会选择 OnDelete 更新策略以实现存量房间不被删除新创建的房间使用新的镜像的效果。此外对应GameServerSet可以通过配置OKG自定义服务质量与自动伸缩策略实现自动化生命周期管理。有关房间服最佳实践文档可参考 https://openkruise.io/zh/kruisegame/best-practices/session-based-game
### 新版发布流程
基于以上交付内容,运维工程师在更新新版本时将进行以下动作:
1. 更新GameServerSet镜像此时正在运行的房间服不会删除或重建
2. 对GameServerSet进行扩容扩容出足够多的新版本房间服
3. 确认新版本房间服的状态是否正常提供服务
4. 清理旧版本的空闲房间
接下来我们将通过一个示例展示如何一键式完成以上流程。
### 模拟存量旧版本房间服状态
*注集群中需安装OKG*
集群中有3个旧版本房间服版本号为`1.12.2`
```
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
updateStrategy:
type: OnDelete
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
name: minecraft
EOF
```
其中minecraft-0存在玩家
```
kubectl patch gs minecraft-0 --type=merge -p '{"spec":{"opsState":"Allocated"}}'
```
现状:
```
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready Allocated 0 0 27s
minecraft-1 Ready None 0 0 27s
minecraft-2 Ready None 0 0 27s
```
### 通过Workflow进行新版本一键发布
*注:集群中需要安装[Argo Workflow组件](https://argoproj.github.io/workflows/)*
部署如下Yaml执行 kubectl create -f workflow-demo.yaml
```
# workflow-demo.yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: workflow-demo-
namespace: kruise-game-system
spec:
serviceAccountName: kruise-game-controller-manager
entrypoint: main
templates:
- name: main
steps:
- - name: patch-image
template: patch-image
- - name: scale-replicas
template: scale-replicas
- - name: check-pods-ready
template: check-pods-ready
- - name: gs-update
template: gs-update
- name: patch-image
resource:
action: patch
mergeStrategy: merge
manifest: |
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
gameServerTemplate:
spec:
containers:
- name: minecraft
image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
- name: scale-replicas
resource:
action: patch
mergeStrategy: merge
manifest: |
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 6
- name: check-pods-ready
retryStrategy:
retryPolicy: "OnError"
backoff:
duration: "10s"
factor: 2
script:
image: registry.cn-beijing.aliyuncs.com/chrisliu95/kubectl:latest
command: [bash]
source: |
#!/bin/bash
DESIRED_IMAGE="registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new"
LABEL_SELECTOR="game.kruise.io/owner-gss=minecraft"
DESIRED_READY_COUNT=3 # 你期望的就绪 Pod 的数量
NAMESPACE="default" # 适当地更改名称空间
# 使用 kubectl 获取所有包含正确标签的 Pods
PODS_JSON=$(kubectl get pods -n ${NAMESPACE} -l ${LABEL_SELECTOR} -o json)
# 初始化就绪的 Pods 数量
COUNT_READY=0
# 根据提供的JSON信息来查找匹配的pods以及它们的状态
for pod in $(echo "$PODS_JSON" | jq -r '.items[] | select(.status.phase=="Running") | .metadata.name'); do
# 获取 Pod 的每个容器的镜像并检查是否 Pod 就绪
POD_IMAGES=$(echo "$PODS_JSON" | jq -r ".items[] | select(.metadata.name==\"$pod\") | .spec.containers[].image")
IS_READY=$(echo "$PODS_JSON" | jq -r ".items[] | select(.metadata.name==\"$pod\") | .status.conditions[] | select(.type==\"Ready\") | .status")
if [[ "$POD_IMAGES" == *"$DESIRED_IMAGE"* && "$IS_READY" == "True" ]]; then
COUNT_READY=$((COUNT_READY+1))
fi
done
# 输出就绪的 Pods 数量
echo "Ready Pods with image ${DESIRED_IMAGE}: ${COUNT_READY}"
# 比较实际就绪的 Pods 数量与期待数量
if [[ "${COUNT_READY}" -eq "${DESIRED_READY_COUNT}" ]]; then
echo "The number of ready Pods matches the desired count of ${DESIRED_READY_COUNT}."
else
echo "The number of ready Pods (${COUNT_READY}) does not match the desired count of ${DESIRED_READY_COUNT}."
exit 1
fi
- name: gs-update
container:
image: registry.cn-beijing.aliyuncs.com/chrisliu95/gs-updater:v1.2
command:
- /updater
args:
- --gss-name=minecraft
- --namespace=default
- --select-opsState=None
- --select-not-container-image=minecraft/registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new
- --exp-opsState=WaitToBeDeleted
```
该workflow包含了四个step分别对应上述发布流程的四个步骤涉及到三种类型的模版
- resource —— 对K8s对象进行操作。本例中step 1/2 对GameServerSet进行了patch操作分别更新镜像版本为`1.12.2-new`、以及扩充了`3`个新版本房间。注意该workflow配置了service account以获取操作集群资源的相应权限为了方便展示此处直接使用了 kruise-game-system 命名空间下的 kruise-game-controller-manager 对应的sa。
- script —— 启动容器执行自定义脚本。本例中 step 3 使用了带有kubectl命令行基础容器以脚本的方式对新扩容出来的pod进行状态校验确保Ready数量准确时再进入下一阶段。这里配置了重试策略当执行失败OnError后会再次校验间隔时长为10s间隔时间延长的倍数为2。
- container —— 启动一个容器执行相应参数命令。本例中 step 3 使用了 [GameServer-Updater](https://github.com/CloudNativeGame/GameServers-Updater) 工具可以批量设置gs的状态。此时将版本号不为1.12.2-new且opsState是None的房间服的opsState设置为WaitToBeDeleted。这样后续通过自动缩容即可完成自动化旧版本清理。
该workflow执行后依次产生四个workflow pod。房间服新扩容出3、4、5号且最终将空闲的1、2号的旧版本房间服设置为WaitToBeDeleted
```
kubectl get workflow -n kruise-game-system
NAME STATUS AGE
workflow-demo-cb56r Running 8s
kubectl get po -n kruise-game-system | grep workflow
workflow-demo-cb56r-1053258062 0/1 Completed 0 40s
workflow-demo-cb56r-2754258264 0/2 Completed 0 10s
workflow-demo-cb56r-3772280549 0/2 Completed 0 30s
workflow-demo-cb56r-644543209 0/1 Completed 0 61s
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready Allocated 0 0 2m18s
minecraft-1 Ready WaitToBeDeleted 0 0 2m18s
minecraft-2 Ready WaitToBeDeleted 0 0 2m18s
minecraft-3 Ready None 0 0 57s
minecraft-4 Ready None 0 0 57s
minecraft-5 Ready None 0 0 57s
kubectl get workflow -n kruise-game-system
NAME STATUS AGE
workflow-demo-cb56r Succeeded 108s
```

View File

@ -0,0 +1,7 @@
# ACK One x OpenKruiseGame 全球游戏服多地域一致性交付最佳实践
> 作者:刘秋阳、蔡靖
>
> 时间2024-04-26
>
> **[原文链接](https://mp.weixin.qq.com/s/TOPcOsE5WCIIXkgo9jujlA)**

View File

@ -0,0 +1,19 @@
# Cloud Forward | 云原生游戏系列视频
## Cost Efficiency and DevOps Boost Optimization in the Gaming Industry
<iframe width="560" height="315" src="https://www.youtube.com/embed/jfrGGfzSJhw?si=ixL5NyRcwi03qkDF" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## OpenKruiseGame for Game Workloads
<iframe width="560" height="315" src="https://www.youtube.com/embed/r5zv2qBM5wU?si=8ZwZGFrd2ImlEkz5" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## Implementing PvP Session based Games with OpenKruiseGame
<iframe width="560" height="315" src="https://www.youtube.com/embed/eRO4t_0T9Hc?si=zHrN8uoaqx2E_6ZX" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## Deploying PvE Zone server Games with OpenKruiseGame
<iframe width="560" height="315" src="https://www.youtube.com/embed/qgIbG55Gls8?si=9TfynO9NvnvQcihy" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## Migrating H5 and Comprehensive Games to the Cloud with OpenKruiseGame
<iframe width="560" height="315" src="https://www.youtube.com/embed/dsRHuf65N_A?si=61sVHvYkgZn6RUHw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
## Global Delivery and O&M Management via ACK One + OpenKruiseGame
<iframe width="560" height="315" src="https://www.youtube.com/embed/gSoWCe0bJHo?si=Kbd_5CdmNH41bqwA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

View File

@ -0,0 +1,65 @@
# 冠赢互娱基于OpenKrusieGame实现游戏云原生架构升级
> 摘要传统区服PvE类型游戏平滑落地Kubernetes的实践
>
> 作者:刘秋阳
>
> 时间2023-11-29
## 关于冠赢互娱
冠赢互娱是一家集手游、网游、VR游戏等研发、发行于一体的游戏公司旗下官方正版授权的传奇类手游——《仙境传奇》系列深受广大玩家们的喜爱。基于多年MMORPG类型游戏的自研与运营经验冠赢互娱正式推出了2D MMO游戏开发引擎Thousand并成功应用至近期上线的《仙境传奇-梦回零三》 手游。其背后采用的云原生架构大幅度提升了游戏开服、更新等运维效率,同时降低了服务器的资源成本,并为后续开发更优秀的产品、加快游戏生态成型提供扎实基奠。
<img src={require('/static/img/kruisegame/blog-video/menghuilingsan.jpeg').default} style={{ width: '700px'}} />
## 启用云原生架构的初衷
在Thousand引擎立项之初研发团队基于传统区服类游戏的特点决定采用云原生架构。主要的考虑如下
1. 区服之间具有强隔离属性,应尽量避免资源抢占。过往运营游戏时会出现同台宿主机上不同区之间相互资源干扰的情况,加大了受影响的玩家数量。而利用容器技术可以实现精细化的资源控制,避免区服之间相互干扰,能够有效降低故障影响面。
2. 通过声明式的方式进行游戏服管理带来了效率优势。从过去运维机器、执行一系列脚本演化为以服务为对象、批量且自动化管理的方式,不仅可大幅度提升了开服效率,同时也能降低游戏维护时的出错概率。
3. 需要更加精细化的故障定位、及业务快速恢复的能力。区服共享计算节点当故障发生时无法及时定位故障根因源自区服A、区服B、还是宿主机且当机器故障时业务迁移效率十分低下。通过云原生架构基础设施资源与业务一定程度的解耦带来了业务故障快速定位的能力并且容器轻量且环境一致性的特点带来了高效的业务恢复能力问题定位及恢复的效率大幅度提升。
4. 云原生生态日益茁壮,通过云原生技术不仅可高度集成计算、网络、存储等基础设施资源、而且可以非常轻便地利用上可观测、调度、应用交付等能力。
## 游戏服落地Kubernetes的挑战
然而云原生化绕不开的容器编排标准Kubernetes对游戏的支持力度十分有限冠赢传奇类游戏在落地Kubernetes的过程中也遇到了众多挑战
<img src={require('/static/img/kruisegame/blog-video/qufuguanli.png').default} style={{ width: '700px'}} />
1. 每个区服需要单独暴露公网地址玩家选择服务器后可直连对应区服。额外进行接入层网络管理无疑增加了区服批量管理时的运维成本同时如果选择单个区服pod绑定EIP的模式又会消耗大量的EIP资源造成经济成本浪费。
2. 单个区服是由多个服务共同组成容器化后以一种“富容器”的形态存在。原生Kubernetes对业务状态管理停留在容器层面无法精细化感知容器中特定进程状态造成故障或异常难以定位处理而将服务拆分单独部署增加了架构复杂性、改造难度将急剧上升。
3. 一个完整的游戏服由引擎侧与脚本侧组成游戏服引擎支持热更脚本避免频繁停服造成玩家流失。研发团队设计了多种游戏服落地Kubernetes后的热更方案包括从公共服务器拉取最新热更文件、或通过云储存动态挂载热更文件。但无论哪种方式都会遇到各式问题包括1不支持版本化管理热更文件更新频繁后实际存在的众多版本无法与文件形成对应关系造成更新失败后回滚复杂2更新状态难以定位。即使对容器中的文件进行了更新替换但执行重载命令时难以确定当前热更文件是否已经挂载完毕这种更新成功与否的状态维护需要交给运维者额外管理也一定程度上提高了运维复杂度3在容器异常时pod重建拉起旧版本的镜像热更文件并未能持续化保留4更新速度始终不尽人意。
## OpenKruiseGame助力游戏服云原生化落地
冠赢利用OpenKruiseGame解决了上述问题实现了2D MMO游戏开发引擎Thousand在Kubernetes的平滑落地。OpenKruiseGame简称OKG是CNCF孵化项目OpenKruise在游戏领域的子项目其专门为游戏打造协助游戏开发者实现更敏捷的游戏弹性架构、统一标准的运维动作、多云一致性交付、建立游戏自运维平台等能力。
面对以上挑战冠赢使用了OKG以下能力
1. OKG提供了接入层网络自动化管理的能力用户无需手动为每个区服构建/析构网络并且针对不同场景支持了不同的网络模型。冠赢根据自身业务特点使用了NATGW模型区服开服时自动生成dnat entry区服被合并删除时自动删除dnat entry多个区服将共享EIP可充分利用EIP的带宽资源。
2. OKG认为游戏服的服务质量应由用户定义用户可根据业务针对性地设置游戏服所处的状态并精细化地进行相应处理。冠赢面对“富容器”的游戏场景通过OKG的”自定义服务质量“探测到具体进程异常状态并将其透出至Kubernetes侧再利用kube-event等事件通知组件将异常告警至运维群中帮助运维工程师快速发现问题实现秒级故障定位分钟级的故障处理。
3. OKG提供了基于容器镜像的原地热更方案热更脚本作为sidecar容器与main容器一同部署在同一个游戏服二者通过emptyDir共享热更文件更新时只需更新sidecar容器即可。这样一来游戏服的热更将以云原生的方式进行1sidecar容器镜像具有版本属性解决了版本管理问题2Kubernetes容器更新成功后处于Ready状态能够感知sidecar脚本更新是否成功3即使容器异常发生重启热更文件随着镜像的固化而持续化保留了下来4通过镜像预热机制能够快速完成热更过程。
## 云原生化成果
Thousand引擎整体云原生架构图如下所示。冠赢实现了基于OKG的平台化工程
1. 游戏工程师上传新脚本触发CI流程自动打包镜像后自动部署新的GameServer到Kubernetes集群通过编辑旧脚本同样可以触发CICD并基于OKG的原地升级更新对应GameServer的sidecar镜像实现游戏服热更。整个过程无需游戏运维工程师参与通过云原生技术将游戏服部署更新的能力交予游戏开发者提高了游戏生产效率。
2. 自动生成的GameServer基于OKG的网络功能具备独立的公网访问地址EIP:端口 唯一Thousand引擎平台提供服务发现机制使玩家直连对应区服进行游戏。
3. 当游戏服偶发异常时通过OKG提供的自定义服务质量功能Thousand引擎平台将感知到具体异常信息并将其通知于运维工程师运维工程师可快速定位并响应问题最大程度保证玩家的游戏质量。
<img src={require('/static/img/kruisegame/blog-video/thousand.png').default} style={{ width: '700px'}} />
Thousand引擎的诞生标志着冠赢互娱实现了游戏云原生架构升级。经过生产验证云原生架构带来了以下优势
1. 在开服效率方面传统开服时需要进行手动进行各个全服之间的IP端口配置关联由于是手动配置故障率也比较高导致新区开服时间比较长。而在容器化之后一切参数都标准化、可视化面对流量高峰可以快速开服保证容器开服的速度和配置的完整性。开新区的时间效率从30分钟优化为15秒开新服的时间效率从2分钟优化为10秒极大提高了开服效率。
2. 在更新效率方面传统更新流程会将各个目录文件的可执行文件进行覆盖更新更新速度慢且出错率高而在容器化后引擎与脚本拆分为两个容器二者可分别定向被更新更新的粒度更加细致可控降低了更新错误率同时通过镜像预热的方式带来了秒级的更新体验更新效率提高了5倍。
3. 在成本节约方面传统开服时会通过预估人数采购好对应的游戏服服务器配置做资源预留同时服务需要的资源无法准确隔离导致服务资源存在较大冗余且资源配置无法及时调整造成了大量资源浪费而在容器化后游戏服相互之间资源隔离结合精细化调度可以充分利用宿主机资源资源成本至少节约10%。
4. 在问题定位方面传统手动部署的环境中经常出现区服崩溃无法及时发现的问题而容器化后可以直接透出区服具体报错进程快速定位服务问题并解决问题响应效率提升5倍。
## 云原生游戏展望
尽管游戏云原生化后硕果累累但冠赢的云原生化进程仍未结束。冠赢云平台技术负责人盛浩表示“云原生技术蓬勃发展未来冠赢将更加全面地拥抱云原生与OKG社区携手并进计划引入混沌工程并建立故障自愈体系进一步加强平台自动化运维能力通过垂直伸缩动态调配在保证区服玩家可玩性的同时进一步节约资源成本”。其实未来并不遥远OKG已经开放了自定义故障定义功能、并支持自动向特定状态容器执行运维脚本的功能而k8s 1.27版本也引入了原地自动垂直伸缩的能力,对区服类游戏的资源调配意义重大。也许,属于游戏的云原生时代就在我们眼前。

View File

@ -0,0 +1,118 @@
# Higress × OpenKruiseGame 游戏网关最佳实践
> 作者:赵伟基/刘秋阳/张添翼
>
> 时间2024-01-24
OpenKruiseGameOKG是一个面向多云的开源游戏服 Kubernetes 工作负载,是 CNCF 工作负载开源项目 OpenKruise 在游戏领域的子项目,其提供了热更新、原地升级、定向管理等常用的游戏服管理功能。而游戏作为典型的流量密集型场景,在吞吐量、延迟性能、弹性与安全性等方面对入口网关提出了很高的要求。
Higress 是基于阿里内部两年多的 Envoy 网关实践沉淀,以开源 Istio 与 Envoy 为核心构建的下一代云原生网关。Higress 实现了安全防护网关、流量网关、微服务网关三层网关合一可以显著降低网关的部署和运维成本。Higress 可以作为 K8s 集群的 Ingress 入口网关,并且兼容了大量 K8s Nginx Ingress 的注解,可以从 K8s Nginx Ingress 快速平滑迁移到 Higress。同时也支持 K8s Gateway API 标准,支持用户从 Ingress API 平滑迁移到 Gateway API。
本文将演示 Higress 如何无缝对接 OKG 游戏服,并为其带来的优秀特性
## Higress 无缝接入 OKG
前置步骤:
1. [安装 OpenKruiseGame](../installation.md)。
2. [安装 Higress](https://higress.io/zh-cn/docs/user/quickstart/)。
OKG 提供诸多游戏服热更新和游戏服伸缩的优秀特性,便于游戏运维人员管理游戏服的全生命周期。游戏不同于无状态类型的服务,玩家战斗的网络流量是不允许被负载均衡的,因此每一个游戏服需要独立的访问地址。
使用原生工作负载(如 Deployment 或 StatefulSet运维工程师需要为众多游戏服一一配置接入层网络这无疑阻碍了开服效率同时手动配置也无形中增加了故障的概率。OKG 提供的 GameServerSet 工作负载可以自动化地管理游戏服的接入网络,大幅度降低运维工程师的负担。
对于 TCP/UDP 网络游戏OKG 提供了诸如 HostPort、SLB、NATGW 等网络模型;而对于 H5/WebSocket 类型的网络游戏OKG 也相应提供了 Ingress 网络模型,如 Higress、Nginx、ALB 等。
<img src={require('/static/img/kruisegame/blog-video/gss-workload-comparison.png').default} />
本文采用了一款开源游戏 Posio 来构建 demo 游戏服。下述配置中IngressClassName="higress" 指定了 Higress 作为游戏服的网络层Higress 通过下面配置可以无缝接入 Posio 游戏服,并且可以基于 Annotation 实现 Higress 定义的高阶流量治理等功能。示例 Yaml 如下所示GameServerSet 生成的游戏服对应的访问域名与游戏服 ID 相关。
在此例中,游戏服 0 的访问域名为 game0.postio.example.com游戏服 1 的访问域名为 game1.postio.example.com. 客户端以此来访问不同的游戏服。
```yaml
piVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: postio
namespace: default
spec:
replicas: 1
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: Kubernetes-Ingress
networkConf:
- name: IngressClassName
value: "higress"
- name: Port
value: "5000"
- name: Path
value: /
- name: PathType
value: Prefix
- name: Host
value: game<id>.postio.example.com
gameServerTemplate:
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/chrisliu95/posio:8-24
name: postio
```
OKG [水平伸缩](../user-manuals/gameservers-scale.md)提供自动扩容、根据游戏服的 OpsState 缩容、根据 DeletionPriority 缩容、根据游戏服序号缩容等功能来支持游戏运维的业务需求。水平伸缩的特性在给游戏开发者带来便利的同时,也对入口网关提出了更高的要求:入口网关必须具备配置热更新的能力,完成路由配置的平滑下发。
原因在于在进行游戏服的扩容时OKG 会同步创建 Ingress 等相关网络相关资源,以此来保障游戏服的自动上线。如果入口网关不具备配置热更新的能力,在扩容时就,线上玩家就会遇到连接断开等问题,影响游玩体验。
## Nginx reload 无法优雅热更新
在游戏服出现扩容或者定义的路由策略发生变更时Nginx 的配置变更会触发 reload导致上下游的连接都断开并触发重连。
我们以 Posio 游戏服为例,模拟 Nginx+OKG 在游戏服扩容时出现的问题。Posio 服务端依赖于 Socket 连接与客户端通信。游戏服扩容时,触发对应的 Ingress 资源创建,此时 Nginx-ingress-controller 监听到 Ingress 资源变更,触发自身 reload 机制,此时原来与游戏服建立的连接(如本例中的 Socket 连接会被断开)。在正在游戏的玩家侧的体感便是出现了异常卡顿。
为了直观的展示 Nginx Ingress reload 带来的影响,我们对 Nginx 默认配置参数进行一些更改:
```shell
kubectl edit configmap nginx-configuration -n kube-system
data:
...
worker-shutdown-timeout: 30s # 一个很难做权衡的配置
```
Nginx 配置参数中 worker-shutdown-timeout 是 Nginx 的 worker 进程优雅下线的超时配置worker 进程会先停止接收新的连接,并等待老的连接逐渐关闭,达到超时时间后,才会去强制关闭当前的所有连接,完成进程退出。
此参数配置过小,会导致大量活跃连接瞬间断开;而此参数配置过大时,又会导致 websocket 长连接始终维持住 Nginx 进程,当发生频繁 reload 时会产生大量 shutting down 状态的 worker 进程,老 worker 占有的内存迟迟得不到释放,可能会导致 OOM 引发线上故障:
<img src={require('/static/img/kruisegame/blog-video/oom.png').default} />
实际游玩的测试过程如下:客户端访问游戏服,进行正常游玩。在此过程中通过 OKG 能力触发游戏服扩容,查看此时客户端的响应。通过网页开发者工具可以看到,出现了两条 Socket 连接,一条是原先浏览器访问游戏服建立,另一条是由于 Nginx 断连后重连产生的 Socket 连接。
<img src={require('/static/img/kruisegame/blog-video/nginx-config-update.jpg').default} style={{ height: '400px' , width: '700px'}} />
原有连接收到的最后一个包时间戳是 15:10:26。
<img src={require('/static/img/kruisegame/blog-video/socket1.png').default} />
而新建连接到获取第一个正常游戏包的时间是 15:10:37网页与游戏服的断连大概持续 5s 左右。
<img src={require('/static/img/kruisegame/blog-video/socket2.png').default} />
除了玩家的游玩体验受影响,这个机制也会给业务整体稳定性埋雷。在高并发场景下,因为连接瞬断,导致大批量客户端的并发重连,会导致 Nginx 的 CPU 瞬间飙升;而后端游戏服务器需要处理更多业务逻辑,一般比网关的资源需求更高,因此 Nginx 透传过来的大量并发重连,也更容易打垮后端,造成业务雪崩。
## Higress 如何实现优雅热更新
Higress 支持采用 K8s Ingress 暴露游戏服外部 IP 端口供玩家连接访问。当游戏服伸缩或者定义的路由配置发生变化时Higress 支持路由配置的热更新,以此保障玩家连接的稳定性。
<img src={require('/static/img/kruisegame/blog-video/higress-config.png').default} />
Higress 基于 Envoy 的精确配置变更管理,做到了真正的配置动态热更新。在 Envoy 中 downstream 对应 listener 配置,交由 LDS 实现配置发现upstream 对应 cluster 配置,交由 CDS 实现配置发现。listener 配置更新重建,只会导致 downstream 连接断开,不会影响 upstream 的连接downstream 和 upstream 的配置可以独立变更互不影响。再进一步listener 下的证书(cert),过滤器插件(filter),路由(router)均可以实现配置独立变更,这样不论是证书/插件/路由配置变更都不再会引起 downstream 连接断开。
精确的配置变更机制,除了让 Envoy 可以实现真正的热更新,也让 Envoy 的架构变的更可靠Envoy 配置管理从设计之初就是为数据面(DP)和控制面(CP)分离而设计的,因此使用 gRPC 实现远程配置动态拉取,并借助 proto 来规范配置字段,并保持版本兼容。这个设计实现了数据面和控制面的安全域隔离,增强了架构的安全性。
使用 OKG 接入 Higress 后,下面依然模拟客户端访问游戏服,进行正常游玩。在此过程中通过 OKG 能力触发游戏服扩容,查看此时客户端的响应。通过网页开发者工具可以看到,在此过程中客户端与游戏服建立的连接稳定不受影响。
<img src={require('/static/img/kruisegame/blog-video/router-config-update.jpg').default} style={{ height: '400px' , width: '700px'}} />
此外,在大规模游戏服场景下,每个游戏服对应一个独立的 Ingress会产生大量的 Ingress 资源,我们测试在达到 1k 级别规模时Nginx Ingress 要新扩一个游戏服需要分钟级生效,而 Higress 可以在秒级生效。Nginx Ingress 的这一问题也被 Sealos 踩坑,并最终通过切换到 Higress 解决,有兴趣可以阅读这篇文章了解:[《云原生网关哪家强Sealos 网关血泪史》](https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247561453&idx=1&sn=de22e31a1ab59311072b468de907e282&scene=21#wechat_redirect)

View File

@ -0,0 +1,9 @@
# 尚游网络基于OpenKruiseGame游戏云原生化实践
> KubeCon & CloudNativeCon 2023
>
> 演讲者:胡炼壮 / 刘秋阳
>
> 时间2023-9-26
<iframe src="//player.bilibili.com/player.html?aid=534421895&bvid=BV1Bu411M7Ys&cid=1291860945&p=1" width="720" height="480" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

View File

@ -0,0 +1,9 @@
# OpenKruiseGame 助力游戏运维管理提效
> KubeSphere社区 云原生Meetup 广州站
>
> 演讲者:刘秋阳
>
> 时间2023-11-25
<iframe src="//player.bilibili.com/player.html?aid=748948642&bvid=BV16C4y1w7jV&cid=1345824813&p=1" width="720" height="480" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

View File

@ -0,0 +1,8 @@
# 久幺幺科技《百变大侦探》游戏云原生架构落地实践
## 架构大图
<img src={require('/static/img/kruisegame/blog-video/okg-911.png').default} style={{ width: '700px'}} />
## 原文链接
https://www.aliyun.com/customer-stories/games-2024-911tech

View File

@ -0,0 +1,8 @@
# GssHosting 游戏服托管平台实现多地域高效管理
## 架构大图
<img src={require('/static/img/kruisegame/blog-video/okg-gsshosting.png').default} style={{ width: '700px'}} />
## 原文链接
https://www.aliyun.com/customer-stories/games-2024-gss

View File

@ -0,0 +1,68 @@
# 设计理念
## 开源OpenKruiseGameOKG)的初衷
>我是从2015年开始做云原生产品的从最开始的Swarm到后来的Kubernetes在容器集群之上运行的负载类型从早期的网站、API服务到后来的转码、AI训练再到元宇宙、Web3、图形化应用。我们见证了云原生技术在改变一个又一个行业。但是游戏是一个非常特殊的行业一个大型的游戏包含网关、平台服、游戏服、匹配服等不同种角色。很多游戏公司早已将平台服、网关等业务进行了云原生化改造但是游戏服的容器化进展都比较缓慢。通过和大量的游戏开发者/运维人员进行交流,大致可以归纳为如下三个重要的原因。
>
>1. 运行中的游戏服更换部署架构的风险收益比过高。
>2. 游戏服云原生化过程中存在缺失的核心功能,例如:游戏热更新,定向合服/停服等。
>3. 缺少游戏服云原生化的最佳实践与成功案例。
>
>为了解决上述问题我们联合了灵犀互娱等多家游戏公司将游戏服云原生化场景下的通用能力进行了抽象开源了OpenKruiseGame项目。希望能够通过一个云厂商无关的开源项目将游戏服的云原生化最佳实践交付给更多的游戏开发者。同时我们也希望越来越多的游戏公司/工作室/开发者可以参与到社区,将遇到的难题、场景和大家一起讨论,分享游戏服云原生化的经验。
<p align="right">来自 刘中巍阿里云容器服务OpenKruiseGame项目发起人</p>
>灵犀互娱已全面拥抱云原生架构在云原生化过程中我们清楚地认识到游戏服不同于其他Web类型应用在k8s集群之中对其的管理是非常复杂的。原生k8s workload 提供的管理功能很难满足游戏服日常运维需求Deployment 无法固定ID不适配有状态的特性、而StatefulSet又缺乏定向管理的灵活性为此我们自研了Paas平台提供对游戏服的编排管理的能力以实现高效开服/更新等游戏服运维操作。
<p align="right"> 来自 冯谋杰 阿里灵犀互娱容器云负责人</p>
>作为一个大型的游戏分发平台B站有着海量且异构架构的内外部游戏项目需要管理维护在当前降本增效的大环境下游戏项目从传统虚拟机迁移至k8s势在必行。但是原生的k8s面对游戏热更、多环境管理、滚服游戏的区服抽象、业务接流等场景是比较疲软的。需要一个成本低廉、高效的跨云解决方案为上述问题提供支持基于OpenKruise衍生的OpenKruiseGame所提供的固定id、原地升级等功能对游戏场景有着很大的吸引力给游戏的容器化增加了一种选择。
<p align="right"> 来自 李宁 bilibili游戏运维负责人</p>
>在尝试对游戏服进行云原生化改造的过程中网络是首要考虑的问题。由于游戏服从虚拟机迁移至容器基于机器IP的运维方式在k8s中难以保障衍生出固定IP的需求对外服务的方式也不像直接在虚拟机暴露端口那么简单增加了许多复杂性。除了网络问题之外一个游戏服的各个进程在pod中的状态难以感知原生k8s重建的策略太过“粗暴”不利于游戏稳定运行亟需一种针对性的感知策略针对不同的探测结果执行不同的动作。
<p align="right"> 来自 盛浩 冠赢互娱游戏云平台负责人</p>
## 为什么OpenKruiseGameOKG是一个工作负载
<img src={require('/static/img/kruisegame/workload.png').default} width="90%" />
游戏服云原生化核心要解决两个问题游戏服的生命周期管理与游戏服的运维管理。Kubernetes内置了一些通用的工作负载模型例如无状态Deployment、有状态StatefulSet、任务Job等。但是游戏服的状态管理不论从粒度还是确定性上面都有更高的要求。例如游戏服需要热更新的机制来确保更短的游戏中断游戏服需要原地更新确保元数据信息网络为主不变游戏服需要确保在自动伸缩过程中只有0玩家的游戏服可以下线需要具备手动运维/诊断/隔离任意一个游戏服的能力等。这些都是Kubernetes内置负载不能够解决的问题。
此外Kubernetes中的工作负载还承担了与基础设施无缝整合的重要枢纽角色。例如通过Annotations中的字段自动实现监控系统、日志系统与应用的对接通过nodeSelector字段实现应用与底层资源的调度绑定关系通过labels中的字段记录分组等元数据信息替代传统的CMDB系统。这些都让自定义工作负载成为了Kubernetes中适配不同类型应用的最佳方式OpenKruiseGameOKG是一个完全面向游戏场景的Kubernetes工作负载通过OpenKruiseGameOKG开发者不止可以获得更好的游戏服的生命周期管理和游戏服的运维管理还可以以OpenKruiseGameOKG为纽带无需开发额外的代码充分发挥云产品带来的强大能力。
## OpenKruiseGameOKG的设计理念
OpenKruiseGameOKG只包含两个CRD对象GameServerSet与GameServer。OpenKruiseGameOKG的设计理念是基于状态控制的将不同的职责划分在不同的工作负载维度来控制。
* GameServerSet生命周期管理
对一组GameServer的生命周期管理的抽象主要用于副本数目管理、游戏服发布等生命周期控制。
* GameServer定向管理运维动作
对一个GameServer的运维/管理动作的抽象,主要用于更新顺序控制、游戏服状态控制、游戏服网络变更等定向运维管理动作。
当我们理解了OpenKruiseGameOKG的设计理念后一些非常有趣的推论就可以快速的得出例如
* 当不小心删除GameServer的时候会触发游戏服的删除吗
不会GameServer只是游戏服的差异性运维动作的状态记录如果删除GameServer之后会重新创建一个使用默认配置的GameServer对象。此时你的GameServer也会重置为默认定义在GameServerSet中的游戏服模板配置。
* 如何让匹配服务与自动伸缩更好的配合防止出现玩家被强制下线?
可以通过服务质量能力将游戏的玩家任务转换为GameServer的状态匹配框架感知GameServer的状态并控制伸缩的副本数目GameServerSet也会根据GameServer的状态来判断删除的顺序从而实现优雅下线。
## OpenKruiseGameOKG的部署架构
<img src={require('/static/img/kruisegame/arch.png').default} width="90%" />
OpenKruiseGameOKG的部署模型分为三个部分
1. OpenKruiseGameOKG控制器
负责管理GameServerSet与GameServer的生命周期管理在OpenKruiseGame控制器中内置一个Cloud Provider模块用来适配不同云服务厂商在网络插件等场景下的差异让OpenKruiseGame可以真正做到一套代码无差异部署。
2. OpenKruise控制器
负责管理Pod的生命周期管理是OpenKruiseGameOKG的依赖组件对OpenKruiseGameOKG使用者/开发者是无感的。
3. OpenKruiseGameOKG运维后台【待完成】
针对希望白屏化使用OpenKruiseGameOKG的开发者提供的运维后台与API主要提供游戏服的生命周期管理和编排能力。

View File

@ -0,0 +1,29 @@
# 项目贡献
欢迎来到 OpenKruiseGame 社区。随时提供帮助、报告问题、提高文档质量、修复错误或引入新功能。有关如何向 OpenKruiseGame 提交内容的详细信息,请参见下文。
## 提交问题并参与基于场景的讨论
OpenKruiseGame 是一个非常开放的社区,随时提交各种类型的问题,以下列表显示了问题类型:
* 错误报告
* 功能要求
* 性能问题
* 功能提案
* 特征设计
* 征求帮助
* 文档不完整
* 测试改进
* 关于项目的任何问题
当您提交问题时,请确保您已经进行了数据屏蔽,以确保您的信息的机密性,例如 AccessKey。
## 贡献代码和文档
能够为 OpenKruiseGame 提供帮助的行动值得鼓励,您可以提交您希望在拉取请求中修复的内容。
*如果您发现拼写错误,请更正它。
* 如果您发现代码错误,请修复它。
* 如果您发现缺少的单元测试,请解决问题。
* 如果您发现文档不完整或有错误,请更新它。
## 需要额外帮助
如果您在游戏服务器云原生改造过程中遇到其他类型的问题需要帮助,请发邮件给我们寻求进一步的帮助,邮箱:zhongwei.lzw@alibaba-inc.com
## 成为 OpenKruiseGame 的核心贡献者
也非常欢迎大家参与OpenKruiseGame会议共同决定OpenKruiseGame的未来发展方向.作为OpenKruise的一个子项目OpenKruiseGame在我们双周会上讨论OpenKruise的时候也会讨论。有关详细信息请参阅 <a target="_blank" href="https://github.com/openkruise/kruise#community">时间表</a>.

View File

@ -0,0 +1,25 @@
# FAQ
## 如何调试你的代码
0) 编辑Makefile将IMG字段的值修改为Makefile的仓库地址。
1) 编译打包kruise-game-manager镜像。
```bash
make docker-build
```
2) 将打包后的镜像上传到镜像仓库。
```bash
make docker-push
```
3) 在 Kubernetes 集群 (~/.kube/conf) 中部署 kruise-game-manager 组件。
```bash
make deploy
```

View File

@ -0,0 +1,157 @@
# Golang Client
如果要在一个 Golang 项目中对 OKG 的资源做 create/get/update/delete 这些操作、或者通过 informer 做 list-watch你需要一个支持 OKG 的 client。
你需要在你的项目中引入 [kruise-game](https://github.com/openkruise/kruise-game) 仓库
## 使用方式
首先,在你的 `go.mod` 中引入 `kruise-game` 依赖 (版本号最好和你安装的 kruise-game 版本相同):
```
require github.com/openkruise/kruise-game v0.7.0
```
使用kruise-game要求Kubernetes版本>= 1.16。
### 使用 OKG api
这里我们使用GameServerSet作为示例GameServer的使用方法与GameServerSet相同
1. 使用您的rest config新建 kruise-game 客户端:
```go
kruisegameclientset "github.com/openkruise/kruise-game/pkg/client/clientset/versioned"
// cfg 是在client-go中定义的rest config你可以使用 kubeconfig 或 serviceaccount 获取
kruisegameClient := kruisegameclientset.NewForConfigOrDie(cfg)
```
2. 查询/列出 kruise-game 资源:
```go
gamekruiseiov1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
gameServerSet, err := kruisegameClient.GameV1alpha1().GameServerSets(namespace).Get(context.TODO(), "GameServerSetName", metav1.GetOptions{})
// gss 是 GameServerSet 对象
gssName := gss.GetName()
// labelSelector用于过滤 GameServer示例中我们使用GameServerSet名称筛选出归属GameServerSet管理的GameServer你也可以使用自定义的labelSelector。
labelSelector := labels.SelectorFromSet(map[string]string{
gamekruiseiov1alpha1.GameServerOwnerGssKey: gssName,
}).String()
gameServerList, err := kruisegameclientset.GameV1alpha1().GameServerSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector})
```
3. 创建/更新 kruise-game resources:
```go
import gameKruiseV1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
cloneSet := &gameKruiseV1alpha1.GameServerSet{
// ...
}
gameServerSet, err = kruisegameclientset.GameV1alpha1().GameServerSet(namespace).Create(context.TODO(), cloneSet, metav1.CreateOptions{})
```
```go
gameServerSet, err := kruisegameclientset.GameV1alpha1().GameServerSets(namespace).Get(context.TODO(), "GameServerSetName", metav1.GetOptions{})
if err != nil {
return err
}
// 修改对象, 例如副本数
gameServerSet.Spec.Replicas = pointer.Int32Ptr(3)
newGameServerSet, err := kruisegameclientset.GameV1alpha1().GameServerSets(namespace).Update(context.TODO(), gameServerSet, metav1.UpdateOptions{})
if err != nil{
return err
}
```
4. 删除现有的GameServerSet:
```go
// 删除 gss
err := kruisegameclientset.GameV1alpha1().GameServerSets(namespace).Delete(context.TODO(), "GameServerSetName", metav1.DeleteOptions{})
if err != nil {
return err
}
```
5. 监测Kruise-Game资源:
```go
import gameinformer "github.com/openkruise/kruise-api/client/informers/externalversions"
gameInformerFactory := gameinformer.NewSharedInformerFactory(kruisegameclientset, 0)
gameInformerFactory.Game().V1alpha1().GameServerSets().Informer().AddEventHandler(...)
gameInformerFactory.Start(...)
```
### RABC
当您的组件部署在k8s集群内部时您需要赋予组件操作 OKG 资源的权限
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: okg-role
rules:
- apiGroups:
- game.kruise.io
resources:
- gameserversets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- game.kruise.io
resources:
- gameservers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: okg-sa # 为你的pod设置serviceAccount名字
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: okg-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: okg-role
subjects:
- kind: ServiceAccount
name: okg-sa
namespace: kube-system
```
## 代码示例
以下项目都使用了OKG API开发者可作为示例阅读源码参考
- https://github.com/CloudNativeGame/aigc-gateway
- https://github.com/CloudNativeGame/kruise-game-open-match-director
- https://github.com/CloudNativeGame/palworld-okg-playground

View File

@ -0,0 +1,98 @@
# 安装
## 安装OpenKruiseGameOKG
### 安装说明
安装OpenKruiseGame需安装Kruise与Kruise-Game且要求 Kubernetes版本 >= 1.18。
### 安装Kruise
建议采用 helm v3.5+ 来安装 Kruise。
```shell
# Firstly add openkruise charts repository if you haven't do this.
$ helm repo add openkruise https://openkruise.github.io/charts/
# [Optional]
$ helm repo update
# Install the latest version.
$ helm install kruise openkruise/kruise --version 1.6.3
```
### 安装Kruise-Game
```shell
$ helm install kruise-game openkruise/kruise-game --version 0.8.0
```
### 升级 Kruise-Game
```shell
$ helm upgrade kruise-game openkruise/kruise-game --version 0.8.0 [--force]
```
### 可选项
#### 可选:使用自定义配置安装/升级
下表列出了 kruise-game 的可配置参数及其默认值。
| Parameter | Description | Default |
|----------------------------------|---------------------------------------------------------|----------------------------------|
| `installation.namespace` | kruise-game 安装到的 namespace一般不建议修改 | `kruise-game-system` |
| `installation.createNamespace` | 是否需要创建上述 namespace一般不建议修改除非指定安装到已有的 ns 中 | `true` |
| `kruiseGame.fullname` | kruise-game 部署和其他配置的名称 | `kruise-game-controller-manager` |
| `kruiseGame.healthBindPort` | 用于检查 kruise-game 容器健康检查的端口 | `8082` |
| `kruiseGame.webhook.port` | kruise-game 容器服务的 webhook 端口 | `443` |
| `kruiseGame.webhook.targetPort` | 用于 MutatingWebhookConfigurations 中工作负载的 ObjectSelector | `9876` |
| `kruiseGame.apiServerQps` | kruise-game-controller-manager 每秒发送到 API server的最大持续查询数 | `5` |
| `kruiseGame.apiServerQpsBurst` | kruise-game-controller-manager 每秒发送到 API server的最大突发查询数 | `10` |
| `replicaCount` | kruise-game 的期望副本数 | `1` |
| `image.repository` | kruise-game 的镜像仓库 | `openkruise/kruise-game-manager` |
| `image.tag` | kruise-game 的镜像版本 | `v0.8.0` |
| `image.pullPolicy` | kruise-game 的镜像拉取策略 | `Always` |
| `serviceAccount.annotations` | kruise-game的serviceAccount注解 | ` ` |
| `resources.limits.cpu` | kruise-game容器的CPU资源限制 | `500m` |
| `resources.limits.memory` | kruise-game容器的内存资源限制 | `1Gi` |
| `resources.requests.cpu` | kruise-game容器的CPU资源请求 | `10m` |
| `resources.requests.memory` | kruise-game容器的内存资源请求 | `64Mi` |
| `prometheus.enabled` | 是否创建指标监控服务 | `true` |
| `prometheus.monitorService.port` | monitorService的监听端口 | `8080` |
| `scale.service.port` | 伸缩服务监听端口 | `6000` |
| `scale.service.targetPort` | 伸缩服务目标端口 | `6000` |
| `network.totalWaitTime` | 等待网络Ready的最长时间单位是秒 | `60` |
| `network.probeIntervalTime` | 探测网络状态的时间间隔,单位是秒 | `5` |
| `cloudProvider.installCRD` | 是否安装 CloudProvider 相关CRD资源 | `true` |
使用 `--set key=value[,key=value]` 参数指定每个参数到 `helm install`,例如,
#### 可选:中国地区的镜像
如果你在中国并且无法从官方 DockerHub 拉取镜像,你可以使用托管在阿里云上的镜像:
```bash
$ helm install kruise-game https://... --set image.repository=registry-cn-hangzhou.ack.aliyuncs.com/acs/kruise-game-manager
```
## 卸载OpenKruiseGameOKG
请注意,这将导致删除 kruise-game 创建的所有资源,包括 webhook 配置、服务、命名空间、CRD 和 CR 实例 kruise-game 控制器!
请仅在您完全了解后果后才这样做。
如果安装了 helm charts则卸载 kruise-game:
```bash
$ helm uninstall kruise-game
release "kruise-game" uninstalled
```
## 常见问题
Q: 出现错误 `no matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1"`
A: 这是因为集群并没有安装prometheus operator。启用游戏服监控功能需要安装prometheus operator于Kubernetes集群。若您不使用该功能可以在安装时将 prometheus.enabled 设置为false默认为true
Q: 出现错误 `CustomResourceDefinition "poddnats.alibabacloud.com" in namespace "" exists and cannot be imported into the cureent release`
A: 这是因为在集群中已经安装了该CRD您可以在安装时将cloudProvider.installCRD设置为false默认为true
## What's Next
接下来,我们推荐你:
- 了解 kruise-game 的 [部署游戏服](user-manuals/deploy-gameservers.md).

View File

@ -0,0 +1,96 @@
# OpenKruiseGame简介
***If you like OpenKruiseGame, give it a star on <a target="_blank" rel="noopener noreferrer" href="https://github.com/openkruise/kruise-game">GitHub</a>!***
## 概览
OpenKruiseGameOKG是一个面向多云的开源游戏服Kubernetes工作负载是CNCF工作负载开源项目OpenKruise在游戏领域的子项目让游戏服的云原生化变得更加简单、快速、稳定。
<img src={require('/static/img/kruisegame/intro.png').default} width="90%" />
## 什么是OpenKruiseGame(OKG)
OpenKruiseGameOKG是简化游戏服云原生化的自定义Kubernetes工作负载相比Kubernetes内置的无状态Deployment、有状态StatefulSet等工作负载而言OpenKruiseGameOKG提供了热更新、原地升级、定向管理等常用的游戏服管理功能是完全面向游戏服场景而设计的Kubernetes工作负载。
除此之外OpenKruiseGameOKG还承担了游戏服与云服务、匹配服务、运维平台对接的角色通过低代码或者0代码的方式实现游戏服云原生化时日志、监控、网络、存储、弹性、匹配等功能的自动化集成通过Kubernetes的一致性交付标准实现多云/混合云/多集群的统一管理。
OpenKruiseGameOKG是一个完全开源的项目开发者可以通过二次开发的方式定制属于自己的游戏服工作负载构建游戏服的发布运维后台等。除了通过Kubernetes的模板/API的方式进行调用和扩展OpenKruiseGameOKG还支持与KubeVela等交付系统进行对接通过白屏化的方式实现游戏服的编排与全生命周期管理。
## 为什么需要OpenKruiseGame(OKG)
Kubernetes作为云原生时代的应用交付/运维标准其具备的声明式资源管理、自动弹性伸缩、多云环境一致性交付等能力与游戏服的场景是非常匹配的能够在开服效率、成本控制、版本管理、全球同服等场景提供支持。但是游戏服的一些特性导致了它与Kubernetes进行适配的时候存在一些障碍例如
* 热更新/热重载
为了让玩家能够得到更好的游戏体验很多游戏服都是通过热更新或者配置热重载的方式进行更新而在Kubernetes的各种不同负载中Pod的生命周期和镜像的生命周期是一致的当业务的镜像需要发布的时候Pod会进行重建而重建Pod的代价往往意味着玩家对局的中断玩家服网络元数据的变更等。
* 定向运维管理
玩家服在大部分的场景下是有状态的例如PVP游戏在更新或者下线的时候应该优先且只能变更没有活跃玩家在线的游戏服PVE游戏在停服或者合服的时候应该能够定向管理特定ID的玩家服。
* 适合游戏的网络模型
Kubernetes中的网络模型是通过Service进行抽象的更多的是面向无状态场景的适配。对于网络敏感的游戏服而言高性能网关或者IP端口固定的无损直连的方案更符合真实的业务场景。
* 游戏服编排
当下的游戏服架构越来越复杂很多MMORPG的玩家服已经抽象成了多种不同功能和用途的游戏服的组合例如负责网络接入的网关服、负责游戏引擎的中心服负责游戏脚本和玩法的策略服等。每个游戏服的容量和管理策略有所不同通过单一的负载类型很难描述和快速交付。
这些能力的缺失让游戏服进行云原生化变得非常困难。OpenKruiseGameOKG设计的初衷就是将这些游戏行业通用的需求进行抽象通过语义化的方式将不同类型游戏服的云原生化过程变得简单、高效、安全。
## 核心功能列表
OpenKruiseGameOKG具有如下核心能力
* 镜像热更新/配置热重载
* 定向更新/删除/隔离
* 内置多种网络模型IP端口不变/无损直连/全球加速)
* 自动弹性伸缩
* 自动化运维管理(服务质量)
* 云服务厂商无关
* 复杂的游戏服务编排
## 谁在使用OpenKruiseGame(OKG)
<table>
<tr style={{"border":0}}>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/hypergryph-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/lilith-logo.png').default} width="120" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/bilibili-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/shangyou-logo.jpeg').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/xingzhe-logo.png').default} width="125" /></center></td>
</tr>
<tr style={{"border":0}}>
</tr>
<tr style={{"border":0}}>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/juren-logo.png').default} width="95" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/baibian-logo.png').default} width="120" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/chillyroom-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/wuduan-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/xingchao-logo.png').default} width="110" /></center></td>
</tr>
<tr style={{"border":0}}>
</tr>
<tr style={{"border":0}}>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/wanglong-logo.png').default} width="145" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/guanying-logo.png').default} width="140" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/booming-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/gsshosting-logo.png').default} width="140" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/yongshi-logo.png').default} width="130" /></center></td>
</tr>
<tr style={{"border":0}}>
</tr>
<tr style={{"border":0}}>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/yahaha-logo.png').default} width="160" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/yostar-logo.png').default} width="140" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/360-logo.png').default} width="150" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/vma-logo.png').default} width="145" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/bekko-logo.png').default} width="130" /></center></td>
</tr>
</table>
## What's Next
接下来,我们推荐你:
* 安装 OpenKruiseGame。有关详细信息请参阅 [安装](./installation.md)。
* 为 OpenKruiseGame 提交代码。更多信息,请参见 [开发者指南](./developer-manuals/contribution.md)。
* 加入钉钉群ID44862615与OpenKruiseGame核心贡献者一起讨论。
* 通过电子邮件 zhongwei.lzw@alibaba-inc.com 联系我们。

View File

@ -0,0 +1,35 @@
# 容器启动顺序控制
## 功能概述
单个游戏服Pod存在多个容器的情况下有时候会需要对容器的启动顺序有所要求。OKG提供了自定义顺序启动的功能
## 使用示例
在GameServerSet.Spec.GameServerTemplate.spec.containers 中添加 KRUISE_CONTAINER_PRIORITY 环境变量:
```
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
# ...
spec:
gameServerTemplate:
spec:
containers:
- name: main
# ...
- name: sidecar
env:
- name: KRUISE_CONTAINER_PRIORITY
value: "1"
# ...
```
- 值的范围在 [-2147483647, 2147483647],不写默认是 0。
- 权重高的容器,会保证在权重低的容器之前启动。
- 相同权重的容器不保证启动顺序。
上述例子中游戏服启动时由于sidecar权重更高所以先启动sidecar容器再启动main容器

View File

@ -0,0 +1,288 @@
# CRD字段说明
## GameServerSet
### GameServerSetSpec
```
type GameServerSetSpec struct {
// 游戏服数目必须指定最小值为0
Replicas *int32 `json:"replicas"`
// 游戏服模版,新生成的游戏服将以模版定义的参数创建
GameServerTemplate GameServerTemplate `json:"gameServerTemplate,omitempty"`
// serviceName 是管理此 GameServerSet 的服务的名称。
// 该服务必须在GameServerSet之前存在并负责该集合的网络标识。
// Pod 获取遵循以下模式的 DNS/主机名pod-specific-string.serviceName.default.svc.cluster.local
// 其中“pod-specific-string”由 GameServerSet 控制器管理。
ServiceName string `json:"serviceName,omitempty"`
// 保留的游戏服序号,可选项。若指定了该序号,已经存在的游戏服将被删除;而未存在的游戏服,新建时将跳过、不创建该序号
ReserveGameServerIds []int `json:"reserveGameServerIds,omitempty"`
// 游戏服自定义服务质量。用户通过该字段实现游戏服自动化状态感知。
ServiceQualities []ServiceQuality `json:"serviceQualities,omitempty"`
// 游戏服批量更新策略
UpdateStrategy UpdateStrategy `json:"updateStrategy,omitempty"`
// 游戏服水平伸缩策略
ScaleStrategy ScaleStrategy `json:"scaleStrategy,omitempty"`
// 游戏服接入层网络设置
Network *Network `json:"network,omitempty"`
}
```
#### GameServerTemplate
```
type GameServerTemplate struct {
// 继承至PodTemplateSpec的所有字段字段详情参考 https://pkg.go.dev/k8s.io/api/core/v1#PodTemplateSpec
corev1.PodTemplateSpec `json:",inline"`
// 对持久卷的请求和声明
VolumeClaimTemplates []corev1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"`
// ReclaimPolicy 表明GameServer的回收策略
// 当前支持两种Cascade与Delete。默认为Cascade
ReclaimPolicy GameServerReclaimPolicy `json:"reclaimPolicy,omitempty"`
}
type GameServerReclaimPolicy string
const (
// Cascade 表明pod删除时GameServer一并删除。GameServer的生命周期与pod相同
CascadeGameServerReclaimPolicy GameServerReclaimPolicy = "Cascade"
// Delete 表明 GameServers 只会在GameServerSet副本数目减少时被删除。
// 当对应的pod被手动删除、更新重建、被驱逐时GameServer都不会被删除。
DeleteGameServerReclaimPolicy GameServerReclaimPolicy = "Delete"
)
```
#### UpdateStrategy
```
type UpdateStrategy struct {
// 更新策略类型,可选择 OnDelete 或 RollingUpdate
Type apps.StatefulSetUpdateStrategyType `json:"type,omitempty"`
// 当策略类型为RollingUpdate时可用指定RollingUpdate具体策略
RollingUpdate *RollingUpdateStatefulSetStrategy `json:"rollingUpdate,omitempty"`
}
type RollingUpdateStatefulSetStrategy struct {
// 保留旧版本游戏服的数量或百分比,默认为 0。
Partition *int32 `json:"partition,omitempty"`
// 会保证发布过程中最多有多少个游戏服处于不可用状态,默认值为 1。
// 支持设置百分比比如20%意味着最多有20%个游戏服处于不可用状态。
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
// 表明游戏服更新的方式。可选择ReCreate / InPlaceIfPossible / InPlaceOnly。默认为ReCreate。
PodUpdatePolicy kruiseV1beta1.PodUpdateStrategyType `json:"podUpdatePolicy,omitempty"`
// 是否暂停发布默认为false。
Paused bool `json:"paused,omitempty"`
// 原地升级的策略
InPlaceUpdateStrategy *appspub.InPlaceUpdateStrategy `json:"inPlaceUpdateStrategy,omitempty"`
// 游戏服在更新后多久被视为准备就绪默认为0最大值为300。
MinReadySeconds *int32 `json:"minReadySeconds,omitempty"`
}
type InPlaceUpdateStrategy struct {
// 将游戏服状态设置为NotReady和更新游戏服Spec中的镜像之间的时间跨度。
GracePeriodSeconds int32 `json:"gracePeriodSeconds,omitempty"`
}
```
#### ScaleStrategy
```
type ScaleStrategy struct {
// 扩缩期间游戏服最大不可用的数量,可为绝对值或百分比
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
// 缩容策略类型目前支持两种General 与 ReserveIds。
// 默认为General缩容时优先考虑reserveGameServerIds字段
// 当预留的GameServer数量不满足缩减数量时继续从当前游戏服务器列表中选择并删除GameServer。
// 当该字段设置为ReserveIds时无论是保留的游戏服还是控制器按照优先级删除的游戏服
// 被删除的游戏服的序号都会回填至ReserveGameServerIds字段。
ScaleDownStrategyType ScaleDownStrategyType `json:"scaleDownStrategyType,omitempty"`
}
```
#### ServiceQualities
```
type ServiceQuality struct {
// 继承至corev1.Probe所有字段此处指定探测方式字段详情参考 https://pkg.go.dev/k8s.io/api/core/v1#Probe
corev1.Probe `json:",inline"`
// 自定义服务质量的名称,区别定义不同的服务质量
Name string `json:"name"`
// 探测的容器名称
ContainerName string `json:"containerName,omitempty"`
// 是否让GameServerSpec在ServiceQualityAction执行后不发生变化。
// 当Permanent为true时无论检测结果如何ServiceQualityAction只会执行一次。
// 当Permanent为false时即使ServiceQualityAction已经执行过也可以再次执行ServiceQualityAction。
Permanent bool `json:"permanent"`
// 服务质量对应执行动作
ServiceQualityAction []ServiceQualityAction `json:"serviceQualityAction,omitempty"`
}
type ServiceQualityAction struct {
// 用户设定当探测结果为true/false时执行动作
State bool `json:"state"`
// Result为对应探测脚本返回的信息。当Result被指定时只有当探测的结果符合该声明的结果时action才会执行
Result string `json:"result,omitempty"`
// 动作为更改GameServerSpec中的字段
GameServerSpec `json:",inline"`
}
```
#### Network
```
type Network struct {
// 网络类型
NetworkType string `json:"networkType,omitempty"`
// 网络参数,不同网络类型需要填写不同的网络参数
NetworkConf []NetworkConfParams `json:"networkConf,omitempty"`
}
type NetworkConfParams KVParams
type KVParams struct {
// 参数名,名称由网络插件决定
Name string `json:"name,omitempty"`
// 参数值,格式由网络插件决定
Value string `json:"value,omitempty"`
}
```
### GameServerSetStatus
```
type GameServerSetStatus struct {
// 控制器观察到GameServerSet的迭代版本
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// 游戏服数目
Replicas int32 `json:"replicas"`
// 处于Ready的游戏服数目
ReadyReplicas int32 `json:"readyReplicas"`
// 可用的游戏服数目
AvailableReplicas int32 `json:"availableReplicas"`
// 当前的游戏服数目
CurrentReplicas int32 `json:"currentReplicas"`
// 已更新的游戏服数目
UpdatedReplicas int32 `json:"updatedReplicas"`
// 已更新并Ready的游戏服数目
UpdatedReadyReplicas int32 `json:"updatedReadyReplicas,omitempty"`
// 处于Maintaining状态的游戏服数目
MaintainingReplicas *int32 `json:"maintainingReplicas,omitempty"`
// 处于WaitToBeDeleted状态的游戏服数目
WaitToBeDeletedReplicas *int32 `json:"waitToBeDeletedReplicas,omitempty"`
// LabelSelector 是标签选择器,用于查询应与 HPA 使用的副本数相匹配的游戏服。
LabelSelector string `json:"labelSelector,omitempty"`
}
```
## GameServer
### GameServerSpec
```
type GameServerSpec struct {
// 游戏服运维状态,表示业务相关的游戏服状态,可以由用户定义为任何值。
// OKG提供了一些保留值有着特殊含义包括
// None —— 默认值,代表不存在任何异常和特殊状态
// WaitToBeDeleted —— gs缩容优先级最高且配置自动伸缩策略后会被自动回收
// Maintaining —— gs缩容优先级最低
// Allocated —— gs缩容优先级大于Maintaining小于None。通常代表gs已经被分配在游戏匹配场景下可以使用。
// Kill —— 设置Kill的gs将被OKG控制器直接删除
OpsState OpsState `json:"opsState,omitempty"`
// 更新优先级,优先级高则优先被更新
UpdatePriority *intstr.IntOrString `json:"updatePriority,omitempty"`
// 删除优先级,优先级高则优先被删除
DeletionPriority *intstr.IntOrString `json:"deletionPriority,omitempty"`
// 是否进行网络隔离、切断接入层网络默认为false
NetworkDisabled bool `json:"networkDisabled,omitempty"`
// 使对应的GameServer Containers字段与GameServerSetSpec中GameServerTemplate定义的字段不同意味着该GameServer可以拥有独立的参数配置。
// 当前支持更改 Image 与 Resources
Containers []GameServerContainer `json:"containers,omitempty"`
}
type GameServerContainer struct {
// Name 表示要更新的容器的名称。
Name string `json:"name"`
// Image 表示要更新的容器的镜像。
// 当Image更新时pod.spec.containers[*].image会立即更新。
Image string `json:"image,omitempty"`
// Resources 表示要更新的容器的资源。
// 当Resources更新时pod.spec.containers[*].Resources不会立即更新它会在pod重建时更新。
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}
```
### GameServerStatus
```
type GameServerStatus struct {
// 期望游戏服状态Ready
DesiredState GameServerState `json:"desiredState,omitempty"`
// 当前游戏服实际状态
CurrentState GameServerState `json:"currentState,omitempty"`
// 网络状态信息
NetworkStatus NetworkStatus `json:"networkStatus,omitempty"`
// 游戏服对应pod状态
PodStatus corev1.PodStatus `json:"podStatus,omitempty"`
// 游戏服服务质量状况
ServiceQualitiesCondition []ServiceQualityCondition `json:"serviceQualitiesConditions,omitempty"`
// 与该GameServer相关的Condition集合
// 当前支持聚合 pod Conditions / node Conditions / pv Conditions
Conditions []GameServerCondition `json:"conditions,omitempty" `
// 当前更新优先级
UpdatePriority *intstr.IntOrString `json:"updatePriority,omitempty"`
// 当前删除优先级
DeletionPriority *intstr.IntOrString `json:"deletionPriority,omitempty"`
// 上次变更时间
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}
```

View File

@ -0,0 +1,191 @@
# 部署游戏服
## "Hello World" of OKG
您可以使用GameServerSet进行游戏服的部署一个简单的部署案例如下
```bash
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
name: minecraft
EOF
```
当前GameServerSet创建完成后由于指定的副本数为3故在集群中将会出现3个GameServer以及对应的3个Pod
```bash
kubectl get gss
NAME AGE
minecraft 9s
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 10s
minecraft-1 Ready None 0 0 10s
minecraft-2 Ready None 0 0 10s
kubectl get pod
NAME READY STATUS RESTARTS AGE
minecraft-0 1/1 Running 0 10s
minecraft-1 1/1 Running 0 10s
minecraft-2 1/1 Running 0 10s
```
## ID业务感知
游戏服由于有状态的特性通常需要唯一标识来区别彼此这也是GameServerSet管理的GameServer的名称会以ID序号结尾的原因。
在有些情况下游戏服业务自身需要感知到自己的ID以作为区服属性标志或配置管理的依据等。
这个时候,通过 DownwardAPI 可以实现将对应标识ID下沉至容器中。下面是一个示例
部署一个GameServerSet其生成的游戏服容器中的环境变量GS_NAME会以对应名称为值
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
name: minecraft
env:
- name: GS_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
```
同样会生成3个游戏服
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 31s
minecraft-1 Ready None 0 0 31s
minecraft-2 Ready None 0 0 31s
```
分别查看这三个游戏服的GS_NAME环境变量发现三个游戏服的GS_NAME与自身的名称一一对应。
```bash
kubectl exec minecraft-0 -- env | grep GS_NAME
GS_NAME=minecraft-0
kubectl exec minecraft-1 -- env | grep GS_NAME
GS_NAME=minecraft-1
kubectl exec minecraft-2 -- env | grep GS_NAME
GS_NAME=minecraft-2
```
这样一来业务程序在启动时可以通过解析GS_NAME环境变量来进行配置管理等操作了。
## 游戏服有状态实例的服务发现
游戏服由于有状态的特性访问时往往需要具体到pod实例无法使用传统k8s service负载均衡的特性。OKG支持有状态服务的DNS机制以实现**集群内**游戏服之间的相互访问。
接下来示例中将涉及两个服务minecraft游戏服与accessorminecraft被accessor调用。
### 服务注册
通常游戏服需要被内部访问时需要将自身信息注册到对应服务中以便accessor能够知道哪些pod是可访问状态相对应地游戏服退出时也需要相应的析构动作反注册以便accessor知道该pod已经不提供服务了。
如上文所提到的游戏服的唯一标志即其ID或名称利用上文提到的DownwardAPI将GS_NAME下沉至容器中之后在容器启动时将其注册至对应的服务即可。
按照前文Yaml部署完成后集群存在3个minecraft pod
```bash
kubectl get po -owide
...
minecraft-0 1/1 Running 0 10s 172.16.0.64 xxx <none> 2/2
minecraft-1 1/1 Running 0 10s 172.16.0.6 xxx <none> 2/2
minecraft-2 1/1 Running 0 10s 172.16.0.12 xxx <none> 2/2
```
### DNS
为了使游戏服的pod能够单独被访问除了部署GameServerSet之外还需部署与GameServerSet同名称的headless service在本例中其Yaml如下
```yaml
apiVersion: v1
kind: Service
metadata:
name: minecraft
spec:
clusterIP: None # 设置为 None 使得服务成为 Headless
selector:
game.kruise.io/owner-gss: minecraft # 填写GameServerSet的名称
```
部署一个简单的accessor Yaml目的是在该容器内访问对应的minecraft pod
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: accessor
namespace: default
spec:
replicas: 1
gameServerTemplate:
spec:
containers:
- image: busybox
name: accessor
args:
- sleep
- "3600"
command: ["/bin/sh", "-c", "sleep 3600"]
```
进入accessor容器中ping对应的minecraft pod这一步模拟真实环境中的访问逻辑当然选择访问哪一个pod需要一定的筛选规则
```bash
kubectl exec -it accessor-0 /bin/sh
/ #
/ # ping minecraft-2.minecraft.default.svc.cluster.local
PING minecraft-2.minecraft.default.svc.cluster.local (172.16.0.12): 56 data bytes
64 bytes from 172.16.0.12: seq=0 ttl=63 time=0.082 ms
64 bytes from 172.16.0.12: seq=1 ttl=63 time=0.061 ms
64 bytes from 172.16.0.12: seq=2 ttl=63 time=0.072 ms
```
可以发现accessor访问minecraft-2成功DNS成功解析到对应的内网IP地址。在这里的DNS访问规则如下{pod-name}.{gss-name}.{namespace-name}.svc.cluster.local
## GameServer 与 Pod 注释同步
如上所述,通过 DownwardAPI 可以将 pod annotation的信息下沉至容器中。我们有时希望将 GameServer 的 annotation 可以同步到 Pod 上以完成GameServer元数据信息的下沉动作。
OKG 支持以 "gs-sync/" 开头的 annotation 从 GameServer 同步到 Pod 之上,如下所示:
```bash
kubectl patch gs minecraft-0 --type='merge' -p '{"metadata":{"annotations":{"gs-sync/test-key":"some-value"}}}'
gameserver.game.kruise.io/minecraft-0 patched
```
此时查看 pod 注释发现可以找到对应key-value
```bash
kubectl get po minecraft-0 -oyaml | grep gs-sync
gs-sync/test-key: some-value
```

View File

@ -0,0 +1,148 @@
# 游戏服运维控制台
OpenKruiseGame基于KubeSphere 4.0 LuBan架构提供了游戏服白屏化管理控制台。本文介绍如何安装KubeSphere 与 OKG 游戏服运维控制台,以及对应的使用说明。
当前OKG Dashboard版本0.1.0
## 安装KubeSphere 与 OKG Dashboard
### 安装KubeSphere 4.0
通过helm安装:
```
helm upgrade --install -n kubesphere-system --create-namespace ks-core https://charts.kubesphere.io/main/ks-core-0.4.0.tgz
```
显示以下信息,则为安装成功:
```
Release "ks-core" does not exist. Installing it now.
NAME: ks-core
LAST DEPLOYED: Wed Dec 20 19:59:19 2023
NAMESPACE: kubesphere-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Please wait for several seconds for KubeSphere deployment to complete.
1. Make sure KubeSphere components are running:
kubectl get pods -n kubesphere-system
2. Then you should be able to visit the console NodePort:
Console: http://xxx.xx.x.xx:30880
3. To login to your KubeSphere console:
Account: admin
Password: "P@88w0rd"
NOTE: Please change the default password after login.
For more details, please visit https://kubesphere.io.
```
默认情况下ks-console暴露方式是nodeport若希望更改暴露方式则安装后编辑对应svc如更改为LoadBalancer
```
kubectl edit svc ks-console -n kubesphere-system
...
type: LoadBalancer
...
```
有关KubeSphere更多安装说明请参考https://docs.kubesphere.com.cn/v4.0/03-install-and-uninstall/01-install-ks-core
### 安装OKG Dashboard
安装KubeSphere完成后访问控制台点击扩展市场
![](/img/kruisegame/user-manuals/dash-mainpage.png)
选择OKG Dashboard点击订阅。OKG Dashboard完全免费提交订单即可。支付成功后返回扩展市场此时订阅按钮已转变为管理按钮
![](/img/kruisegame/user-manuals/dash-okg.png)
进入OKG Dashboard管理页面点击安装
![](/img/kruisegame/user-manuals/dash-management.png)
根据弹出的窗口,依次 1选择版本点击下一步2开始安装
![](/img/kruisegame/user-manuals/dash-version-select.png)
![](/img/kruisegame/user-manuals/dash-begin-install.png)
安装成功后,可以看到界面显示已安装,并处于启用状态:
![](/img/kruisegame/user-manuals/dash-installed.png)
## 使用说明
OKG Dashboard 为集群级别组件。选择要操作的集群,进入后,看到左边导航栏,点击“游戏服运维管理”:
![](/img/kruisegame/user-manuals/dash-ops.png)
### 概览页
点击“游戏服运维管理”后,默认进入概览页。概览页统计了当前集群中游戏服处于不同状态的数量
![](/img/kruisegame/user-manuals/dash-overview.jpg)
字段说明
- 总数当前集群中GameServer的总计数量
- 正在创建当前集群中State 为 Creating 的 GameServer数量
- 正在更新当前集群中State 为 Updating 的 GameServer数量
- 正在删除当前集群中State 为 Deleting 的 GameServer数量
- Ready当前集群中State 为 Ready 的 GameServer数量
- NotReady当前集群中State 为 NotReady 的 GameServer数量
- 使用OKG网络当前集群中使用了OKG网络模型 的 GameServer数量
- 网络Ready当前集群中使用了OKG网络模型 且 NetworkState 为 Ready 的 GameServer数量
- 默认运维状态当前集群中opsState 为 None 的 GameServer数量
- 已被分配当前集群中opsState 为 Allocated 的 GameServer数量
- 待删除当前集群中opsState 为 WaitToBeDeleted 的 GameServer数量
- 正在维护当前集群中opsState 为 Maintaining 的 GameServer数量
### 游戏服部署集列表页
点击“游戏服部署集”查看当前集群所有GameServerSet
![](/img/kruisegame/user-manuals/dash-gss.png)
字段说明
- 模版镜像GameServerTemplate设置的Image。格式为 {容器名称} -> {镜像名称及版本}
- 模版资源配置GameServerTemplate设置的Resources。格式为 {容器名称} -> { cpu request / mem request / cpu limit / mem limit },留白意味着未设置对应字段。
操作
- 跳转资源页查看详情
![](/img/kruisegame/user-manuals/dash-gss-jump.png)
在GameServerSet详情页可以编辑对应Yaml或者删除对应对象
![](/img/kruisegame/user-manuals/dash-gss-ops.png)
### 游戏服列表页
点击“游戏服”查看当前集群所有GameServer
![](/img/kruisegame/user-manuals/dash-gs.png)
字段说明
- 运行镜像当前游戏服运行的镜像及版本可能与对应GameServerSet的GameServerTemplate设置的镜像不同。格式为 {容器名称} -> {镜像名称及版本}
- 游戏服异常情况GameServerStatus Condition State 为 False 的情况将在此显示。
操作
- 跳转资源页查看详情
![](/img/kruisegame/user-manuals/dash-gs-jump.png)
在GameServer详情页可以编辑对应Yaml或者删除对应对象
![](/img/kruisegame/user-manuals/dash-gs-ops.png)
- 更新运维状态
![](/img/kruisegame/user-manuals/dash-gs-update-opsState.png)
弹窗显示后输入希望更改的opsState点击OK即可更新
![](/img/kruisegame/user-manuals/dash-gs-opsState-updated.png)

View File

@ -0,0 +1,16 @@
# 游戏匹配
会话类游戏通常需要`匹配服务`让玩家找到合适的队友及对手组成对局,并为该对局分配合适的游戏服。组成对局的玩家拿到游戏服地址后方可进入游戏。
OKG支持云原生游戏匹配框架[Open Match](https://github.com/googleforgames/open-match)
并基于Open Match构建了[kruise-game-open-match-director](https://github.com/CloudNativeGame/kruise-game-open-match-director)组件,
为形成对局的玩家分配游戏服地址。
## 使用说明
- Kubernetes集群中需要安装 `OpenKruiseGame``Open Match` 以及 `kruise-game-open-match-director`
- 被GameServerSet管理且待匹配的游戏服需要配置Network字段使游戏服具备直连网络。详细可参考[网络功能文档](./network.md)
- kruise-game-open-match-director 将选择网络可用且OpsState为None的游戏服获取对应网络连接信息分配予Match中的Tickets。
- kruise-game-open-match-director 将已分配的GameServer对应的OpsState字段标记为Allocated此时该GameServer不会再被分配且水平缩容时优先级较低避免被轻易删除。游戏服具体缩容顺序可参考[游戏服伸缩文档](./gameservers-scale.md#openkruisegame的水平伸缩特性)
- kruise-game-open-match-director 更多功能请参考[GitHub](https://github.com/CloudNativeGame/kruise-game-open-match-director)
- 关于OKG + Open Match更多示例请参考[GitHub](https://github.com/CloudNativeGame/kruise-game-open-match-example)

View File

@ -0,0 +1,35 @@
# 游戏服监控
## 可用指标
OKG 默认透出游戏服相关 prometheus metrics其中指标包括
| 名称 | 描述 | 类型 |
| --- |----------------------|---------|
| GameServersStateCount | 不同state状态下的游戏服数量 | gauge |
| GameServersOpsStateCount | 不同opsState状态下的游戏服数量 | gauge |
| GameServersTotal | 存在过的游戏服总数 | counter |
| GameServerSetsReplicasCount | 每个GameServerSet的副本数量 | gauge |
| GameServerDeletionPriority | 游戏服删除优先级 | gauge |
| GameServerUpdatePriority | 游戏服更新优先级 | gauge |
## 监控仪表盘
### 仪表盘导入
1. 将 [grafana.json](https://github.com/openkruise/kruise-game/blob/master/config/prometheus/grafana.json) 导入至Grafana中
2. 选择数据源
3. 替换UID并完成导入
### 仪表盘说明
完成导入后的仪表盘如下所示:
<img src={require('/static/img/kruisegame/user-manuals/gra-dash.png').default} width="90%" />
从上至下,依次包含
- 第一行:当前游戏服各个状态的数量、当前游戏服各个状态的比例饼图
- 第二行:游戏服各个状态数量变化折线图
- 第三行游戏服删除优先级、更新优先级变化折线图可根据左上角namespace与gsName筛选游戏服
- 第四、五行游戏服集合中不同状态的游戏服数量变化折线图可根据左上角namespace与gssName筛选游戏服集合

View File

@ -0,0 +1,513 @@
# 游戏服伸缩
## OpenKruiseGame的水平伸缩特性
### 缩容顺序
OKG提供游戏服状态设置的能力您可以手动/自动(服务质量功能)地设置游戏服的运维状态或删除优先级。当缩容时GameServerSet负载会根据游戏服的状态进行缩容选择缩容规则如下
1根据游戏服的opsState缩容。按顺序依次缩容opsState为`WaitToBeDeleted`、`None`、`Allocated`、`Maintaining`的游戏服
2当opsState相同时按照DeletionPriority(删除优先级)缩容优先删除DeletionPriority大的游戏服
3当opsState与DeletionPriority都相同时优先删除名称尾部序号较大的游戏服
#### 示例
部署一个副本为5的游戏服
```bash
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 5
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2
name: minecraft
EOF
```
生成5个GameServer
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 0
minecraft-2 Ready None 0 0
minecraft-3 Ready None 0 0
minecraft-4 Ready None 0 0
```
对minecraft-2设置删除优先级为10
```bash
kubectl edit gs minecraft-2
...
spec:
deletionPriority: 10 #初始为0调大到10
opsState: None
updatePriority: 0
...
```
手动缩容到4个副本
```bash
kubectl scale gss minecraft --replicas=4
gameserverset.game.kruise.io/minecraft scale
```
游戏服的数目最终变为4可以看到2号游戏服因为删除优先级最大所以被删除
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 0
minecraft-2 Deleting None 10 0
minecraft-3 Ready None 0 0
minecraft-4 Ready None 0 0
# After a while
...
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 0
minecraft-3 Ready None 0 0
minecraft-4 Ready None 0 0
```
设置minecraft-3的opsState为WaitToBeDeleted
```bash
kubectl edit gs minecraft-3
...
spec:
deletionPriority: 0
opsState: WaitToBeDeleted #初始为None, 将其改为WaitToBeDeleted
updatePriority: 0
...
```
手动缩容到3个副本
```bash
kubectl scale gss minecraft --replicas=3
gameserverset.game.kruise.io/minecraft scaled
```
游戏服的数目最终变为3可以看到3号游戏服因为处于WaitToBeDeleted状态所以被删除
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 0
minecraft-3 Deleting WaitToBeDeleted 0 0
minecraft-4 Ready None 0 0
# After a while
...
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 0
minecraft-4 Ready None 0 0
```
手动扩容回5个副本
```bash
kubectl scale gss minecraft --replicas=5
gameserverset.game.kruise.io/minecraft scaled
```
游戏服的数目最终变为5此时扩容出的游戏服序号为2与3
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 0
minecraft-2 Ready None 0 0
minecraft-3 Ready None 0 0
minecraft-4 Ready None 0 0
```
### 游戏服 ID Reserve
GameServerSet提供了`Spec.ReserveGameServerIds`字段。通过该字段用户指定ID将对应的游戏服删除或者在创建新游戏服时避免该序号对应的游戏服生成。
例如gss下存在5个游戏服ID分别为0、1、2、3、4。此时设置`ReserveGameServerIds`填写3和4在不更改副本数目的情况下gss将会删除3和4同时生成5和6的游戏服如下所示
```bash
kubectl edit gss minecraft
...
spec:
reserveGameServerIds:
- 3
- 4
...
# After a while
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 79s
minecraft-1 Ready None 0 0 79s
minecraft-2 Ready None 0 0 79s
minecraft-3 Deleting None 0 0 78s
minecraft-4 Deleting None 0 0 78s
minecraft-5 Ready None 0 0 23s
minecraft-6 Ready None 0 0 23s
```
如若填写在`ReserveGameServerIds`字段增加5和6同时减少副本数目到3则gss会删除5和6的游戏服如下所示
```bash
kubectl edit gss minecraft
...
spec:
replicas: 3
reserveGameServerIds:
- 3
- 4
- 5
- 6
...
# After a while
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 10m
minecraft-1 Ready None 0 0 10m
minecraft-2 Ready None 0 0 10m
minecraft-5 Deleting None 0 0 9m55s
minecraft-6 Deleting None 0 0 9m55s
```
**在缩容时OKG将优先考虑被Reserve的游戏服再按照上文提到的缩容顺序进行缩容**
### 游戏服 Kill
OKG 提供 Kill 模式指定游戏服删除。用户只需将希望删除的游戏服的OpsState标记为`Kill`即可。
与`游戏服 ID Reserve`不同的是该模式下不需要用户手动调整replicasOKG将根据OpsState为Kill的GameServer的数量自动缩减对应的副本数此外被删除的游戏服ID也不会出现在`ReserveGameServerIds`字段中默认scaleDownStrategyType为General的情况下这意味着对应序号的游戏服在扩容时可能会被重新生成。
示例如下:
```bash
# 初始存在3个游戏服
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 70s
minecraft-1 Ready None 0 0 70s
minecraft-2 Ready None 0 0 70s
# 若希望删除1号游戏服只需将其OpsState标记为`Kill`
kubectl edit gs minecraft-1
...
spec:
opsState: Kill
...
# minecraft-1 被删除, 同时gss的副本数变为2
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 78s
minecraft-1 Deleting Kill 0 0 78s
minecraft-2 Ready None 0 0 78s
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 82s
minecraft-2 Ready None 0 0 82s
```
### 缩容策略
OKG 提供两种缩容策略1General2ReserveIds。您可在`GameServerSet.Spec.ScaleStrategy.ScaleDownStrategyType`设置对应策略
#### General
当用户不配置ScaleDownStrategyType字段General为默认配置。缩容行为如上文中所述。
#### ReserveIds
用户设置ScaleDownStrategyType为`ReserveIds`当游戏服集合发生缩容时被删掉的游戏服尾部序号会被记录在reserveGameServerIds中后续发生扩容时这些尾部序号不会再使用如果想再使用这些尾部序号只需要将它们从reserveGameServerIds中拿出来同时调整副本数即可。
#### 示例
例如gss下存在5个游戏服ID分别为0、1、2、3、4。此时设置`GameServerSet.Spec.ScaleStrategy.ScaleDownStrategyType`为`ReserveIds`同时减少副本数目到3
```bash
kubectl edit gss minecraft
...
spec:
replicas: 3
scaleStrategy:
scaleDownStrategyType: ReserveIds
...
# After a while
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 10m
minecraft-1 Ready None 0 0 10m
minecraft-2 Ready None 0 0 10m
minecraft-3 Deleting None 0 0 9m55s
minecraft-4 Deleting None 0 0 9m55s
...
kubectl get gss minecraft -o yaml
spec:
replicas: 3
reserveGameServerIds:
- 3
- 4
scaleStrategy:
scaleDownStrategyType: ReserveIds
```
可以看到序号为3和4的游戏服被回填到了`reserveGameServerIds`字段此时若希望指定4号游戏服扩容则将4从reserveGameServerIds去除并增加副本数到4
```bash
kubectl edit gss minecraft
...
spec:
replicas: 4
reserveGameServerIds:
- 3
scaleStrategy:
scaleDownStrategyType: ReserveIds
...
# After a while
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 17m
minecraft-1 Ready None 0 0 17m
minecraft-2 Ready None 0 0 17m
minecraft-4 Ready None 0 0 6s
```
通过该功能可以实现指定序号游戏服扩容。
## 游戏服的水平自动伸缩
### 自动缩容
游戏服与无状态业务类型不同,对于自动伸缩特性有着更高的要求,其要求主要体现在缩容方面。
由于游戏为强有状态业务,随着时间的推移,游戏服之间的差异性愈加明显,缩容的精确度要求极高,粗糙的缩容机制容易造成玩家断线等负面影响,给业务造成巨大损失。
原生Kubernetes中的水平伸缩机制如下图所示
<img src={require('/static/img/kruisegame/user-manuals/autoscaling-k8s.png').default} style={{ height: '400px' , width: '700px'}} />
在游戏场景下,它的主要问题在于:
- 在pod层面无法感知游戏服业务状态进而无法通过业务状态设置删除优先级
- 在workload层面无法根据业务状态选择缩容对象
- 在autoscaler层面无法定向感知游戏服业务状态计算合适的副本数目
这样一来基于原生Kubernetes的自动伸缩机制将在游戏场景下造成两大问题
- 缩容数目不精确。容易删除过多或过少的游戏服。
- 缩容对象不精确。容易删除业务负载水平高的游戏服。
OKG 的自动伸缩机制如下所示
<img src={require('/static/img/kruisegame/user-manuals/autoscaling-okg.png').default} style={{ height: '400px' , width: '700px'}} />
- 在游戏服层面每个游戏服可以上报自身状态通过自定义服务质量或外部组件来暴露自身是否为WaitToBeDeleted状态。
- 在workload层面GameServerSet可根据游戏服上报的业务状态来决定缩容的对象如[游戏服水平伸缩](gameservers-scale.md)中所述WaitToBeDeleted的游戏服是删除优先级最高的游戏服缩容时最优先删除。
- 在autoscaler层面精准计算WaitToBeDeleted的游戏服个数将其作为缩容数量不会造成误删的情况。
如此一来OKG的自动伸缩器在缩容窗口期内只会删除处于WaitToBeDeleted状态的游戏服真正做到定向缩容、精准缩容。
**使用示例如下**
_**前置条件:在集群中安装 [KEDA](https://keda.sh/docs/2.10/deploy/)**_
部署ScaledObject对象来设置自动伸缩策略具体字段含义可参考 [ScaledObject API](https://github.com/kedacore/keda/blob/main/apis/keda/v1alpha1/scaledobject_types.go)
```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: minecraft #填写对应GameServerSet的名称
spec:
scaleTargetRef:
name: minecraft #填写对应GameServerSet的名称
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
pollingInterval: 30
minReplicaCount: 0
advanced:
horizontalPodAutoscalerConfig:
behavior: #继承HPA策略,可参考文档 https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/#configurable-scaling-behavior
scaleDown:
stabilizationWindowSeconds: 45 #设置缩容稳定窗口时间为45秒
policies:
- type: Percent
value: 100
periodSeconds: 15
triggers:
- type: external
metricType: AverageValue
metadata:
scalerAddress: kruise-game-external-scaler.kruise-game-system:6000
```
部署完成后更改gs minecraft-0 的 opsState 为 WaitToBeDeleted可参考[自定义服务质量](service-qualities.md)实现自动化设置游戏服状态)
```bash
kubectl edit gs minecraft-0
...
spec:
deletionPriority: 0
opsState: WaitToBeDeleted #初始为None, 将其改为WaitToBeDeleted
updatePriority: 0
...
```
经过缩容窗口期后游戏服minecraft-0自动被删除
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Deleting WaitToBeDeleted 0 0
minecraft-1 Ready None 0 0
minecraft-2 Ready None 0 0
# After a while
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-1 Ready None 0 0
minecraft-2 Ready None 0 0
```
### 自动扩容
除了设置自动缩容策略,也可以设置自动扩容策略。
#### 利用资源指标或自定义指标进行扩容
例如原生Kubernetes支持使用CPU利用率进行扩容其完整的yaml如下
```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: minecraft #填写对应GameServerSet的名称
spec:
scaleTargetRef:
name: minecraft #填写对应GameServerSet的名称
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
pollingInterval: 30
minReplicaCount: 0
advanced:
horizontalPodAutoscalerConfig:
behavior: #继承HPA策略,可参考文档 https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/#configurable-scaling-behavior
scaleDown:
stabilizationWindowSeconds: 45 #设置缩容稳定窗口时间为45秒
policies:
- type: Percent
value: 100
periodSeconds: 15
triggers:
- type: external
metricType: AverageValue
metadata:
scalerAddress: kruise-game-external-scaler.kruise-game-system:6000
- type: cpu
metricType: Utilization # 允许的类型是 "利用率 "或 "平均值"
metadata:
value: "50"
```
对游戏服进行压测,可以看到游戏服开始扩容
```bash
kubectl get gss
NAME DESIRED CURRENT UPDATED READY MAINTAINING WAITTOBEDELETED AGE
minecraft 5 5 5 0 0 0 7s
# After a while
kubectl get gss
NAME DESIRED CURRENT UPDATED READY MAINTAINING WAITTOBEDELETED AGE
minecraft 20 20 20 20 0 0 137s
```
#### 设置opsState为None的游戏服的最小个数
OKG支持设置游戏服最小数目。在当前所有opsState为None的游戏服数量少于设置的值时OKG将自动扩容出新的游戏服使opsState为None的游戏服数量满足设置的最小个数。
配置方式如下在此例中设置opsState为None的游戏服的最小个数为3
```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: minecraft #填写对应GameServerSet的名称
spec:
scaleTargetRef:
name: minecraft #填写对应GameServerSet的名称
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
pollingInterval: 30
minReplicaCount: 0
advanced:
horizontalPodAutoscalerConfig:
behavior: #继承HPA策略,可参考文档 https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/#configurable-scaling-behavior
scaleDown:
stabilizationWindowSeconds: 45 #设置缩容稳定窗口时间为45秒
policies:
- type: Percent
value: 100
periodSeconds: 15
triggers:
- type: external
metricType: AverageValue
metadata:
minAvailable: "3" # 设置opsState为None的游戏服的最小个数
scalerAddress: kruise-game-external-scaler.kruise-game-system:6000
```
初始部署replicas为1的GameServerSet经过KEDA探测周期后马上扩容出两个新的游戏服。此时opsState为None的游戏服数量不小于设置的minAvailable值完成了自动扩容。
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 7s
# After a while
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 20s
minecraft-1 Ready None 0 0 5s
minecraft-2 Ready None 0 0 5s
```
### 其他设置
Kubernetes对于自动伸缩行为具备一定容忍度该值由kube-controller-manager 参数 --horizontal-pod-autoscaler-tolerance 决定默认为0.1这意味着理想副本数与当前副本数的差值在10%以内时不会触发扩容或缩容。
如果做到更加精准地自动伸缩可以调低该参数例如设置0.0时OKG将会缩容所有WaitToBeDeleted的游戏服。

View File

@ -0,0 +1,345 @@
# 游戏服热更新
游戏服更新是游戏服应用交付中尤为重要的一环。作为有状态类型业务游戏服的更新往往对云原生基础设施有着更高的要求。本文主要介绍如何利用OKG的原地升级能力实现游戏服热更新。
## 游戏服与容器
在介绍热更方法之前或许我们需要先明确游戏服与容器的关系。在OKG的概念里一个游戏服(GameServer)中可以包含多个容器,每个容器功能作用不尽相同,各自对应不同的容器镜像。当然,一个游戏服也可以只包含一个容器。游戏服包含一个容器、还是包含多个容器对应着两种不同的架构思想。
单容器的游戏服更贴近虚拟机的运维管理方式。无论是状态的管理、或者小版本的热更都不借助Kubernetes的能力沿用过去的运维方式进行。比如游戏服的单容器中存在多个进程多个脚本文件或配置文件游戏服引擎常驻进程通常会通过构建新的容器进行实现新版发布而新的脚本、资源、或配置的更新往往依赖对象存储的挂载、或是自研程序的动态拉取。并且更新的情况由业务自行判断整个过程以非云原生的方式进行。在业内我们称这种游戏服为富容器。富容器热更新的问题在于
- 无法对脚本/资源/配置文件进行云原生化的版本管理。由于容器镜像并没有发生变化,运维人员对当前容器中运行的脚本文件等版本不得而知。游戏上线后小版本的迭代十分频繁,当故障出现时,没有版本管理的系统将难以定位问题,这很大程度上提高了运维复杂度。
- 更新状态难以定位。即使对容器中的文件进行了更新替换,但执行重载命令时难以确定当前热更文件是否已经挂载完毕,这种更新成功与否的状态维护需要交给运维者额外管理,也一定程度上提高了运维复杂度。
- 无法灰度升级。在更新时,为了控制影响面,往往需要先更新低重要性的游戏服,确认无误后再灰度其余游戏服。但无论是对象存储挂载的方式还是程序拉取的方式很难做到灰度发布。一旦全量发布出现问题,故障影响面是非常大的。
- 在容器异常时pod重建拉起旧版本的镜像热更文件并未能持续化保留。
针对游戏服热更场景更理想的做法是使用多容器的游戏服架构将热更的部分作为sidecar容器与main容器一同部署在同一个游戏服(GameServer)中二者通过emptyDir共享热更文件。更新时只需更新sidecar容器即可。这样一来游戏服的热更将以云原生的方式进行
- sidecar容器镜像具有版本属性解决了版本管理问题。
- Kubernetes容器更新成功后处于Ready状态能够感知sidecar更新是否成功。
- OKG提供多种更新策略可按照发布需求自行控制发布对象完成灰度发布。
- 即使容器异常发生重启,热更文件随着镜像的固化而持续化保留了下来。
## 基于原地升级的游戏服热更新
### 原地升级
在标准的Kubernetes中应用的更新是通过更改资源对象中Image字段实现的。但原生的workload如Deployment或StatefulSet管理的pod在更新了Image之后会出现重建的情况pod的生命周期与容器的生命周期耦合在一起上文提到的多容器架构的游戏服热更新在原生Kubernetes的workload下变成了无稽之谈。
OKG的GameServerSet提供了一种原地升级的能力在保证整个游戏服生命周期不变的情况下定向更新其中某一个容器不会导致游戏服重新创建。sidecar容器更新过程游戏服正常运行玩家不会收到任何影响。
如下图所示蓝色部分为热更部分橘色部分为非热更部分。我们将Game Script容器从版本V1更新至版本V2后整个pod不会重建橘色部分不受到任何影响Game Engine正常平稳运行
![hot-update.png](/img/kruisegame/user-manuals/hot-update.png)
### 使用示例
本文使用2048网页版作为示例。在示例中我们将看到如何在不影响游戏服生命周期的前提条件下更新游戏脚本。
部署带有sidecar容器的游戏服使用GameServerSet作为游戏服负载设置
- pod更新策略选择原地升级
- 使用AlibabaCloud-SLB网络模型暴露服务
- 两个容器其中app-2048为主容器承载主要游戏逻辑sidecar为伴生容器存放热更文件。二者通过emptyDir共享文件目录
- sidecar启动时将存放热更文件的目录下文件/app/js同步至共享目录下/app/scripts同步后sleep不退出
- app-2048容器使用/var/www/html/js目录下的游戏脚本
```bash
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gss-2048
namespace: default
spec:
replicas: 1
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: AlibabaCloud-SLB
networkConf:
- name: SlbIds
value: lb-bp1oqahx3jnr7j3f6vyp8
- name: PortProtocols
value: 80/TCP
gameServerTemplate:
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/acs/2048:v1.0
name: app-2048
volumeMounts:
- name: shared-dir
mountPath: /var/www/html/js
- image: registry.cn-beijing.aliyuncs.com/acs/2048-sidecar:v1.0
name: sidecar
args:
- bash
- -c
- rsync -aP /app/js/* /app/scripts/ && while true; do echo 11;sleep 2; done
volumeMounts:
- name: shared-dir
mountPath: /app/scripts
volumes:
- name: shared-dir
emptyDir: {}
EOF
```
生成1个GameServer以及对应的1个Pod
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
gss-2048-0 Ready None 0 0 13s
kubectl get pod
NAME READY STATUS RESTARTS AGE
gss-2048-0 2/2 Running 0 13s
```
此时访问游戏网页(游戏服网络相关内容可参考网络模型文档),游戏结束时显示`Game over!`字样:
<img src={require('/static/img/kruisegame/user-manuals/2048-v1.png').default} style={{ height: '600px' , width: '400px'}} />
接下来,我们希望更新游戏服脚本,将游戏结束时的显示字样变为 `*_* Game over!`
修改对应脚本文件html_actuator.js并构建新的sidecar镜像将镜像tag命名为v2.0。在实际生产中这一过程可通过CI流程完成
镜像更新后只需更新GameServerSet对应的容器镜像版本即可
```bash
kubectl edit gss gss-2048
...
- image: registry.cn-beijing.aliyuncs.com/acs/2048-sidecar:v2.0
name: sidecar
...
```
一段时间过后发现gs已从Updating变为ReadyPod已经更新完毕restarts次数变为1但Age并没有减少。
```bash
kubectl get pod
NAME READY STATUS RESTARTS AGE
gss-2048-0 2/2 Running 1 (33s ago) 8m55s
```
此时对app-2048容器执行重载命令
```bash
kubectl exec gss-2048-0 -c app-2048 -- /usr/sbin/nginx -s reload
```
打开无痕浏览器,进行游戏,游戏结束时提示字样已更新:
<img src={require('/static/img/kruisegame/user-manuals/2048-v2.png').default} style={{ height: '600px' , width: '400px'}} />
### 文件热更后的重载方式
在上面的示例中对单个pod使用exec执行命令的方式重载。
而在批量管理时,重载操作太过繁琐复杂。下面提供了几种文件热更后的重载方式,以供参考。
#### 手动批量重载
当全部游戏服更新Ready后可借助批量管理工具kubectl-pexec批量在容器中执行exec重载命令。完成游戏服热重载。
#### 通过inotify跟踪热更文件目录
inotify是Linux文件监控系统框架。通过inotify主游戏服业务容器可以监听热更文件目录下文件的变化进而触发更新。
使用inotify需要在容器中安装inotify-tools:
```bash
apt-get install inotify-tools
```
以上述2048游戏为例在原镜像基础之上app-2048容器监听 /var/www/html/js/ 目录,当发现文件变化时自动执行重载命令。脚本如下所示,在容器启动时执行即可。值得注意的是重载命令应为幂等的。
```shell
inotifywait -mrq --timefmt '%d/%m/%y %H:%M' --format '%T %w%f%e' -e modify,delete,create,attrib /var/www/html/js/ | while read file
do
/usr/sbin/nginx -s reload
echo "reload successfully"
done
```
将上述程序固化至镜像中,构建出新的镜像`registry.cn-beijing.aliyuncs.com/acs/2048:v1.0-inotify`再次实验其他字段不变将sidecar镜像从v1.0替换到v2.0后,会发现已经不需要手动输入重载命令已完成全部热更过程。
完整的yaml如下
```yaml
kind: GameServerSet
metadata:
name: gss-2048
namespace: default
spec:
replicas: 1
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: AlibabaCloud-SLB
networkConf:
- name: SlbIds
value: lb-bp1oqahx3jnr7j3f6vyp8
- name: PortProtocols
value: 80/TCP
gameServerTemplate:
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/acs/2048:v1.0-inotify
name: app-2048
volumeMounts:
- name: shared-dir
mountPath: /var/www/html/js
- image: registry.cn-beijing.aliyuncs.com/acs/2048-sidecar:v1.0 #热更时替换成v2.0
name: sidecar
args:
- bash
- -c
- rsync -aP /app/js/* /app/scripts/ && while true; do echo 11;sleep 2; done
volumeMounts:
- name: shared-dir
mountPath: /app/scripts
volumes:
- name: shared-dir
emptyDir: {}
```
#### sidecar触发http请求
主游戏服业务容器暴露一个http接口sidecar在启动成功后向本地127.0.0.1发送重载请求由于pod下容器共享网络命名空间主容器接收到请求后进行文件重载。
以上述2048游戏为例在原镜像基础之上
- app-2048容器新增reload接口以下是js代码示例
```js
var http = require('http');
var exec = require('child_process').exec;
var server = http.createServer(function(req, res) {
if (req.url === '/reload') {
exec('/usr/sbin/nginx -s reload', function(error, stdout, stderr) {
if (error) {
console.error('exec error: ' + error);
res.statusCode = 500;
res.end('Error: ' + error.message);
return;
}
console.log('stdout: ' + stdout);
console.error('stderr: ' + stderr);
res.statusCode = 200;
res.end();
});
} else {
res.statusCode = 404;
res.end('Not found');
}
});
server.listen(3000, function() {
console.log('Server is running on port 3000');
});
```
- 同时sidecar容器新增请求脚本request.sh容器启动后利用postStart增加发送请求命令如下所示
```yaml
...
name: sidecar
lifecycle:
postStart:
exec:
command:
- bash
- -c
- ./request.sh
...
```
对应request.sh脚本如下所示具有重试机制确认重载成功再退出
```shell
#!/bin/bash
# 循环发送 HTTP 请求,直到服务器返回成功响应为止
while true; do
response=$(curl -s -w "%{http_code}" http://localhost:3000/reload)
if [[ $response -eq 200 ]]; then
echo "Server reloaded successfully!"
break
else
echo "Server reload failed, response code: $response"
fi
sleep 1
done
```
这样一来,在文件更新后也可完成自动重载。
将上述程序固化至镜像中,构建出以下新的镜像:
- `registry.cn-beijing.aliyuncs.com/acs/2048:v1.0-http`
- `registry.cn-beijing.aliyuncs.com/acs/2048-sidecar:v1.0-http`
- `registry.cn-beijing.aliyuncs.com/acs/2048-sidecar:v2.0-http`
替换新镜像再次实验注意yaml中sidecar需要增加lifecycle字段。将sidecar镜像从v1.0-http替换到v2.0-http后会发现已经不需要手动输入重载命令已完成全部热更过程。
完整的yaml如下:
```yaml
kind: GameServerSet
metadata:
name: gss-2048
namespace: default
spec:
replicas: 1
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: AlibabaCloud-SLB
networkConf:
- name: SlbIds
value: lb-bp1oqahx3jnr7j3f6vyp8
- name: PortProtocols
value: 80/TCP
gameServerTemplate:
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/acs/2048:v1.0-http
name: app-2048
volumeMounts:
- name: shared-dir
mountPath: /var/www/html/js
- image: registry.cn-beijing.aliyuncs.com/acs/2048-sidecar:v1.0-http #热更时替换成v2.0-http
name: sidecar
lifecycle:
postStart:
exec:
command:
- bash
- -c
- ./request.sh
args:
- bash
- -c
- rsync -aP /app/js/* /app/scripts/ && while true; do echo 11;sleep 2; done
volumeMounts:
- name: shared-dir
mountPath: /app/scripts
volumes:
- name: shared-dir
emptyDir: {}
```
#### 全托管的热更重载
OKG具备触发容器中执行命令的能力基于该功能OKG可提供全自动化的热更新能力让用户不再过度关心热更重载问题。如若您有这方面需求可以在GitHub提交issue和社区开发者一起讨论OKG热更功能演进路线。
### 停服原地热更
游戏场景下狭义上的热更是指不影响玩家正常游戏的不停服更新。然而在有些场景下,游戏服停服更新也需要依赖原地升级能力。
#### 网络元数据不变
游戏服的有状态特性时常体现在网络信息上。由于每个游戏服都是独特的无法使用k8s svc负载均衡的概念往往游戏开发者会基于IP实现路由分发机制这时我们需要在游戏更新时避免游戏服IP信息变化。OKG的原地升级能力能够满足上述需求。
#### 共享内存不丢失
游戏服创建后调度到某宿主机上游戏业务利用共享内存降低数据落盘延迟这样一来相当于游戏服在本地增加了一层缓存。在游戏服更新时即时出现短暂的服务暂停时间但由于缓存的存在游戏服的终止以及启动速度较快停服时间也会大大减少。共享内存的实现也依赖于OKG的原地升级能力保证对应缓存数据不会丢失。

View File

@ -0,0 +1,128 @@
# 自定义服务质量
## 功能概述
由于游戏是有状态服务,很多时候游戏服是以一种 "富容器" 的形态存在于Pod之中多个进程在Pod中统一管理。
然而,每个进程重要性却有所不同,对于"轻量级进程"错误的情况用户并不希望将整个pod删除重建像k8s原生的liveness probe并不能很好地满足这种需求过于僵化的模式与游戏场景并不适配。
OKG 认为游戏服的服务质量水平应该交由游戏开发者定义,开发者可以根据不同游戏服状态去设置对应的处理动作。自定义服务质量功能是探测+动作的组合,通过这种方式帮助用户自动化地处理各类游戏服状态问题。
## 使用示例
### 游戏服空闲设置即将下线
部署一个带有自定义服务质量的GameServerSet
```shell
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/gs-demo/gameserver:idle
name: minecraft
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
maxUnavailable: 100%
serviceQualities: # 设置了一个idle的服务质量
- name: idle
containerName: minecraft
permanent: false
#与原生probe类似,本例使用执行脚本的方式探测游戏服是否空闲,不存在玩家
exec:
command: ["bash", "./idle.sh"]
serviceQualityAction:
#不存在玩家标记该游戏服运维状态为WaitToBeDeleted
- state: true
opsState: WaitToBeDeleted
#存在玩家标记该游戏服运维状态为None
- state: false
opsState: None
EOF
```
部署完成后,由于还未导入玩家,故所有游戏服都为空闲状态,可以任意被删除:
```shell
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready WaitToBeDeleted 0 0
minecraft-1 Ready WaitToBeDeleted 0 0
minecraft-2 Ready WaitToBeDeleted 0 0
```
当有玩家进入游戏服minecraft-1则游戏服的运维状态发生变化
```shell
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready WaitToBeDeleted 0 0
minecraft-1 Ready None 0 0
minecraft-2 Ready WaitToBeDeleted 0 0
```
此时若发生缩容游戏服minecraft-1将得到保护避免优先删除。
### 游戏服状态异常设置维护中
部署一个带有自定义服务质量的GameServerSet
```shell
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: demo-gs
namespace: default
spec:
replicas: 3
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/gs-demo/gameserver:healthy
name: minecraft
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
maxUnavailable: 100%
serviceQualities: # 设置了一个healthy的服务质量
- name: healthy
containerName: minecraft
permanent: false
#与原生probe类似,本例使用执行脚本的方式探测游戏服是否健康
exec:
command: ["bash", "./healthy.sh"]
serviceQualityAction:
#探测健康标记该游戏服运维状态为None
- state: true
opsState: None
#探测不健康标记该游戏服运维状态为Maintaining
- state: false
opsState: Maintaining
EOF
```
部署完成后由于一切正常故所有游戏服都为None
```shell
kubectl get gs
NAME STATE OPSSTATE DP UP
demo-gs-0 Ready None 0 0
demo-gs-1 Ready None 0 0
demo-gs-2 Ready None 0 0
```
模拟demo-gs-0某进程宕机游戏服变为Maintaining状态
```shell
kubectl get gs
NAME STATE OPSSTATE DP UP
demo-gs-0 Ready Maintaining 0 0
demo-gs-1 Ready None 0 0
demo-gs-2 Ready None 0 0
```
此时gameserver controller会发出 GameServer demo-gs-0 Warning 的 event配合[kube-event项目](https://github.com/AliyunContainerService/kube-eventer)可实现异常通知:
![](/img/kruisegame/user-manuals/warning-ding.png)
此外OKG 未来会集成游戏服自动排障/恢复工具,进一步丰富游戏服的自动化运维能力。

View File

@ -0,0 +1,84 @@
# 游戏服更新策略
## 功能概述
OKG 提供了原地升级([热更新](./hot-update.md))、批量更新、按优先级更新等多种更新策略。
用户可设置GameServer的更新优先级配合partition参数实现在实际生产场景下把控更新范围、更新顺序、更新节奏。
如下图所示提高序号为1的游戏服优先级同时设置partition为2则会优先更新1号游戏服随后更改partition为0则会再更新其余游戏服。详情可参考使用示例。
![update-priority.png](/img/kruisegame/user-manuals/update-priority.png)
## 使用示例
本示例中将一组游戏服分成两批次更新,模拟灰度更新,逐步验证的场景。
此时GameServerSet下有3个游戏服副本
```shell
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 0
minecraft-2 Ready None 0 0
```
设置更新优先级将1号游戏服优先级调大
```shell
kubectl edit gs minecraft-1
...
spec:
deletionPriority: 0
opsState: None
updatePriority: 10 #初始为0调大成10
...
```
接下来设置 GameServerSet partition、以及即将更新的新镜像
```shell
kubectl edit gss minecraft
...
image: registry.cn-hangzhou.aliyuncs.com/acs/minecraft-demo:1.12.2-new # 更新镜像
name: gameserver
...
updateStrategy:
rollingUpdate:
maxUnavailable: 5
partition: 2 # 设置保留的游戏服数目这里只更新一个所以要保留余下2个
podUpdatePolicy: InPlaceIfPossible
...
```
此时只有minecraft-1将会更新:
```shell
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Updating None 0 10
minecraft-2 Ready None 0 0
# 一段时间过后
...
kubectl get gs
NAME STATE OPSSTATE DP UP
minecraft-0 Ready None 0 0
minecraft-1 Ready None 0 10
minecraft-2 Ready None 0 0
```
待minecraft-1验证通过后更新其余游戏服
```shell
kubectl edit gss minecraft
...
updateStrategy:
rollingUpdate:
maxUnavailable: 5
partition: 0 # 设置保留的游戏服数目将其设置为0更新余下全部
podUpdatePolicy: InPlaceIfPossible
...
```

View File

@ -38,8 +38,8 @@ OpenKruiseGame(OKG) has the following core features:
<table>
<tr style={{"border":0}}>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/hypergryph-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/lilith-logo.png').default} width="120" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/hypergryph-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/bilibili-logo.png').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/shangyou-logo.jpeg').default} width="130" /></center></td>
<td style={{"border":0}}><center><img src={require('/static/img/kruisegame/xingzhe-logo.png').default} width="125" /></center></td>

View File

@ -33,6 +33,9 @@ type GameServerSetSpec struct {
// Network settings for game server access layer.
Network *Network `json:"network,omitempty"`
// Lifecycle hook defined by users
Lifecycle *appspub.Lifecycle `json:"lifecycle,omitempty"`
}
```
@ -186,6 +189,9 @@ type ServiceQualityAction struct {
Result string `json:"result,omitempty"`
GameServerSpec `json:",inline"`
Annotations map[string]string `json:"annotations,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
```
@ -211,6 +217,28 @@ type KVParams struct {
}
```
#### Lifecycle
```
// Lifecycle contains the hooks for Pod lifecycle.
type Lifecycle struct {
// PreDelete is the hook before Pod to be deleted.
PreDelete *LifecycleHook `json:"preDelete,omitempty"`
// InPlaceUpdate is the hook before Pod to update and after Pod has been updated.
InPlaceUpdate *LifecycleHook `json:"inPlaceUpdate,omitempty"`
}
type LifecycleHook struct {
LabelsHandler map[string]string `json:"labelsHandler,omitempty"`
FinalizersHandler []string `json:"finalizersHandler,omitempty"`
// MarkPodNotReady = true means:
// - Pod will be set to 'NotReady' at preparingDelete/preparingUpdate state.
// - Pod will be restored to 'Ready' at Updated state if it was set to 'NotReady' at preparingUpdate state.
// Default to false.
MarkPodNotReady bool `json:"markPodNotReady,omitempty"`
}
```
### GameServerSetStatus
```

View File

@ -173,16 +173,16 @@ PING minecraft-2.minecraft.default.svc.cluster.local (172.16.0.12): 56 data byte
It can be found that the accessor successfully accessed minecraft-2, and the DNS successfully resolved to the corresponding intranet IP address. The DNS rules here are as follows: {pod-name}.{gss-name}.{namespace-name}.svc.cluster.local
## Synchronization of Annotations from GameServer to Pod
## Synchronization of Labels/Annotations from GameServer to Pod
As mentioned above, through the DownwardAPI, information from pod annotations can be propagated downwards into containers. Sometimes, we wish to synchronize the annotations of a GameServer to its Pod, in order to complete the action of sinking GameServer metadata information.
As mentioned above, through the DownwardAPI, information from pod labels/annotations can be propagated downwards into containers. Sometimes, we wish to synchronize the labels/annotations of a GameServer to its Pod, in order to complete the action of sinking GameServer metadata information.
```bash
kubectl patch gs minecraft-0 --type='merge' -p '{"metadata":{"annotations":{"gs-sync/test-key":"some-value"}}}'
gameserver.game.kruise.io/minecraft-0 patched
```
OKG supports the synchronization of annotations from GameServer to Pod starting with "gs-sync/," as demonstrated below:
Then, we get pod minecraft-0:
```bash
kubectl get po minecraft-0 -oyaml | grep gs-sync

View File

@ -0,0 +1,115 @@
# Custom Lifecycle Management
Game servers, due to their strong stateful characteristics, have a high demand for graceful shutdown operations.
A game server typically needs to wait until data is fully persisted to disk and ensured to be safe before it can be thoroughly removed.
Although Kubernetes natively provides the preStop hook, which allows containers to execute specific actions before they are about to shut down, there is a limitation: once the preset time limit is exceeded, the container will have to be forcibly terminated, regardless of whether the data processing is complete or not.
In some cases, this approach lacks real gracefulness. We need a more flexible mechanism to ensure that game servers can exit smoothly while protecting all critical states.
OpenKruise has introduced the Lifecycle Hook feature, which provides precise control and waiting mechanisms for game servers at critical lifecycle moments.
This allows servers to execute the actual deletion or update operations only after meeting specific conditions.
By providing a configurable Lifecycle field, combined with the ability to customize service quality, OKG ensures that the game server's shutdown process is both graceful and reliable.
With this advanced feature, maintainers can ensure that all necessary data persistence and internal state synchronization are safely and correctly completed before the server is smoothly removed or updated.
## Usage Example
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
lifecycle:
preDelete:
labelsHandler:
gs-sync/delete-block: "true"
gameServerTemplate:
metadata:
labels:
gs-sync/delete-block: "true"
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/chrisliu95/minecraft-demo:probe-v0
name: minecraft
volumeMounts:
- name: gsState
mountPath: /etc/gsinfo
volumes:
- name: gsinfo
downwardAPI:
items:
- path: "state"
fieldRef:
fieldPath: metadata.labels['game.kruise.io/gs-state']
serviceQualities:
- name: healthy
containerName: minecraft
permanent: false
exec:
command: ["bash", "./probe.sh"]
serviceQualityAction:
- state: true
result: done
labels:
gs-sync/delete-block: "false"
- state: true
result: WaitToBeDeleted
opsState: WaitToBeDeleted
- state: false
opsState: None
```
The corresponding script is as follows. The script performs the following actions:
- Acquires the current state of gs from /etc/gsinfo/state and determines whether it is "PreDelete"
- If it is PreDelete, it indicates that the current gs should be in the offline phase. It checks whether the data flushing has been completed (in this example, the presence of a file indicates data flushing completion)
- If the data flushing is not completed, it executes the data flushing action (in this example, it creates a file)
- If the data flushing is completed, it outputs "done" and exits with 1.
- If it is not PreDelete, it indicates that the gs has not entered the offline stage. It uses the number of people in the game server to determine whether it should now go offline.
- If the number of people on the game server equals 0, it outputs "WaitToBeDeleted" and exits with 1.
- If the number of people on the game server is not 0, it exits with 0.
```
#!/bin/bash
file_path="/etc/gsinfo/state"
data_flushed_file="/etc/gsinfo/data_flushed"
if [[ ! -f "$file_path" ]]; then
exit 0
fi
state_content=$(cat "$file_path")
if [[ "$state_content" == "PreDelete" ]]; then
if [[ -f "$data_flushed_file" ]]; then
echo "done"
exit 1
else
touch "$data_flushed_file"
echo "WaitToBeDeleted"
exit 1
fi
else
people_count_file="/etc/gsinfo/people_count"
people_count=$(cat "$people_count_file")
if [[ "$people_count" -eq 0 ]]; then
echo "WaitToBeDeleted"
exit 1
else
exit 0
fi
fi
```
![grace-deletion.png](../../static/img/kruisegame/user-manuals/gs-lifecycle-delete.png)
The process of elegant delete as follow:
1. The game server is running normally, and the number of players is not 0.
2. When the number of players drops to 0, set the opsState to WaitToBeDeleted using custom service quality settings.
3. Through the automatic scaling policy, OKG deletes the GameServer with WaitToBeDeleted opsState. Since the lifecycle hook is configured and the delete-block label wil be set to true, the gs is not truly deleted but enters the PreDelete state, and the data flushing process is triggered by custom service quality.
4. Once data flushing is complete, set the delete-block label to false using custom service quality to release the checkpoint.
5. After the checkpoint is released, the PreDelete phase moves into the Delete phase. The gs is then truly deleted.

View File

@ -19,6 +19,7 @@ OpenKruiseGame supports the following network plugins:
- AlibabaCloud-SLB-SharedPort
- AlibabaCloud-NLB-SharedPort
- Volcengine-CLB
- AmazonWebServices-NLB
---
### Kubernetes-HostPort
@ -533,6 +534,72 @@ AllowNotReadyContainers
- Value: {containerName_0},{containerName_1},... Examplesidecar
- Configuration change supported or not: It cannot be changed during the in-place updating process.
LBHealthCheckSwitch
- MeaningWhether to enable health check
- Format"on" means on, "off" means off. Default is on
- Whether to support changes: Yes
LBHealthCheckFlag
- Meaning: Whether to enable http type health check
- Format: "on" means on, "off" means off. Default is on
- Whether to support changes: Yes
LBHealthCheckType
- Meaning: Health Check Protocol
- Format: fill in "tcp" or "http", the default is tcp
- Whether to support changes: Yes
LBHealthCheckConnectTimeout
- Meaning: Maximum timeout for health check response.
- Format: Unit: seconds. The value range is [1, 300]. The default value is "5"
- Whether to support changes: Yes
LBHealthyThreshold
- Meaning: After the number of consecutive successful health checks, the health check status of the server will be determined from failure to success.
- Format: Value range [2, 10]. Default value is "2"
- Whether to support changes: Yes
LBUnhealthyThreshold
- Meaning: After the number of consecutive health check failures, the health check status of the server will be determined from success to failure.
- Format: Value range [2, 10]. The default value is "2"
- Whether to support changes: Yes
LBHealthCheckInterval
- Meaning: health check interval.
- Format: Unit: seconds. The value range is [1, 50]. The default value is "10"
- Whether to support changes: Yes
LBHealthCheckProtocolPort
- Meaningthe protocols & ports of HTTP type health check.
- FormatMultiple values are separated by ','. e.g. https:443,http:80
- Whether to support changes: Yes
LBHealthCheckUri
- Meaning: The corresponding uri when the health check type is HTTP.
- Format: The length is 1~80 characters, only letters, numbers, and characters can be used. Must start with a forward slash (/). Such as "/test/index.html"
- Whether to support changes: Yes
LBHealthCheckDomain
- Meaning: The corresponding domain name when the health check type is HTTP.
- Format: The length of a specific domain name is limited to 1~80 characters. Only lowercase letters, numbers, dashes (-), and half-width periods (.) can be used.
- Whether to support changes: Yes
LBHealthCheckMethod
- Meaning: The corresponding method when the health check type is HTTP.
- Format: "GET" or "HEAD"
- Whether to support changes: Yes
#### Plugin configuration
```
[alibabacloud]
@ -585,6 +652,66 @@ AllowNotReadyContainers
- Value: {containerName_0},{containerName_1},... Examplesidecar
- Configuration change supported or not: It cannot be changed during the in-place updating process.
LBHealthCheckFlag
- Meaning: Whether to enable health check
- Format: "on" means on, "off" means off. Default is on
- Whether to support changes: Yes
LBHealthCheckType
- Meaning: Health Check Protocol
- Format: fill in "tcp" or "http", the default is tcp
- Whether to support changes: Yes
LBHealthCheckConnectPort
- Meaning: Server port for health check.
- Format: Value range [0, 65535]. Default value is "0"
- Whether to support changes: Yes
LBHealthCheckConnectTimeout
- Meaning: Maximum timeout for health check response.
- Format: Unit: seconds. The value range is [1, 300]. The default value is "5"
- Whether to support changes: Yes
LBHealthyThreshold
- Meaning: After the number of consecutive successful health checks, the health check status of the server will be determined from failure to success.
- Format: Value range [2, 10]. Default value is "2"
- Whether to support changes: Yes
LBUnhealthyThreshold
- Meaning: After the number of consecutive health check failures, the health check status of the server will be determined from success to failure.
- Format: Value range [2, 10]. The default value is "2"
- Whether to support changes: Yes
LBHealthCheckInterval
- Meaning: health check interval.
- Format: Unit: seconds. The value range is [1, 50]. The default value is "10"
- Whether to support changes: Yes
LBHealthCheckUri
- Meaning: The corresponding uri when the health check type is HTTP.
- Format: The length is 1~80 characters, only letters, numbers, and characters can be used. Must start with a forward slash (/). Such as "/test/index.html"
- Whether to support changes: Yes
LBHealthCheckDomain
- Meaning: The corresponding domain name when the health check type is HTTP.
- Format: The length of a specific domain name is limited to 1~80 characters. Only lowercase letters, numbers, dashes (-), and half-width periods (.) can be used.
- Whether to support changes: Yes
LBHealthCheckMethod
- Meaning: The corresponding method when the health check type is HTTP.
- Format: "GET" or "HEAD"
- Whether to support changes: Yes
#### Plugin configuration
```
[alibabacloud]
@ -1081,6 +1208,194 @@ networkStatus:
networkType: Volcengine-CLB
```
---
### AmazonWebServices-NLB
#### Plugin name
`AmazonWebServices-NLB`
#### Plugin description
For game businesses using OKG in AWS EKS clusters, routing traffic directly to Pod ports via network load balancing is the foundation for achieving high-performance real-time service discovery. Using NLB for dynamic port mapping simplifies the forwarding chain and avoids the performance loss caused by Kubernetes kube-proxy load balancing. These features are particularly crucial for handling replica combat-type game servers. For GameServerSets with the network type specified as AmazonWebServices-NLB, the AmazonWebServices-NLB network plugin will schedule an NLB, automatically allocate ports, create listeners and target groups, and associate the target group with Kubernetes services through the TargetGroupBinding CRD. If the cluster is configured with VPC-CNI, the traffic will be automatically forwarded to the Pod's IP address; otherwise, it will be forwarded through ClusterIP. The process is considered successful when the network of the GameServer is in the Ready state.
#### Preparation
Due to the difference in AWS design, to achieve NLB port-to-Pod port mapping, three types of CRD resources need to be created: Listener/TargetGroup/TargetGroupBinding
##### Deploy elbv2-controller
Definition and controller for Listener/TargetGroup CRDs: https://github.com/aws-controllers-k8s/elbv2-controller. This project links k8s resources with AWS cloud resources. Download the chart: https://gallery.ecr.aws/aws-controllers-k8s/elbv2-chart, example value.yaml:
```yaml
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::xxxxxxxxx:role/test"
aws:
region: "us-east-1"
endpoint_url: "https://elasticloadbalancing.us-east-1.amazonaws.com"
```
The key to deploying this project lies in authorizing the k8s ServiceAccount to access the NLB SDK, which is recommended to be done through an IAM role:
###### Step 1Enable OIDC provider for the EKS cluster
1. Sign in to the AWS Management Console.
2. Navigate to the EKS consolehttps://console.aws.amazon.com/eks/
3. Select your cluster.
4. On the cluster details page, ensure that the OIDC provider is enabled. Obtain the OIDC provider URL for the EKS cluster. In the "Configuration" section of the cluster details page, find the "OpenID Connect provider URL".
###### Step 2Configure the IAM role trust policy
1. In the IAM console, create a new identity provider and select "OpenID Connect".
- For the Provider URL, enter the OIDC provider URL of your EKS cluster.
- For Audience, enter: `sts.amazonaws.com`
2. In the IAM console, create a new IAM role and select "Custom trust policy".
- Use the following trust policy to allow EKS to use this role:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:<NAMESPACE>:ack-elbv2-controller",
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
}
}
}
]
}
```
- Replace `<AWS_ACCOUNT_ID>`、`<REGION>`、`<OIDC_ID>`、`<NAMESPACE>` and `<SERVICE_ACCOUNT_NAME>` with your actual values.
- Add the permission `ElasticLoadBalancingFullAccess`
##### Deploy AWS Load Balancer Controller
CRD and controller for TargetGroupBinding: https://github.com/kubernetes-sigs/aws-load-balancer-controller/
Official deployment documentation: https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html, essentially authorizing k8s ServiceAccount in a way similar to an IAM role.
#### Network Parameters
NlbARNs
- Meaning: Fill in the ARN of the nlb, you can fill in multiple, and nlb needs to be created in AWS in advance.
- Format: Separate each nlbARN with a comma. For example: arn:aws:elasticloadbalancing:us-east-1:888888888888:loadbalancer/net/aaa/3b332e6841f23870,arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/net/bbb/5fe74944d794d27e
- Support for change: Yes
NlbVPCId
- Meaning: Fill in the vpcid where nlb is located, needed for creating AWS target groups.
- Format: String. For example: vpc-0bbc9f9f0ffexxxxx
- Support for change: Yes
NlbHealthCheck
- Meaning: Fill in the health check parameters for the nlb target group, can be left blank to use default values.
- Format: Separate each configuration with a comma. For example: "healthCheckEnabled:true,healthCheckIntervalSeconds:30,healthCheckPath:/health,healthCheckPort:8081,healthCheckProtocol:HTTP,healthCheckTimeoutSeconds:10,healthyThresholdCount:5,unhealthyThresholdCount:2"
- Support for change: Yes
- Parameter explanation
- **healthCheckEnabled**Indicates whether health checks are enabled. If the target type is lambda, health checks are disabled by default but can be enabled. If the target type is instance, ip, or alb, health checks are always enabled and cannot be disabled.
- **healthCheckIntervalSeconds**The approximate amount of time, in seconds, between health checks of an individual target. The range is 5-300. If the target group protocol is TCP, TLS, UDP, TCP_UDP, HTTP, or HTTPS, the default is 30 seconds. If the target group protocol is GENEVE, the default is 10 seconds. If the target type is lambda, the default is 35 seconds.
- **healthCheckPath**The destination for health checks on the targets. For HTTP/HTTPS health checks, this is the path. For GRPC protocol version, this is the path of a custom health check method with the format /package.service/method. The default is /Amazon Web Services.ALB/healthcheck.
- **healthCheckPort**The port the load balancer uses when performing health checks on targets. The default is traffic-port, which is the port on which each target receives traffic from the load balancer. If the protocol is GENEVE, the default is port 80.
- **healthCheckProtocol**The protocol the load balancer uses when performing health checks on targets. For Application Load Balancers, the default is HTTP. For Network Load Balancers and Gateway Load Balancers, the default is TCP. The GENEVE, TLS, UDP, and TCP_UDP protocols are not supported for health checks.
- **healthCheckTimeoutSeconds**The amount of time, in seconds, during which no response from a target means a failed health check. The range is 2120 seconds. For target groups with a protocol of HTTP, the default is 6 seconds. For target groups with a protocol of TCP, TLS, or HTTPS, the default is 10 seconds. For target groups with a protocol of GENEVE, the default is 5 seconds. If the target type is lambda, the default is 30 seconds.
- **healthyThresholdCount**The number of consecutive health check successes required before considering a target healthy. The range is 2-10. If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP, or HTTPS, the default is 5. For target groups with a protocol of GENEVE, the default is 5. If the target type is lambda, the default is 5.
- **unhealthyThresholdCount**The number of consecutive health check failures required before considering a target unhealthy. The range is 2-10. If the target group protocol is TCP, TCP_UDP, UDP, TLS, HTTP, or HTTPS, the default is 2. For target groups with a protocol of GENEVE, the default is 2. If the target type is lambda, the default is 5.
PortProtocols
- Meaning: Ports and protocols exposed by the pod, supports specifying multiple ports/protocols.
- Format: port1/protocol1,port2/protocol2,... (protocol should be uppercase)
- Support for change: Yes
Fixed
- Meaning: Whether the access port is fixed. If yes, even if the pod is deleted and rebuilt, the mapping between the internal and external networks will not change.
- Format: false / true
- Support for change: Yes
AllowNotReadyContainers
- Meaning: The corresponding container name that allows continuous traffic during in-place upgrades.
- Format: {containerName_0},{containerName_1},... For example: sidecar
- Support for change: Not changeable during in-place upgrades
Annotations
- Meaning: Annotations added to the service, supports specifying multiple annotations.
- Format: key1:value1,key2:value2...
- Support for change: Yes
#### #### Plugin configuration
```toml
[aws]
enable = true
[aws.nlb]
# Specify the range of free ports that NLB can use to allocate external access ports for pods, with a maximum range of 50 (closed interval)
# The limit of 50 comes from AWS's limit on the number of listeners, see: https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-limits.html
max_port = 32050
min_port = 32001
```
#### Example
```shell
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gs-demo
namespace: default
spec:
replicas: 1
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: AmazonWebServices-NLB
networkConf:
- name: NlbARNs
value: "arn:aws:elasticloadbalancing:us-east-1:xxxxxxxxxxxx:loadbalancer/net/okg-test/yyyyyyyyyyyyyyyy"
- name: NlbVPCId
value: "vpc-0bbc9f9f0ffexxxxx"
- name: PortProtocols
value: "80/TCP"
- name: NlbHealthCheck
value: "healthCheckIntervalSeconds:15"
gameServerTemplate:
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/gs-demo/gameserver:network
name: gameserver
EOF
```
## Network Isolation
Consider the following scenarios, such as:
- A major anomaly occurs in the game server, and players need to be prevented from connecting
- Before the version is updated, the player's network connection is cut off
- Other temporary service-related needs, and the service will be reopened after the operation and maintenance is processed
Compared to the method of directly deleting the game server Pod, network isolation at the access layer is a simpler and lighter operation. It can retain the Pod and GameServer metadata information without reconstruction, which improves the efficiency of reopening the service.
### Usage
GameServer.Spec has a NetworkDisabled field. The default NetworkDisabled of the GameServer generated by the GameServerSet is false, which means that network isolation will not be set when the game server is deployed at first.
When the game server has a network isolation requirement, you can set GameServer.Spec.NetworkDisabled to true manually (kubectl/K8s API) / custom service quality to start network isolation; when it is set to false, the network will be restored.
It should be noted that the network isolation function is provided by each network plugin, and users need to check whether the corresponding plugin supports the network isolation function when using the network plugin.
## Access to network information
GameServer Network Status can be obtained in two ways

View File

@ -5,8 +5,222 @@ Because a game server is stateful, a game server usually exists in a pod in the
However, the processes in a pod vary in importance. If an error occurs in a lightweight process, you may not want to delete and recreate the entire pod. Therefore, the native liveness probe feature of Kubernetes does not suit gaming scenarios.
In OpenKruiseGame, the service quality of game servers is defined by game developers. Game developers can set handling actions based on the statuses of game servers. The custom service quality feature is a combination of probing and action. This combination helps automatically deal with various issues related to game server statuses.
## Instructions for use
Use custom quality of service features via `GameServerSet.Spec.ServiceQualities`. Its detailed data structure is as follows:
```
type GameServerSetSpec struct {
// ...
ServiceQualities []ServiceQuality `json:"serviceQualities,omitempty"`
// ...
}
type ServiceQuality struct {
corev1.Probe `json:",inline"`
Name string `json:"name"`
ContainerName string `json:"containerName,omitempty"`
// Whether to make GameServerSpec not change after the ServiceQualityAction is executed.
// When Permanent is true, regardless of the detection results, ServiceQualityAction will only be executed once.
// When Permanent is false, ServiceQualityAction can be executed again even though ServiceQualityAction has been executed.
Permanent bool `json:"permanent"`
ServiceQualityAction []ServiceQualityAction `json:"serviceQualityAction,omitempty"`
}
type ServiceQualityAction struct {
State bool `json:"state"`
// Result indicate the probe message returned by the script.
// When Result is defined, it would exec action only when the according Result is actually returns.
Result string `json:"result,omitempty"`
GameServerSpec `json:",inline"`
Annotations map[string]string `json:"annotations,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
```
Users implement a detection script to reveal the business/operation and maintenance status in the container to the Kubernetes GameServer object.
Supports multiple result output: the exit code 0 in the script corresponds to the State of ServiceQualityAction is true; the exit code 1 in the script corresponds to the State of ServiceQualityAction is false; the echo string in the script corresponds to the Result value of ServiceQualityAction.
When State and Result are satisfied at the same time, GameServer's GameServerSpec/Annotations/Labels will be set according to the parameters filled in by the user. GameServerSpec includes OpsState/NetworkDisabled, etc. The specific fields are as follows:
```
type GameServerSpec struct {
OpsState OpsState `json:"opsState,omitempty"`
UpdatePriority *intstr.IntOrString `json:"updatePriority,omitempty"`
DeletionPriority *intstr.IntOrString `json:"deletionPriority,omitempty"`
NetworkDisabled bool `json:"networkDisabled,omitempty"`
// Containers can be used to make the corresponding GameServer container fields
// different from the fields defined by GameServerTemplate in GameServerSetSpec.
Containers []GameServerContainer `json:"containers,omitempty"`
}
```
## Example
Lets take an example to see how to realize multiple status awareness of the game server through a detection script.
When making a container image, write a script to detect the status of the container. The sample script probe.sh will detect whether the gate process and data process exist.
When the gate process does not exist, it outputs "gate" and exits normally; when the data process does not exist, it outputs "data" and exits normally; when there is no exception, it exits with exit code 1.
The probe.sh script is a detection script within the business container, which is periodically called by OKG.
Its principle is similar to the Kubernetes native liveness/readiness probes. In the aforementioned scenario, the pseudocode for probe.sh is as follows:
```shell
#!/bin/bash
gate=$(ps -ef | grep gate | grep -v grep | wc -l)
data=$(ps -ef | grep data | grep -v grep | wc -l)
if [ $gate != 1 ]
then
echo "gate"
exit 0
fi
if [ $data != 1 ]
then
echo "data"
exit 0
fi
exit 1
```
The corresponding yaml of GameServerSet is as follows:
```yaml
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: minecraft
namespace: default
spec:
replicas: 3
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
maxUnavailable: 100%
gameServerTemplate:
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/chrisliu95/minecraft-demo:probe-v0
name: minecraft
serviceQualities:
- name: healthy
containerName: minecraft
permanent: false
exec:
command: ["bash", "./probe.sh"]
serviceQualityAction:
- state: true
result: gate
opsState: GateMaintaining
- state: true
result: data
opsState: DataMaintaining
- state: false
opsState: None
```
After the deployment is completed, 3 Pods and GameServer are generated
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 14s
minecraft-1 Ready None 0 0 14s
minecraft-2 Ready None 0 0 14s
kubectl get po
NAME READY STATUS RESTARTS AGE
minecraft-0 1/1 Running 0 15s
minecraft-1 1/1 Running 0 15s
minecraft-2 1/1 Running 0 15s
```
Enter the minecraft-0 container, simulate the gate process failure, and kill its corresponding process number.
```bash
kubectl exec -it minecraft-0 /bin/bash
/data# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:00 ? 00:00:00 /bin/bash ./start.sh
root 7 1 0 03:00 ? 00:00:00 /bin/bash ./gate.sh
root 8 1 0 03:00 ? 00:00:00 /bin/bash ./data.sh
root 9 1 99 03:00 ? 00:00:24 java -jar /minecraft_server.
...
/data# kill -9 7
/data# exit
```
Get the opsState of the current gs, which has changed to GateMaintaining
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready GateMaintaining 0 0 2m14s
minecraft-1 Ready None 0 0 2m14s
minecraft-2 Ready None 0 0 2m14s
```
Enter the minecraft-1 container, simulate the data process failure, and kil its corresponding process number.
```bash
kubectl exec -it minecraft-1 /bin/bash
/data# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:00 ? 00:00:00 /bin/bash ./start.sh
root 7 1 0 03:00 ? 00:00:00 /bin/bash ./gate.sh
root 8 1 0 03:00 ? 00:00:00 /bin/bash ./data.sh
root 9 1 99 03:00 ? 00:00:24 java -jar /minecraft_server.
...
/data# kill -9 8
/data# exit
```
Get the opsState of the current gs, which has changed to DataMaintaining
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready GateMaintaining 0 0 3m10s
minecraft-1 Ready DataMaintaining 0 0 3m10s
minecraft-2 Ready None 0 0 3m10s
```
Enter minecraft-0 and minecraft-1 respectively, and manually pull up the hung process:
```bash
kubectl exec -it minecraft-0 /bin/bash
/data# bash ./gate.sh &
/data# exit
kubectl exec -it minecraft-1 /bin/bash
/data# bash ./data.sh &
/data# exit
```
At this time, the operation and maintenance status of gs has returned to None.
```bash
kubectl get gs
NAME STATE OPSSTATE DP UP AGE
minecraft-0 Ready None 0 0 5m6s
minecraft-1 Ready None 0 0 5m6s
minecraft-2 Ready None 0 0 5m6s
```
## Usage Scenarios
### Set the O&M status of idle game servers to WaitToBeDeleted
Deploy a GameServerSet that contains the custom service quality field.

View File

@ -33,6 +33,7 @@ module.exports = {
'user-manuals/container-startup-sequence-control',
'user-manuals/service-qualities',
'user-manuals/network',
'user-manuals/lifecycle',
'user-manuals/gameserver-monitor',
'user-manuals/game-matchmaking',
'user-manuals/game-dashboard',

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB