github.com/Azure/aad-pod-identity@v1.8.17/pkg/k8s/client.go (about)

     1  package k8s
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	aadpodid "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity"
    12  	crd "github.com/Azure/aad-pod-identity/pkg/crd"
    13  	"github.com/Azure/aad-pod-identity/pkg/metrics"
    14  	"github.com/Azure/aad-pod-identity/version"
    15  
    16  	v1 "k8s.io/api/core/v1"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	informersv1 "k8s.io/client-go/informers/core/v1"
    19  	"k8s.io/client-go/informers/internalinterfaces"
    20  	"k8s.io/client-go/kubernetes"
    21  	"k8s.io/client-go/rest"
    22  	"k8s.io/client-go/tools/cache"
    23  	"k8s.io/client-go/tools/clientcmd"
    24  	"k8s.io/klog/v2"
    25  )
    26  
    27  const (
    28  	getPodListRetries               = 4
    29  	getPodListSleepTimeMilliseconds = 300
    30  )
    31  
    32  // Client api client
    33  type Client interface {
    34  	// Start just starts any informers required.
    35  	Start(<-chan struct{})
    36  	// GetPod returns the pod object based on name and namespce
    37  	GetPod(namespace, name string) (v1.Pod, error)
    38  	// GetPodInfo returns the pod name, namespace & replica set name for a given pod ip
    39  	GetPodInfo(podip string) (podns, podname, rsName string, selectors *metav1.LabelSelector, err error)
    40  	// ListPodIds pod matching azure identity or nil
    41  	ListPodIds(podns, podname string) (map[string][]aadpodid.AzureIdentity, error)
    42  	// ListPodIdsWithBinding pod matching azure identity or nil
    43  	ListPodIdsWithBinding(podns string, labels map[string]string) ([]aadpodid.AzureIdentity, error)
    44  	// GetSecret returns secret the secretRef represents
    45  	GetSecret(secretRef *v1.SecretReference) (*v1.Secret, error)
    46  	// ListPodIdentityExceptions returns list of azurepodidentityexceptions
    47  	ListPodIdentityExceptions(namespace string) (*[]aadpodid.AzurePodIdentityException, error)
    48  }
    49  
    50  // KubeClient k8s client
    51  type KubeClient struct {
    52  	// Main Kubernetes client
    53  	ClientSet kubernetes.Interface
    54  	// Crd client used to access our CRD resources.
    55  	CrdClient   *crd.Client
    56  	PodInformer cache.SharedIndexInformer
    57  	reporter    *metrics.Reporter
    58  }
    59  
    60  // NewKubeClient new kubernetes api client
    61  func NewKubeClient(nodeName string, scale, isStandardMode bool) (Client, error) {
    62  	config, err := buildConfig()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	config.UserAgent = version.GetUserAgent("NMI", version.NMIVersion)
    67  	clientset, err := getkubeclient(config)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	crdclient, err := crd.NewCRDClientLite(config, nodeName, scale, isStandardMode)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	reporter, err := metrics.NewReporter()
    76  	if err != nil {
    77  		return nil, fmt.Errorf("failed to create reporter for metrics, error: %+v", err)
    78  	}
    79  
    80  	podInformer := informersv1.NewFilteredPodInformer(clientset, v1.NamespaceAll, 10*time.Minute,
    81  		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
    82  		NodeNameFilter(nodeName))
    83  
    84  	kubeClient := &KubeClient{
    85  		CrdClient:   crdclient,
    86  		ClientSet:   clientset,
    87  		PodInformer: podInformer,
    88  		reporter:    reporter,
    89  	}
    90  
    91  	return kubeClient, nil
    92  }
    93  
    94  // Sync syncs the cache from the K8s client.
    95  func (c *KubeClient) Sync(exit <-chan struct{}) {
    96  	if !cache.WaitForCacheSync(exit, c.PodInformer.HasSynced) {
    97  		klog.Error("pod cache could not be synchronized")
    98  	}
    99  }
   100  
   101  // Start the corresponding starts
   102  func (c *KubeClient) Start(exit <-chan struct{}) {
   103  	go c.PodInformer.Run(exit)
   104  	c.CrdClient.StartLite(exit)
   105  	c.Sync(exit)
   106  }
   107  
   108  func (c *KubeClient) getReplicasetName(pod v1.Pod) string {
   109  	for _, owner := range pod.OwnerReferences {
   110  		if strings.EqualFold(owner.Kind, "ReplicaSet") {
   111  			return owner.Name
   112  		}
   113  	}
   114  	return ""
   115  }
   116  
   117  // NodeNameFilter will tweak the options to include the node name as field
   118  // selector.
   119  func NodeNameFilter(nodeName string) internalinterfaces.TweakListOptionsFunc {
   120  	return func(l *metav1.ListOptions) {
   121  		if l == nil {
   122  			l = &metav1.ListOptions{}
   123  		}
   124  		l.FieldSelector = l.FieldSelector + "spec.nodeName=" + nodeName
   125  	}
   126  }
   127  
   128  // GetPod returns pod that matches namespace and name
   129  func (c *KubeClient) GetPod(namespace, name string) (v1.Pod, error) {
   130  	// TODO (aramase) wrap this with retries
   131  	obj, exists, err := c.PodInformer.GetIndexer().GetByKey(namespace + "/" + name)
   132  	if err != nil {
   133  		return v1.Pod{}, fmt.Errorf("failed to get pod %s/%s, error: %+v", namespace, name, err)
   134  	}
   135  	if !exists {
   136  		return v1.Pod{}, fmt.Errorf("pod %s/%s doesn't exist", namespace, name)
   137  	}
   138  	pod, ok := obj.(*v1.Pod)
   139  	if !ok {
   140  		return v1.Pod{}, fmt.Errorf("could not cast %T to v1.Pod", pod)
   141  	}
   142  	return *pod, nil
   143  }
   144  
   145  // GetPodInfo get pod ns,name from apiserver
   146  func (c *KubeClient) GetPodInfo(podip string) (string, string, string, *metav1.LabelSelector, error) {
   147  	if podip == "" {
   148  		return "", "", "", nil, fmt.Errorf("pod IP is empty")
   149  	}
   150  
   151  	podList, err := c.getPodListRetry(podip, getPodListRetries, getPodListSleepTimeMilliseconds)
   152  
   153  	if err != nil {
   154  		return "", "", "", nil, err
   155  	}
   156  	numMatching := len(podList)
   157  	if numMatching == 1 {
   158  		return podList[0].Namespace, podList[0].Name, c.getReplicasetName(*podList[0]), &metav1.LabelSelector{
   159  			MatchLabels: podList[0].Labels}, nil
   160  	}
   161  
   162  	return "", "", "", nil, fmt.Errorf("failed to match pod IP %s with %v", podip, podList)
   163  }
   164  
   165  func isPhaseValid(p v1.PodPhase) bool {
   166  	return p == v1.PodPending || p == v1.PodRunning
   167  }
   168  
   169  func (c *KubeClient) getPodList(podip string) ([]*v1.Pod, error) {
   170  	var podList []*v1.Pod
   171  	list := c.PodInformer.GetStore().List()
   172  	for _, o := range list {
   173  		pod, ok := o.(*v1.Pod)
   174  		if !ok {
   175  			err := fmt.Errorf("could not cast %T to v1.Pod", pod)
   176  			return nil, err
   177  		}
   178  		if pod.Status.PodIP == podip && isPhaseValid(pod.Status.Phase) {
   179  			podList = append(podList, pod)
   180  		}
   181  	}
   182  	if len(podList) == 0 {
   183  		return nil, fmt.Errorf("pod list is empty")
   184  	}
   185  	return podList, nil
   186  }
   187  
   188  func (c *KubeClient) getPodListRetry(podip string, retries int, sleeptime time.Duration) ([]*v1.Pod, error) {
   189  	var podList []*v1.Pod
   190  	var err error
   191  	i := 0
   192  
   193  	for {
   194  		// Atleast run the getpodlist once.
   195  		podList, err = c.getPodList(podip)
   196  		if err == nil {
   197  			return podList, nil
   198  		}
   199  		merr := c.reporter.ReportKubernetesAPIOperationError(metrics.GetPodListOperationName)
   200  		if merr != nil {
   201  			klog.Warningf("failed to report metrics, error: %+v", merr)
   202  		}
   203  
   204  		if i >= retries {
   205  			break
   206  		}
   207  		i++
   208  		klog.Warningf("list pod error: %+v. Retrying, attempt number: %d", err, i)
   209  		time.Sleep(sleeptime * time.Millisecond)
   210  	}
   211  	// We reach here only if there is an error and we have exhausted all retries.
   212  	// Return the last error
   213  	return nil, err
   214  }
   215  
   216  // GetLocalIP returns the non loopback local IP of the host
   217  func GetLocalIP() (string, error) {
   218  	addrs, err := net.InterfaceAddrs()
   219  	if err != nil {
   220  		return "", err
   221  	}
   222  	for _, address := range addrs {
   223  		// check the address type and if it is not a loopback
   224  		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
   225  			if ipnet.IP.To4() != nil {
   226  				return ipnet.IP.String(), nil
   227  			}
   228  		}
   229  	}
   230  	return "", fmt.Errorf("non loopback IP address not found")
   231  }
   232  
   233  // ListPodIds lists matching ids for pod or error
   234  func (c *KubeClient) ListPodIds(podns, podname string) (map[string][]aadpodid.AzureIdentity, error) {
   235  	return c.CrdClient.ListPodIds(podns, podname)
   236  }
   237  
   238  // ListPodIdsWithBinding list matching ids for pod based on the bindings
   239  func (c *KubeClient) ListPodIdsWithBinding(podns string, labels map[string]string) ([]aadpodid.AzureIdentity, error) {
   240  	return c.CrdClient.GetPodIDsWithBinding(podns, labels)
   241  }
   242  
   243  // ListPodIdentityExceptions lists azurepodidentityexceptions
   244  func (c *KubeClient) ListPodIdentityExceptions(ns string) (*[]aadpodid.AzurePodIdentityException, error) {
   245  	return c.CrdClient.ListPodIdentityExceptions(ns)
   246  }
   247  
   248  // GetSecret returns secret the secretRef represents
   249  func (c *KubeClient) GetSecret(secretRef *v1.SecretReference) (*v1.Secret, error) {
   250  	secret, err := c.ClientSet.CoreV1().Secrets(secretRef.Namespace).Get(context.TODO(), secretRef.Name, metav1.GetOptions{})
   251  	if err != nil {
   252  		merr := c.reporter.ReportKubernetesAPIOperationError(metrics.GetSecretOperationName)
   253  		if merr != nil {
   254  			klog.Warningf("failed to report metrics, error: %+v", err)
   255  		}
   256  		return nil, err
   257  	}
   258  	return secret, nil
   259  }
   260  
   261  func getkubeclient(config *rest.Config) (*kubernetes.Clientset, error) {
   262  	// creates the clientset
   263  	kubeClient, err := kubernetes.NewForConfig(config)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	return kubeClient, err
   269  }
   270  
   271  // Create the client config. Use kubeconfig if given, otherwise assume in-cluster.
   272  func buildConfig() (*rest.Config, error) {
   273  	kubeconfigPath := os.Getenv("KUBECONFIG")
   274  	if kubeconfigPath != "" {
   275  		return clientcmd.BuildConfigFromFlags("", kubeconfigPath)
   276  	}
   277  
   278  	return rest.InClusterConfig()
   279  }