rockcrafters / dotnet

Ubuntu ROCKs for the .NET runtime and family
Apache License 2.0
50 stars 4 forks source link

How should we version the Dockerfiles? #24

Closed richlander closed 2 years ago

richlander commented 2 years ago

There are are multiple dimensions for us to consider (not all of which are versioning):

I'm thinking we're going to need some directories.

mthalman commented 2 years ago

For architecture, see https://github.com/ubuntu-rocks/dotnet/issues/19.

I'll throw out a proposal here: ubuntu/dotnet-<image-type>:<dotnet-version>-<ubuntu-version-codename>-<arch>

Example: ubuntu/dotnet-runtime:6.0-jammy-arm64v8

We can, of course, use multi-arch tags for things as well.

richlander commented 2 years ago

It would be good if the Canonical folks suggested a pattern that all Rocks use (or at least consider). I was thinking less about the registry repos (which is still an important topic) and more the GitHub repos. Ideally, there was a README that defined the pattern for both.

@cjdcordeiro

cjdcordeiro commented 2 years ago

these concepts are still very much in progress.

I do have some preliminary structures I can share for the time being, but I'll only apply them to the project in a few weeks, once I gather more feedback and assess the technical feasibility.

For now, feedback is welcome. This is the current thinking:


ROCKS should adopt (to some extent) the existing channels terminology from Snaps.

In practice, this means there are a few concepts one needs to be acquainted with when building a ROCK:

For the time being, while our ROCKS infrastructure and release management are still coming to life, I'd propose organizing this .NET project as follows:

Dir Tree

The ROCKs' recipes (Dockerfiles in this case) should all be located in the same place. The ROCKs' builds are driven by the rockcraft.yaml files located in the rock folder.

~~dotnet/ ├── .github ├── tests # global scope tests ├── LICENSE ├── README.md └── rockcraft ├── rockcraft.runtime.yaml ├── Dockerfile.runtime.amd64 ├── Dockerfile.runtime.arm ├── rockcraft.runtime-deps.yaml ├── Dockerfile.runtime-deps.amd64 ├── Dockerfile.runtime-deps.arm ├── .dockerignore ├── runtime │   ├── install-slices │   ├── LICENSE # if it doesn't exist, defaults to the project LICENSE │   ├── README.md │   ├── src │   └── tests # local scope (unit) tests └── runtime-deps ├── install-slices ├── LICENSE ├── README.md ├── src └── tests~~

dotnet/
├── tests   # global scope tests
├── rock
│   ├── rockcraft.2.runtime_22.04_build.yaml
│   ├── rockcraft.2.runtime_20.04_build.yaml
│   ├── rockcraft.1.runtime-deps_22.04_build.yaml
│   └── rockcraft.1.runtime-deps_20.04_build.yaml
├── README.md
├── LICENSE
├── .github
├── dotnet-runtime-deps
│   ├── tests   # local scope (unit) tests
│   ├── src
│   ├── README.md
│   ├── LICENSE   # if it doesn't exist, defaults to the project LICENSE
│   ├── install-slices
│   ├── .dockerignore
│   ├── Dockerfile.22.04
│   └── Dockerfile.20.04
└── dotnet-runtime
    ├── tests
    ├── src
    ├── README.md
    ├── LICENSE
    ├── install-slices
    ├── .dockerignore
    ├── Dockerfile.22.04
    └── Dockerfile.20.04

You might be asking about: *what are the rockcraft..yaml** files? These are metadata files, indicating the existing projects (ROCKs) to be built. So each YAML fille will contain the following information:

Each rockcraft YAML file has 1 ROCK as an output!

Example of the rockcraft.1.runtime-deps_20.04_build.yaml's content:

name: dotnet-runtime-deps    # a folder with this name must exist. This is the final ROCK's name (eg "ubuntu/dotnet-runtime-deps:latest")
base: 20.04                            
architectures:
  - build-for: [arm, amd64]

Multi-arch

As described above, for each arch, let's, for now, have a dedicated Dockerfile with the corresponding architecture suffix. The target architectures are defined by the driving rockcraft YAML file (as exemplified above).

Note: multi-arch ROCKs do not have the arch in the OCI tag name. So multi-arch ROCKs are in fact comprising OCI manifest lists, with one image manifest per architecture.

Rules:

Multiple .NET versions

Let's create separate branches for that. See below So in the end, a branch name would look something like: /channels/<version>/<risk>. Examples:

Multiple Ubuntu bases

As for versions, let's create multiple branches for this. So in the end, a branch name would look something like: /channels/<version>/<base>/<risk>. Examples: - /channels/6.0/20.04/stable - /channels/6.0/22.04/stable - /channels/6.0/22.10 (defaults to risk=edge) - /channels/7.0/20.04/candidate

