Skip to content

Commit

Permalink
feat: Support Pod relationships
Browse files Browse the repository at this point in the history
Signed-off-by: Justin Toh <tohjustin@hotmail.com>
  • Loading branch information
tohjustin committed Sep 27, 2021
1 parent 28ca41a commit 0781ef4
Showing 2 changed files with 150 additions and 4 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -33,7 +33,8 @@ List of supported relationships used for discovering dependent objects:

- Kubernetes
- [Controller References](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/controller-ref.md) & [Owner References](https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/)
- [Event Regarding & Related References](https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/event-v1/)
- [Event References](https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/event-v1/)
- [Pod References](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/)
- Helm (Coming Soon)

## Installation
151 changes: 148 additions & 3 deletions pkg/cmd/lineage/graph.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package lineage

import (
"fmt"
"sort"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
)
@@ -58,6 +61,12 @@ func (n *Node) GetNestedString(fields ...string) string {
return val
}

// Key returns a key composed of the node's group, kind, namespace & name which
// can be used to identify or reference a node.
func (n *Node) Key() string {
return fmt.Sprintf("%s/%s/%s/%s", n.Group, n.Kind, n.Namespace, n.Name)
}

// NodeMap contains a relationship tree stored as a map of nodes.
type NodeMap map[types.UID]*Node

@@ -69,14 +78,25 @@ const (
// Kubernetes Owner-Dependent relationships.
RelationshipControllerRef Relationship = "ControllerReference"
RelationshipOwnerRef Relationship = "OwnerReference"

// Kubernetes Pod relationships.
RelationshipPodContainerEnv Relationship = "PodContainerEnvironment"
RelationshipPodImagePullSecret Relationship = "PodImagePullSecret" //nolint:gosec
RelationshipPodNode Relationship = "PodNode"
RelationshipPodPriorityClass Relationship = "PodPriorityClass"
RelationshipPodRuntimeClass Relationship = "PodRuntimeClass"
RelationshipPodServiceAccount Relationship = "PodServiceAccount"
RelationshipPodVolume Relationship = "PodVolume"
)

// resolveDependents resolves all dependents of the provided root object and
// returns a relationship tree.
//nolint:funlen,gocognit
func resolveDependents(objects []unstructuredv1.Unstructured, rootUID types.UID) NodeMap {
// Create global node map of all objects
// Create global node maps of all objects, one mapped by node UIDs & the other
// mapped by node keys
globalMap := NodeMap{}
globalMapByKey := map[string]*Node{}
for ix, o := range objects {
gvk := o.GroupVersionKind()
node := Node{
@@ -90,6 +110,7 @@ func resolveDependents(objects []unstructuredv1.Unstructured, rootUID types.UID)
Dependents: map[types.UID]RelationshipSet{},
}
globalMap[node.UID] = &node
globalMapByKey[node.Key()] = &node

if node.Group == "" && node.Kind == "Node" {
// Node events sent by the Kubelet uses the node's name as the
@@ -105,7 +126,7 @@ func resolveDependents(objects []unstructuredv1.Unstructured, rootUID types.UID)
}
}

// Populate dependents based on ControllerRef & OwnerRef relationships
// Populate dependents based on Owner-Dependent relationships
for _, node := range globalMap {
for _, ref := range node.OwnerReferences {
if n, ok := globalMap[ref.UID]; ok {
@@ -117,7 +138,7 @@ func resolveDependents(objects []unstructuredv1.Unstructured, rootUID types.UID)
}
}

// Populate dependents based on EventRegarding & EventRelated relationships
// Populate dependents based on Event relationships
// TODO: It's possible to have events to be in a different namespace from the
// its referenced object, so update the resource fetching logic to
// always try to fetch events at the cluster scope for event resources
@@ -137,6 +158,24 @@ func resolveDependents(objects []unstructuredv1.Unstructured, rootUID types.UID)
}
}

// Populate dependents based on Pod relationships
for _, node := range globalMap {
if node.Group == "" && node.Kind == "Pod" {
keyToRsetMap, err := getPodRelationships(node)
if err != nil {
klog.V(4).Infof("Failed to get object references for pod named \"%s\" in namespace \"%s\": %s", node.Name, node.Namespace, err)
continue
}
for k, rset := range keyToRsetMap {
if n, ok := globalMapByKey[k]; ok {
for r := range rset {
n.AddDependent(node.UID, r)
}
}
}
}
}

