diff --git a/go.mod b/go.mod index 9695eb33ba..53cd217079 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/apache/arrow/go/v16 v16.1.0 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be - github.com/containerd/containerd v1.7.27 + github.com/containerd/containerd v1.7.29 github.com/docker/docker v28.3.3+incompatible github.com/dustin/go-humanize v1.0.1 github.com/elastic/go-freelru v0.16.0 @@ -52,15 +52,19 @@ require ( k8s.io/api v0.32.3 k8s.io/apimachinery v0.32.3 k8s.io/client-go v0.32.3 + k8s.io/cri-api v0.32.3 + k8s.io/cri-client v0.32.3 ) require ( buf.build/gen/go/gogo/protobuf/protocolbuffers/go v1.36.6-20240617172848-e1dbca2775a7.1 // indirect + cyphar.com/go-pathrs v0.2.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.9 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cilium/ebpf v0.19.0 // indirect @@ -74,6 +78,7 @@ require ( github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect @@ -101,6 +106,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect @@ -126,13 +132,15 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect - github.com/opencontainers/selinux v1.12.0 // indirect + github.com/opencontainers/selinux v1.13.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/procfs v0.16.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect @@ -150,7 +158,7 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/term v0.34.0 // indirect golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.11.0 // indirect + golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto v0.0.0-20250414145226-207652e42e2e // indirect @@ -159,6 +167,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/component-base v0.32.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect diff --git a/go.sum b/go.sum index baa6df54bb..56877f8f2f 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.6-20250212095114-4db6f buf.build/gen/go/prometheus/prometheus/protocolbuffers/go v1.36.6-20250320161912-af2aab87b1b3.1 h1:EuFqAB/kfs/jh9aUGcvBjcxtU89wnXwsuQfcwGX1rhE= buf.build/gen/go/prometheus/prometheus/protocolbuffers/go v1.36.6-20250320161912-af2aab87b1b3.1/go.mod h1:ea/VK8bRnfyOuhQRzIk5hGigCqbZdzI8SHNLC3IyABU= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= +cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= @@ -36,6 +38,8 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloD github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -55,8 +59,8 @@ github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= -github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= -github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= +github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= +github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= @@ -81,6 +85,9 @@ github.com/coreos/pkg v0.0.0-20240122114842-bbd7aa9bf6fb h1:GIzvVQ9UkUlOhSDlqmrQ github.com/coreos/pkg v0.0.0-20240122114842-bbd7aa9bf6fb/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -197,6 +204,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= @@ -285,8 +294,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= -github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= +github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= +github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/parca-dev/oomprof v0.1.5 h1:FrfjL9CLDNb2oUfy1XO2BF6wi7SGVBKKSgW75m83lWI= github.com/parca-dev/oomprof v0.1.5/go.mod h1:iqI6XrmiNWOa8m2vEIKo+GtQrqbWCMLFpBWuk8RuAPs= @@ -316,10 +325,13 @@ github.com/prometheus/prometheus v0.303.0 h1:wsNNsbd4EycMCphYnTmNY9JASBVbp7NWwJn github.com/prometheus/prometheus v0.303.0/go.mod h1:8PMRi+Fk1WzopMDeb0/6hbNs9nV6zgySkU/zds5Lu3o= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -465,8 +477,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -536,6 +548,12 @@ k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= +k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= +k8s.io/cri-api v0.32.3 h1:E8VXbXNn4yAgmuKTeNzg0C1MFSxzTdlHSwUvjuYlPTY= +k8s.io/cri-api v0.32.3/go.mod h1:DCzMuTh2padoinefWME0G678Mc3QFbLMF2vEweGzBAI= +k8s.io/cri-client v0.32.3 h1:+D2ajlFpXsUcr/9ofYcE5kVqVK4Q97wnZHeH80oDEzw= +k8s.io/cri-client v0.32.3/go.mod h1:W1+Z8QsVnLkoGqtJ41B5SRHfQn6/mqGORdfNDl2cEkw= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= diff --git a/reporter/metadata/containermetadata.go b/reporter/metadata/containermetadata.go index ab8deb9d20..55345973b7 100644 --- a/reporter/metadata/containermetadata.go +++ b/reporter/metadata/containermetadata.go @@ -108,6 +108,7 @@ type containerMetadataProvider struct { kubernetesClientQueryCount atomic.Uint64 dockerClientQueryCount atomic.Uint64 containerdClientQueryCount atomic.Uint64 + criClientQueryCount atomic.Uint64 // the kubernetes node name used to retrieve the pod information. nodeName string @@ -128,6 +129,9 @@ type containerMetadataProvider struct { deferredPID *lru.SyncedLRU[libpf.PID, libpf.Void] kubernetesNode *corev1.Node + + // CRI client for direct container runtime queries + criClient *criClient } // hashString is a helper function for containerMetadataCache @@ -189,6 +193,15 @@ func NewContainerMetadataProvider(ctx context.Context, nodeName string) (Metadat if err != nil { return nil, fmt.Errorf("failed to create kubernetes client %v", err) } + + // Try to initialize CRI client for faster pod metadata retrieval + criClient, err := newCRIClient("") + if err != nil { + log.Infof("CRI client not available, will use kube-apiserver: %v", err) + } else { + p.criClient = criClient + log.Infof("Using CRI for pod metadata retrieval (reduced apiserver load)") + } } else { log.Infof("Environment variable %s not set", kubernetesServiceHost) p.containerMetadataCache, err = lru.NewSynced[string, model.LabelSet]( @@ -600,61 +613,82 @@ func (p *containerMetadataProvider) getKubernetesPodMetadata(ctx context.Context model.LabelSet, error) { log.Debugf("Get kubernetes pod metadata for container id %v", pidContainerID) - p.kubernetesClientQueryCount.Add(1) - pods, err := p.kubeClientSet.CoreV1().Pods("").List(ctx, v1.ListOptions{ - FieldSelector: "spec.nodeName=" + p.nodeName, - }) - if err != nil { - return nil, fmt.Errorf("failed to retrieve kubernetes pods, %v", err) + var pods []corev1.Pod + var err error + + // Try CRI first if available (fastest, no apiserver load) + if p.criClient != nil { + p.criClientQueryCount.Add(1) + pods, err = p.criClient.listPodsViaCRI(ctx, p.nodeName) + if err != nil { + log.Debugf("Failed to list pods via CRI, falling back to apiserver: %v", err) + pods = nil + } + } + + // Fallback to apiserver if CRI failed or not available + if len(pods) == 0 { + p.kubernetesClientQueryCount.Add(1) + + // Use resourceVersion=0 to read from apiserver cache instead of etcd + podList, err := p.kubeClientSet.CoreV1().Pods("").List(ctx, v1.ListOptions{ + FieldSelector: "spec.nodeName=" + p.nodeName, + ResourceVersion: "0", + }) + if err != nil { + return nil, fmt.Errorf("failed to retrieve kubernetes pods, %v", err) + } + pods = podList.Items } - for j := range pods.Items { - for i := range pods.Items[j].Status.ContainerStatuses { + // Search for the container in pods + for i := range pods { + pod := &pods[i] + + for _, cs := range pod.Status.ContainerStatuses { var containerID string - if pods.Items[j].Status.ContainerStatuses[i].ContainerID == "" { + if cs.ContainerID == "" { continue } - if containerID, err = matchContainerID(pods.Items[j].Status.ContainerStatuses[i].ContainerID); err != nil { + if containerID, err = matchContainerID(cs.ContainerID); err != nil { log.Error(err) continue } if containerID == pidContainerID { - name := pods.Items[j].Status.ContainerStatuses[i].Name - ctr := containerForName(name, pods.Items[j].Spec.Containers) + ctr := containerForName(cs.Name, pod.Spec.Containers) if ctr == nil { - log.Infof("failed to find kubernetes container in spec named: %s", name) + log.Infof("failed to find kubernetes container in spec named: %s", cs.Name) continue } - return p.addPodContainerMetadata(&pods.Items[j], ctr, containerID, false), nil + return p.addPodContainerMetadata(pod, ctr, containerID, false), nil } } - for i := range pods.Items[j].Status.InitContainerStatuses { + for _, cs := range pod.Status.InitContainerStatuses { var containerID string - if pods.Items[j].Status.InitContainerStatuses[i].ContainerID == "" { + if cs.ContainerID == "" { continue } - if containerID, err = matchContainerID(pods.Items[j].Status.InitContainerStatuses[i].ContainerID); err != nil { + if containerID, err = matchContainerID(cs.ContainerID); err != nil { log.Error(err) continue } if containerID == pidContainerID { - name := pods.Items[j].Status.InitContainerStatuses[i].Name - ctr := containerForName(name, pods.Items[j].Spec.InitContainers) + ctr := containerForName(cs.Name, pod.Spec.InitContainers) if ctr == nil { - log.Infof("failed to find init kubernetes container in spec named: %s", name) + log.Infof("failed to find init kubernetes container in spec named: %s", cs.Name) continue } - return p.addPodContainerMetadata(&pods.Items[j], ctr, containerID, false), nil + return p.addPodContainerMetadata(pod, ctr, containerID, false), nil } } } return nil, fmt.Errorf("failed to find matching kubernetes pod/container metadata for "+ - "containerID '%v' in %d pods", pidContainerID, len(pods.Items)) + "containerID '%v' in %d pods", pidContainerID, len(pods)) } func (p *containerMetadataProvider) getDockerContainerMetadata(ctx context.Context, pidContainerID string) ( diff --git a/reporter/metadata/cri_client.go b/reporter/metadata/cri_client.go new file mode 100644 index 0000000000..ea87e6438c --- /dev/null +++ b/reporter/metadata/cri_client.go @@ -0,0 +1,148 @@ +package metadata + +import ( + "context" + "fmt" + "os" + "time" + + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + internalapi "k8s.io/cri-api/pkg/apis" + runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + remote "k8s.io/cri-client/pkg" +) + +const criTimeout = 2 * time.Second // same as crictl + +// criClient wraps the CRI RuntimeService for pod metadata retrieval +type criClient struct { + service internalapi.RuntimeService +} + +// newCRIClient creates a new CRI client using the official k8s.io/cri-client +func newCRIClient(socketPath string) (*criClient, error) { + if socketPath == "" { + socketPath = findCRISocket() + if socketPath == "" { + return nil, fmt.Errorf("no CRI socket found") + } + } + + // Check if socket exists + if _, err := os.Stat(socketPath); err != nil { + return nil, fmt.Errorf("CRI socket not accessible: %w", err) + } + + log.Infof("Connecting to CRI socket: %s", socketPath) + + // Use the official CRI client library (same as crictl) + service, err := remote.NewRemoteRuntimeService(socketPath, criTimeout, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to create CRI client: %w", err) + } + + // Verify connection + ctx, cancel := context.WithTimeout(context.Background(), criTimeout) + defer cancel() + + _, err = service.Version(ctx, "v1") + if err != nil { + return nil, fmt.Errorf("failed to verify CRI connection: %w", err) + } + + log.Info("Successfully connected to CRI runtime") + + return &criClient{service: service}, nil +} + +// findCRISocket tries to find a CRI socket in common locations +func findCRISocket() string { + commonPaths := []string{ + "/run/containerd/containerd.sock", // containerd + "/var/run/containerd/containerd.sock", // containerd (alternative) + "/var/run/crio/crio.sock", // CRI-O + "/run/crio/crio.sock", // CRI-O (alternative) + "/run/cri-dockerd.sock", // cri-dockerd + "/var/run/cri-dockerd.sock", // cri-dockerd (alternative) + } + + for _, path := range commonPaths { + if _, err := os.Stat(path); err == nil { + return path + } + } + + return "" +} + +// listPodsViaCRI retrieves all pods on the node via CRI +func (c *criClient) listPodsViaCRI(ctx context.Context, nodeName string) ([]corev1.Pod, error) { + // List all pod sandboxes (no need for detailed status calls) + sandboxes, err := c.service.ListPodSandbox(ctx, nil) + if err != nil { + return nil, fmt.Errorf("failed to list pod sandboxes: %w", err) + } + + // List all containers once + containers, err := c.service.ListContainers(ctx, nil) + if err != nil { + return nil, fmt.Errorf("failed to list containers: %w", err) + } + + // Build map of sandbox ID -> containers + sandboxContainers := make(map[string][]*runtimeapi.Container) + for _, container := range containers { + sandboxID := container.PodSandboxId + sandboxContainers[sandboxID] = append(sandboxContainers[sandboxID], container) + } + + // Convert sandboxes to pods + pods := make([]corev1.Pod, 0, len(sandboxes)) + for _, sandbox := range sandboxes { + pod := convertSandboxToPod(sandbox, nodeName, sandboxContainers[sandbox.Id]) + pods = append(pods, *pod) + } + + log.Debugf("Listed %d pods via CRI", len(pods)) + return pods, nil +} + +// convertSandboxToPod converts CRI PodSandbox to corev1.Pod +func convertSandboxToPod(sandbox *runtimeapi.PodSandbox, nodeName string, containers []*runtimeapi.Container) *corev1.Pod { + pod := &corev1.Pod{} + + // Basic metadata from sandbox + if sandbox.Metadata != nil { + pod.Name = sandbox.Metadata.Name + pod.Namespace = sandbox.Metadata.Namespace + pod.UID = types.UID(sandbox.Metadata.Uid) + } + + // Labels and annotations + pod.Labels = sandbox.Labels + pod.Annotations = sandbox.Annotations + + // Node info + pod.Spec.NodeName = nodeName + if hostname, ok := sandbox.Labels["io.kubernetes.pod.nodename"]; ok && hostname != "" { + pod.Spec.NodeName = hostname + } + + // Convert containers to container statuses + pod.Status.ContainerStatuses = make([]corev1.ContainerStatus, 0, len(containers)) + for _, container := range containers { + cs := corev1.ContainerStatus{ + ContainerID: container.Id, + Image: container.Image.Image, + ImageID: container.ImageRef, + } + if container.Metadata != nil { + cs.Name = container.Metadata.Name + } + pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, cs) + } + + return pod +}