Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use contaier image as kamelet repository #4350

Closed
wants to merge 1 commit into from

Conversation

lburgazzoli
Copy link
Contributor

@lburgazzoli lburgazzoli commented May 9, 2023

This is a PR opened mainly for discussion with some POC quality code that I had in my repo for some time now.
The goal is to explore the option of using OCI containers and registries as a repository for kamelets (relates to #2732) leveraging oras

Release Note

NONE

return "", err
}

if _, err = oras.Copy(ctx, or, nt.Tag(), store, nt.Tag(), oras.DefaultCopyOptions); err != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not super optimized as it unpack the entire image in a temporary folder but I have not yet found a good way to filter out the content.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Well, we can always clean, keeping only what we need. As this would happen on operator startup, I guess it's not a big deal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the image only contains kamelet files, the overhead sounds negligible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the image only contains kamelet files, the overhead sounds negligible

correct, if we assume that the repository includes only kamelet files, then it won't be a big deal.

}

func (r *ociKameletRepository) pullAll(ctx context.Context) {
r.once.Do(func() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this stage, the repository is only loaded once, so any new container image is not taken into account till the restart of the camel-k operator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may eventually create a KameletRepository type which is reconciled and take care of the sync when any change happen. However, it would be a future optimization.

Copy link
Contributor

@oscerd oscerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks super promising. What is missing for having it merged?

Copy link
Contributor

@squakez squakez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work, I think the idea and the implementation are a good step forward. There are just a couple of consideration we need to take into account tough.

  1. is about authentication to the registry. If we implement this, we will have to probably deal with it as well (but I am pretty sure it's something you are already taking into account).
  2. As Kamelets are already a dedicated entity no longer bound to Camel K, I think we need to find the time to migrate all the pkg/kamelet/repository and the same API definition to the same Kamelet Catalog project. From Camel K we'd have a way to synch with such definition, according to the version we're willing to use. This mean that all this logic may be moved to some other Camel K package. It's not an important detail at this stage of development, but I think it's important for all to already think on this future action.

return "", err
}

if _, err = oras.Copy(ctx, or, nt.Tag(), store, nt.Tag(), oras.DefaultCopyOptions); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Well, we can always clean, keeping only what we need. As this would happen on operator startup, I guess it's not a big deal.

}

func (r *ociKameletRepository) pullAll(ctx context.Context) {
r.once.Do(func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may eventually create a KameletRepository type which is reconciled and take care of the sync when any change happen. However, it would be a future optimization.

Copy link
Member

@astefanutti astefanutti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Eager to see an end-to-end scenario, from a kamelet source code repository, with some CI/CD building and pushing the container image, up to consuming it in Camel K!

}

func (r *ociKameletRepository) pullAll(ctx context.Context) {
r.once.Do(func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could imagine to support different image pull policies, in a similar way Kubernetes does.

A less advanced alternative would be to rely on a rate limiter to invalidate the cached data, e.g.:

rateLimiter = rate.NewLimiter(rate.Every(period), 1)

if !rateLimiter.Allow() {
  return
}

...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right I was about to add pull policies but noticed that as today the way a repository is defined is limited as it is essentially an uri :

type IntegrationPlatformKameletRepositorySpec struct {
// the remote repository in the format github:ORG/REPO/PATH_TO_KAMELETS_FOLDER
URI string `json:"uri,omitempty"`
}

So I wonder if this must be changed in something more type safe, in line to what has been done for traits, like:

type IntegrationPlatformKameletRepositorySpec struct {
    GitHub *GitHubKameletRepo `json:"github,omitempty"`
    OCI    *OCIKameletRepo    `json:"oci,omitempty"`
}

So we can add additional repo specific option, as example the authentication option for the oci repo.

The only issue is that by definition, if the tag of the image is latest, then the pull policy should become Always so I was thinking to use a rate limiter alike apporach that would also cache and check the checksum/digest of the manifest before actually pulling the layers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for structuring the repository definitions.

+1 to comply with Kubernetes imagePullPolicy defaulting: https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving that, I believe the IntegrationPlatform reconcile loop should be in charge of resolving the container image & so on.

One question though, is how to deal with errors: should the integration platform have a related repository condition set to false and the Ready condition set to false ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we'll probably have to manage that as soon as we introduce certain logic into the IP. Right now, the only possibility we have for an IP to be in error is that it cannot connect to the registry for any reason (although we are not doing that check).

}

func (r *ociKameletRepository) Get(ctx context.Context, name string) (*camelv1.Kamelet, error) {
r.pullAll(ctx)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what's the state of seekable / addressable image that would enable to lazily pull blobs / layers when needed. I think that would be applicable to our use case, but it doesn't seem it has landed into the OCI specs yet. Or maybe the repository image / manifest could already be built in a way that would serve that need.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could store a Kamelet in a layer and have it referenced in the Manifest. That way if you could pull a single Kamelet by getting it's address in the Manifest. The only drawback is the 127 layer limit so you could "only" store 127 Kamelets per image ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So theoretically it should be possible to inspect the manifest and lookup the layer i.e. by using some annotations, however I think the main decision factor would be how we would like to package the kamelets:

  1. 1 kamelet per layer
  2. 1 kamelet group per layer
  3. all in one layer (as tar file)

As today to keep things simpler, I would opt for option 3

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go with 3 for the moment. To avoid other complexities

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for 3) to keep things simple 👍🏼.

@johnpoth
Copy link
Member

Looks great! As Antonin said, eager to see everything in action!

I also think it would be cool if you could pull the image via docker/podman:

docker pull lburgazzoli/camel-kamelets:latest
podman pull lburgazzoli/camel-kamelets:latest

Right now I'm getting

Error response from daemon: missing signature key

I think I stumbled this when working on custom built images and it might be the way the Manifest was built.

As a side note, since we are already publishing the Kamelets on Maven it might be cool to support that too if we don't already.

@squakez
Copy link
Contributor

squakez commented May 10, 2023

Looks great! As Antonin said, eager to see everything in action!

I also think it would be cool if you could pull the image via docker/podman:

docker pull lburgazzoli/camel-kamelets:latest
podman pull lburgazzoli/camel-kamelets:latest

Right now I'm getting

Error response from daemon: missing signature key

I think I stumbled this when working on custom built images and it might be the way the Manifest was built.

As a side note, since we are already publishing the Kamelets on Maven it might be cool to support that too if we don't already.

I started working some time ago on pulling the Kamelets from maven (see #3919). I stopped the development because it was unclear to me the way we wanted to support the bundling. If the effort is not that much, we may think to support several strategies as you suggest, I'd be +1 on that.

@christophd
Copy link
Contributor

Awesome stuff!

@lburgazzoli
Copy link
Contributor Author

Looks great! As Antonin said, eager to see everything in action!

I also think it would be cool if you could pull the image via docker/podman:

docker pull lburgazzoli/camel-kamelets:latest
podman pull lburgazzoli/camel-kamelets:latest

Right now I'm getting

Error response from daemon: missing signature key

I think I stumbled this when working on custom built images and it might be the way the Manifest was built.

As today, I've blindly followed the oras tutorial so it could be possible that what they create is not compatible with containers tools. If you have any time to digg into the issue, I would really appreciate it.

@lburgazzoli
Copy link
Contributor Author

lburgazzoli commented May 10, 2023

1. is about authentication to the registry. If we implement this, we will have to probably deal with it as well (but I am pretty sure it's something you are already taking into account).

Yes this has intentional been skipped as some other changes may be required, so I didn't want to add to many stuffs before we agree if this is a good thing to do and how to move forward

@johnpoth
Copy link
Member

Looks great! As Antonin said, eager to see everything in action!
I also think it would be cool if you could pull the image via docker/podman:

docker pull lburgazzoli/camel-kamelets:latest
podman pull lburgazzoli/camel-kamelets:latest

Right now I'm getting

Error response from daemon: missing signature key

I think I stumbled this when working on custom built images and it might be the way the Manifest was built.

As today, I've blindly followed the oras tutorial so it could be possible that what they create is not compatible with containers tools. If you have any time to digg into the issue, I would really appreciate it.

For sure!

IIRC that's one of the reasons I didn't use ORAS and went for spectrum and Google's go-containerregistry ...

I think it boils down to the list of registries that support the ORAS spec is pretty limited. That being said there might be a workaround ...

@lburgazzoli
Copy link
Contributor Author

Looks great! As Antonin said, eager to see everything in action!
I also think it would be cool if you could pull the image via docker/podman:

docker pull lburgazzoli/camel-kamelets:latest
podman pull lburgazzoli/camel-kamelets:latest

Right now I'm getting

Error response from daemon: missing signature key

I think I stumbled this when working on custom built images and it might be the way the Manifest was built.

As today, I've blindly followed the oras tutorial so it could be possible that what they create is not compatible with containers tools. If you have any time to digg into the issue, I would really appreciate it.

For sure!

IIRC that's one of the reasons I didn't use ORAS and went for spectrum and Google's go-containerregistry ...

I think it boils down to the list of registries that support the ORAS spec is pretty limited. That being said there might be a workaround ...

I think I did some exploration with go-containerregistry in the past, but I have not seen an option to define a custom media type or annotations for the layers, do you know if that's possible ?

@johnpoth
Copy link
Member

Looks great! As Antonin said, eager to see everything in action!
I also think it would be cool if you could pull the image via docker/podman:

docker pull lburgazzoli/camel-kamelets:latest
podman pull lburgazzoli/camel-kamelets:latest

Right now I'm getting

Error response from daemon: missing signature key

I think I stumbled this when working on custom built images and it might be the way the Manifest was built.

As today, I've blindly followed the oras tutorial so it could be possible that what they create is not compatible with containers tools. If you have any time to digg into the issue, I would really appreciate it.

For sure!
IIRC that's one of the reasons I didn't use ORAS and went for spectrum and Google's go-containerregistry ...
I think it boils down to the list of registries that support the ORAS spec is pretty limited. That being said there might be a workaround ...

I think I did some exploration with go-containerregistry in the past, but I have not seen an option to define a custom media type or annotations for the layers, do you know if that's possible ?

Yes it does!
BUT setting a custom media type comes at the cost of the image being rejected in most registries or clients. This is why I didn't set the custom media type when working on local dependencies. Although very appealing it wasn't worth the interoperability drawbacks IMO. This was ~1 years ago so maybe things have changed but looking at the list of supported registries it doesn't look like it...

@lburgazzoli
Copy link
Contributor Author

lburgazzoli commented May 11, 2023

I think I did some exploration with go-containerregistry in the past, but I have not seen an option to define a custom media type or annotations for the layers, do you know if that's possible ?

Yes it does! BUT setting a custom media type comes at the cost of the image being rejected in most registries or clients. This is why I didn't set the custom media type when working on local dependencies. Although very appealing it wasn't worth the interoperability drawbacks IMO. This was ~1 years ago so maybe things have changed but looking at the list of supported registries it doesn't look like it...

Yeah, that is essentially also the conclusion in #2732, but I had hope for things to have become better, but looks like it is not. The alternative would be to use some annotations to help distinguish between the type of layers but, given the spec from the ORAS Artifacts have been merged to the OpenContainers specs I had an hope to get things more widely supported :)

Happy to switch to go-containerregistry at some point, @johnpoth do you by chance have some code I can take a look ?

@johnpoth
Copy link
Member

I think I did some exploration with go-containerregistry in the past, but I have not seen an option to define a custom media type or annotations for the layers, do you know if that's possible ?

Yes it does! BUT setting a custom media type comes at the cost of the image being rejected in most registries or clients. This is why I didn't set the custom media type when working on local dependencies. Although very appealing it wasn't worth the interoperability drawbacks IMO. This was ~1 years ago so maybe things have changed but looking at the list of supported registries it doesn't look like it...

Yeah, that is essentially also the conclusion in #2732, but I had hope for things to have become better, but looks like it is not. The alternative would be to use some annotations to help distinguish between the type of layers but, given the spec from the ORAS Artifacts have been merged to the OpenContainers specs I had an hope to get things more widely supported :)

Happy to switch to go-containerregistry at some point, @johnpoth do you by chance have some code I can take a look ?

Sure! So for building the Image I would just use Spectrum or Crane tbh.. both are built on top of go-containerregistry anyway so:

spectrum build -b scratch  -t docker.io/jpoth/camel-kamelets camel-kamelets/kamelets:/

or Crane

crane append  -f <(tar -f - -c camel-kamelets/kamelets/) -t docker.io/jpoth/camel-kamelets

This will create an Image with just the Kamelet files in a single layer

And for the Operator part I would use the equivalent of

crane pull  jpoth/camel-kamelets kamelets.tar

which is just a Crane Pull followed by an Export. The mutate.Export is used which returns a stream. This allows you to not create a temporary file when extracting the Kamelets: for example.

For authentication, you can reuse the Spectrum auth we already have...

@github-actions
Copy link
Contributor

This PR has been automatically marked as stale due to 90 days of inactivity.
It will be closed if no further activity occurs within 15 days.
If you think that’s incorrect or the issue should never stale, please simply write any comment.
Thanks for your contributions!

@lburgazzoli
Copy link
Contributor Author

lburgazzoli commented Oct 13, 2023

bump

Copy link
Contributor

This PR has been automatically marked as stale due to 90 days of inactivity.
It will be closed if no further activity occurs within 15 days.
If you think that’s incorrect or the issue should never stale, please simply write any comment.
Thanks for your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants