Skip to content

Commit 97f1877

Browse files
committed
feat(securityGroup): add security group of cloud config
1 parent 40582ad commit 97f1877

File tree

7 files changed

+187
-6
lines changed

7 files changed

+187
-6
lines changed

docs/create-cloud-config-secret.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ project-id=62fac0038c52b5eb0970dd9c1a0026a
1414
# The VPC where your cluster resides
1515
id=0b876fcb-6a0b-47a3-8067-80fe9245a3da
1616
subnet-id=d2aa75cc-e356-4dc3-abb0-87078923a168
17+
security-group-id=48288809-1234-5678-abcd-eb30b2d16cd4
1718
1819
EOF
1920

docs/huawei-cloud-controller-manager-configuration.md

+9-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ auth-url=
2525
[Vpc]
2626
id=
2727
subnet-id=
28+
security-group-id=
2829
```
2930

3031
> After modification, CCM needs to be restarted to load the data.
@@ -37,7 +38,7 @@ This section provides Huawei Cloud IAM configuration and authentication informat
3738

3839
* `region` Required. This is the Huawei Cloud region.
3940

40-
**Note**: The `region` must be the same as the ECSes of the Kubernetes cluster.
41+
**Note**: The `region` must be the same as the ECS of the Kubernetes cluster.
4142

4243
* `access-key` Required. The access key of the Huawei Cloud.
4344

@@ -46,7 +47,7 @@ This section provides Huawei Cloud IAM configuration and authentication informat
4647
* `project-id` Optional. The Project ID of the Huawei Cloud.
4748
See [Obtaining a Project ID](https://support.huaweicloud.com/intl/en-us/api-evs/evs_04_0046.html).
4849

49-
**Note**: The `project-id` must be the same as the ECSes of the Kubernetes cluster.
50+
**Note**: The `project-id` must be the same as the ECS of the Kubernetes cluster.
5051

5152
* `cloud` Optional. The endpoint of the cloud provider. Defaults to `myhuaweicloud.com`'`.
5253

@@ -56,9 +57,13 @@ This section provides Huawei Cloud IAM configuration and authentication informat
5657

5758
This section contains network configuration information.
5859

59-
* `id` Optional. Specifies the VPC used by ECSes of the Kubernetes cluster.
60+
* `id` Optional. Specifies the VPC used by ECS of the Kubernetes cluster.
6061

61-
* `subnet-id` Optional. Specifies the IPv4 subnet ID used by ECSes of the Kubernetes cluster.
62+
* `subnet-id` Optional. Specifies the IPv4 subnet ID used by ECS of the Kubernetes cluster.
63+
64+
* `security-group-id` Optional. A security group ID used for associating with nodes.
65+
When new nodes are added to the cluster, they will be automatically associated with the security group.
66+
Conversely, when nodes are removed, the association will be automatically removed as well.
6267

6368
## Loadbalancer Configuration
6469

manifests/cloud-config

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ auth-url=<optional, the cloud IAM domain name>
1010
[Vpc]
1111
id=<your vpc ID>
1212
subnet-id=<your IPV4 subnet ID>
13+
security-group-id=<optional, your security group ID>

pkg/cloudprovider/huaweicloud/huaweicloud.go

+11
Original file line numberDiff line numberDiff line change
@@ -767,13 +767,23 @@ func (h *CloudProvider) listenerDeploy() error {
767767
invalidServiceCache: gocache.New(5*time.Minute, 10*time.Minute),
768768
}
769769

770+
secListener := &SecurityGroupListener{
771+
kubeClient: h.kubeClient,
772+
ecsClient: h.ecsClient,
773+
securityGroupID: h.cloudConfig.VpcOpts.SecurityGroupID,
774+
775+
stopChannel: make(chan struct{}, 1),
776+
}
777+
770778
clusterName := h.cloudControllerManagerOpts.KubeCloudShared.ClusterName
771779
id, err := os.Hostname()
772780
if err != nil {
773781
return fmt.Errorf("failed to elect leader in listener EndpointSlice : %s", err)
774782
}
775783

