Compare commits

...

27 Commits

Author SHA1 Message Date
Alex Jones
6ead5b356d Merge pull request #173 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.7
2023-04-02 14:26:40 +01:00
github-actions[bot]
c733292b92 chore(main): release 0.1.7 2023-04-02 13:14:01 +00:00
Alex Jones
3c95a5db82 Merge pull request #186 from yeahservice/feature/pdb-analyzer
feat: add pdb analyzer
2023-04-02 14:13:21 +01:00
Alex Jones
ea1ca44dba Merge branch 'main' into feature/pdb-analyzer 2023-04-02 14:11:46 +01:00
Alex Jones
33319b8ef5 Merge pull request #189 from yeahservice/fix/hpa-analyzer-parent-object
fix: hpaAnalyzer analysis result is using wrong parent
2023-04-02 14:05:49 +01:00
Dominik Augustin
1190fe60fd fix: hpaAnalyzer analysis result is using wrong parent
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 15:05:07 +02:00
Dominik Augustin
ceff0084df fix: spelling of PodDisruptionBudget
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:57:24 +02:00
Dominik Augustin
f6974d0758 docs: add pdbAnalyzer as optional analyzer
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:50:03 +02:00
Dominik Augustin
532a5ce033 feat: add pda analyzer
Adds a PodDisruptionBudget analyzer, that checks if PDBs have matching
pods with their defined selector.

Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:38:39 +02:00
AlexsJones
071ee560f3 chore: merge branch 'chetanguptaa-some-fixes' 2023-04-01 17:23:10 +01:00
AlexsJones
e1d89920b0 chore: removes bar on normal analyze events
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-01 17:22:50 +01:00
AlexsJones
96d0d754ea chore: removes bar on normal analyze events
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-01 17:22:37 +01:00
chetan gupta
86e9564aaf Merge remote-tracking branch 'origin/some-fixes' into some-fixes 2023-04-01 21:30:04 +05:30
chetan gupta
9cef52fb8e Merge branch 'main' of https://github.com/k8sgpt-ai/k8sgpt into some-fixes
docs: updated README.md
2023-04-01 21:24:20 +05:30
chetan gupta
9915f9406b Merge branch 'main' into some-fixes 2023-04-01 21:15:26 +05:30
chetan gupta
a24a8da246 updated README.md
Signed-off-by: chetan gupta <chetangupta123raj@gmail.com>
2023-04-01 21:14:07 +05:30
Alex Jones
f337f6b447 Merge pull request #177 from k8sgpt-ai/renovate/anchore-sbom-action-0.x
chore(deps): update anchore/sbom-action action to v0.14.1
2023-04-01 16:33:54 +01:00
renovate[bot]
80f29dae4f chore(deps): update anchore/sbom-action action to v0.14.1
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-01 15:24:01 +00:00
Alex Jones
a76d60a652 Merge pull request #171 from matthisholleville/feature/hpa-analyzer
feat: add hpa analyzer and init additionalAnalyzers
2023-04-01 16:23:39 +01:00
Alex Jones
dea3ad9ebf Merge branch 'main' into feature/hpa-analyzer 2023-04-01 16:22:44 +01:00
Roberth Strand
207f26432c Merge pull request #175 from k8sgpt-ai/101-need-more-space-between-issues-with-explain
refactor: 101 need more space between issues with explain
2023-04-01 16:27:45 +02:00
Roberth Strand
3e836d81b7 refactor: merged main into branch
Merged origin/main into the branch to get the PR up to date and mergable.

Closes #101

Signed-off-by: Roberth Strand <me@robstr.dev>
2023-04-01 11:36:15 +02:00
Matthis Holleville
006751567f Merge branch 'main' into feature/hpa-analyzer 2023-03-31 23:25:07 +02:00
Matthis Holleville
5dad75fbe9 feat: check if ScaleTargetRef is possible option
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 19:47:10 +02:00
HOLLEVILLE Matthis
4916fef9d6 fix: update client API call to use StatefulSet instead of Deployment
Co-authored-by: Dominik Augustin <yeahservice@users.noreply.github.com>
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 19:39:17 +02:00
Matthis Holleville
360387249f feat: add hpa analyzer and init additionalAnalyzers
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 18:22:01 +02:00
Roberth Strand
c0aebb21a0 refactor: added newline to explanation
The newline at the end of the explanation will make is so that the wall of text that you get with several issues get a bit of space. This does not effect the JSON output, only the regular output.

