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

Infer container image properties from Project metadata #11

Closed
6 tasks done
baronfel opened this issue Jun 7, 2022 · 14 comments
Closed
6 tasks done

Infer container image properties from Project metadata #11

baronfel opened this issue Jun 7, 2022 · 14 comments
Milestone

Comments

@baronfel
Copy link
Member

baronfel commented Jun 7, 2022

Users should be able to easily set the key aspects of image configuration from their project file. There should be a clear integration point at which those values are 'fixed' and can no longer be changed, and there should be reasonable inference for these properties where it makes sense. Where relevant, we should rely on properties that are already conventionally available - packaging information from NuGet and source control information from SourceLink are good examples of these. That lets us make use of documentation and examples from those projects to lessen the number of new concepts here.

Image Metadata Required* Element Type Element Name Data Type Inference algorithm Example
Base Image Name Property ContainerBaseImageName string Based on the ProjectCapabilities of the project - if AspNetCore is present, prefer the dotnet/aspnet base image, otherwise prefer the dotnet/runtime image mcr.microsoft.com/dotnet/aspnet
Base Image Tag Property ContainerBaseImageTag string Based on the TargetFrameworkVersion of the project - net6.0 maps to the 6.0 tag, for example 6.0
Base Image Property ContainerBaseImage string If this is specified, it completely overrides ContainerBaseImageName and ContainerBaseImage. Otherwise, it is the result of concatenating them together, separated by :. mcr.microsoft.com/dotnet/aspnet:6.0
Image Name Property ContainerImageName string The value of the $(AssemblyName) property, lowercased so that it matches image name requirements weatherapp
Image Tag Property ContainerImageTag string The value of the Version property. This one is a bit loose, would folks prefer multiple tags here? 1.0.0
Working Directory Property ContainerWorkingDirectory string Defaults to /app. This directory will be the 'root' of the project's deployed files. /app
Entrypoint Item? ContainerEntrypoint string[] If the project is self-contained, the entrypoint defaults to the name of the generated apphost. Otherwise, for a framework-dependent project, the entrypoint defaults to dotnet <path to project's output.dll> dotnet weatherapp.dll. The path to the apphost or output dll will change based on the ContainerWorkingDirectory
Cmd Item? ContainerCommand string[] No inference. If specified, these arguments will be supplied to the container's entrypoint -
Ports Item ContainerPort <ContainerPort Include="<port number>" Type="tcp, udp" /> If the project has the AspNetCore capability, port entries for TCP 5000 and TCP 5001 will be created <ContainerPort Include="5000" Type="tcp"/><ContainerPort Include="5001" Type="tcp"/>
Environment Variables Item ContainerEnvironmentVariable <ContainerEnvironmentVariable Include="ASPNETCORE_URLS" Value="http://localhost:5000" /> If the project has the AspNetCore capability, the ASPNETCORE_URLS variable will be set to http://localhost:5000;https://localhost:5001
Labels Item ContainerLabel string key, string value We may set some of the well known labels, see below. <ContainerLabel Include="org.opencontainers.image.created" Value="2022-06-07T12:10:12Z" />
User Property ContainerUser string No default
Group Property ContainerGroup string No default, if specified the User must be specified as well

NOTE
Required in this case means required to have a working container image, not that the user is required to provide a value for the item. We will do inference to fill in all required items, erroring on validation if necessary.

Labels

There are some well-known labels that we should consider setting by default:

Annotation Key Derived from
org.opencontainers.image.created $([System.DateTime]::UtcNow.ToString("o"))
org.opencontainers.image.authors $(Authors)
org.opencontainers.image.url $(RepositoryUrl) if present.
org.opencontainers.image.documentation Since $(PackageLicenseUrl) is deprecated, we may just have to default to $(RepositoryUrl). Users should be able to override this easily though.
org.opencontainers.image.source $(RepositoryUrl) if present.
org.opencontainers.image.version $(Version) if present. Note that there's not really a consistent place to hook into where we can be assured of a version being set, so we'll want to be sometime directly before Build, but as late as possible otherwise.
org.opencontainers.image.vendor There's no $(PackageOwners) or $(Owners) to bootstrap off of, but maybe we should make one?
org.opencontainers.image.licenses $(PackageLicenseExpression)
org.opencontainers.image.ref.name $(RepositoryBranch) or $(RepositoryCommit). There's not a baked-in for git repo tags in SourceLink, which would be ideal. Commit is probably the best one.
org.opencontainers.image.title $(Title)
org.opencontainers.image.description $(Description)
org.opencontainers.image.base.digest The resolved digest of $(ContainerBaseImage)
org.opencontainers.image.base.name $(ContainerBaseImage)

