├── .gitignore ├── Gopkg.lock ├── Gopkg.toml ├── README.md ├── controller.go ├── handler.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/PuerkitoBio/purell" 6 | packages = ["."] 7 | revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4" 8 | version = "v1.1.0" 9 | 10 | [[projects]] 11 | branch = "master" 12 | name = "github.com/PuerkitoBio/urlesc" 13 | packages = ["."] 14 | revision = "de5bf2ad457846296e2031421a34e2568e304e35" 15 | 16 | [[projects]] 17 | name = "github.com/Sirupsen/logrus" 18 | packages = ["."] 19 | revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" 20 | version = "v1.0.5" 21 | 22 | [[projects]] 23 | name = "github.com/davecgh/go-spew" 24 | packages = ["spew"] 25 | revision = "346938d642f2ec3594ed81d874461961cd0faa76" 26 | version = "v1.1.0" 27 | 28 | [[projects]] 29 | name = "github.com/emicklei/go-restful" 30 | packages = [ 31 | ".", 32 | "log" 33 | ] 34 | revision = "26b41036311f2da8242db402557a0dbd09dc83da" 35 | version = "v2.6.0" 36 | 37 | [[projects]] 38 | name = "github.com/ghodss/yaml" 39 | packages = ["."] 40 | revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7" 41 | version = "v1.0.0" 42 | 43 | [[projects]] 44 | branch = "master" 45 | name = "github.com/go-openapi/jsonpointer" 46 | packages = ["."] 47 | revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2" 48 | 49 | [[projects]] 50 | branch = "master" 51 | name = "github.com/go-openapi/jsonreference" 52 | packages = ["."] 53 | revision = "3fb327e6747da3043567ee86abd02bb6376b6be2" 54 | 55 | [[projects]] 56 | branch = "master" 57 | name = "github.com/go-openapi/spec" 58 | packages = ["."] 59 | revision = "bcff419492eeeb01f76e77d2ebc714dc97b607f5" 60 | 61 | [[projects]] 62 | branch = "master" 63 | name = "github.com/go-openapi/swag" 64 | packages = ["."] 65 | revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5" 66 | 67 | [[projects]] 68 | name = "github.com/gogo/protobuf" 69 | packages = [ 70 | "gogoproto", 71 | "plugin/compare", 72 | "plugin/defaultcheck", 73 | "plugin/description", 74 | "plugin/embedcheck", 75 | "plugin/enumstringer", 76 | "plugin/equal", 77 | "plugin/face", 78 | "plugin/gostring", 79 | "plugin/marshalto", 80 | "plugin/oneofcheck", 81 | "plugin/populate", 82 | "plugin/size", 83 | "plugin/stringer", 84 | "plugin/testgen", 85 | "plugin/union", 86 | "plugin/unmarshal", 87 | "proto", 88 | "protoc-gen-gogo/descriptor", 89 | "protoc-gen-gogo/generator", 90 | "protoc-gen-gogo/grpc", 91 | "protoc-gen-gogo/plugin", 92 | "sortkeys", 93 | "vanity", 94 | "vanity/command" 95 | ] 96 | revision = "1adfc126b41513cc696b209667c8656ea7aac67c" 97 | version = "v1.0.0" 98 | 99 | [[projects]] 100 | branch = "master" 101 | name = "github.com/golang/glog" 102 | packages = ["."] 103 | revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" 104 | 105 | [[projects]] 106 | name = "github.com/golang/protobuf" 107 | packages = [ 108 | "proto", 109 | "ptypes", 110 | "ptypes/any", 111 | "ptypes/duration", 112 | "ptypes/timestamp" 113 | ] 114 | revision = "925541529c1fa6821df4e44ce2723319eb2be768" 115 | version = "v1.0.0" 116 | 117 | [[projects]] 118 | branch = "master" 119 | name = "github.com/google/gofuzz" 120 | packages = ["."] 121 | revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" 122 | 123 | [[projects]] 124 | name = "github.com/googleapis/gnostic" 125 | packages = [ 126 | "OpenAPIv2", 127 | "compiler", 128 | "extensions" 129 | ] 130 | revision = "ee43cbb60db7bd22502942cccbc39059117352ab" 131 | version = "v0.1.0" 132 | 133 | [[projects]] 134 | branch = "master" 135 | name = "github.com/hashicorp/golang-lru" 136 | packages = [ 137 | ".", 138 | "simplelru" 139 | ] 140 | revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" 141 | 142 | [[projects]] 143 | branch = "master" 144 | name = "github.com/howeyc/gopass" 145 | packages = ["."] 146 | revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" 147 | 148 | [[projects]] 149 | name = "github.com/imdario/mergo" 150 | packages = ["."] 151 | revision = "9d5f1277e9a8ed20c3684bda8fde67c05628518c" 152 | version = "v0.3.4" 153 | 154 | [[projects]] 155 | name = "github.com/json-iterator/go" 156 | packages = ["."] 157 | revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4" 158 | version = "1.1.3" 159 | 160 | [[projects]] 161 | branch = "master" 162 | name = "github.com/mailru/easyjson" 163 | packages = [ 164 | "buffer", 165 | "jlexer", 166 | "jwriter" 167 | ] 168 | revision = "8b799c424f57fa123fc63a99d6383bc6e4c02578" 169 | 170 | [[projects]] 171 | name = "github.com/modern-go/concurrent" 172 | packages = ["."] 173 | revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" 174 | version = "1.0.3" 175 | 176 | [[projects]] 177 | name = "github.com/modern-go/reflect2" 178 | packages = ["."] 179 | revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f" 180 | version = "1.0.0" 181 | 182 | [[projects]] 183 | name = "github.com/spf13/pflag" 184 | packages = ["."] 185 | revision = "583c0c0531f06d5278b7d917446061adc344b5cd" 186 | version = "v1.0.1" 187 | 188 | [[projects]] 189 | branch = "master" 190 | name = "golang.org/x/crypto" 191 | packages = ["ssh/terminal"] 192 | revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e" 193 | 194 | [[projects]] 195 | branch = "master" 196 | name = "golang.org/x/net" 197 | packages = [ 198 | "context", 199 | "http2", 200 | "http2/hpack", 201 | "idna", 202 | "lex/httplex" 203 | ] 204 | revision = "61147c48b25b599e5b561d2e9c4f3e1ef489ca41" 205 | 206 | [[projects]] 207 | branch = "master" 208 | name = "golang.org/x/sys" 209 | packages = [ 210 | "unix", 211 | "windows" 212 | ] 213 | revision = "2281fa97ef7b0c26324634d5a22f04babdac8713" 214 | 215 | [[projects]] 216 | name = "golang.org/x/text" 217 | packages = [ 218 | "collate", 219 | "collate/build", 220 | "internal/colltab", 221 | "internal/gen", 222 | "internal/tag", 223 | "internal/triegen", 224 | "internal/ucd", 225 | "language", 226 | "secure/bidirule", 227 | "transform", 228 | "unicode/bidi", 229 | "unicode/cldr", 230 | "unicode/norm", 231 | "unicode/rangetable", 232 | "width" 233 | ] 234 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 235 | version = "v0.3.0" 236 | 237 | [[projects]] 238 | branch = "master" 239 | name = "golang.org/x/time" 240 | packages = ["rate"] 241 | revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" 242 | 243 | [[projects]] 244 | branch = "master" 245 | name = "golang.org/x/tools" 246 | packages = [ 247 | "go/ast/astutil", 248 | "imports" 249 | ] 250 | revision = "d9caac373771d868e508d835ebc3294a9a611c50" 251 | 252 | [[projects]] 253 | name = "gopkg.in/inf.v0" 254 | packages = ["."] 255 | revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf" 256 | version = "v0.9.1" 257 | 258 | [[projects]] 259 | name = "gopkg.in/yaml.v2" 260 | packages = ["."] 261 | revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" 262 | version = "v2.2.1" 263 | 264 | [[projects]] 265 | branch = "master" 266 | name = "k8s.io/api" 267 | packages = [ 268 | "admissionregistration/v1alpha1", 269 | "admissionregistration/v1beta1", 270 | "apps/v1", 271 | "apps/v1beta1", 272 | "apps/v1beta2", 273 | "authentication/v1", 274 | "authentication/v1beta1", 275 | "authorization/v1", 276 | "authorization/v1beta1", 277 | "autoscaling/v1", 278 | "autoscaling/v2beta1", 279 | "batch/v1", 280 | "batch/v1beta1", 281 | "batch/v2alpha1", 282 | "certificates/v1beta1", 283 | "core/v1", 284 | "events/v1beta1", 285 | "extensions/v1beta1", 286 | "networking/v1", 287 | "policy/v1beta1", 288 | "rbac/v1", 289 | "rbac/v1alpha1", 290 | "rbac/v1beta1", 291 | "scheduling/v1alpha1", 292 | "settings/v1alpha1", 293 | "storage/v1", 294 | "storage/v1alpha1", 295 | "storage/v1beta1" 296 | ] 297 | revision = "6fb8575d8ac324ad9f9605dec64b7a4b8a632e79" 298 | 299 | [[projects]] 300 | branch = "master" 301 | name = "k8s.io/apimachinery" 302 | packages = [ 303 | "pkg/api/errors", 304 | "pkg/api/meta", 305 | "pkg/api/resource", 306 | "pkg/apis/meta/internalversion", 307 | "pkg/apis/meta/v1", 308 | "pkg/apis/meta/v1/unstructured", 309 | "pkg/apis/meta/v1beta1", 310 | "pkg/conversion", 311 | "pkg/conversion/queryparams", 312 | "pkg/fields", 313 | "pkg/labels", 314 | "pkg/runtime", 315 | "pkg/runtime/schema", 316 | "pkg/runtime/serializer", 317 | "pkg/runtime/serializer/json", 318 | "pkg/runtime/serializer/protobuf", 319 | "pkg/runtime/serializer/recognizer", 320 | "pkg/runtime/serializer/streaming", 321 | "pkg/runtime/serializer/versioning", 322 | "pkg/selection", 323 | "pkg/types", 324 | "pkg/util/cache", 325 | "pkg/util/clock", 326 | "pkg/util/diff", 327 | "pkg/util/errors", 328 | "pkg/util/framer", 329 | "pkg/util/intstr", 330 | "pkg/util/json", 331 | "pkg/util/net", 332 | "pkg/util/runtime", 333 | "pkg/util/sets", 334 | "pkg/util/validation", 335 | "pkg/util/validation/field", 336 | "pkg/util/wait", 337 | "pkg/util/yaml", 338 | "pkg/version", 339 | "pkg/watch", 340 | "third_party/forked/golang/reflect" 341 | ] 342 | revision = "ba81bc69c72dde92d5f1fefa2fa6d82aa83a6676" 343 | 344 | [[projects]] 345 | name = "k8s.io/client-go" 346 | packages = [ 347 | "discovery", 348 | "kubernetes", 349 | "kubernetes/scheme", 350 | "kubernetes/typed/admissionregistration/v1alpha1", 351 | "kubernetes/typed/admissionregistration/v1beta1", 352 | "kubernetes/typed/apps/v1", 353 | "kubernetes/typed/apps/v1beta1", 354 | "kubernetes/typed/apps/v1beta2", 355 | "kubernetes/typed/authentication/v1", 356 | "kubernetes/typed/authentication/v1beta1", 357 | "kubernetes/typed/authorization/v1", 358 | "kubernetes/typed/authorization/v1beta1", 359 | "kubernetes/typed/autoscaling/v1", 360 | "kubernetes/typed/autoscaling/v2beta1", 361 | "kubernetes/typed/batch/v1", 362 | "kubernetes/typed/batch/v1beta1", 363 | "kubernetes/typed/batch/v2alpha1", 364 | "kubernetes/typed/certificates/v1beta1", 365 | "kubernetes/typed/core/v1", 366 | "kubernetes/typed/events/v1beta1", 367 | "kubernetes/typed/extensions/v1beta1", 368 | "kubernetes/typed/networking/v1", 369 | "kubernetes/typed/policy/v1beta1", 370 | "kubernetes/typed/rbac/v1", 371 | "kubernetes/typed/rbac/v1alpha1", 372 | "kubernetes/typed/rbac/v1beta1", 373 | "kubernetes/typed/scheduling/v1alpha1", 374 | "kubernetes/typed/settings/v1alpha1", 375 | "kubernetes/typed/storage/v1", 376 | "kubernetes/typed/storage/v1alpha1", 377 | "kubernetes/typed/storage/v1beta1", 378 | "pkg/apis/clientauthentication", 379 | "pkg/apis/clientauthentication/v1alpha1", 380 | "pkg/version", 381 | "plugin/pkg/client/auth/exec", 382 | "rest", 383 | "rest/watch", 384 | "testing", 385 | "tools/auth", 386 | "tools/cache", 387 | "tools/clientcmd", 388 | "tools/clientcmd/api", 389 | "tools/clientcmd/api/latest", 390 | "tools/clientcmd/api/v1", 391 | "tools/metrics", 392 | "tools/pager", 393 | "tools/reference", 394 | "transport", 395 | "util/buffer", 396 | "util/cert", 397 | "util/flowcontrol", 398 | "util/homedir", 399 | "util/integer", 400 | "util/retry", 401 | "util/workqueue" 402 | ] 403 | revision = "23781f4d6632d88e869066eaebb743857aa1ef9b" 404 | version = "v7.0.0" 405 | 406 | [[projects]] 407 | branch = "master" 408 | name = "k8s.io/code-generator" 409 | packages = [ 410 | "cmd/client-gen/args", 411 | "cmd/client-gen/generators", 412 | "cmd/client-gen/generators/fake", 413 | "cmd/client-gen/generators/scheme", 414 | "cmd/client-gen/generators/util", 415 | "cmd/client-gen/path", 416 | "cmd/client-gen/types", 417 | "cmd/conversion-gen/args", 418 | "cmd/conversion-gen/generators", 419 | "cmd/deepcopy-gen/args", 420 | "cmd/defaulter-gen/args", 421 | "cmd/go-to-protobuf/protobuf", 422 | "cmd/informer-gen/args", 423 | "cmd/informer-gen/generators", 424 | "cmd/lister-gen/args", 425 | "cmd/lister-gen/generators", 426 | "cmd/openapi-gen/args", 427 | "pkg/util", 428 | "third_party/forked/golang/reflect" 429 | ] 430 | revision = "692f126559b2e6f2033edd97ed24174439fb3de6" 431 | 432 | [[projects]] 433 | branch = "master" 434 | name = "k8s.io/gengo" 435 | packages = [ 436 | "args", 437 | "examples/deepcopy-gen/generators", 438 | "examples/defaulter-gen/generators", 439 | "examples/import-boss/generators", 440 | "examples/set-gen/generators", 441 | "examples/set-gen/sets", 442 | "generator", 443 | "namer", 444 | "parser", 445 | "types" 446 | ] 447 | revision = "01a732e01d00cb9a81bb0ca050d3e6d2b947927b" 448 | 449 | [[projects]] 450 | branch = "master" 451 | name = "k8s.io/kube-openapi" 452 | packages = [ 453 | "pkg/common", 454 | "pkg/generators" 455 | ] 456 | revision = "f442ecb314a3679150c272e2b9713d8deed5955d" 457 | 458 | [solve-meta] 459 | analyzer-name = "dep" 460 | analyzer-version = 1 461 | inputs-digest = "f8bd8294010388ad4ee8f54d6a4024eeaf73b4fcf6bf9d174c089c3e3c1a344e" 462 | solver-name = "gps-cdcl" 463 | solver-version = 1 464 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/Sirupsen/logrus" 30 | version = "1.0.5" 31 | 32 | [[constraint]] 33 | name = "github.com/davecgh/go-spew" 34 | version = "1.1.0" 35 | 36 | [[constraint]] 37 | name = "github.com/ghodss/yaml" 38 | version = "1.0.0" 39 | 40 | [[constraint]] 41 | name = "github.com/gogo/protobuf" 42 | version = "1.0.0" 43 | 44 | [[constraint]] 45 | branch = "master" 46 | name = "github.com/golang/glog" 47 | 48 | [[constraint]] 49 | name = "github.com/golang/protobuf" 50 | version = "1.0.0" 51 | 52 | [[constraint]] 53 | branch = "master" 54 | name = "github.com/google/gofuzz" 55 | 56 | [[constraint]] 57 | name = "github.com/googleapis/gnostic" 58 | version = "0.1.0" 59 | 60 | [[constraint]] 61 | branch = "master" 62 | name = "github.com/hashicorp/golang-lru" 63 | 64 | [[constraint]] 65 | name = "github.com/json-iterator/go" 66 | version = "1.1.3" 67 | 68 | [[constraint]] 69 | name = "github.com/modern-go/concurrent" 70 | version = "1.0.3" 71 | 72 | [[constraint]] 73 | name = "github.com/modern-go/reflect2" 74 | version = "1.0.0" 75 | 76 | [[constraint]] 77 | name = "github.com/spf13/pflag" 78 | version = "1.0.1" 79 | 80 | [[constraint]] 81 | branch = "master" 82 | name = "golang.org/x/crypto" 83 | 84 | [[constraint]] 85 | branch = "master" 86 | name = "golang.org/x/net" 87 | 88 | [[constraint]] 89 | branch = "master" 90 | name = "golang.org/x/sys" 91 | 92 | [[constraint]] 93 | name = "golang.org/x/text" 94 | version = "0.3.0" 95 | 96 | [[constraint]] 97 | branch = "master" 98 | name = "golang.org/x/time" 99 | 100 | [[constraint]] 101 | name = "gopkg.in/inf.v0" 102 | version = "0.9.1" 103 | 104 | [[constraint]] 105 | name = "gopkg.in/yaml.v2" 106 | version = "2.2.1" 107 | 108 | [[constraint]] 109 | branch = "master" 110 | name = "k8s.io/api" 111 | 112 | [[constraint]] 113 | branch = "master" 114 | name = "k8s.io/apimachinery" 115 | 116 | [[constraint]] 117 | name = "k8s.io/client-go" 118 | version = "7.0.0" 119 | 120 | [[constraint]] 121 | branch = "master" 122 | name = "k8s.io/code-generator" 123 | 124 | [[constraint]] 125 | branch = "master" 126 | name = "k8s.io/gengo" 127 | 128 | [[constraint]] 129 | branch = "master" 130 | name = "k8s.io/kube-openapi" 131 | 132 | [prune] 133 | go-tests = true 134 | unused-packages = true 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Custom Controller - Core Resource Handling 2 | 3 | **Note**: the source code is _verbosely_ commented, so the source is meant to be read and to teach 4 | 5 | ## What is this? 6 | 7 | An example of a custom Kubernetes controller that's only purpose is to watch for the creation, updating, or deletion of all pods (in the default namespace). This was created as an exercise to understand how Kubernetes controllers work and interact with the cluster and resources. 8 | 9 | ## Running 10 | 11 | ``` 12 | $ git clone https://github.com/trstringer/k8s-controller-core-resource 13 | $ cd k8s-controller-core-resource 14 | $ go run *.go 15 | ``` 16 | 17 | ## Inspecting resources in the handler 18 | 19 | You are welcome to dump the resources themselves in handler but logging would be extremely verbose (and not interactive). I recommend you use a debugger... 20 | 21 | ``` 22 | $ dlv debug 23 | (dlv) b main.ObjectCreated 24 | (dlv) c 25 | ``` 26 | 27 | You can then trigger an event by creating a deployment of nginx... 28 | 29 | ``` 30 | $ kubectl run nginx --image=nginx 31 | ``` 32 | 33 | The breakpoint should be hit and you can analyze in the debugger the object... 34 | 35 | ``` 36 | (dlv) p obj 37 | ``` 38 | -------------------------------------------------------------------------------- /controller.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 9 | "k8s.io/apimachinery/pkg/util/wait" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/tools/cache" 12 | "k8s.io/client-go/util/workqueue" 13 | ) 14 | 15 | // Controller struct defines how a controller should encapsulate 16 | // logging, client connectivity, informing (list and watching) 17 | // queueing, and handling of resource changes 18 | type Controller struct { 19 | logger *log.Entry 20 | clientset kubernetes.Interface 21 | queue workqueue.RateLimitingInterface 22 | informer cache.SharedIndexInformer 23 | handler Handler 24 | } 25 | 26 | // Run is the main path of execution for the controller loop 27 | func (c *Controller) Run(stopCh <-chan struct{}) { 28 | // handle a panic with logging and exiting 29 | defer utilruntime.HandleCrash() 30 | // ignore new items in the queue but when all goroutines 31 | // have completed existing items then shutdown 32 | defer c.queue.ShutDown() 33 | 34 | c.logger.Info("Controller.Run: initiating") 35 | 36 | // run the informer to start listing and watching resources 37 | go c.informer.Run(stopCh) 38 | 39 | // do the initial synchronization (one time) to populate resources 40 | if !cache.WaitForCacheSync(stopCh, c.HasSynced) { 41 | utilruntime.HandleError(fmt.Errorf("Error syncing cache")) 42 | return 43 | } 44 | c.logger.Info("Controller.Run: cache sync complete") 45 | 46 | // run the runWorker method every second with a stop channel 47 | wait.Until(c.runWorker, time.Second, stopCh) 48 | } 49 | 50 | // HasSynced allows us to satisfy the Controller interface 51 | // by wiring up the informer's HasSynced method to it 52 | func (c *Controller) HasSynced() bool { 53 | return c.informer.HasSynced() 54 | } 55 | 56 | // runWorker executes the loop to process new items added to the queue 57 | func (c *Controller) runWorker() { 58 | log.Info("Controller.runWorker: starting") 59 | 60 | // invoke processNextItem to fetch and consume the next change 61 | // to a watched or listed resource 62 | for c.processNextItem() { 63 | log.Info("Controller.runWorker: processing next item") 64 | } 65 | 66 | log.Info("Controller.runWorker: completed") 67 | } 68 | 69 | // processNextItem retrieves each queued item and takes the 70 | // necessary handler action based off of if the item was 71 | // created or deleted 72 | func (c *Controller) processNextItem() bool { 73 | log.Info("Controller.processNextItem: start") 74 | 75 | // fetch the next item (blocking) from the queue to process or 76 | // if a shutdown is requested then return out of this to stop 77 | // processing 78 | key, quit := c.queue.Get() 79 | 80 | // stop the worker loop from running as this indicates we 81 | // have sent a shutdown message that the queue has indicated 82 | // from the Get method 83 | if quit { 84 | return false 85 | } 86 | 87 | defer c.queue.Done(key) 88 | 89 | // assert the string out of the key (format `namespace/name`) 90 | keyRaw := key.(string) 91 | 92 | // take the string key and get the object out of the indexer 93 | // 94 | // item will contain the complex object for the resource and 95 | // exists is a bool that'll indicate whether or not the 96 | // resource was created (true) or deleted (false) 97 | // 98 | // if there is an error in getting the key from the index 99 | // then we want to retry this particular queue key a certain 100 | // number of times (5 here) before we forget the queue key 101 | // and throw an error 102 | item, exists, err := c.informer.GetIndexer().GetByKey(keyRaw) 103 | if err != nil { 104 | if c.queue.NumRequeues(key) < 5 { 105 | c.logger.Errorf("Controller.processNextItem: Failed processing item with key %s with error %v, retrying", key, err) 106 | c.queue.AddRateLimited(key) 107 | } else { 108 | c.logger.Errorf("Controller.processNextItem: Failed processing item with key %s with error %v, no more retries", key, err) 109 | c.queue.Forget(key) 110 | utilruntime.HandleError(err) 111 | } 112 | } 113 | 114 | // if the item doesn't exist then it was deleted and we need to fire off the handler's 115 | // ObjectDeleted method. but if the object does exist that indicates that the object 116 | // was created (or updated) so run the ObjectCreated method 117 | // 118 | // after both instances, we want to forget the key from the queue, as this indicates 119 | // a code path of successful queue key processing 120 | if !exists { 121 | c.logger.Infof("Controller.processNextItem: object deleted detected: %s", keyRaw) 122 | c.handler.ObjectDeleted(item) 123 | c.queue.Forget(key) 124 | } else { 125 | c.logger.Infof("Controller.processNextItem: object created detected: %s", keyRaw) 126 | c.handler.ObjectCreated(item) 127 | c.queue.Forget(key) 128 | } 129 | 130 | // keep the worker loop running by returning true 131 | return true 132 | } 133 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | log "github.com/Sirupsen/logrus" 5 | core_v1 "k8s.io/api/core/v1" 6 | ) 7 | 8 | // Handler interface contains the methods that are required 9 | type Handler interface { 10 | Init() error 11 | ObjectCreated(obj interface{}) 12 | ObjectDeleted(obj interface{}) 13 | ObjectUpdated(objOld, objNew interface{}) 14 | } 15 | 16 | // TestHandler is a sample implementation of Handler 17 | type TestHandler struct{} 18 | 19 | // Init handles any handler initialization 20 | func (t *TestHandler) Init() error { 21 | log.Info("TestHandler.Init") 22 | return nil 23 | } 24 | 25 | // ObjectCreated is called when an object is created 26 | func (t *TestHandler) ObjectCreated(obj interface{}) { 27 | log.Info("TestHandler.ObjectCreated") 28 | // assert the type to a Pod object to pull out relevant data 29 | pod := obj.(*core_v1.Pod) 30 | log.Infof(" ResourceVersion: %s", pod.ObjectMeta.ResourceVersion) 31 | log.Infof(" NodeName: %s", pod.Spec.NodeName) 32 | log.Infof(" Phase: %s", pod.Status.Phase) 33 | } 34 | 35 | // ObjectDeleted is called when an object is deleted 36 | func (t *TestHandler) ObjectDeleted(obj interface{}) { 37 | log.Info("TestHandler.ObjectDeleted") 38 | } 39 | 40 | // ObjectUpdated is called when an object is updated 41 | func (t *TestHandler) ObjectUpdated(objOld, objNew interface{}) { 42 | log.Info("TestHandler.ObjectUpdated") 43 | } 44 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | api_v1 "k8s.io/api/core/v1" 10 | meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/apimachinery/pkg/watch" 13 | "k8s.io/client-go/kubernetes" 14 | "k8s.io/client-go/tools/cache" 15 | "k8s.io/client-go/tools/clientcmd" 16 | "k8s.io/client-go/util/workqueue" 17 | ) 18 | 19 | // retrieve the Kubernetes cluster client from outside of the cluster 20 | func getKubernetesClient() kubernetes.Interface { 21 | // construct the path to resolve to `~/.kube/config` 22 | kubeConfigPath := os.Getenv("HOME") + "/.kube/config" 23 | 24 | // create the config from the path 25 | config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) 26 | if err != nil { 27 | log.Fatalf("getClusterConfig: %v", err) 28 | } 29 | 30 | // generate the client based off of the config 31 | client, err := kubernetes.NewForConfig(config) 32 | if err != nil { 33 | log.Fatalf("getClusterConfig: %v", err) 34 | } 35 | 36 | log.Info("Successfully constructed k8s client") 37 | return client 38 | } 39 | 40 | // main code path 41 | func main() { 42 | // get the Kubernetes client for connectivity 43 | client := getKubernetesClient() 44 | 45 | // create the informer so that we can not only list resources 46 | // but also watch them for all pods in the default namespace 47 | informer := cache.NewSharedIndexInformer( 48 | // the ListWatch contains two different functions that our 49 | // informer requires: ListFunc to take care of listing and watching 50 | // the resources we want to handle 51 | &cache.ListWatch{ 52 | ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) { 53 | // list all of the pods (core resource) in the deafult namespace 54 | return client.CoreV1().Pods(meta_v1.NamespaceDefault).List(options) 55 | }, 56 | WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) { 57 | // watch all of the pods (core resource) in the default namespace 58 | return client.CoreV1().Pods(meta_v1.NamespaceDefault).Watch(options) 59 | }, 60 | }, 61 | &api_v1.Pod{}, // the target type (Pod) 62 | 0, // no resync (period of 0) 63 | cache.Indexers{}, 64 | ) 65 | 66 | // create a new queue so that when the informer gets a resource that is either 67 | // a result of listing or watching, we can add an idenfitying key to the queue 68 | // so that it can be handled in the handler 69 | queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) 70 | 71 | // add event handlers to handle the three types of events for resources: 72 | // - adding new resources 73 | // - updating existing resources 74 | // - deleting resources 75 | informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 76 | AddFunc: func(obj interface{}) { 77 | // convert the resource object into a key (in this case 78 | // we are just doing it in the format of 'namespace/name') 79 | key, err := cache.MetaNamespaceKeyFunc(obj) 80 | log.Infof("Add pod: %s", key) 81 | if err == nil { 82 | // add the key to the queue for the handler to get 83 | queue.Add(key) 84 | } 85 | }, 86 | UpdateFunc: func(oldObj, newObj interface{}) { 87 | key, err := cache.MetaNamespaceKeyFunc(newObj) 88 | log.Infof("Update pod: %s", key) 89 | if err == nil { 90 | queue.Add(key) 91 | } 92 | }, 93 | DeleteFunc: func(obj interface{}) { 94 | // DeletionHandlingMetaNamsespaceKeyFunc is a helper function that allows 95 | // us to check the DeletedFinalStateUnknown existence in the event that 96 | // a resource was deleted but it is still contained in the index 97 | // 98 | // this then in turn calls MetaNamespaceKeyFunc 99 | key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 100 | log.Infof("Delete pod: %s", key) 101 | if err == nil { 102 | queue.Add(key) 103 | } 104 | }, 105 | }) 106 | 107 | // construct the Controller object which has all of the necessary components to 108 | // handle logging, connections, informing (listing and watching), the queue, 109 | // and the handler 110 | controller := Controller{ 111 | logger: log.NewEntry(log.New()), 112 | clientset: client, 113 | informer: informer, 114 | queue: queue, 115 | handler: &TestHandler{}, 116 | } 117 | 118 | // use a channel to synchronize the finalization for a graceful shutdown 119 | stopCh := make(chan struct{}) 120 | defer close(stopCh) 121 | 122 | // run the controller loop to process items 123 | go controller.Run(stopCh) 124 | 125 | // use a channel to handle OS signals to terminate and gracefully shut 126 | // down processing 127 | sigTerm := make(chan os.Signal, 1) 128 | signal.Notify(sigTerm, syscall.SIGTERM) 129 | signal.Notify(sigTerm, syscall.SIGINT) 130 | <-sigTerm 131 | } 132 | --------------------------------------------------------------------------------