The ROCKs bases are defined by the driving rockcraft YAML file (as exemplified above). If the YAML file states something like name: foo and base: bar, then we expect to see a folder called foo, with a Dockerfile named Dockerfile.bar.

OCI Tags

We still need to understand if we can do Continuous Delivery from the CI directly (mostly need to sort out how to securely define and use credentials, and how to define registry destinations). But, OCI Tags shall be driven by the channel structure exemplified above.

So, for now, to respect the aforementioned structure whilst still being aligned with all the existing Ubuntu "ROCKS", which means a valid OCI Tag would be ubuntu/dotnet-runtime:6.0-22.04_stable.

OCI Aliases

For the OCI Tag example from above, the following tags would emerge: 6.0 and 6.0-22.04, all pointing to 6.0-22.04_stable, since that is the "most stable" .NET 6.0 on 22.04.

What is still TBD are other special tags like latest and stable, since these might point to any version and any base. Let me know if you have ideas.

~~--- Finally, you might be asking about: what are the rockcraft..yaml files? These are metadata files, indicating the existing projects (ROCKs) to be built. So rockcraft.runtime.yaml tells us (and the CI infra) to look for Dockerfile.runtime.* files and build them.~~

`Inside thisrockcraft.*.yaml` files we shall find the necessary metadata information to build the ROCKs. For now, let's just say the YAML content will be:~~

~~ name: dotnet-runtime # or dotnet-runtime-deps~~

This name will dictate the name of the ROCK to build.


With all this being said, please note that this structure is just a means to an end since we can still progress with the current structure, by focusing on a single base and .NET version.

richlander commented 2 years ago

That will result in a lot of branches. Do you think we need a README that describes which are live/supported? With everything in main, that is easy. With branches, not so much.

Have you thought about how CI and CD will work? I guess if you make a change, that's to just one branch and we only need to validate assets for that one branch. In terms of ship day, how do we reason about that? Will there be a global view of Rocks with a mapping to which branch that the rock lives in?

All the arches are together so producing multi-arch tags for a given configuration (like Ubuntu 22.04 + .NET 6.0) is straightforward. That's good.

We have a samples directory, which we use to ship samples based on our images. They are very useful since our samples are diagnostic. Do you think we should include them? Here they are: https://mcr.microsoft.com/en-us/product/dotnet/samples/about.

mthalman commented 2 years ago

Multiple branches also implies that multiple versions of runtime-deps images would be built/published for each version and base. That's quite an explosion of images for something that you could literally have one image for. That one image could be tagged such that it maps to each version and base.

richlander commented 2 years ago

Good point, however, that issue could be handled via a global view of images and infra. That's what we're doing with .NET images, too. The fact that we only have one Dockerfile where there are multiple here is immaterial, right?

mthalman commented 2 years ago

I guess it depends on the complexity of the infrastructure. For a simplistic system, each branch would have its own GitHub Actions to handle CI/CD. With such a system, my point here is that a multi-branch pattern would imply multiple versions of that image. A more complex system would be needed to avoid that with a multi-branch pattern.

cjdcordeiro commented 2 years ago

Hi again and thanks for the feedback.

After taking your comments into account and with @woky 's big help, I've edited the previous proposal above: https://github.com/ubuntu-rocks/dotnet/issues/24#issuecomment-1171396382

Summary of changes:

Outstanding concerns:



Addressing your comments:

Do you think we need a README that describes which are live/supported?

With this current structure, branches only get built if you commit to them, so if we don't, no new builds are triggered. No need for special READMEs I'd say


Have you thought about how CI and CD will work?

Yes. The CI/CD is centralized, outside this repository, thus all the effort towards a generic normalization of the project structure, such that the same CI can also fit other ROCKs in the future (even if for a limited time)


I guess if you make a change, that's to just one branch and we only need to validate assets for that one branch.

Yes


In terms of ship day, how do we reason about that?

All ROCKs are built and shipped whenever new changes are introduced to the respective branch.


Will there be a global view of Rocks with a mapping to which branch that the rock lives in?

Yes. Whenever a central build happens, a branch/tag clone is created in the builder repository (we own this repo).


Will there be a global view of Rocks with a mapping to which branch that the rock lives in?

I wouldn't consider samples to be ROCKs. I think this is something interesting to be discussed...but my gut feeling is that samples should be treated as auxiliary artefacts. How to handle them though, that's a good question. @richlander can you open a ticket on this?

A possible idea would be to not build samples within the ROCKs CI/CD, but in this repo itself, and upload them separately to a different registry namespace...(thinking out loud)


