Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Cache image metadata to speed image details call #2173

Merged
merged 1 commit into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions pkg/apis/internal.acorn.io/v1/imageinstance.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,19 @@ func (in *ImageInstance) ShortID() string {
}
return string(in.UID)
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type ImageMetadataCacheList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ImageMetadataCache `json:"items"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type ImageMetadataCache struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Data []byte `json:"data,omitempty"`
}
2 changes: 2 additions & 0 deletions pkg/apis/internal.acorn.io/v1/scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func AddToScheme(scheme *runtime.Scheme) error {
&DevSessionInstanceList{},
&ProjectInstance{},
&ProjectInstanceList{},
&ImageMetadataCache{},
&ImageMetadataCacheList{},
)

// Add common types
Expand Down
62 changes: 62 additions & 0 deletions pkg/apis/internal.acorn.io/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions pkg/autoupgrade/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (

"github.com/acorn-io/runtime/pkg/imagepattern"
imagename "github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sirupsen/logrus"
"k8s.io/utils/strings/slices"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

const invalidTag = "+notfound+"

func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, image string) (imagename.Reference, []string, error) {
func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, image string, remoteOpts ...remote.Option) (imagename.Reference, []string, error) {
current, err := imagename.ParseReference(image, imagename.WithDefaultRegistry(defaultNoReg))
if err != nil {
return nil, nil, fmt.Errorf("problem parsing image referece %v: %v", image, err)
Expand All @@ -25,7 +26,7 @@ func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, imag
var tags []string
var pullErr error
if hasValidRegistry {
tags, pullErr = c.listTags(ctx, namespace, image)
tags, pullErr = c.listTags(ctx, namespace, image, remoteOpts...)
}
localTags, err := c.getTagsMatchingRepo(ctx, current, namespace, defaultNoReg)
if err != nil {
Expand All @@ -37,8 +38,8 @@ func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, imag
return current, append(tags, localTags...), nil
}

func findLatestTagForImageWithPattern(ctx context.Context, c daemonClient, current, namespace, image, pattern string) (string, bool, error) {
ref, tags, err := getTagsForImagePattern(ctx, c, namespace, strings.TrimSuffix(image, ":"+pattern))
func findLatestTagForImageWithPattern(ctx context.Context, c daemonClient, current, namespace, image, pattern string, remoteOpts ...remote.Option) (string, bool, error) {
ref, tags, err := getTagsForImagePattern(ctx, c, namespace, strings.TrimSuffix(image, ":"+pattern), remoteOpts...)
if err != nil {
return "", false, err
}
Expand Down Expand Up @@ -82,8 +83,8 @@ func findLatestTagForImageWithPattern(ctx context.Context, c daemonClient, curre
}

// FindLatestTagForImageWithPattern will return the latest tag for image corresponding to the pattern.
func FindLatestTagForImageWithPattern(ctx context.Context, c kclient.Client, current, namespace, image, pattern string) (string, bool, error) {
return findLatestTagForImageWithPattern(ctx, &client{c}, current, namespace, image, pattern)
func FindLatestTagForImageWithPattern(ctx context.Context, c kclient.Client, current, namespace, image, pattern string, remoteOpts ...remote.Option) (string, bool, error) {
return findLatestTagForImageWithPattern(ctx, &client{c}, current, namespace, image, pattern, remoteOpts...)
}

// FindLatest returns the tag from the tags slice that sorts as the "latest" according to the supplied pattern.
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/appdefinition/pullappimage.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func PullAppImage(transport http.RoundTripper, recorder event.Recorder) router.H
})
}

type pullImageFunc func(ctx context.Context, c kclient.Reader, namespace, image, nestedDigest string, opts ...remote.Option) (*v1.AppImage, error)
type pullImageFunc func(ctx context.Context, c kclient.Client, namespace, image, nestedDigest string, opts ...remote.Option) (*v1.AppImage, error)

type pullClient struct {
pull pullImageFunc
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/appdefinition/pullappimage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestAutoUpgradeImageResolution(t *testing.T) {

func testPullAppImage(transport http.RoundTripper) router.HandlerFunc {
return pullAppImage(transport, pullClient{
pull: func(_ context.Context, _ kclient.Reader, _ string, _ string, _ string, _ ...remote.Option) (*v1.AppImage, error) {
pull: func(_ context.Context, _ kclient.Client, _ string, _ string, _ string, _ ...remote.Option) (*v1.AppImage, error) {
return &v1.AppImage{
Name: "myimage:latest",
Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/acorn-io/runtime/pkg/crds"
"github.com/acorn-io/runtime/pkg/dns"
"github.com/acorn-io/runtime/pkg/event"
"github.com/acorn-io/runtime/pkg/imagemetadatacache"
"github.com/acorn-io/runtime/pkg/imagesystem"
"github.com/acorn-io/runtime/pkg/k8sclient"
"github.com/acorn-io/runtime/pkg/logserver"
Expand Down Expand Up @@ -113,5 +114,7 @@ func (c *Controller) Start(ctx context.Context) error {
// Note: the cache will only be populated for EventInstances if a handler for EventInstances has been registered.
go event.Truncate(ctx, c.Router.Backend(), 5*time.Minute, 1000)

go imagemetadatacache.Purge(ctx, c.client)

return c.Router.Start(ctx)
}
22 changes: 18 additions & 4 deletions pkg/imagedetails/imagedetails.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,25 @@ type GetImageDetailsOptions struct {
}

func GetImageDetails(ctx context.Context, c kclient.Client, namespace, imageName string, opts GetImageDetailsOptions) (*apiv1.ImageDetails, error) {
remoteOpts, err := images.GetAuthenticationRemoteOptions(ctx, c, namespace, opts.RemoteOpts...)
if err != nil {
return nil, err
}

puller, err := remote.NewPuller(remoteOpts...)
if err != nil {
return nil, err
}
opts.RemoteOpts = append(opts.RemoteOpts, remote.Reuse(puller))
return getImageDetails(ctx, c, namespace, imageName, opts)
}

func getImageDetails(ctx context.Context, c kclient.Client, namespace, imageName string, opts GetImageDetailsOptions) (*apiv1.ImageDetails, error) {
imageName = strings.ReplaceAll(imageName, "+", "/")
name := strings.ReplaceAll(imageName, "/", "+")

if tagPattern, isPattern := autoupgrade.AutoUpgradePattern(imageName); isPattern {
if latestImage, found, err := autoupgrade.FindLatestTagForImageWithPattern(ctx, c, "", namespace, imageName, tagPattern); err != nil {
if latestImage, found, err := autoupgrade.FindLatestTagForImageWithPattern(ctx, c, "", namespace, imageName, tagPattern, opts.RemoteOpts...); err != nil {
return nil, err
} else if !found {
// Check and see if no registry was specified on the image.
Expand Down Expand Up @@ -120,7 +134,7 @@ func GetImageDetails(ctx context.Context, c kclient.Client, namespace, imageName

var nestedImages []apiv1.NestedImage
if opts.IncludeNested {
nestedImages, err = getNested(ctx, c, namespace, imageName, details.AppSpec, appImageWithData.AppImage.ImageData, opts.RemoteOpts)
nestedImages, err = getNested(ctx, c, namespace, imageName, details.AppSpec, appImageWithData.AppImage.ImageData, remoteOpts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -214,7 +228,7 @@ func getNestedAcorns(ctx context.Context, c kclient.Client, namespace, image str
acornImage = image
}

details, err := GetImageDetails(ctx, c, namespace, acornImage, GetImageDetailsOptions{
details, err := getImageDetails(ctx, c, namespace, acornImage, GetImageDetailsOptions{
Profiles: acorn.Profiles,
DeployArgs: acorn.DeployArgs.GetData(),
Nested: nestedImage,
Expand Down Expand Up @@ -247,7 +261,7 @@ func getNestedServices(ctx context.Context, c kclient.Client, namespace, image s
serviceImage = image
}

details, err := GetImageDetails(ctx, c, namespace, serviceImage, GetImageDetailsOptions{
details, err := getImageDetails(ctx, c, namespace, serviceImage, GetImageDetailsOptions{
// Services don't have profiles
Profiles: nil,
DeployArgs: service.ServiceArgs.GetData(),
Expand Down
46 changes: 46 additions & 0 deletions pkg/imagemetadatacache/imagemetadatacache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package imagemetadatacache

import (
"context"
"time"

internalv1 "github.com/acorn-io/runtime/pkg/apis/internal.acorn.io/v1"
"github.com/sirupsen/logrus"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

var interval = 24 * time.Hour

func Purge(ctx context.Context, c kclient.Client) {
for {
err := doPurge(ctx, c)
if err != nil {
logrus.Errorf("Failed to purge imagemetadatacache: %v", err)
}
time.Sleep(interval)
}
}

func doPurge(ctx context.Context, c kclient.Client) error {
// We don't want to cache any of this data, so do live API calls
list := &v1.PartialObjectMetadataList{
TypeMeta: v1.TypeMeta{
Kind: "ImageMetadataCacheList",
APIVersion: internalv1.SchemeGroupVersion.String(),
},
}

if err := c.List(ctx, list); err != nil {
return err
}

purge := time.Now().Add(-interval)
for _, item := range list.Items {
if item.CreationTimestamp.Time.Before(purge) {
_ = c.Delete(ctx, &item)
}
}

return nil
}