776784
go leaderElection(id, h.restConfig, h.eventRecorder, func(ctx context.Context) {
785+
go secListener.startSecurityGroupListener()
786+
777787
listener.startEndpointListener(func(service *v1.Service, isDelete bool) {
778788
klog.Infof("Got service %s/%s using loadbalancer class %s",
779789
service.Namespace, service.Name, utils.ToString(service.Spec.LoadBalancerClass))
@@ -832,6 +842,7 @@ func (h *CloudProvider) listenerDeploy() error {
832842
}, func() {
833843
listener.goroutinePool.Stop()
834844
listener.stopListenerSlice()
845+
secListener.stopSecurityGroupListener()
835846
})
836847

837848
return nil
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package huaweicloud
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
v1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/runtime"
10+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
11+
"k8s.io/client-go/tools/cache"
12+
"sigs.k8s.io/cloud-provider-huaweicloud/pkg/cloudprovider/huaweicloud/wrapper"
13+
14+
"k8s.io/apimachinery/pkg/watch"
15+
"k8s.io/klog/v2"
16+
)
17+
18+
type SecurityGroupListener struct {
19+
kubeClient *corev1.CoreV1Client
20+
ecsClient *wrapper.EcsClient
21+
22+
securityGroupID string
23+
24+
stopChannel chan struct{}
25+
}
26+
27+
func (s *SecurityGroupListener) startSecurityGroupListener() {
28+
if s.securityGroupID == "" {
29+
klog.Infof(`"security-group-id" is empty, nodes are added or removed will not be associated or disassociated
30+
any security groups`)
31+
return
32+
}
33+
34+
klog.Info("starting SecurityGroupListener")
35+
for {
36+
securityGroupInformer, err := s.CreateSecurityGroupInformer()
37+
if err != nil {
38+
klog.Errorf("failed to watch kubernetes cluster node list, starting SecurityGroupListener failed: %s", err)
39+
continue
40+
}
41+
42+
go securityGroupInformer.Run(s.stopChannel)
43+
break
44+
}
45+
}
46+
47+
func (s *SecurityGroupListener) CreateSecurityGroupInformer() (cache.SharedIndexInformer, error) {
48+
nodeList, err := s.kubeClient.Nodes().List(context.TODO(), metav1.ListOptions{})
49+
if err != nil {
50+
klog.Errorf("failed to query a list of node, try again later, error: %s", err)
51+
time.Sleep(5 * time.Second)
52+
return nil, err
53+
}
54+
nodeInformer := cache.NewSharedIndexInformer(
55+
&cache.ListWatch{
56+
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
57+
if options.ResourceVersion == "" || options.ResourceVersion == "0" {
58+
options.ResourceVersion = nodeList.ResourceVersion
59+
}
60+
return s.kubeClient.Nodes().List(context.TODO(), options)
61+
},
62+
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
63+
if options.ResourceVersion == "" || options.ResourceVersion == "0" {
64+
options.ResourceVersion = nodeList.ResourceVersion
65+
}
66+
return s.kubeClient.Nodes().Watch(context.TODO(), options)
67+
},
68+
},
69+
&v1.Node{},
70+
0,
71+
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
72+
)
73+
74+
_, err = nodeInformer.AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{
75+
AddFunc: func(obj interface{}) {
76+
kubeNode, ok := obj.(*v1.Node)
77+
if !ok {
78+
klog.Errorf("detected that a node has been added, but the type conversion failed: %#v", obj)
79+
return
80+
}
81+
klog.Infof("detected that a new node has been added to the cluster(%v): %s", ok, kubeNode.Name)
82+
83+
ecsNode, err := s.ecsClient.GetByNodeName(kubeNode.Name)
84+
if err != nil {
85+
klog.Error("Add node: can not get kubernetes node name: %v", err)
86+
return
87+
}
88+
err = s.ecsClient.AssociateSecurityGroup(ecsNode.Id, s.securityGroupID)
89+
if err != nil {
90+
klog.Errorf("failed to associate security group %s to ECS %s: %s", s.securityGroupID, ecsNode.Id, err)
91+
}
92+
},
93+
UpdateFunc: func(oldObj, newObj interface{}) {},
94+
DeleteFunc: func(obj interface{}) {
95+
kubeNode, ok := obj.(*v1.Node)
96+
if !ok {
97+
klog.Errorf("detected that a node has been removed, but the type conversion failed: %#v", obj)
98+
return
99+
}
100+
klog.Infof("detected that a node has been removed to the cluster: %s", kubeNode.Name)
101+
102+
ecsNode, err := s.ecsClient.GetByNodeName(kubeNode.Name)
103+
if err != nil {
104+
klog.Error("Delete node: can not get kubernetes node name: %v", err)
105+
return
106+
}
107+
err = s.ecsClient.DisassociateSecurityGroup(ecsNode.Id, s.securityGroupID)
108+
if err != nil {
109+
klog.Errorf("failed to disassociate security group %s from ECS %s: %s", s.securityGroupID, ecsNode.Id, err)
110+
}
111+
},
112+
}, 5*time.Second)
113+
if err != nil {
114+
klog.Errorf("failed to start nodeEventHandler, try again later, error: %s", err)
115+
time.Sleep(5 * time.Second)
116+
return nil, err
117+
}
118+
return nodeInformer, nil
119+
}
120+
121+
func (s *SecurityGroupListener) stopSecurityGroupListener() {
122+
klog.Warningf("Stop listening to Security Group")
123+
s.stopChannel <- struct{}{}
124+
}