Multiple branches also implies that multiple versions of runtime-deps images would be built/published for each version and base.

Yes, that's the consequence of having a monolithic repository (check my comment above, under "outstanding concerns"). Sure, if the CI/CD was dedicated to this repo, we could do that, but the CI is generic, and I'd like to avoid having tailored and complicated conditions in our CI just to serve very specific cases which could be better resolved through other means (like splitting the repo)



hope this helps ;)

richlander commented 2 years ago

With this current structure, branches only get built if you commit to them, so if we don't, no new builds are triggered. No need for special READMEs I'd say

There are two good options (IMO): README for what's a live branch or delete branches that are no longer live. I gotta think our two organizations care equally deeply about communicating support levels to users. Leaving live branches for EOL versions don't seem good for that.

Samples

Fair enough.

https://github.com/ubuntu-rocks/dotnet/issues/27

Structure

If we're going to use branches, I think the approach that would make the most sense is a branch per Ubuntu version and all .NET versions within that branch. That would solve the runtime-deps issue that @mthalman raised and would allow Canonical to declare a support policy per branch w/rt Ubuntu.

cjdcordeiro commented 2 years ago

thanks for the feedback.

wrt EOL, yes, the strategy is that 1) all ROCKs shall have self-contained information about EOL, and 2) the respective sources shall get archived (if we are talking about GitHub sources) at EOL. On top of that, sources will also have community health files, which include a SUPPORT.md file

https://github.com/ubuntu-rocks/dotnet/issues/27

thanks

If we're going to use branches, I think the approach that would make the most sense is a branch per Ubuntu version and all .NET versions within that branch. That would solve the runtime-deps issue that @mthalman raised and would allow Canonical to declare a support policy per branch w/rt Ubuntu.

we've considered this option as it is something we've also been using for other projects (thus why I've also proposed doing it in the 1st proposal). However, there are a few things to consider here:


In any case, this is not to say that one approach is wrong and another is right...the reality is that we need to be as generic as possible to accommodate a variety of ROCKs (at least the ones in https://hub.docker.com/u/ubuntu) without disrupting too much the most popular practices of OSS management.

richlander commented 2 years ago

Got it. That context helps.

We can look at what we do. It's nuanced in a somewhat similar way to what you raise:

We've found that this works super well for us. Our product has something like two dozen product repos, so this separation makes sense. We're thinking of having a separate monorepo projection of these two dozen product repos to enable scenarios that are hard today. However, I think we'd keep the dotnet-docker repo separate as it is today.

A nice benefit of our current system is that most (all?) of our Dockerfiles are generated from templates. We make changes to the templates and to some data files, and then run scripts that generate all the desired Dockerfiles. That's really nice to do from one branch with a single invocation.

I agree that branching on Ubuntu version probably doesn't make sense. We're clearly not going to branch on architecture. That leaves target version (like .NET version) as the only remaining pivot. We can branch on that or not at all. However, if we were to branch on .NET version, which version would be in main? Latest LTS or latest (stable) version?

cjdcordeiro commented 2 years ago

thanks for the info @richlander . I think @valentincanonical would be keen to read such feedback on how you handle EOL, since this is something he's been working on for ROCKs as well.

However, if we were to branch on .NET version, which version would be in main? Latest LTS or latest (stable) version?

This is an excellent question. Typically, main corresponds to the latest, most stable development of the software. But in this case, we don't have source code in the repo. I think we can discuss this in more detail...

cjdcordeiro commented 2 years ago

wrt to the main branch, I propose we keep it, but populate it only with a README.md file. Why this decision:

  1. be aligned with our existing container image projects (the Ubuntu base container image included)
  2. avoid becoming GitHub-dependant, since other Git systems don't really mandate a default branch, like Launchap (even if that causes clones to not checkout anything by default)
  3. allow for the default branch to serve as an exploratory entrypoint, giving users an understanding of how the repository is being managed

@richlander sounds good?

richlander commented 2 years ago

populate main with only a README.md file

That works. It's good since its self-descriptive of the scheme. That's what I care about most. However, what will the branches be modeled as? I assume .NET version in the same way the repo you pointed me to was Ubuntu version. Yes?

We could put samples in main, too. They would rely on the latest stable version. That's what our samples already do, in dotnet/dotnet-docker.

What do others think?

cjdcordeiro commented 2 years ago

yes, branches are created per .NET version.

richlander commented 2 years ago

@MichaelSimons @mthalman you good with this plan?

mthalman commented 2 years ago

Yep, sounds good to me.

MichaelSimons commented 2 years ago

Sounds good to me as well.

cjdcordeiro commented 2 years ago

great, closing in favour of https://github.com/ubuntu-rocks/dotnet/issues/29