// Create submap of the root node & its dependents from the global map
nodeMap, uidQueue, uidSet := NodeMap{}, []types.UID{}, map[types.UID]struct{}{}
if node := globalMap[rootUID]; node != nil {
@@ -186,3 +225,109 @@ func getEventReferenceUIDs(n *Node) (types.UID, types.UID) {
}
return types.UID(regUID), types.UID(relUID)
}

// getPodRelationships returns returns a map of relationships (keyed by the
// nodes representing each object reference) that this Pod has.
//nolint:funlen,gocognit
func getPodRelationships(n *Node) (map[string]RelationshipSet, error) {
var pod corev1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(n.UnstructuredContent(), &pod)
if err != nil {
return nil, err
}

var node Node
ns := pod.Namespace
result := map[string]RelationshipSet{}
addRelationship := func(r Relationship, n Node) {
k := n.Key()
if _, ok := result[k]; !ok {
result[k] = RelationshipSet{r: {}}
}
result[k][r] = struct{}{}
}

// RelationshipPodContainerEnv
var cList []corev1.Container
cList = append(cList, pod.Spec.InitContainers...)
cList = append(cList, pod.Spec.Containers...)
for _, c := range cList {
for _, env := range c.EnvFrom {
switch {
case env.ConfigMapRef != nil:
node = Node{Kind: "ConfigMap", Name: env.ConfigMapRef.Name, Namespace: ns}
addRelationship(RelationshipPodContainerEnv, node)
case env.SecretRef != nil:
node = Node{Kind: "Secret", Name: env.SecretRef.Name, Namespace: ns}
addRelationship(RelationshipPodContainerEnv, node)
}
}
for _, env := range c.Env {
if env.ValueFrom == nil {
continue
}
switch {
case env.ValueFrom.ConfigMapKeyRef != nil:
node = Node{Kind: "ConfigMap", Name: env.ValueFrom.ConfigMapKeyRef.Name, Namespace: ns}
addRelationship(RelationshipPodContainerEnv, node)
case env.ValueFrom.SecretKeyRef != nil:
node = Node{Kind: "Secret", Name: env.ValueFrom.SecretKeyRef.Name, Namespace: ns}
addRelationship(RelationshipPodContainerEnv, node)
}
}
}

// RelationshipPodImagePullSecret
for _, ips := range pod.Spec.ImagePullSecrets {
node = Node{Kind: "Secret", Name: ips.Name, Namespace: ns}
addRelationship(RelationshipPodImagePullSecret, node)
}

// RelationshipPodNode
node = Node{Kind: "Node", Name: pod.Spec.NodeName}
addRelationship(RelationshipPodNode, node)

// RelationshipPodPriorityClass
if pc := pod.Spec.PriorityClassName; len(pc) != 0 {
node = Node{Group: "scheduling.k8s.io", Kind: "PriorityClass", Name: pc}
addRelationship(RelationshipPodPriorityClass, node)
}

// RelationshipPodRuntimeClass
if rc := pod.Spec.RuntimeClassName; rc != nil && len(*rc) != 0 {
node = Node{Group: "node.k8s.io", Kind: "RuntimeClass", Name: *rc}
addRelationship(RelationshipPodRuntimeClass, node)
}

// RelationshipPodServiceAccount
node = Node{Kind: "ServiceAccount", Name: pod.Spec.ServiceAccountName, Namespace: ns}
addRelationship(RelationshipPodServiceAccount, node)

// RelationshipPodVolume
for _, v := range pod.Spec.Volumes {
switch {
case v.ConfigMap != nil:
node = Node{Kind: "ConfigMap", Name: v.ConfigMap.Name, Namespace: ns}
addRelationship(RelationshipPodVolume, node)
case v.PersistentVolumeClaim != nil:
node = Node{Kind: "PersistentVolumeClaim", Name: v.PersistentVolumeClaim.ClaimName, Namespace: ns}
addRelationship(RelationshipPodVolume, node)
case v.Secret != nil:
node = Node{Kind: "Secret", Name: v.Secret.SecretName, Namespace: ns}
addRelationship(RelationshipPodVolume, node)
case v.Projected != nil:
for _, src := range v.Projected.Sources {
switch {
case src.ConfigMap != nil:
node = Node{Kind: "ConfigMap", Name: src.ConfigMap.Name, Namespace: ns}
addRelationship(RelationshipPodVolume, node)
case src.Secret != nil:
node = Node{Kind: "Secret", Name: src.Secret.Name, Namespace: ns}
addRelationship(RelationshipPodVolume, node)
}
}
}
}

return result, nil
}

0 comments on commit 0781ef4

Please sign in to comment.