@@ -4,110 +4,158 @@ import (
4
4
"fmt"
5
5
"net/url"
6
6
"strings"
7
+ "sync"
7
8
"time"
8
9
9
10
"github.com/golang/glog"
11
+ "k8s.io/apimachinery/pkg/util/runtime"
12
+ "k8s.io/apimachinery/pkg/util/wait"
13
+ "k8s.io/client-go/util/retry"
10
14
11
15
"github.com/aws/aws-sdk-go/aws"
12
16
"github.com/aws/aws-sdk-go/service/ec2"
13
- "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
14
- "github.com/ottoyiu/k8s-ec2-srcdst/pkg/common"
15
-
16
17
"k8s.io/api/core/v1"
17
- "k8s.io/apimachinery/pkg/fields "
18
+ informer "k8s.io/client-go/informers/core/v1 "
18
19
"k8s.io/client-go/kubernetes"
19
20
"k8s.io/client-go/tools/cache"
21
+ "k8s.io/client-go/tools/record"
22
+ "k8s.io/client-go/util/workqueue"
20
23
)
21
24
22
25
type Controller struct {
23
- client kubernetes.Interface
24
- Controller cache.Controller
25
- ec2Client ec2iface.EC2API
26
+ client kubernetes.Interface
27
+ nodeInformer cache.SharedIndexInformer
28
+ nodeSynced cache.InformerSynced
29
+ workqueue workqueue.RateLimitingInterface
30
+ recorder record.EventRecorder
31
+ ec2Client * ec2.EC2
32
+ wg sync.WaitGroup
26
33
}
27
34
28
35
const (
29
- SrcDstCheckDisabledAnnotation = "kubernetes-ec2-srcdst-controller.ottoyiu.com/srcdst-check-disabled" // used as the Node Annotation key
36
+ // used as the Node Annotation key
37
+ SrcDstCheckDisabledAnnotation = "kubernetes-ec2-srcdst-controller.ottoyiu.com/srcdst-check-disabled"
30
38
)
31
39
32
- // NewSrcDstController creates a new Kubernetes controller using client-go's Informer
33
- func NewSrcDstController (client kubernetes.Interface , ec2Client * ec2.EC2 ) * Controller {
40
+ // NewSrcDstController creates a new Kubernetes controller to monitor Kubernetes nodes and disable src-dst
41
+ // check on EC2 instances.
42
+ func NewSrcDstController (client kubernetes.Interface ,
43
+ nodeInformer informer.NodeInformer ,
44
+ ec2Client * ec2.EC2 ) * Controller {
45
+
34
46
c := & Controller {
35
- client : client ,
36
- ec2Client : ec2Client ,
47
+ client : client ,
48
+ nodeInformer : nodeInformer .Informer (),
49
+ nodeSynced : nodeInformer .Informer ().HasSynced ,
50
+ workqueue : workqueue .NewNamedRateLimitingQueue (workqueue .DefaultControllerRateLimiter (), "Nodes" ),
51
+ ec2Client : ec2Client ,
37
52
}
38
53
39
- nodeListWatcher := cache .NewListWatchFromClient (
40
- client .Core ().RESTClient (),
41
- "nodes" ,
42
- v1 .NamespaceAll ,
43
- fields .Everything ())
44
-
45
- _ , controller := cache .NewInformer (
46
- nodeListWatcher ,
47
- & v1.Node {},
48
- 60 * time .Second ,
49
- // Callback Functions to trigger on add/update/delete
50
- cache.ResourceEventHandlerFuncs {
51
- AddFunc : c .handler ,
52
- UpdateFunc : func (old , new interface {}) { c .handler (new ) },
54
+ nodeInformer .Informer ().AddEventHandler (cache.ResourceEventHandlerFuncs {
55
+ AddFunc : c .handler ,
56
+ UpdateFunc : func (old , new interface {}) {
57
+ c .handler (new )
53
58
},
54
- )
55
-
56
- c .Controller = controller
59
+ })
57
60
58
61
return c
59
62
}
60
63
64
+ func (c * Controller ) Run (numWorkers int , stopCh <- chan struct {}) error {
65
+ defer runtime .HandleCrash ()
66
+ defer c .workqueue .ShutDown ()
67
+
68
+ if ok := cache .WaitForCacheSync (stopCh , c .nodeSynced ); ! ok {
69
+ return fmt .Errorf ("caches have failed to sync" )
70
+ }
71
+
72
+ var wg sync.WaitGroup
73
+ for i := 0 ; i < numWorkers ; i ++ {
74
+ c .wg .Add (1 )
75
+ go func () {
76
+ defer wg .Done ()
77
+ go wait .Until (c .runWorker , time .Second , stopCh )
78
+ }()
79
+ }
80
+
81
+ c .wg .Wait ()
82
+ <- stopCh
83
+ return nil
84
+ }
85
+
86
+ func (c * Controller ) runWorker () {
87
+ for c .processNextWorkItem () {
88
+ }
89
+ }
90
+
91
+ func (c * Controller ) processNextWorkItem () bool {
92
+ key , quit := c .workqueue .Get ()
93
+ if quit {
94
+ return false
95
+ }
96
+ glog .Infof ("Get key %s" , key .(string ))
97
+
98
+ defer c .workqueue .Forget (key )
99
+ if srcDstEnabled , err := c .checkSrcDstAttributeEnabled (key .(string )); err != nil {
100
+ glog .Error (err )
101
+ c .workqueue .AddRateLimited (key )
102
+ } else if srcDstEnabled {
103
+ return true
104
+ }
105
+
106
+ if err := c .disableSrcDstCheck (key .(string )); err != nil {
107
+ c .workqueue .AddRateLimited (key )
108
+ }
109
+
110
+ return true
111
+ }
112
+
61
113
func (c * Controller ) handler (obj interface {}) {
62
- // this handler makes sure that all nodes within a cluster has its src/dst check disabled in EC2
63
- node , ok := obj .(* v1.Node )
64
- if ! ok {
65
- glog .Errorf ("Expected Node but handler received: %+v" , obj )
114
+
115
+ key , err := cache .MetaNamespaceKeyFunc (obj )
116
+ if err != nil {
66
117
return
67
118
}
68
- glog .V (4 ).Infof ("Received update of node: %s" , node .Name )
69
- c .disableSrcDstIfEnabled (node )
119
+ glog .Infof ("Adding key %s" , key )
120
+ c .workqueue .Add (key )
121
+ return
70
122
}
71
123
72
- func (c * Controller ) disableSrcDstIfEnabled (node * v1.Node ) {
73
- srcDstCheckEnabled := true
74
- if node .Annotations != nil {
75
- if _ , ok := node .Annotations [SrcDstCheckDisabledAnnotation ]; ok {
76
- srcDstCheckEnabled = false
124
+ func (c * Controller ) disableSrcDstCheck (key string ) error {
125
+ defer c .workqueue .Done (key )
126
+
127
+ return retry .RetryOnConflict (retry .DefaultBackoff , func () error {
128
+ nodeObj , err := c .getNodeObjByKey (key )
129
+ if err != nil {
130
+ return err
77
131
}
78
- }
79
132
80
- if srcDstCheckEnabled {
133
+ nodeCopy := nodeObj . DeepCopy ()
81
134
// src dst check disabled annotation does not exist
82
135
// call AWS ec2 api to disable
83
- instanceID , err := GetInstanceIDFromProviderID (node .Spec .ProviderID )
84
- if err != nil {
85
- glog .Errorf ("Fail to retrieve Instance ID from Provider ID: %v" , node .Spec .ProviderID )
86
- return
87
- }
88
- err = c .disableSrcDstCheck (* instanceID )
136
+ instanceID , err := GetInstanceIDFromProviderID (nodeCopy .Spec .ProviderID )
89
137
if err != nil {
90
- glog .Errorf ("Fail to disable src dst check for EC2 instance : %v; %v " , * instanceID , err )
91
- return
138
+ glog .Errorf ("Failed to retrieve Instance ID from Provider ID : %v" , nodeCopy . Spec . ProviderID )
139
+ return err
92
140
}
93
- // We should not modify the cache object directly, so we make a copy first
94
- nodeCopy , err := common .CopyObjToNode (node )
95
- if err != nil {
96
- glog .Errorf ("Failed to make copy of node: %v" , err )
97
- return
141
+ if err = c .modifySrcDstCheckAttribute (* instanceID ); err != nil {
142
+ glog .Errorf ("Failed to disable src dst check for EC2 instance: %v; %v" , * instanceID , err )
143
+ return err
98
144
}
99
- glog .Infof ("Marking node %s with SrcDstCheckDisabledAnnotation" , node .Name )
145
+
146
+ glog .Infof ("Marking node %s with SrcDstCheckDisabledAnnotation" , nodeCopy .Name )
100
147
nodeCopy .Annotations [SrcDstCheckDisabledAnnotation ] = "true"
101
- if _ , err := c .client .Core ().Nodes ().Update (nodeCopy ); err != nil {
148
+
149
+ if _ , err := c .client .CoreV1 ().Nodes ().Update (nodeCopy ); err != nil {
102
150
glog .Errorf ("Failed to set %s annotation: %v" , SrcDstCheckDisabledAnnotation , err )
151
+ return err
103
152
}
104
- } else {
105
- glog .V (4 ).Infof ("Skipping node %s because it already has the SrcDstCheckDisabledAnnotation" , node .Name )
106
153
107
- }
154
+ return nil
155
+ })
108
156
}
109
157
110
- func (c * Controller ) disableSrcDstCheck (instanceID string ) error {
158
+ func (c * Controller ) modifySrcDstCheckAttribute (instanceID string ) error {
111
159
_ , err := c .ec2Client .ModifyInstanceAttribute (
112
160
& ec2.ModifyInstanceAttributeInput {
113
161
InstanceId : aws .String (instanceID ),
@@ -123,14 +171,13 @@ func (c *Controller) disableSrcDstCheck(instanceID string) error {
123
171
// GetInstanceIDFromProviderID will only retrieve the InstanceID from AWS
124
172
func GetInstanceIDFromProviderID (providerID string ) (* string , error ) {
125
173
// providerID is in this format: aws:///availability-zone/instanceID
126
- // TODO: why the extra slash in the provider ID of kubernetes anyways?
127
174
if ! strings .HasPrefix (providerID , "aws" ) {
128
- return nil , fmt .Errorf ("Node is not in AWS EC2, skipping... " )
175
+ return nil , fmt .Errorf ("node is not in AWS EC2, skipping! " )
129
176
}
130
177
providerID = strings .Replace (providerID , "///" , "//" , 1 )
131
178
url , err := url .Parse (providerID )
132
179
if err != nil {
133
- return nil , fmt .Errorf ("Invalid providerID (%s): %v" , providerID , err )
180
+ return nil , fmt .Errorf ("invalid providerID (%s): %v" , providerID , err )
134
181
}
135
182
instanceID := url .Path
136
183
instanceID = strings .Trim (instanceID , "/" )
@@ -139,8 +186,39 @@ func GetInstanceIDFromProviderID(providerID string) (*string, error) {
139
186
// i-12345678 and i-12345678abcdef01
140
187
// TODO: Regex match?
141
188
if strings .Contains (instanceID , "/" ) || ! strings .HasPrefix (instanceID , "i-" ) {
142
- return nil , fmt .Errorf ("Invalid format for AWS instanceID (%s)" , instanceID )
189
+ return nil , fmt .Errorf ("invalid format for AWS instanceID (%s)" , instanceID )
143
190
}
144
191
145
192
return & instanceID , nil
146
193
}
194
+
195
+ func (c * Controller ) getNodeObjByKey (key string ) (nodeObj * v1.Node , err error ) {
196
+ nodeItem , exists , err := c .nodeInformer .GetIndexer ().GetByKey (key )
197
+ if err != nil {
198
+ return nil , err
199
+ }
200
+
201
+ if ! exists {
202
+ return nil , fmt .Errorf ("node object %s doesn't exist" , key )
203
+ }
204
+
205
+ return nodeItem .(* v1.Node ), nil
206
+ }
207
+
208
+ func (c * Controller ) checkSrcDstAttributeEnabled (key string ) (enabled bool , err error ) {
209
+ defer c .workqueue .Done (key )
210
+
211
+ nodeObj , err := c .getNodeObjByKey (key )
212
+ if err != nil {
213
+ return false , err
214
+ }
215
+
216
+ if nodeObj .Annotations != nil {
217
+ if _ , ok := nodeObj .Annotations [SrcDstCheckDisabledAnnotation ]; ok {
218
+ glog .Info ("The node has the annotation" )
219
+ return true , nil
220
+ }
221
+ }
222
+
223
+ return false , nil
224
+ }
0 commit comments