closes #101

Signed-off-by: Roberth Strand <me@robstr.dev>
2023-03-31 11:23:35 +02:00
15 changed files with 263 additions and 61 deletions

View File

@@ -102,7 +102,7 @@ jobs:
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
- name: Generate SBOM
uses: anchore/sbom-action@448520c4f19577ffce70a8317e619089054687e3 # v0.13.4
uses: anchore/sbom-action@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
with:
image: ${{ env.IMAGE_TAG }}
artifact-name: sbom-${{ env.IMAGE_NAME }}

View File

@@ -1 +1 @@
{".":"0.1.6"}
{".":"0.1.7"}

View File

@@ -1,5 +1,41 @@
# Changelog
## [0.1.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.6...v0.1.7) (2023-04-02)
### Features
* add hpa analyzer and init additionalAnalyzers ([3603872](https://github.com/k8sgpt-ai/k8sgpt/commit/360387249feb9a999286aaa874a13007986219a5))
* add pda analyzer ([532a5ce](https://github.com/k8sgpt-ai/k8sgpt/commit/532a5ce0332a8466df42bc944800e6668e349801))
* check if ScaleTargetRef is possible option ([5dad75f](https://github.com/k8sgpt-ai/k8sgpt/commit/5dad75fbe9fd15cfa7bfa69c046b851ea905876f))
### Bug Fixes
* hpaAnalyzer analysis result is using wrong parent ([1190fe6](https://github.com/k8sgpt-ai/k8sgpt/commit/1190fe60fdd6e66ce435874628039df7047a52b9))
* spelling of PodDisruptionBudget ([ceff008](https://github.com/k8sgpt-ai/k8sgpt/commit/ceff0084df1b6de16f1ed503ee8a4b3c1a9f8648))
* update client API call to use StatefulSet instead of Deployment ([4916fef](https://github.com/k8sgpt-ai/k8sgpt/commit/4916fef9d6b75c54bcfbc5d136550018e96e3632))
### Refactoring
* merged main into branch ([3e836d8](https://github.com/k8sgpt-ai/k8sgpt/commit/3e836d81b7c33ce5c0c133c2e1ca3b0c8d3eeeb0)), closes [#101](https://github.com/k8sgpt-ai/k8sgpt/issues/101)
### Other
* **deps:** update anchore/sbom-action action to v0.14.1 ([80f29da](https://github.com/k8sgpt-ai/k8sgpt/commit/80f29dae4fd6f6348967192ce2f51f0e0fb5dea0))
* merge branch 'chetanguptaa-some-fixes' ([071ee56](https://github.com/k8sgpt-ai/k8sgpt/commit/071ee560f36b64b4c65274181e2d13bb14d5b914))
* refine renovate config ([#172](https://github.com/k8sgpt-ai/k8sgpt/issues/172)) ([d23da9a](https://github.com/k8sgpt-ai/k8sgpt/commit/d23da9ae836a07f0fd59c20a1c3c71d6b7f75277))
* removes bar on normal analyze events ([e1d8992](https://github.com/k8sgpt-ai/k8sgpt/commit/e1d89920b097db4417c55b020fb23dd8cbaf19ed))
* removes bar on normal analyze events ([96d0d75](https://github.com/k8sgpt-ai/k8sgpt/commit/96d0d754eab67c0742d3a36a1eefb9c28df59e96))
* update dependencies ([#174](https://github.com/k8sgpt-ai/k8sgpt/issues/174)) ([9d9c262](https://github.com/k8sgpt-ai/k8sgpt/commit/9d9c26214fbb4c4faba7ef85f2204bc961396de8))
### Docs
* add pdbAnalyzer as optional analyzer ([f6974d0](https://github.com/k8sgpt-ai/k8sgpt/commit/f6974d07581384e260059f121242854320dfc58b))
## [0.1.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.5...v0.1.6) (2023-03-31)

View File

@@ -3,9 +3,9 @@
<img alt="Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'" src="./images/banner-black.png" width="600px;">
</picture>
`k8sgpt` is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english.
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
It has SRE experience codified into it's analyzers and helps to pull out the most relevent information to enrich it with AI.
It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
## Installation
@@ -21,8 +21,8 @@ brew install k8sgpt
When installing Homebrew on WSL or Linux, you may encounter the following error:
```
==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from bottle and must be
built from source. k8sgpt Install Clang or run brew install gcc.
==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be
built from the source. k8sgpt Install Clang or run brew install gcc.
```
If you install gcc as suggested, the problem will persist. Therefore, you need to install the build-essential package.
@@ -60,6 +60,8 @@ you will be able to write your own analyzers.
### Built in analyzers
#### Enabled by default
- [x] podAnalyzer
- [x] pvcAnalyzer
- [x] rsAnalyzer
@@ -67,6 +69,11 @@ you will be able to write your own analyzers.
- [x] eventAnalyzer
- [x] ingressAnalyzer
#### Optional
- [x] hpaAnalyzer
- [x] pdbAnalyzer
## Usage
```

View File

@@ -83,16 +83,14 @@ var AnalyzeCmd = &cobra.Command{
os.Exit(1)
}
var bar *progressbar.ProgressBar
if len(*analysisResults) > 0 {
bar = progressbar.Default(int64(len(*analysisResults)))
} else {
if len(*analysisResults) == 0 {
color.Green("{ \"status\": \"OK\" }")
os.Exit(0)
}
// This variable is used to store the results that will be printed
// It's necessary because the heap memory is lost when the function returns
var bar = progressbar.Default(int64(len(*analysisResults)))
if !explain {
bar.Clear()
}
var printOutput []analyzer.Analysis
for _, analysis := range *analysisResults {
@@ -132,7 +130,7 @@ var AnalyzeCmd = &cobra.Command{
for _, err := range analysis.Error {
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err))
}
fmt.Println(color.GreenString(analysis.Details))
fmt.Println(color.GreenString(analysis.Details + "\n"))
}
}
},

View File

@@ -18,7 +18,9 @@ var addCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
inputFilters := strings.Split(args[0], ",")
availableFilters := analyzer.ListFilters()
coreFilters, additionalFilters := analyzer.ListFilters()
availableFilters := append(coreFilters, additionalFilters...)
// Verify filter exist
invalidFilters := []string{}
@@ -47,7 +49,7 @@ var addCmd = &cobra.Command{
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
if len(activeFilters) == 0 {
activeFilters = availableFilters
activeFilters = coreFilters
}
mergedFilters := append(activeFilters, inputFilters...)

View File

@@ -16,10 +16,11 @@ var listCmd = &cobra.Command{
Long: `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`,
Run: func(cmd *cobra.Command, args []string) {
activeFilters := viper.GetStringSlice("active_filters")
availableFilters := analyzer.ListFilters()
coreFilters, additionalFilters := analyzer.ListFilters()
availableFilters := append(coreFilters, additionalFilters...)
if len(activeFilters) == 0 {
activeFilters = availableFilters
activeFilters = coreFilters
}
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)

View File

@@ -21,8 +21,10 @@ var removeCmd = &cobra.Command{
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
coreFilters, _ := analyzer.ListFilters()
if len(activeFilters) == 0 {
activeFilters = analyzer.ListFilters()
activeFilters = coreFilters
}
// Check if input input filters is not empty

View File

@@ -2,8 +2,10 @@ package analyzer
import (
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
policyv1 "k8s.io/api/policy/v1"
)
type AnalysisConfiguration struct {
@@ -13,12 +15,14 @@ type AnalysisConfiguration struct {
}
type PreAnalysis struct {
Pod v1.Pod
FailureDetails []string
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
Ingress networkingv1.Ingress
Pod v1.Pod
FailureDetails []string
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
Ingress networkingv1.Ingress
HorizontalPodAutoscalers autoscalingv1.HorizontalPodAutoscaler
PodDisruptionBudget policyv1.PodDisruptionBudget
}
type Analysis struct {

View File

@@ -11,7 +11,7 @@ import (
"github.com/spf13/viper"
)
var analyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration,
var coreAnalyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error{
"Pod": AnalyzePod,
"ReplicaSet": AnalyzeReplicaSet,
@@ -20,12 +20,20 @@ var analyzerMap = map[string]func(ctx context.Context, config *AnalysisConfigura
"Ingress": AnalyzeIngress,
}
var additionalAnalyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error{
"HorizontalPodAutoScaler": AnalyzeHpa,
"PodDisruptionBudget": AnalyzePdb,
}
func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfiguration,
client *kubernetes.Client,
aiClient ai.IAI, analysisResults *[]Analysis) error {
activeFilters := viper.GetStringSlice("active_filters")
analyzerMap := getAnalyzerMap()
// if there are no filters selected and no active_filters then run all of them
if len(filters) == 0 && len(activeFilters) == 0 {
for _, analyzer := range analyzerMap {
@@ -97,10 +105,34 @@ func ParseViaAI(ctx context.Context, config *AnalysisConfiguration,
return response, nil
}
func ListFilters() []string {
keys := make([]string, 0, len(analyzerMap))
for k := range analyzerMap {
keys = append(keys, k)
func ListFilters() ([]string, []string) {
coreKeys := make([]string, 0, len(coreAnalyzerMap))
for k := range coreAnalyzerMap {
coreKeys = append(coreKeys, k)
}
return keys
additionalKeys := make([]string, 0, len(additionalAnalyzerMap))
for k := range additionalAnalyzerMap {
additionalKeys = append(additionalKeys, k)
}
return coreKeys, additionalKeys
}
func getAnalyzerMap() map[string]func(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
mergedMap := make(map[string]func(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error)
// add core analyzer
for key, value := range coreAnalyzerMap {
mergedMap[key] = value
}
// add additional analyzer
for key, value := range additionalAnalyzerMap {
mergedMap[key] = value
}
return mergedMap
}

View File

@@ -2,42 +2,17 @@ package analyzer
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func FetchLatestPodEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pod *v1.Pod) (*v1.Event, error) {
func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(pod.Namespace).List(ctx,
events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + pod.Name,
})
if err != nil {
return nil, err
}
// find most recent event
var latestEvent *v1.Event
for _, event := range events.Items {
if latestEvent == nil {
latestEvent = &event
}
if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {
latestEvent = &event
}
}
return latestEvent, nil
}
func FetchLatestPvcEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pvc *v1.PersistentVolumeClaim) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(pvc.Namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + pvc.Name,
FieldSelector: "involvedObject.name=" + name,
})
if err != nil {

View File

@@ -0,0 +1,81 @@
package analyzer
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzeHpa(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
list, err := client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(config.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, hpa := range list.Items {
var failures []string
// check ScaleTargetRef exist
scaleTargetRef := hpa.Spec.ScaleTargetRef
scaleTargetRefNotFound := false
switch scaleTargetRef.Kind {
case "Deployment":
_, err := client.GetClient().AppsV1().Deployments(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicationController":
_, err := client.GetClient().CoreV1().ReplicationControllers(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicaSet":
_, err := client.GetClient().AppsV1().ReplicaSets(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "StatefulSet":
_, err := client.GetClient().AppsV1().StatefulSets(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
default:
failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which does not possible option.", scaleTargetRef.Kind))
}
if scaleTargetRefNotFound {
failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name))
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = PreAnalysis{
HorizontalPodAutoscalers: hpa,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "HorizontalPodAutoscaler",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.HorizontalPodAutoscalers.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

View File

@@ -0,0 +1,64 @@
package analyzer
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzePdb(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
list, err := client.GetClient().PolicyV1().PodDisruptionBudgets(config.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, pdb := range list.Items {
var failures []string
evt, err := FetchLatestEvent(ctx, client, pdb.Namespace, pdb.Name)
if err != nil || evt == nil {
continue
}
if evt.Reason == "NoPods" && evt.Message != "" {
if pdb.Spec.Selector != nil {
for k, v := range pdb.Spec.Selector.MatchLabels {
failures = append(failures, fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v))
}
for _, v := range pdb.Spec.Selector.MatchExpressions {
failures = append(failures, fmt.Sprintf("%s, expected expression %s", evt.Message, v))
}
} else {
failures = append(failures, fmt.Sprintf("%s, selector is nil", evt.Message))
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = PreAnalysis{
PodDisruptionBudget: pdb,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "PodDisruptionBudget",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.PodDisruptionBudget.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

View File

@@ -47,7 +47,7 @@ func AnalyzePod(ctx context.Context, config *AnalysisConfiguration,
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestPodEvent(ctx, client, &pod)
evt, err := FetchLatestEvent(ctx, client, pod.Namespace, pod.Name)
if err != nil || evt == nil {
continue
}

View File

@@ -27,7 +27,7 @@ func AnalyzePersistentVolumeClaim(ctx context.Context, config *AnalysisConfigura
if pvc.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestPvcEvent(ctx, client, &pvc)
evt, err := FetchLatestEvent(ctx, client, pvc.Namespace, pvc.Name)
if err != nil || evt == nil {
continue
}