You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

jointinferenceservice.go 20 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. /*
  2. Copyright 2021 The KubeEdge Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package globalmanager
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "strconv"
  19. "time"
  20. v1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. utilrand "k8s.io/apimachinery/pkg/util/rand"
  24. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  25. "k8s.io/apimachinery/pkg/util/wait"
  26. kubeinformers "k8s.io/client-go/informers"
  27. "k8s.io/client-go/kubernetes"
  28. "k8s.io/client-go/kubernetes/scheme"
  29. v1core "k8s.io/client-go/kubernetes/typed/core/v1"
  30. corelisters "k8s.io/client-go/listers/core/v1"
  31. "k8s.io/client-go/tools/cache"
  32. "k8s.io/client-go/tools/record"
  33. "k8s.io/client-go/util/workqueue"
  34. "k8s.io/klog/v2"
  35. k8scontroller "k8s.io/kubernetes/pkg/controller"
  36. sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1"
  37. clientset "github.com/kubeedge/sedna/pkg/client/clientset/versioned"
  38. sednaclientset "github.com/kubeedge/sedna/pkg/client/clientset/versioned/typed/sedna/v1alpha1"
  39. informers "github.com/kubeedge/sedna/pkg/client/informers/externalversions"
  40. sednav1listers "github.com/kubeedge/sedna/pkg/client/listers/sedna/v1alpha1"
  41. "github.com/kubeedge/sedna/pkg/globalmanager/config"
  42. messageContext "github.com/kubeedge/sedna/pkg/globalmanager/messagelayer/ws"
  43. "github.com/kubeedge/sedna/pkg/globalmanager/utils"
  44. )
  45. const (
  46. jointInferenceForEdge = "Edge"
  47. jointInferenceForCloud = "Cloud"
  48. )
  49. // jointServiceControllerKind contains the schema.GroupVersionKind for this controller type.
  50. var jointServiceControllerKind = sednav1.SchemeGroupVersion.WithKind("JointInferenceService")
  51. // JointInferenceServiceController ensures that all JointInferenceService objects
  52. // have corresponding pods to run their configured workload.
  53. type JointInferenceServiceController struct {
  54. kubeClient kubernetes.Interface
  55. client sednaclientset.SednaV1alpha1Interface
  56. // podStoreSynced returns true if the pod store has been synced at least once.
  57. podStoreSynced cache.InformerSynced
  58. // A store of pods
  59. podStore corelisters.PodLister
  60. // serviceStoreSynced returns true if the jointinferenceservice store has been synced at least once.
  61. serviceStoreSynced cache.InformerSynced
  62. // A store of service
  63. serviceLister sednav1listers.JointInferenceServiceLister
  64. // JointInferenceServices that need to be updated
  65. queue workqueue.RateLimitingInterface
  66. recorder record.EventRecorder
  67. cfg *config.ControllerConfig
  68. }
  69. // Start starts the main goroutine responsible for watching and syncing services.
  70. func (jc *JointInferenceServiceController) Start() error {
  71. workers := 1
  72. stopCh := messageContext.Done()
  73. go func() {
  74. defer utilruntime.HandleCrash()
  75. defer jc.queue.ShutDown()
  76. klog.Infof("Starting joint inference service controller")
  77. defer klog.Infof("Shutting down joint inference service controller")
  78. if !cache.WaitForNamedCacheSync("jointinferenceservice", stopCh, jc.podStoreSynced, jc.serviceStoreSynced) {
  79. klog.Errorf("failed to wait for joint inferce service caches to sync")
  80. return
  81. }
  82. klog.Infof("Starting joint inference service workers")
  83. for i := 0; i < workers; i++ {
  84. go wait.Until(jc.worker, time.Second, stopCh)
  85. }
  86. <-stopCh
  87. }()
  88. return nil
  89. }
  90. // enqueueByPod enqueues the jointInferenceService object of the specified pod.
  91. func (jc *JointInferenceServiceController) enqueueByPod(pod *v1.Pod, immediate bool) {
  92. controllerRef := metav1.GetControllerOf(pod)
  93. if controllerRef == nil {
  94. return
  95. }
  96. if controllerRef.Kind != jointServiceControllerKind.Kind {
  97. return
  98. }
  99. service, err := jc.serviceLister.JointInferenceServices(pod.Namespace).Get(controllerRef.Name)
  100. if err != nil {
  101. return
  102. }
  103. if service.UID != controllerRef.UID {
  104. return
  105. }
  106. jc.enqueueController(service, immediate)
  107. }
  108. // When a pod is created, enqueue the controller that manages it and update it's expectations.
  109. func (jc *JointInferenceServiceController) addPod(obj interface{}) {
  110. pod := obj.(*v1.Pod)
  111. if pod.DeletionTimestamp != nil {
  112. // on a restart of the controller, it's possible a new pod shows up in a state that
  113. // is already pending deletion. Prevent the pod from being a creation observation.
  114. jc.deletePod(pod)
  115. return
  116. }
  117. // backoff to queue when PodFailed
  118. immediate := pod.Status.Phase != v1.PodFailed
  119. jc.enqueueByPod(pod, immediate)
  120. }
  121. // When a pod is updated, figure out what joint inference service manage it and wake them up.
  122. func (jc *JointInferenceServiceController) updatePod(old, cur interface{}) {
  123. curPod := cur.(*v1.Pod)
  124. oldPod := old.(*v1.Pod)
  125. // no pod update, no queue
  126. if curPod.ResourceVersion == oldPod.ResourceVersion {
  127. return
  128. }
  129. jc.addPod(curPod)
  130. }
  131. // deletePod enqueues the jointinferenceservice obj When a pod is deleted
  132. func (jc *JointInferenceServiceController) deletePod(obj interface{}) {
  133. pod, ok := obj.(*v1.Pod)
  134. // comment from https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/job_controller.go
  135. // When a delete is dropped, the relist will notice a pod in the store not
  136. // in the list, leading to the insertion of a tombstone object which contains
  137. // the deleted key/value. Note that this value might be stale. If the pod
  138. // changed labels the new jointinferenceservice will not be woken up till the periodic resync.
  139. if !ok {
  140. tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
  141. if !ok {
  142. klog.Warningf("couldn't get object from tombstone %+v", obj)
  143. return
  144. }
  145. pod, ok = tombstone.Obj.(*v1.Pod)
  146. if !ok {
  147. klog.Warningf("tombstone contained object that is not a pod %+v", obj)
  148. return
  149. }
  150. }
  151. jc.enqueueByPod(pod, true)
  152. }
  153. // obj could be an *sednav1.JointInferenceService, or a DeletionFinalStateUnknown marker item,
  154. // immediate tells the controller to update the status right away, and should
  155. // happen ONLY when there was a successful pod run.
  156. func (jc *JointInferenceServiceController) enqueueController(obj interface{}, immediate bool) {
  157. key, err := k8scontroller.KeyFunc(obj)
  158. if err != nil {
  159. klog.Warningf("Couldn't get key for object %+v: %v", obj, err)
  160. return
  161. }
  162. backoff := time.Duration(0)
  163. if !immediate {
  164. backoff = getBackoff(jc.queue, key)
  165. }
  166. jc.queue.AddAfter(key, backoff)
  167. }
  168. // worker runs a worker thread that just dequeues items, processes them, and marks them done.
  169. // It enforces that the sync is never invoked concurrently with the same key.
  170. func (jc *JointInferenceServiceController) worker() {
  171. for jc.processNextWorkItem() {
  172. }
  173. }
  174. func (jc *JointInferenceServiceController) processNextWorkItem() bool {
  175. key, quit := jc.queue.Get()
  176. if quit {
  177. return false
  178. }
  179. defer jc.queue.Done(key)
  180. forget, err := jc.sync(key.(string))
  181. if err == nil {
  182. if forget {
  183. jc.queue.Forget(key)
  184. }
  185. return true
  186. }
  187. klog.Warningf("Error syncing jointinference service: %v", err)
  188. jc.queue.AddRateLimited(key)
  189. return true
  190. }
  191. // sync will sync the jointinferenceservice with the given key.
  192. // This function is not meant to be invoked concurrently with the same key.
  193. func (jc *JointInferenceServiceController) sync(key string) (bool, error) {
  194. startTime := time.Now()
  195. defer func() {
  196. klog.V(4).Infof("Finished syncing jointinference service %q (%v)", key, time.Since(startTime))
  197. }()
  198. ns, name, err := cache.SplitMetaNamespaceKey(key)
  199. if err != nil {
  200. return false, err
  201. }
  202. if len(ns) == 0 || len(name) == 0 {
  203. return false, fmt.Errorf("invalid jointinference service key %q: either namespace or name is missing", key)
  204. }
  205. sharedJointinferenceservice, err := jc.serviceLister.JointInferenceServices(ns).Get(name)
  206. if err != nil {
  207. if errors.IsNotFound(err) {
  208. klog.V(4).Infof("JointInferenceService has been deleted: %v", key)
  209. return true, nil
  210. }
  211. return false, err
  212. }
  213. jointinferenceservice := *sharedJointinferenceservice
  214. // if jointinferenceservice was finished previously, we don't want to redo the termination
  215. if isJointinferenceserviceFinished(&jointinferenceservice) {
  216. return true, nil
  217. }
  218. // set kind for jointinferenceservice in case that the kind is None
  219. // more details at https://github.com/kubernetes/kubernetes/issues/3030
  220. jointinferenceservice.SetGroupVersionKind(jointServiceControllerKind)
  221. selector, _ := GenerateSelector(&jointinferenceservice)
  222. pods, err := jc.podStore.Pods(jointinferenceservice.Namespace).List(selector)
  223. if err != nil {
  224. return false, err
  225. }
  226. klog.V(4).Infof("list jointinference service %v/%v, %v pods: %v", jointinferenceservice.Namespace, jointinferenceservice.Name, len(pods), pods)
  227. latestConditionLen := len(jointinferenceservice.Status.Conditions)
  228. active := calcActivePodCount(pods)
  229. var failed int32 = 0
  230. // neededCounts means that two pods should be created successfully in a jointinference service currently
  231. // two pods consist of edge pod and cloud pod
  232. var neededCounts int32 = 2
  233. // jointinferenceservice first start
  234. if jointinferenceservice.Status.StartTime == nil {
  235. now := metav1.Now()
  236. jointinferenceservice.Status.StartTime = &now
  237. } else {
  238. failed = neededCounts - active
  239. }
  240. var manageServiceErr error
  241. serviceFailed := false
  242. var latestConditionType sednav1.JointInferenceServiceConditionType = ""
  243. // get the latest condition type
  244. // based on that condition updated is appended, not inserted.
  245. jobConditions := jointinferenceservice.Status.Conditions
  246. if len(jobConditions) > 0 {
  247. latestConditionType = (jobConditions)[len(jobConditions)-1].Type
  248. }
  249. var newCondtionType sednav1.JointInferenceServiceConditionType
  250. var reason string
  251. var message string
  252. if failed > 0 {
  253. serviceFailed = true
  254. // TODO: get the failed worker, and knows that which worker fails, edge inference worker or cloud inference worker
  255. reason = "workerFailed"
  256. message = "the worker of Jointinferenceservice failed"
  257. newCondtionType = sednav1.JointInferenceServiceCondFailed
  258. jc.recorder.Event(&jointinferenceservice, v1.EventTypeWarning, reason, message)
  259. } else {
  260. if len(pods) == 0 {
  261. active, manageServiceErr = jc.createPod(&jointinferenceservice)
  262. }
  263. if manageServiceErr != nil {
  264. serviceFailed = true
  265. message = error.Error(manageServiceErr)
  266. newCondtionType = sednav1.JointInferenceServiceCondFailed
  267. failed = neededCounts - active
  268. } else {
  269. // TODO: handle the case that the pod phase is PodSucceeded
  270. newCondtionType = sednav1.JointInferenceServiceCondRunning
  271. }
  272. }
  273. //
  274. if newCondtionType != latestConditionType {
  275. jointinferenceservice.Status.Conditions = append(jointinferenceservice.Status.Conditions, NewJointInferenceServiceCondition(newCondtionType, reason, message))
  276. }
  277. forget := false
  278. // no need to update the jointinferenceservice if the status hasn't changed since last time
  279. if jointinferenceservice.Status.Active != active || jointinferenceservice.Status.Failed != failed || len(jointinferenceservice.Status.Conditions) != latestConditionLen {
  280. jointinferenceservice.Status.Active = active
  281. jointinferenceservice.Status.Failed = failed
  282. if err := jc.updateStatus(&jointinferenceservice); err != nil {
  283. return forget, err
  284. }
  285. if serviceFailed && !isJointinferenceserviceFinished(&jointinferenceservice) {
  286. // returning an error will re-enqueue jointinferenceservice after the backoff period
  287. return forget, fmt.Errorf("failed pod(s) detected for jointinference service key %q", key)
  288. }
  289. forget = true
  290. }
  291. return forget, manageServiceErr
  292. }
  293. // NewJointInferenceServiceCondition creates a new joint condition
  294. func NewJointInferenceServiceCondition(conditionType sednav1.JointInferenceServiceConditionType, reason, message string) sednav1.JointInferenceServiceCondition {
  295. return sednav1.JointInferenceServiceCondition{
  296. Type: conditionType,
  297. Status: v1.ConditionTrue,
  298. LastHeartbeatTime: metav1.Now(),
  299. LastTransitionTime: metav1.Now(),
  300. Reason: reason,
  301. Message: message,
  302. }
  303. }
  304. func (jc *JointInferenceServiceController) updateStatus(jointinferenceservice *sednav1.JointInferenceService) error {
  305. serviceClient := jc.client.JointInferenceServices(jointinferenceservice.Namespace)
  306. var err error
  307. for i := 0; i <= statusUpdateRetries; i = i + 1 {
  308. var newJointinferenceservice *sednav1.JointInferenceService
  309. newJointinferenceservice, err = serviceClient.Get(context.TODO(), jointinferenceservice.Name, metav1.GetOptions{})
  310. if err != nil {
  311. break
  312. }
  313. newJointinferenceservice.Status = jointinferenceservice.Status
  314. if _, err = serviceClient.UpdateStatus(context.TODO(), newJointinferenceservice, metav1.UpdateOptions{}); err == nil {
  315. break
  316. }
  317. }
  318. return nil
  319. }
  320. func isJointinferenceserviceFinished(j *sednav1.JointInferenceService) bool {
  321. for _, c := range j.Status.Conditions {
  322. if (c.Type == sednav1.JointInferenceServiceCondFailed) && c.Status == v1.ConditionTrue {
  323. return true
  324. }
  325. }
  326. return false
  327. }
  328. func (jc *JointInferenceServiceController) createPod(service *sednav1.JointInferenceService) (active int32, err error) {
  329. active = 0
  330. // create cloud worker
  331. err = jc.createCloudWorker(service)
  332. if err != nil {
  333. return active, err
  334. }
  335. active++
  336. // create kubernetesService for cloudPod, and get bigServicePort for edgePod
  337. var bigServicePort int32
  338. // FIXME(llhuii): only the case that Spec.NodeName specified is support,
  339. // will support Spec.NodeSelector.
  340. bigModelIP, err := GetNodeIPByName(jc.kubeClient, service.Spec.CloudWorker.Template.Spec.NodeName)
  341. bigServicePort, err = CreateKubernetesService(jc.kubeClient, service, bigModelPort, bigModelIP)
  342. if err != nil {
  343. return active, err
  344. }
  345. // create edge worker
  346. err = jc.createEdgeWorker(service, bigServicePort)
  347. if err != nil {
  348. return active, err
  349. }
  350. active++
  351. return active, err
  352. }
  353. func (jc *JointInferenceServiceController) createCloudWorker(service *sednav1.JointInferenceService) error {
  354. // deliver pod for cloudworker
  355. cloudModelName := service.Spec.CloudWorker.Model.Name
  356. cloudModel, err := jc.client.Models(service.Namespace).Get(context.Background(), cloudModelName, metav1.GetOptions{})
  357. if err != nil {
  358. return fmt.Errorf("failed to get cloud model %s: %w",
  359. cloudModelName, err)
  360. }
  361. var workerParam WorkerParam
  362. secretName := cloudModel.Spec.CredentialName
  363. var modelSecret *v1.Secret
  364. if secretName != "" {
  365. modelSecret, _ = jc.kubeClient.CoreV1().Secrets(service.Namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
  366. }
  367. workerParam.mounts = append(workerParam.mounts, WorkerMount{
  368. URL: &MountURL{
  369. URL: cloudModel.Spec.URL,
  370. Secret: modelSecret,
  371. },
  372. Name: "model",
  373. EnvName: "MODEL_URL",
  374. })
  375. workerParam.env = map[string]string{
  376. "NAMESPACE": service.Namespace,
  377. "SERVICE_NAME": service.Name,
  378. "WORKER_NAME": "cloudworker-" + utilrand.String(5),
  379. "BIG_MODEL_BIND_PORT": strconv.Itoa(int(bigModelPort)),
  380. }
  381. workerParam.workerType = jointInferenceForCloud
  382. // create cloud pod
  383. _, err = createPodWithTemplate(jc.kubeClient,
  384. service,
  385. &service.Spec.CloudWorker.Template,
  386. &workerParam)
  387. return err
  388. }
  389. func (jc *JointInferenceServiceController) createEdgeWorker(service *sednav1.JointInferenceService, bigServicePort int32) error {
  390. // deliver pod for edgeworker
  391. ctx := context.Background()
  392. edgeModelName := service.Spec.EdgeWorker.Model.Name
  393. edgeModel, err := jc.client.Models(service.Namespace).Get(ctx, edgeModelName, metav1.GetOptions{})
  394. if err != nil {
  395. return fmt.Errorf("failed to get edge model %s: %w",
  396. edgeModelName, err)
  397. }
  398. secretName := edgeModel.Spec.CredentialName
  399. var modelSecret *v1.Secret
  400. if secretName != "" {
  401. modelSecret, _ = jc.kubeClient.CoreV1().Secrets(service.Namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
  402. }
  403. // FIXME(llhuii): only the case that Spec.NodeName specified is support,
  404. // will support Spec.NodeSelector.
  405. // get bigModelIP from nodeName in cloudWorker
  406. bigModelIP, err := GetNodeIPByName(jc.kubeClient, service.Spec.CloudWorker.Template.Spec.NodeName)
  407. if err != nil {
  408. return fmt.Errorf("failed to get node ip: %w", err)
  409. }
  410. edgeWorker := service.Spec.EdgeWorker
  411. HEMParameterJSON, _ := json.Marshal(edgeWorker.HardExampleMining.Parameters)
  412. HEMParameterString := string(HEMParameterJSON)
  413. var workerParam WorkerParam
  414. workerParam.mounts = append(workerParam.mounts, WorkerMount{
  415. URL: &MountURL{
  416. URL: edgeModel.Spec.URL,
  417. Secret: modelSecret,
  418. },
  419. Name: "model",
  420. EnvName: "MODEL_URL",
  421. })
  422. workerParam.env = map[string]string{
  423. "NAMESPACE": service.Namespace,
  424. "SERVICE_NAME": service.Name,
  425. "WORKER_NAME": "edgeworker-" + utilrand.String(5),
  426. "BIG_MODEL_IP": bigModelIP,
  427. "BIG_MODEL_PORT": strconv.Itoa(int(bigServicePort)),
  428. "HEM_NAME": edgeWorker.HardExampleMining.Name,
  429. "HEM_PARAMETERS": HEMParameterString,
  430. "LC_SERVER": jc.cfg.LC.Server,
  431. }
  432. workerParam.workerType = jointInferenceForEdge
  433. workerParam.hostNetwork = true
  434. // create edge pod
  435. _, err = createPodWithTemplate(jc.kubeClient,
  436. service,
  437. &service.Spec.EdgeWorker.Template,
  438. &workerParam)
  439. return err
  440. }
  441. // GetName returns the name of the joint inference controller
  442. func (jc *JointInferenceServiceController) GetName() string {
  443. return "JointInferenceServiceController"
  444. }
  445. // NewJointController creates a new JointInferenceService controller that keeps the relevant pods
  446. // in sync with their corresponding JointInferenceService objects.
  447. func NewJointController(cfg *config.ControllerConfig) (FeatureControllerI, error) {
  448. var err error
  449. namespace := cfg.Namespace
  450. if namespace == "" {
  451. namespace = metav1.NamespaceAll
  452. }
  453. kubeClient, _ := utils.KubeClient()
  454. kubecfg, _ := utils.KubeConfig()
  455. crdclient, _ := clientset.NewForConfig(kubecfg)
  456. kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, time.Second*30, kubeinformers.WithNamespace(namespace))
  457. podInformer := kubeInformerFactory.Core().V1().Pods()
  458. serviceInformerFactory := informers.NewSharedInformerFactoryWithOptions(crdclient, time.Second*30, informers.WithNamespace(namespace))
  459. serviceInformer := serviceInformerFactory.Sedna().V1alpha1().JointInferenceServices()
  460. eventBroadcaster := record.NewBroadcaster()
  461. eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})
  462. jc := &JointInferenceServiceController{
  463. kubeClient: kubeClient,
  464. client: crdclient.SednaV1alpha1(),
  465. queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(DefaultBackOff, MaxBackOff), "jointinferenceservice"),
  466. recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "jointinferenceservice-controller"}),
  467. cfg: cfg,
  468. }
  469. serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
  470. AddFunc: func(obj interface{}) {
  471. jc.enqueueController(obj, true)
  472. },
  473. UpdateFunc: func(old, cur interface{}) {
  474. jc.enqueueController(cur, true)
  475. },
  476. DeleteFunc: func(obj interface{}) {
  477. jc.enqueueController(obj, true)
  478. },
  479. })
  480. jc.serviceLister = serviceInformer.Lister()
  481. jc.serviceStoreSynced = serviceInformer.Informer().HasSynced
  482. podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
  483. AddFunc: jc.addPod,
  484. UpdateFunc: jc.updatePod,
  485. DeleteFunc: jc.deletePod,
  486. })
  487. jc.podStore = podInformer.Lister()
  488. jc.podStoreSynced = podInformer.Informer().HasSynced
  489. stopCh := messageContext.Done()
  490. kubeInformerFactory.Start(stopCh)
  491. serviceInformerFactory.Start(stopCh)
  492. return jc, err
  493. }