Example project file

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <OutputType>Exe</OutputType>
        <Version>1.2.3</Version>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Dotnet.ReproducibleBuilds"  Version="1.1.1" PrivateAssets="all" />
    </ItemGroup>

    <ItemGroup>
        <ContainerPort Include="1234" Type="udp" />
        <ContainerEnvironmentVariable Include="LOG_LEVEL" Value="trace" />
        <ContainerLabel Include="org.contoso.businessunit" Value="C+AI" />
    </ItemGroup>
</Project>

Work Items

@baronfel
Copy link
Member Author

baronfel commented Jun 8, 2022

I'd like any feedback on the

  • defaults chosen for inference, especially property mappings given that we're overlapping with NuGet and SourceLink
  • structure of the more esoteric container properties - maybe it's better to have a ContainerConfig item that can have different metadata items inside it:
<ItemGroup>
 <ContainerConfig>
   <Port Include="1234" Type="udp" />
 </ContainerConfig>
</ItemGroup>

and so one, dropping the Container prefix?

@baronfel
Copy link
Member Author

baronfel commented Jun 9, 2022

We should have a public target at which point all the property inference has been done, so that consumers of this information (not just our publish target, but potentially IDE container tooling or scanners, etc) have a solid integration point to hang off of.

@benvillalobos
Copy link
Member

<ItemGroup>
 <ContainerConfig>
   <Port Include="1234" Type="udp" />
 </ContainerConfig>
</ItemGroup>

Unfortunately this isn't possible in today's MSBuild. Were you thinking of adding support for ContainerConfig as a particular type that would support this behavior?

Is there a list of "required" properties we expect customers to fill in the blanks for? Those could fall under a single ContainerConfig item with expected metadata on. It'll be easy in tasks/targets to error on ContainerConfig.Foo == '' in that case.

Your example (pasted below) makes the most sense if we're not planning to modify MSBuild to support a more specialized type. Especially if we expect certain items to increase their number of potential metadata over time.

    <ItemGroup>
        <ContainerPort Include="1234" Type="udp" />
        <ContainerEnvironmentVariable Include="LOG_LEVEL" Value="trace" />
        <ContainerLabel Include="org.contoso.businessunit" Value="C+AI" />
    </ItemGroup>

@benvillalobos benvillalobos mentioned this issue Jun 30, 2022
3 tasks
@baronfel
Copy link
Member Author

baronfel commented Jul 1, 2022

I figured that an entire new item might be too gnarly :) I'm ok with having a set of Container*-prefixed properties/items for these things. I think that there's a pretty clear split between what should be an Item and what should be a Property, as well as what's required - I'll add those two columns to the table above to help us talk about them.

@baronfel
Copy link
Member Author

baronfel commented Jul 1, 2022

@benvillalobos I updated the table - one thing I'm wrestling with is that a mix of properties and items is not great from a end-user experience point of view - the user has to hop around to separate XML elements to describe their container. Another thing that's worth digging into is that the Entrypoint and Cmd properties natively are string arrays, so that we don't have to handle shell token escaping, but the most 'natural' way for a user to define them in MSBuild would be a single string property. @rainersigwald and I have chatted a bit about maybe a CLI-splitting property function? or just accepting the pain and requiring Items for this. What are your thoughts?

@rainersigwald
Copy link
Member

