Skip to content

Commit

Permalink
feat(controller): allow ignoring images using regexes
Browse files Browse the repository at this point in the history
  • Loading branch information
rucciva authored and paullaffitte committed Aug 7, 2023
1 parent c65d4bf commit 884bbcd
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 34 deletions.
20 changes: 17 additions & 3 deletions api/v1/pod_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import (
//+kubebuilder:webhook:path=/mutate-core-v1-pod,mutating=true,failurePolicy=fail,sideEffects=None,groups=core,resources=pods,verbs=create;update,versions=v1,name=mpod.kb.io,admissionReviewVersions=v1

type ImageRewriter struct {
Client client.Client
ProxyPort int
decoder *admission.Decoder
Client client.Client
IgnoreImages []*regexp.Regexp
ProxyPort int
decoder *admission.Decoder
}

func (a *ImageRewriter) Handle(ctx context.Context, req admission.Request) admission.Response {
Expand Down Expand Up @@ -73,6 +74,10 @@ func (a *ImageRewriter) InjectDecoder(d *admission.Decoder) error {
}

func (a *ImageRewriter) handleContainer(pod *corev1.Pod, container *corev1.Container, annotationKey string) {
if a.isImageIgnored(container) {
return
}

re := regexp.MustCompile(`localhost:[0-9]+/`)
image := re.ReplaceAllString(container.Image, "")

Expand All @@ -88,3 +93,12 @@ func (a *ImageRewriter) handleContainer(pod *corev1.Pod, container *corev1.Conta

container.Image = fmt.Sprintf("localhost:%d/%s", a.ProxyPort, image)
}

func (a *ImageRewriter) isImageIgnored(container *corev1.Container) (ignored bool) {
for _, r := range a.IgnoreImages {
if r.MatchString(container.Image) {
return true
}
}
return
}
43 changes: 43 additions & 0 deletions api/v1/pod_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
_ "crypto/sha256"
"fmt"
"regexp"
"testing"

"github.com/enix/kube-image-keeper/controllers"
Expand Down Expand Up @@ -32,6 +33,8 @@ var podStub = corev1.Pod{
}

func TestRewriteImages(t *testing.T) {
podStub := *podStub.DeepCopy()

g := NewWithT(t)
t.Run("Rewrite image", func(t *testing.T) {
ir := ImageRewriter{
Expand Down Expand Up @@ -65,6 +68,46 @@ func TestRewriteImages(t *testing.T) {
})
}

func TestRewriteImagesWithIgnore(t *testing.T) {
podStub := *podStub.DeepCopy()

g := NewWithT(t)
t.Run("Rewrite image", func(t *testing.T) {
ir := ImageRewriter{
ProxyPort: 4242,
IgnoreImages: []*regexp.Regexp{
regexp.MustCompile("original"),
regexp.MustCompile("alpine:latest"),
},
}
ir.RewriteImages(&podStub)

rewrittenInitContainers := []corev1.Container{
{Name: "a", Image: "original-init"},
}

rewrittenContainers := []corev1.Container{
{Name: "b", Image: "original"},
{Name: "c", Image: "localhost:1313/original-2"},
{Name: "d", Image: "localhost:4242/185.145.250.247-30042/alpine"},
{Name: "e", Image: "185.145.250.247:30042/alpine:latest"},
{Name: "f", Image: "invalid:image:8080"},
}

g.Expect(podStub.Spec.InitContainers).To(Equal(rewrittenInitContainers))
g.Expect(podStub.Spec.Containers).To(Equal(rewrittenContainers))

g.Expect(podStub.Labels[controllers.LabelImageRewrittenName]).To(Equal("true"))

g.Expect(podStub.Annotations[fmt.Sprintf(controllers.AnnotationOriginalInitImageTemplate, "a")]).To(Equal(""))
g.Expect(podStub.Annotations[fmt.Sprintf(controllers.AnnotationOriginalImageTemplate, "b")]).To(Equal(""))
g.Expect(podStub.Annotations[fmt.Sprintf(controllers.AnnotationOriginalImageTemplate, "c")]).To(Equal(""))
g.Expect(podStub.Annotations[fmt.Sprintf(controllers.AnnotationOriginalImageTemplate, "d")]).To(Equal("185.145.250.247:30042/alpine"))
g.Expect(podStub.Annotations[fmt.Sprintf(controllers.AnnotationOriginalImageTemplate, "e")]).To(Equal(""))
g.Expect(podStub.Annotations[fmt.Sprintf(controllers.AnnotationOriginalImageTemplate, "f")]).To(Equal(""))
})
}

func TestInjectDecoder(t *testing.T) {
g := NewWithT(t)
t.Run("Inject decoder", func(t *testing.T) {
Expand Down
29 changes: 27 additions & 2 deletions cmd/cache/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"flag"
"os"
"regexp"
"strings"
"time"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
Expand All @@ -27,12 +29,33 @@ var (
setupLog = ctrl.Log.WithName("setup")
)

type regexpArrayFlags []*regexp.Regexp

func (re *regexpArrayFlags) String() string {
s := []string{}
for _, r := range *re {
s = append(s, r.String())
}
return strings.Join(s, ",")
}

func (re *regexpArrayFlags) Set(value string) error {
r, err := regexp.Compile(value)
if err != nil {
setupLog.Error(err, "unable parse ignored images regex", "controller", "Pod")
os.Exit(1)
}
*re = append(*re, r)
return nil
}

func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var expiryDelay uint
var proxyPort int
var ignoreImages regexpArrayFlags
var maxConcurrentCachedImageReconciles int
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
Expand All @@ -41,6 +64,7 @@ func main() {
"Enabling this will ensure there is only one active controller manager.")
flag.UintVar(&expiryDelay, "expiry-delay", 30, "The delay in days before deleting an unused CachedImage.")
flag.IntVar(&proxyPort, "proxy-port", 8082, "The port where the proxy is listening on this machine.")
flag.Var(&ignoreImages, "ignore-images", "List of regexes that represents images to be excluded.")
flag.StringVar(&registry.Endpoint, "registry-endpoint", "kube-image-keeper-registry:5000", "The address of the registry where cached images are stored.")
flag.IntVar(&maxConcurrentCachedImageReconciles, "max-concurrent-cached-image-reconciles", 3, "Maximum number of CachedImages that can be handled and reconciled at the same time (put or removed from cache).")

Expand Down Expand Up @@ -84,8 +108,9 @@ func main() {
os.Exit(1)
}
imageRewriter := kuikenixiov1.ImageRewriter{
Client: mgr.GetClient(),
ProxyPort: proxyPort,
Client: mgr.GetClient(),
IgnoreImages: ignoreImages,
ProxyPort: proxyPort,
}
mgr.GetWebhookServer().Register("/mutate-core-v1-pod", &webhook.Admission{Handler: &imageRewriter})
//+kubebuilder:scaffold:builder
Expand Down
3 changes: 3 additions & 0 deletions helm/kube-image-keeper/templates/controller-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ spec:
- -registry-endpoint={{ include "kube-image-keeper.fullname" . }}-registry:5000
- -max-concurrent-cached-image-reconciles={{ .Values.controllers.maxConcurrentCachedImageReconciles }}
- -zap-log-level={{ .Values.controllers.verbosity }}
{{- range .Values.controllers.webhook.ignoredImages }}
- -ignore-images={{- . }}
{{- end }}
ports:
- containerPort: 9443
name: webhook-server
Expand Down
59 changes: 30 additions & 29 deletions helm/kube-image-keeper/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.


# -- Delay in days before deleting an unused CachedImage
cachedImagesExpiryDelay: 30
# -- If true, install the CRD
Expand Down Expand Up @@ -59,7 +58,9 @@ controllers:
webhook:
# -- Don't enable image caching for pods scheduled into these namespaces
ignoredNamespaces:
- kube-system
- kube-system
# -- Don't enable image caching if the image match the following regexes
ignoredImages: []
# -- If true, create the issuer used to issue the webhook certificate
createCertificateIssuer: true
# -- Issuer reference to issue the webhook certificate, ignored if createCertificateIssuer is true
Expand Down Expand Up @@ -107,33 +108,33 @@ proxy:
nodeSelector: {}
# -- Toleration for the proxy pod
tolerations:
- effect: NoSchedule
operator: Exists
- key: CriticalAddonsOnly
operator: Exists
- effect: NoExecute
operator: Exists
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/disk-pressure
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/memory-pressure
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/pid-pressure
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/unschedulable
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/network-unavailable
operator: Exists
- effect: NoSchedule
operator: Exists
- key: CriticalAddonsOnly
operator: Exists
- effect: NoExecute
operator: Exists
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/disk-pressure
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/memory-pressure
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/pid-pressure
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/unschedulable
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/network-unavailable
operator: Exists
# -- Set the PriorityClassName for the proxy pod
priorityClassName: system-node-critical
# -- Affinity for the proxy pod
Expand Down

0 comments on commit 884bbcd

Please sign in to comment.