feat(*): add volcengine provider and clb plugin (#127)

Co-authored-by: 李志朋 <lizhipeng.629@bytedance.com>
This commit is contained in:
lizhipeng629 2024-01-29 16:36:23 +08:00 committed by GitHub
parent 2b6ce6fcfc
commit 9c203d01c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1140 additions and 0 deletions

View File

@ -45,11 +45,13 @@ type ConfigFile struct {
type CloudProviderConfig struct {
KubernetesOptions CloudProviderOptions
AlibabaCloudOptions CloudProviderOptions
VolcengineOptions CloudProviderOptions
}
type tomlConfigs struct {
Kubernetes options.KubernetesOptions `toml:"kubernetes"`
AlibabaCloud options.AlibabaCloudOptions `toml:"alibabacloud"`
Volcengine options.VolcengineOptions `toml:"volcengine"`
}
func (cf *ConfigFile) Parse() *CloudProviderConfig {
@ -62,6 +64,7 @@ func (cf *ConfigFile) Parse() *CloudProviderConfig {
return &CloudProviderConfig{
KubernetesOptions: config.Kubernetes,
AlibabaCloudOptions: config.AlibabaCloud,
VolcengineOptions: config.Volcengine,
}
}

View File

@ -22,6 +22,7 @@ import (
"github.com/openkruise/kruise-game/cloudprovider"
"github.com/openkruise/kruise-game/cloudprovider/alibabacloud"
"github.com/openkruise/kruise-game/cloudprovider/kubernetes"
volcengine "github.com/openkruise/kruise-game/cloudprovider/volcengine"
corev1 "k8s.io/api/core/v1"
log "k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -116,5 +117,15 @@ func NewProviderManager() (*ProviderManager, error) {
}
}
if configs.VolcengineOptions.Valid() && configs.VolcengineOptions.Enabled() {
// build and register volcengine cloud provider
vcp, err := volcengine.NewVolcengineProvider()
if err != nil {
log.Errorf("Failed to initialize volcengine provider.because of %s", err.Error())
} else {
pm.RegisterCloudProvider(vcp, configs.VolcengineOptions)
}
}
return pm, nil
}

View File

@ -0,0 +1,31 @@
package options
type VolcengineOptions struct {
Enable bool `toml:"enable"`
CLBOptions CLBOptions `toml:"clb"`
}
type CLBOptions struct {
MaxPort int32 `toml:"max_port"`
MinPort int32 `toml:"min_port"`
}
func (v VolcengineOptions) Valid() bool {
clbOptions := v.CLBOptions
if clbOptions.MaxPort-clbOptions.MinPort > 200 {
return false
}
if clbOptions.MaxPort > 65535 {
return false
}
if clbOptions.MinPort < 1 {
return false
}
return true
}
func (v VolcengineOptions) Enabled() bool {
return v.Enable
}

View File

@ -0,0 +1,108 @@
English | [中文](./README.zh_CN.md)
The Volcaengine Kubernetes Engine supports the CLB reuse mechanism in k8s. Different SVCs can use different ports of the same CLB. Therefore, the Volcengine-CLB network plugin will record the port allocation corresponding to each CLB. For the specified network type as Volcengine-CLB, the Volcengine-CLB network plugin will automatically allocate a port and create a service object. Wait for the svc ingress field. After the public network IP is successfully created, the GameServer network is in the Ready state and the process is completed.
![image](https://github.com/lizhipeng629/kruise-game/assets/110802158/209de309-b9b7-4ba8-b2fb-da0d299e2edb)
## Volcengine-CLB configuration
### plugin configuration
```toml
[volcengine]
enable = true
[volcengine.clb]
#Fill in the free port segment that clb can use to allocate external access ports to pods, The maximum port range is 200.
max_port = 700
min_port = 500
```
### Parameter
#### ClbIds
- Meaningfill in the id of the clb. You can fill in more than one. You need to create the clb in [Volcano Engine].
- Valueeach clbId is divided by `,` . For example: `clb-9zeo7prq1m25ctpfrw1m7`,`clb-bp1qz7h50yd3w58h2f8je`,...
- ConfigurableY
#### PortProtocols
- Meaningthe ports and protocols exposed by the pod, support filling in multiple ports/protocols
- Value`port1/protocol1`,`port2/protocol2`,... The protocol names must be in uppercase letters.
- ConfigurableY
#### Fixed
- Meaningwhether the mapping relationship is fixed. If the mapping relationship is fixed, the mapping relationship remains unchanged even if the pod is deleted and recreated.
- Valuefalse / true
- ConfigurableY
#### AllowNotReadyContainers
- Meaningthe container names that are allowed not ready when inplace updating, when traffic will not be cut.
- Value{containerName_0},{containerName_1},... egsidecar
- ConfigurableIt cannot be changed during the in-place updating process.
### Example
```yaml
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gss-2048-clb
namespace: default
spec:
replicas: 3
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: Volcengine-CLB
networkConf:
- name: ClbIds
#Fill in Volcengine Cloud LoadBalancer Id here
value: clb-xxxxx
- name: PortProtocols
#Fill in the exposed ports and their corresponding protocols here.
#If there are multiple ports, the format is as follows: {port1}/{protocol1},{port2}/{protocol2}...
#If the protocol is not filled in, the default is TCP
value: 80/TCP
- name: Fixed
#Fill in here whether a fixed IP is required [optional] ; Default is false
value: "false"
gameServerTemplate:
spec:
containers:
- image: cr-helm2-cn-beijing.cr.volces.com/kruise/2048:v1.0
name: app-2048
volumeMounts:
- name: shared-dir
mountPath: /var/www/html/js
- image: cr-helm2-cn-beijing.cr.volces.com/kruise/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
```
Check the network status in GameServer:
```
networkStatus:
createTime: "2024-01-19T08:19:49Z"
currentNetworkState: Ready
desiredNetworkState: Ready
externalAddresses:
- ip: xxx.xxx.xx.xxx
ports:
- name: "80"
port: 6611
protocol: TCP
internalAddresses:
- ip: 172.16.200.60
ports:
- name: "80"
port: 80
protocol: TCP
lastTransitionTime: "2024-01-19T08:19:49Z"
networkType: Volcengine-CLB
```

View File

@ -0,0 +1,108 @@
中文 | [English](./README.md)
火山引擎容器服务支持在k8s中对CLB复用的机制不同的svc可以使用同一个CLB的不同端口。由此Volcengine-CLB network plugin将记录各CLB对应的端口分配情况对于指定了网络类型为Volcengine-CLBVolcengine-CLB网络插件将会自动分配一个端口并创建一个service对象待svc ingress字段的公网IP创建成功后GameServer的网络处于Ready状态该过程执行完成。
![image](https://github.com/lizhipeng629/kruise-game/assets/110802158/209de309-b9b7-4ba8-b2fb-da0d299e2edb)
## Volcengine-CLB 相关配置
### plugin配置
```toml
[volcengine]
enable = true
[volcengine.clb]
#填写clb可使用的空闲端口段用于为pod分配外部接入端口范围最大为200
max_port = 700
min_port = 500
```
### 参数
#### ClbIds
- 含义填写clb的id可填写多个需要现在【火山引擎】中创建好clb。
- 填写格式各个clbId用,分割。例如clb-9zeo7prq1m25ctpfrw1m7,clb-bp1qz7h50yd3w58h2f8je,...
- 是否支持变更:是
#### PortProtocols
- 含义pod暴露的端口及协议支持填写多个端口/协议
- 填写格式port1/protocol1,port2/protocol2,...(协议需大写)
- 是否支持变更:是
#### Fixed
- 含义是否固定访问IP/端口。若是即使pod删除重建网络内外映射关系不会改变
- 填写格式false / true
- 是否支持变更:是
#### AllowNotReadyContainers
- 含义:在容器原地升级时允许不断流的对应容器名称,可填写多个
- 填写格式:{containerName_0},{containerName_1},... 例如sidecar
- 是否支持变更:在原地升级过程中不可变更。
### 使用示例
```yaml
cat <<EOF | kubectl apply -f -
apiVersion: game.kruise.io/v1alpha1
kind: GameServerSet
metadata:
name: gss-2048-clb
namespace: default
spec:
replicas: 3
updateStrategy:
rollingUpdate:
podUpdatePolicy: InPlaceIfPossible
network:
networkType: Volcengine-CLB
networkConf:
- name: ClbIds
#Fill in Volcengine Cloud LoadBalancer Id here
value: clb-xxxxx
- name: PortProtocols
#Fill in the exposed ports and their corresponding protocols here.
#If there are multiple ports, the format is as follows: {port1}/{protocol1},{port2}/{protocol2}...
#If the protocol is not filled in, the default is TCP
value: 80/TCP
- name: Fixed
#Fill in here whether a fixed IP is required [optional] ; Default is false
value: "false"
gameServerTemplate:
spec:
containers:
- image: cr-helm2-cn-beijing.cr.volces.com/kruise/2048:v1.0
name: app-2048
volumeMounts:
- name: shared-dir
mountPath: /var/www/html/js
- image: cr-helm2-cn-beijing.cr.volces.com/kruise/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
```
检查GameServer中的网络状态:
```
networkStatus:
createTime: "2024-01-19T08:19:49Z"
currentNetworkState: Ready
desiredNetworkState: Ready
externalAddresses:
- ip: xxx.xxx.xx.xxx
ports:
- name: "80"
port: 6611
protocol: TCP
internalAddresses:
- ip: 172.16.200.60
ports:
- name: "80"
port: 80
protocol: TCP
lastTransitionTime: "2024-01-19T08:19:49Z"
networkType: Volcengine-CLB
```

View File

@ -0,0 +1,477 @@
/*
Copyright 2024 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package volcengine
import (
"context"
"fmt"
gamekruiseiov1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
"github.com/openkruise/kruise-game/cloudprovider"
cperrors "github.com/openkruise/kruise-game/cloudprovider/errors"
provideroptions "github.com/openkruise/kruise-game/cloudprovider/options"
"github.com/openkruise/kruise-game/cloudprovider/utils"
"github.com/openkruise/kruise-game/pkg/util"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
log "k8s.io/klog/v2"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"strconv"
"strings"
"sync"
)
const (
ClbNetwork = "Volcengine-CLB"
AliasCLB = "CLB-Network"
ClbIdLabelKey = "service.beta.kubernetes.io/volcengine-loadbalancer-id"
ClbIdsConfigName = "ClbIds"
PortProtocolsConfigName = "PortProtocols"
FixedConfigName = "Fixed"
ClbConfigHashKey = "game.kruise.io/network-config-hash"
ClbIdAnnotationKey = "service.beta.kubernetes.io/volcengine-loadbalancer-id"
ClbAddressTypeKey = "service.beta.kubernetes.io/volcengine-loadbalancer-address-type"
ClbAddressTypePublic = "PUBLIC"
ClbSchedulerKey = "service.beta.kubernetes.io/volcengine-loadbalancer-scheduler"
ClbSchedulerWRR = "wrr"
SvcSelectorKey = "statefulset.kubernetes.io/pod-name"
)
type portAllocated map[int32]bool
type ClbPlugin struct {
maxPort int32
minPort int32
cache map[string]portAllocated
podAllocate map[string]string
mutex sync.RWMutex
}
type clbConfig struct {
lbIds []string
targetPorts []int
protocols []corev1.Protocol
isFixed bool
}
func (c *ClbPlugin) Name() string {
return ClbNetwork
}
func (c *ClbPlugin) Alias() string {
return AliasCLB
}
func (c *ClbPlugin) Init(client client.Client, options cloudprovider.CloudProviderOptions, ctx context.Context) error {
c.mutex.Lock()
defer c.mutex.Unlock()
clbOptions, ok := options.(provideroptions.VolcengineOptions)
if !ok {
return cperrors.ToPluginError(fmt.Errorf("failed to convert options to clbOptions"), cperrors.InternalError)
}
c.minPort = clbOptions.CLBOptions.MinPort
c.maxPort = clbOptions.CLBOptions.MaxPort
svcList := &corev1.ServiceList{}
err := client.List(ctx, svcList)
if err != nil {
return err
}
c.cache, c.podAllocate = initLbCache(svcList.Items, c.minPort, c.maxPort)
return nil
}
func initLbCache(svcList []corev1.Service, minPort, maxPort int32) (map[string]portAllocated, map[string]string) {
newCache := make(map[string]portAllocated)
newPodAllocate := make(map[string]string)
for _, svc := range svcList {
lbId := svc.Labels[ClbIdLabelKey]
if lbId != "" && svc.Spec.Type == corev1.ServiceTypeLoadBalancer {
if newCache[lbId] == nil {
newCache[lbId] = make(portAllocated, maxPort-minPort)
for i := minPort; i < maxPort; i++ {
newCache[lbId][i] = false
}
}
var ports []int32
for _, port := range getPorts(svc.Spec.Ports) {
if port <= maxPort && port >= minPort {
newCache[lbId][port] = true
ports = append(ports, port)
}
}
if len(ports) != 0 {
newPodAllocate[svc.GetNamespace()+"/"+svc.GetName()] = lbId + ":" + util.Int32SliceToString(ports, ",")
}
}
}
log.Infof("[%s] podAllocate cache complete initialization: %v", ClbNetwork, newPodAllocate)
return newCache, newPodAllocate
}
func (c *ClbPlugin) OnPodAdded(client client.Client, pod *corev1.Pod, ctx context.Context) (*corev1.Pod, cperrors.PluginError) {
networkManager := utils.NewNetworkManager(pod, client)
networkConfig := networkManager.GetNetworkConfig()
sc := parseLbConfig(networkConfig)
err := client.Create(ctx, c.consSvc(sc, pod, client, ctx))
return pod, cperrors.ToPluginError(err, cperrors.ApiCallError)
}
func (c *ClbPlugin) OnPodUpdated(client client.Client, pod *corev1.Pod, ctx context.Context) (*corev1.Pod, cperrors.PluginError) {
networkManager := utils.NewNetworkManager(pod, client)
networkStatus, err := networkManager.GetNetworkStatus()
if err != nil {
return pod, cperrors.ToPluginError(err, cperrors.InternalError)
}
networkConfig := networkManager.GetNetworkConfig()
config := parseLbConfig(networkConfig)
if networkStatus == nil {
pod, err := networkManager.UpdateNetworkStatus(gamekruiseiov1alpha1.NetworkStatus{
CurrentNetworkState: gamekruiseiov1alpha1.NetworkNotReady,
}, pod)
return pod, cperrors.ToPluginError(err, cperrors.InternalError)
}
// get svc
svc := &corev1.Service{}
err = client.Get(ctx, types.NamespacedName{
Name: pod.GetName(),
Namespace: pod.GetNamespace(),
}, svc)
if err != nil {
if errors.IsNotFound(err) {
return pod, cperrors.ToPluginError(client.Create(ctx, c.consSvc(config, pod, client, ctx)), cperrors.ApiCallError)
}
return pod, cperrors.NewPluginError(cperrors.ApiCallError, err.Error())
}
// update svc
if util.GetHash(config) != svc.GetAnnotations()[ClbConfigHashKey] {
networkStatus.CurrentNetworkState = gamekruiseiov1alpha1.NetworkNotReady
pod, err = networkManager.UpdateNetworkStatus(*networkStatus, pod)
if err != nil {
return pod, cperrors.NewPluginError(cperrors.InternalError, err.Error())
}
return pod, cperrors.ToPluginError(client.Update(ctx, c.consSvc(config, pod, client, ctx)), cperrors.ApiCallError)
}
// disable network
if networkManager.GetNetworkDisabled() && svc.Spec.Type == corev1.ServiceTypeLoadBalancer {
svc.Spec.Type = corev1.ServiceTypeClusterIP
return pod, cperrors.ToPluginError(client.Update(ctx, svc), cperrors.ApiCallError)
}
// enable network
if !networkManager.GetNetworkDisabled() && svc.Spec.Type == corev1.ServiceTypeClusterIP {
svc.Spec.Type = corev1.ServiceTypeLoadBalancer
return pod, cperrors.ToPluginError(client.Update(ctx, svc), cperrors.ApiCallError)
}
// network not ready
if len(svc.Status.LoadBalancer.Ingress) == 0 {
networkStatus.CurrentNetworkState = gamekruiseiov1alpha1.NetworkNotReady
pod, err = networkManager.UpdateNetworkStatus(*networkStatus, pod)
return pod, cperrors.ToPluginError(err, cperrors.InternalError)
}
// allow not ready containers
if util.IsAllowNotReadyContainers(networkManager.GetNetworkConfig()) {
toUpDateSvc, err := utils.AllowNotReadyContainers(client, ctx, pod, svc, false)
if err != nil {
return pod, err
}
if toUpDateSvc {
err := client.Update(ctx, svc)
if err != nil {
return pod, cperrors.ToPluginError(err, cperrors.ApiCallError)
}
}
}
// network ready
internalAddresses := make([]gamekruiseiov1alpha1.NetworkAddress, 0)
externalAddresses := make([]gamekruiseiov1alpha1.NetworkAddress, 0)
for _, port := range svc.Spec.Ports {
instrIPort := port.TargetPort
instrEPort := intstr.FromInt(int(port.Port))
internalAddress := gamekruiseiov1alpha1.NetworkAddress{
IP: pod.Status.PodIP,
Ports: []gamekruiseiov1alpha1.NetworkPort{
{
Name: instrIPort.String(),
Port: &instrIPort,
Protocol: port.Protocol,
},
},
}
externalAddress := gamekruiseiov1alpha1.NetworkAddress{
IP: svc.Status.LoadBalancer.Ingress[0].IP,
Ports: []gamekruiseiov1alpha1.NetworkPort{
{
Name: instrIPort.String(),
Port: &instrEPort,
Protocol: port.Protocol,
},
},
}
internalAddresses = append(internalAddresses, internalAddress)
externalAddresses = append(externalAddresses, externalAddress)
}
networkStatus.InternalAddresses = internalAddresses
networkStatus.ExternalAddresses = externalAddresses
networkStatus.CurrentNetworkState = gamekruiseiov1alpha1.NetworkReady
pod, err = networkManager.UpdateNetworkStatus(*networkStatus, pod)
return pod, cperrors.ToPluginError(err, cperrors.InternalError)
}
func (c *ClbPlugin) OnPodDeleted(client client.Client, pod *corev1.Pod, ctx context.Context) cperrors.PluginError {
networkManager := utils.NewNetworkManager(pod, client)
networkConfig := networkManager.GetNetworkConfig()
sc := parseLbConfig(networkConfig)
var podKeys []string
if sc.isFixed {
gss, err := util.GetGameServerSetOfPod(pod, client, ctx)
if err != nil && !errors.IsNotFound(err) {
return cperrors.ToPluginError(err, cperrors.ApiCallError)
}
// gss exists in cluster, do not deAllocate.
if err == nil && gss.GetDeletionTimestamp() == nil {
return nil
}
// gss not exists in cluster, deAllocate all the ports related to it.
for key := range c.podAllocate {
gssName := pod.GetLabels()[gamekruiseiov1alpha1.GameServerOwnerGssKey]
if strings.Contains(key, pod.GetNamespace()+"/"+gssName) {
podKeys = append(podKeys, key)
}
}
} else {
podKeys = append(podKeys, pod.GetNamespace()+"/"+pod.GetName())
}
for _, podKey := range podKeys {
c.deAllocate(podKey)
}
return nil
}
func (c *ClbPlugin) allocate(lbIds []string, num int, nsName string) (string, []int32) {
c.mutex.Lock()
defer c.mutex.Unlock()
var ports []int32
var lbId string
// find lb with adequate ports
for _, clbId := range lbIds {
sum := 0
for i := c.minPort; i < c.maxPort; i++ {
if !c.cache[clbId][i] {
sum++
}
if sum >= num {
lbId = clbId
break
}
}
}
// select ports
for i := 0; i < num; i++ {
var port int32
if c.cache[lbId] == nil {
c.cache[lbId] = make(portAllocated, c.maxPort-c.minPort)
for i := c.minPort; i < c.maxPort; i++ {
c.cache[lbId][i] = false
}
}
for p, allocated := range c.cache[lbId] {
if !allocated {
port = p
break
}
}
c.cache[lbId][port] = true
ports = append(ports, port)
}
c.podAllocate[nsName] = lbId + ":" + util.Int32SliceToString(ports, ",")
log.Infof("pod %s allocate clb %s ports %v", nsName, lbId, ports)
return lbId, ports
}
func (c *ClbPlugin) deAllocate(nsName string) {
c.mutex.Lock()
defer c.mutex.Unlock()
allocatedPorts, exist := c.podAllocate[nsName]
if !exist {
return
}
clbPorts := strings.Split(allocatedPorts, ":")
lbId := clbPorts[0]
ports := util.StringToInt32Slice(clbPorts[1], ",")
for _, port := range ports {
c.cache[lbId][port] = false
}
delete(c.podAllocate, nsName)
log.Infof("pod %s deallocate clb %s ports %v", nsName, lbId, ports)
}
func init() {
clbPlugin := ClbPlugin{
mutex: sync.RWMutex{},
}
volcengineProvider.registerPlugin(&clbPlugin)
}
func parseLbConfig(conf []gamekruiseiov1alpha1.NetworkConfParams) *clbConfig {
var lbIds []string
ports := make([]int, 0)
protocols := make([]corev1.Protocol, 0)
isFixed := false
for _, c := range conf {
switch c.Name {
case ClbIdsConfigName:
for _, clbId := range strings.Split(c.Value, ",") {
if clbId != "" {
lbIds = append(lbIds, clbId)
}
}
case PortProtocolsConfigName:
for _, pp := range strings.Split(c.Value, ",") {
ppSlice := strings.Split(pp, "/")
port, err := strconv.Atoi(ppSlice[0])
if err != nil {
continue
}
ports = append(ports, port)
if len(ppSlice) != 2 {
protocols = append(protocols, corev1.ProtocolTCP)
} else {
protocols = append(protocols, corev1.Protocol(ppSlice[1]))
}
}
case FixedConfigName:
v, err := strconv.ParseBool(c.Value)
if err != nil {
continue
}
isFixed = v
}
}
return &clbConfig{
lbIds: lbIds,
protocols: protocols,
targetPorts: ports,
isFixed: isFixed,
}
}
func getPorts(ports []corev1.ServicePort) []int32 {
var ret []int32
for _, port := range ports {
ret = append(ret, port.Port)
}
return ret
}
func (c *ClbPlugin) consSvc(config *clbConfig, pod *corev1.Pod, client client.Client, ctx context.Context) *corev1.Service {
var ports []int32
var lbId string
podKey := pod.GetNamespace() + "/" + pod.GetName()
allocatedPorts, exist := c.podAllocate[podKey]
if exist {
clbPorts := strings.Split(allocatedPorts, ":")
lbId = clbPorts[0]
ports = util.StringToInt32Slice(clbPorts[1], ",")
} else {
lbId, ports = c.allocate(config.lbIds, len(config.targetPorts), podKey)
}
svcPorts := make([]corev1.ServicePort, 0)
for i := 0; i < len(config.targetPorts); i++ {
svcPorts = append(svcPorts, corev1.ServicePort{
Name: strconv.Itoa(config.targetPorts[i]),
Port: ports[i],
Protocol: config.protocols[i],
TargetPort: intstr.FromInt(config.targetPorts[i]),
})
}
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: pod.GetName(),
Namespace: pod.GetNamespace(),
Annotations: map[string]string{
ClbSchedulerKey: ClbSchedulerWRR,
ClbAddressTypeKey: ClbAddressTypePublic,
ClbIdAnnotationKey: lbId,
ClbConfigHashKey: util.GetHash(config),
},
OwnerReferences: getSvcOwnerReference(client, ctx, pod, config.isFixed),
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Selector: map[string]string{
SvcSelectorKey: pod.GetName(),
},
Ports: svcPorts,
},
}
return svc
}
func getSvcOwnerReference(c client.Client, ctx context.Context, pod *corev1.Pod, isFixed bool) []metav1.OwnerReference {
ownerReferences := []metav1.OwnerReference{
{
APIVersion: pod.APIVersion,
Kind: pod.Kind,
Name: pod.GetName(),
UID: pod.GetUID(),
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
}
if isFixed {
gss, err := util.GetGameServerSetOfPod(pod, c, ctx)
if err == nil {
ownerReferences = []metav1.OwnerReference{
{
APIVersion: gss.APIVersion,
Kind: gss.Kind,
Name: gss.GetName(),
UID: gss.GetUID(),
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
}
}
}
return ownerReferences
}

View File

@ -0,0 +1,335 @@
/*
Copyright 2024 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package volcengine
import (
"context"
"reflect"
"sync"
"testing"
gamekruiseiov1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
"github.com/openkruise/kruise-game/pkg/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func TestAllocateDeAllocate(t *testing.T) {
test := struct {
lbIds []string
clb *ClbPlugin
num int
podKey string
}{
lbIds: []string{"xxx-A"},
clb: &ClbPlugin{
maxPort: int32(712),
minPort: int32(512),
cache: make(map[string]portAllocated),
podAllocate: make(map[string]string),
mutex: sync.RWMutex{},
},
podKey: "xxx/xxx",
num: 3,
}
lbId, ports := test.clb.allocate(test.lbIds, test.num, test.podKey)
if _, exist := test.clb.podAllocate[test.podKey]; !exist {
t.Errorf("podAllocate[%s] is empty after allocated", test.podKey)
}
for _, port := range ports {
if port > test.clb.maxPort || port < test.clb.minPort {
t.Errorf("allocate port %d, unexpected", port)
}
if test.clb.cache[lbId][port] == false {
t.Errorf("Allocate port %d failed", port)
}
}
test.clb.deAllocate(test.podKey)
for _, port := range ports {
if test.clb.cache[lbId][port] == true {
t.Errorf("deAllocate port %d failed", port)
}
}
if _, exist := test.clb.podAllocate[test.podKey]; exist {
t.Errorf("podAllocate[%s] is not empty after deallocated", test.podKey)
}
}
func TestParseLbConfig(t *testing.T) {
tests := []struct {
conf []gamekruiseiov1alpha1.NetworkConfParams
lbIds []string
ports []int
protocols []corev1.Protocol
isFixed bool
}{
{
conf: []gamekruiseiov1alpha1.NetworkConfParams{
{
Name: ClbIdsConfigName,
Value: "xxx-A",
},
{
Name: PortProtocolsConfigName,
Value: "80",
},
},
lbIds: []string{"xxx-A"},
ports: []int{80},
protocols: []corev1.Protocol{corev1.ProtocolTCP},
isFixed: false,
},
{
conf: []gamekruiseiov1alpha1.NetworkConfParams{
{
Name: ClbIdsConfigName,
Value: "xxx-A,xxx-B,",
},
{
Name: PortProtocolsConfigName,
Value: "81/UDP,82,83/TCP",
},
{
Name: FixedConfigName,
Value: "true",
},
},
lbIds: []string{"xxx-A", "xxx-B"},
ports: []int{81, 82, 83},
protocols: []corev1.Protocol{corev1.ProtocolUDP, corev1.ProtocolTCP, corev1.ProtocolTCP},
isFixed: true,
},
}
for _, test := range tests {
sc := parseLbConfig(test.conf)
if !reflect.DeepEqual(test.lbIds, sc.lbIds) {
t.Errorf("lbId expect: %v, actual: %v", test.lbIds, sc.lbIds)
}
if !util.IsSliceEqual(test.ports, sc.targetPorts) {
t.Errorf("ports expect: %v, actual: %v", test.ports, sc.targetPorts)
}
if !reflect.DeepEqual(test.protocols, sc.protocols) {
t.Errorf("protocols expect: %v, actual: %v", test.protocols, sc.protocols)
}
if test.isFixed != sc.isFixed {
t.Errorf("isFixed expect: %v, actual: %v", test.isFixed, sc.isFixed)
}
}
}
func TestInitLbCache(t *testing.T) {
test := struct {
svcList []corev1.Service
minPort int32
maxPort int32
cache map[string]portAllocated
podAllocate map[string]string
}{
minPort: 512,
maxPort: 712,
cache: map[string]portAllocated{
"xxx-A": map[int32]bool{
666: true,
},
"xxx-B": map[int32]bool{
555: true,
},
},
podAllocate: map[string]string{
"ns-0/name-0": "xxx-A:666",
"ns-1/name-1": "xxx-B:555",
},
svcList: []corev1.Service{
{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
ClbIdLabelKey: "xxx-A",
},
Namespace: "ns-0",
Name: "name-0",
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Selector: map[string]string{
SvcSelectorKey: "pod-A",
},
Ports: []corev1.ServicePort{
{
TargetPort: intstr.FromInt(80),
Port: 666,
Protocol: corev1.ProtocolTCP,
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
ClbIdLabelKey: "xxx-B",
},
Namespace: "ns-1",
Name: "name-1",
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Selector: map[string]string{
SvcSelectorKey: "pod-B",
},
Ports: []corev1.ServicePort{
{
TargetPort: intstr.FromInt(8080),
Port: 555,
Protocol: corev1.ProtocolTCP,
},
},
},
},
},
}
actualCache, actualPodAllocate := initLbCache(test.svcList, test.minPort, test.maxPort)
for lb, pa := range test.cache {
for port, isAllocated := range pa {
if actualCache[lb][port] != isAllocated {
t.Errorf("lb %s port %d isAllocated, expect: %t, actual: %t", lb, port, isAllocated, actualCache[lb][port])
}
}
}
if !reflect.DeepEqual(actualPodAllocate, test.podAllocate) {
t.Errorf("podAllocate expect %v, but actully got %v", test.podAllocate, actualPodAllocate)
}
}
func TestClbPlugin_consSvc(t *testing.T) {
type fields struct {
maxPort int32
minPort int32
cache map[string]portAllocated
podAllocate map[string]string
}
type args struct {
config *clbConfig
pod *corev1.Pod
client client.Client
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want *corev1.Service
}{
{
name: "convert svc cache exist",
fields: fields{
maxPort: 3000,
minPort: 1,
cache: map[string]portAllocated{
"default/test-pod": map[int32]bool{},
},
podAllocate: map[string]string{
"default/test-pod": "clb-xxx:80,81",
},
},
args: args{
config: &clbConfig{
lbIds: []string{"clb-xxx"},
targetPorts: []int{82},
protocols: []corev1.Protocol{
corev1.ProtocolTCP,
},
isFixed: false,
},
pod: &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
UID: "32fqwfqfew",
},
},
client: nil,
ctx: context.Background(),
},
want: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
Annotations: map[string]string{
ClbSchedulerKey: ClbSchedulerWRR,
ClbAddressTypeKey: ClbAddressTypePublic,
ClbIdAnnotationKey: "clb-xxx",
ClbConfigHashKey: util.GetHash(&clbConfig{
lbIds: []string{"clb-xxx"},
targetPorts: []int{82},
protocols: []corev1.Protocol{
corev1.ProtocolTCP,
},
isFixed: false,
}),
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "pod",
Name: "test-pod",
UID: "32fqwfqfew",
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
},
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Selector: map[string]string{
SvcSelectorKey: "test-pod",
},
Ports: []corev1.ServicePort{{
Name: "82",
Port: 80,
Protocol: "TCP",
TargetPort: intstr.IntOrString{
Type: 0,
IntVal: 82,
},
},
},
},
},
},
}
for _, tt := range tests {
c := &ClbPlugin{
maxPort: tt.fields.maxPort,
minPort: tt.fields.minPort,
cache: tt.fields.cache,
podAllocate: tt.fields.podAllocate,
}
if got := c.consSvc(tt.args.config, tt.args.pod, tt.args.client, tt.args.ctx); !reflect.DeepEqual(got, tt.want) {
t.Errorf("consSvc() = %v, want %v", got, tt.want)
}
}
}

View File

@ -0,0 +1,61 @@
/*
Copyright 2024 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package volcengine
import (
"github.com/openkruise/kruise-game/cloudprovider"
"k8s.io/klog/v2"
)
const (
Volcengine = "Volcengine"
)
var (
volcengineProvider = &Provider{
plugins: make(map[string]cloudprovider.Plugin),
}
)
type Provider struct {
plugins map[string]cloudprovider.Plugin
}
func (vp *Provider) Name() string {
return Volcengine
}
func (vp *Provider) ListPlugins() (map[string]cloudprovider.Plugin, error) {
if vp.plugins == nil {
return make(map[string]cloudprovider.Plugin), nil
}
return vp.plugins, nil
}
// register plugin of cloud provider and different cloud providers
func (vp *Provider) registerPlugin(plugin cloudprovider.Plugin) {
name := plugin.Name()
if name == "" {
klog.Fatal("empty plugin name")
}
vp.plugins[name] = plugin
}
func NewVolcengineProvider() (cloudprovider.CloudProvider, error) {
return volcengineProvider, nil
}

View File

@ -9,3 +9,9 @@ enable = true
[alibabacloud.slb]
max_port = 700
min_port = 500
[volcengine]
enable = true
[volcengine.clb]
max_port = 700
min_port = 500