All of the items other than the command line could be semicolon-delimited properties (and there's precedent for that in other places in MSBuild). That's not ideal but it would keep things together.

Another option to keep things together, maybe, is metadata on an item, but that's a lot harder to customize in targets at build time.

@benvillalobos
Copy link
Member

Focusing on Cmd & how users should define that for a bit, I'm gonna brain dump the options I immediately see.

The Item Route

<Project>
	<!-- I'm a user that wants to containerize an app, but I have a specific command-line that I need to run. Idk... `cmd echo 'foo'`-->

	<ItemGroup>
		<!-- Here's one way to do it, just shove it all into the include. -->
		<!-- Pros: Leaves room for metadata on a command. Not much else...? -->
		<!-- Cons: If the entire cmd could be placed in the include, why not make it a property? -->
		<ContainerCommand Include="cmd.exe echo foo"/>
		
		<!-- Another option: Main command is the include, args come from metadata -->
		<!-- Pros: Slightly more organized. -->
		<!-- Cons: Is the 'possibility of metadata on this item' enough of a pro that it justifies items?-->
		<!-- Thoughts: I like this more than the previous, but I'm leaning towards properties for commands. -->
		<ContainerCommand Include="cmd.exe" Arguments="echo foo"/>
	</ItemGroup>
</Project>

The Property Route

<Project>
	<!-- I'm a user that wants to containerize an app, but I have a specific command-line that I need to run. Idk... `cmd echo 'foo'`-->

	<!-- Pros: Simplicity -->
	<!-- Cons: Needs to be converted eventually. Having tons of properties in general has always felt "disconnected". -->
	<!-- Thoughts: Our future 'ParseTheContainerInfo' task could theoretically handle the logic. This does feel more intuitive.-->
	<PropertyGroup>
		<ContainerCommand>cmd.exe echo 'foo'</ContainerCommand>
	</PropertyGroup>
	

</Project>

I have chatted a bit about maybe a CLI-splitting property function?

The CLI portion is where I'm not 100% in sync. Can you describe a super brief scenario for that? My understanding is: Add a property function that will turn a space-delimited command line into a string[]? Or Item to flow into the build?

Another option to keep things together, maybe, is metadata on an item, but that's a lot harder to customize in targets at build time.

To play devil's advocate: Do we care about modifying this stuff during the build? Would the output of some build step determine some metadata within an item? I think I'm just lacking scenarios to base this on so it's difficult to think in terms of flexibility (complexity) vs simplicity

@benvillalobos
Copy link
Member

Questions as I'm defining defaults:

ContainerBaseImageName

  • Where can I find the names of these images?
  • The Inference algorithm specifies using ProjectCapabilities, where is this defined? I see it used in the SDK, but only as part of dotnet-watch, which only checks for SupportsHotReload. Looks like it's a project system concept? Though I'm not entirely sure because SupportsHotReload isn't in this list.

ContainerBaseImageTag

  • Will 6.x.y always map to 6.0?
  • Are there any weird possible values for this? Will it always be major.minor?

@baronfel
Copy link
Member Author

baronfel commented Jul 5, 2022

The images we're used to can be browsed over on Microsoft Container Registry - IMO we'll mostly be interested in the following images specifically for apps:

  • dotnet/aspnet
  • dotnet/runtime

These are pushed by our internal teams, and they have a regular tag format. You can see some of them here. These tags might be for:

  • specific version tags: 6.0.6
    • these never float - they can be treated as deterministic
  • major.minor tags: 6.0
  • these float, and are updated on each monthly security update
  • there are also a number of tags that encode base linux OS distro (focal, jammy, bullseye for example), processor architectures, and amount of dependencies (slim vs full).
    • these aren't as interesting IMO - my hypothesis is that most folks just want the closest runtime or aspnet image for their applications

@baronfel
Copy link
Member Author

baronfel commented Jul 5, 2022

ProjectCapabilities are Yet Another MSBuild Item that VS (and other IDEs) can use to light-up UI or editor capabilities. They are 'markers' more than anything else. The AspNetCore one is included in a set of props/targets called the ProjectSystem targets, but that's always included in the 'core' WebSDK so for an aspnet project it should always be present. That's why I thought it would be a good, reliable differentiation point for the feature.

@baronfel
Copy link
Member Author

baronfel commented Jul 6, 2022

We should cross-check with the Azure Container tooling docs here: https://docs.microsoft.com/en-us/visualstudio/containers/container-msbuild-properties?view=vs-2022

@baronfel
Copy link
Member Author

baronfel commented Aug 1, 2022

Right now we're fixated on the framework-dependent scenario, especially when it comes to inferring what the base image should be.

For single-file, self-contained style deployments, though, we should support using the runtime-deps images for linux or the nanoserver images for Windows (even though we're not explicitly looking at Windows at the moment).

EDIT: We have pivots for self-contained to use dotnet/runtime-deps. The windows scenario is still lacking.

@tmds
Copy link
Member

tmds commented Nov 3, 2022

Some random thoughts after reading through this list:

  • Currently an ENTRYPOINT gets generated that starts the app.
    Sometimes you want to preserve the entrypoint of the base image, and start the app using a CMD instead.
    Cmd allows to do this, but then requires to manually specify the command which is otherwise auto-generated. It would be nice to be able to opt-in to using a CMD instead of an ENTRYPOINT.

  • For User and Group. If the base image does not have this user/group, will they be added? In that case, does the string format accept both a numeric id, as well as a name (like tmds:1000)?

  • Maybe add something like ContainerBaseImageTagSuffix to enable picking the -alpine, ... variants of the Microsoft images.

@baronfel baronfel added this to the 7.0.300 milestone Jan 24, 2023
@baronfel
Copy link
Member Author

Sorry for the delay here @tmds -

  • Let's talk this one over in Support using the base image ENTRYPOINT and starting the application through CMD #398 - I agree that we need a better pattern for these use cases
  • This tooling supports username, uid, groupname, and gid - with the caveat that anything you provide must already be on the base image (or some other layer included in the image through some other means). We can't easily modify the actual OS configs since there's no underlying VM to run commands against.
  • I'll make a separate issue to track this - it is already very apparent with the MS images, and seems to be a prevalent pattern.

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

No branches or pull requests

4 participants