pkg/cloudprovider/huaweicloud/wrapper/ecs.go

+38
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,44 @@ func (e *EcsClient) ListSecurityGroups(instanceID string) ([]model.NovaSecurityG
287287
return rst, err
288288
}
289289

290+
func (e *EcsClient) AssociateSecurityGroup(instanceID, securityGroupID string) error {
291+
return e.wrapper(func(c *ecs.EcsClient) (interface{}, error) {
292+
return c.NovaAssociateSecurityGroup(&model.NovaAssociateSecurityGroupRequest{
293+
ServerId: instanceID,
294+
Body: &model.NovaAssociateSecurityGroupRequestBody{
295+
AddSecurityGroup: &model.NovaAddSecurityGroupOption{
296+
Name: securityGroupID,
297+
},
298+
},
299+
})
300+
})
301+
}
302+
303+
func (e *EcsClient) DisassociateSecurityGroup(instanceID, securityGroupID string) error {
304+
err := e.wrapper(func(c *ecs.EcsClient) (interface{}, error) {
305+
return c.NovaDisassociateSecurityGroup(&model.NovaDisassociateSecurityGroupRequest{
306+
ServerId: instanceID,
307+
Body: &model.NovaDisassociateSecurityGroupRequestBody{
308+
RemoveSecurityGroup: &model.NovaRemoveSecurityGroupOption{
309+
Name: securityGroupID,
310+
},
311+
},
312+
})
313+
})
314+
315+
if err != nil {
316+
notAssociated := "not associated with the instance"
317+
notFound := "is not found for project"
318+
if strings.Contains(err.Error(), notAssociated) || strings.Contains(err.Error(), notFound) {
319+
klog.Errorf("failed to disassociate security group %v from instance %v: %v",
320+
securityGroupID, instanceID, err)
321+
return nil
322+
}
323+
}
324+
325+
return err
326+
}
327+
290328
// addToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice, only if they do not already exist.
291329
func addToNodeAddresses(addresses *[]v1.NodeAddress, addAddresses ...v1.NodeAddress) {
292330
for _, add := range addAddresses {

pkg/config/config.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ type CloudConfig struct {
4040
}
4141

4242
type VpcOptions struct {
43-
ID string `gcfg:"id"`
44-
SubnetID string `gcfg:"subnet-id"`
43+
ID string `gcfg:"id"`
44+
SubnetID string `gcfg:"subnet-id"`
45+
SecurityGroupID string `gcfg:"security-group-id"`
4546
}
4647

4748
type AuthOptions struct {

0 commit comments

Comments
 (0)