istio
169 строк · 5.7 Кб
1// Copyright Istio Authors
2//
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//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package controller
16
17import (
18"context"
19
20v1 "k8s.io/api/core/v1"
21"k8s.io/apimachinery/pkg/api/errors"
22metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
24"k8s.io/apimachinery/pkg/runtime"
25"k8s.io/apimachinery/pkg/types"
26mcsapi "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
27
28"istio.io/istio/pilot/pkg/model"
29serviceRegistryKube "istio.io/istio/pilot/pkg/serviceregistry/kube"
30"istio.io/istio/pkg/cluster"
31"istio.io/istio/pkg/config/schema/gvk"
32"istio.io/istio/pkg/kube"
33"istio.io/istio/pkg/kube/controllers"
34"istio.io/istio/pkg/kube/kclient"
35"istio.io/istio/pkg/kube/kubetypes"
36"istio.io/istio/pkg/kube/mcs"
37)
38
39type autoServiceExportController struct {
40autoServiceExportOptions
41
42client kube.Client
43queue controllers.Queue
44services kclient.Client[*v1.Service]
45
46// We use this flag to short-circuit the logic and stop the controller
47// if the CRD does not exist (or is deleted)
48mcsSupported bool
49}
50
51// autoServiceExportOptions provide options for creating a autoServiceExportController.
52type autoServiceExportOptions struct {
53Client kube.Client
54ClusterID cluster.ID
55DomainSuffix string
56ClusterLocal model.ClusterLocalProvider
57}
58
59// newAutoServiceExportController creates a new autoServiceExportController.
60func newAutoServiceExportController(opts autoServiceExportOptions) *autoServiceExportController {
61c := &autoServiceExportController{
62autoServiceExportOptions: opts,
63client: opts.Client,
64mcsSupported: true,
65}
66c.queue = controllers.NewQueue("auto export",
67controllers.WithReconciler(c.Reconcile),
68controllers.WithMaxAttempts(5))
69
70c.services = kclient.NewFiltered[*v1.Service](opts.Client, kubetypes.Filter{ObjectFilter: opts.Client.ObjectFilter()})
71
72// Only handle add. The controller only acts on parts of the service
73// that are immutable (e.g. name). When we create ServiceExport, we bind its
74// lifecycle to the Service so that when the Service is deleted,
75// k8s automatically deletes the ServiceExport.
76c.services.AddEventHandler(controllers.EventHandler[controllers.Object]{AddFunc: c.queue.AddObject})
77
78return c
79}
80
81func (c *autoServiceExportController) Run(stopCh <-chan struct{}) {
82kube.WaitForCacheSync("auto service export", stopCh, c.services.HasSynced)
83c.queue.Run(stopCh)
84c.services.ShutdownHandlers()
85}
86
87func (c *autoServiceExportController) logPrefix() string {
88return "AutoServiceExport (cluster=" + c.ClusterID.String() + ") "
89}
90
91// func (c *autoServiceExportController) createServiceExportIfNotPresent(svc *v1.Service) error {
92func (c *autoServiceExportController) Reconcile(key types.NamespacedName) error {
93if !c.mcsSupported {
94// Don't create ServiceExport if MCS is not supported on the cluster.
95log.Debugf("%s ignoring added Service, since !mcsSupported", c.logPrefix())
96return nil
97}
98
99svc := c.services.Get(key.Name, key.Namespace)
100if svc == nil {
101// Service no longer exists, no action needed
102return nil
103}
104
105if c.isClusterLocalService(svc) {
106// Don't create ServiceExport if the service is configured to be
107// local to the cluster (i.e. non-exported).
108log.Debugf("%s ignoring cluster-local service %s/%s", c.logPrefix(), svc.Namespace, svc.Name)
109return nil
110}
111serviceExport := mcsapi.ServiceExport{
112TypeMeta: metav1.TypeMeta{
113Kind: "ServiceExport",
114APIVersion: mcs.MCSSchemeGroupVersion.String(),
115},
116ObjectMeta: metav1.ObjectMeta{
117Namespace: svc.Namespace,
118Name: svc.Name,
119
120// Bind the lifecycle of the ServiceExport to the Service. We do this by making the Service
121// the "owner" of the ServiceExport resource.
122OwnerReferences: []metav1.OwnerReference{
123{
124APIVersion: v1.SchemeGroupVersion.String(),
125Kind: gvk.Service.Kind,
126Name: svc.Name,
127UID: svc.UID,
128},
129},
130},
131}
132
133// Convert to unstructured.
134u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&serviceExport)
135if err != nil {
136log.Warnf("%s failed converting ServiceExport %s/%s to Unstructured: %v", c.logPrefix(),
137svc.Namespace, svc.Name, err)
138return err
139}
140
141if _, err = c.client.Dynamic().Resource(mcs.ServiceExportGVR).Namespace(serviceExport.Namespace).Create(
142context.TODO(), &unstructured.Unstructured{Object: u}, metav1.CreateOptions{}); err != nil {
143switch {
144case errors.IsAlreadyExists(err):
145// The ServiceExport already exists. Nothing to do.
146return nil
147case errors.IsNotFound(err):
148log.Warnf("%s ServiceExport CRD Not found. Shutting down MCS ServiceExport sync. "+
149"Please add the CRD then restart the istiod deployment", c.logPrefix())
150c.mcsSupported = false
151
152// Do not return the error, so that the queue does not attempt a retry.
153return nil
154}
155}
156
157if err != nil {
158log.Warnf("%s failed creating ServiceExport %s/%s: %v", c.logPrefix(), svc.Namespace, svc.Name, err)
159return err
160}
161
162log.Debugf("%s created ServiceExport %s/%s", c.logPrefix(), svc.Namespace, svc.Name)
163return nil
164}
165
166func (c *autoServiceExportController) isClusterLocalService(svc *v1.Service) bool {
167hostname := serviceRegistryKube.ServiceHostname(svc.Name, svc.Namespace, c.DomainSuffix)
168return c.ClusterLocal.GetClusterLocalHosts().IsClusterLocal(hostname)
169}
170