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

Add support for multi-platform images #54

Merged
merged 5 commits into from
Aug 8, 2023
Merged

Conversation

shanejbrown
Copy link
Contributor

@shanejbrown shanejbrown commented Aug 7, 2023

Description

Adds support for docker multi-platform images using docker buildx. Because a common use case in buildrunner is to build a image locally, run some tests in the new image and then push the newly built image to a remote registry if the tests pass some specially steps had to be taken. In it's simplest form you can do docker buildx build --platform <list of platforms> -t <tag> --push . which will build and push a multi-platform image in a single command (but you may need to create a new builder for this). However, using this way will not allow for intermediate testing on the image prior to pushing it to a remote registry. For this reason the following methodology was contrived:

  1. Start a ephemeral local docker registry container
  2. Build each image for each platform with unique temporary images names pushed to the local docker registry container
  3. Pull specific image for native architecture, else pull any of the created images
  4. Run tests inside of pulled image
  5. If tests pass, push multi-platform image to remote registry

During testing the local docker registry container was used since it appeared that a manifest was never created unless the --push flag was used. And the final push in step 5 is done by using docker buildx imagetools create which creates a list of existing manifests.

Related Issue

Motivation and Context

Multi-platform images are becoming for common and the standard for many images. As well as a push by some to begin to use ARM images.

How Has This Been Tested?

Unit tests have been created for the MultiplatformImageBuilder class which does most of this logic. I also created and used a simple repo with a buildrunner.yaml config to build and push single and multiple platform images.

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@shanejbrown shanejbrown marked this pull request as ready for review August 7, 2023 22:08
README.rst Outdated
@@ -432,6 +432,13 @@ shows the different configuration options available:
<or>
platform: linux/arm64/v8 # an apple m1 architecture

# Multi-platform images
# Specify a list of platforms to build for, and buildrunner will build a manifest
# list image with the given platforms
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 we need to make it clear that this uses the builders configured in build for these platforms, which may or may not be emulated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree this should be more clear. But I am not sure what you mean by this uses the builders configured in build for these platforms. Can you elaborate on that some more?

Copy link
Contributor

Choose a reason for hiding this comment

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

builders configured in buildx is what I meant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I see what you mean. Right now, it doesn't use any configured buildx builders, it just uses the default builder. In the future we may use different builders.

However, for now how about this for the comment?
# For Multi-platform images please specify a list of platforms to build for
# and buildrunner will build images with the given platforms.
# Please note that these may be built using emulated architectures which
# will be slower than native builds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated this, but let me know if you think it still needs work.

README.rst Outdated Show resolved Hide resolved
@@ -573,85 +574,92 @@ def run(self): # pylint: disable=too-many-statements,too-many-branches,too-many

exit_explanation = None
try: # pylint: disable=too-many-nested-blocks
with MultiplatformImageBuilder(keep_images=not self.cleanup_images) as multi_platform:
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this always end up starting the local registry, or just when multiple platforms are specified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! This is a bad design, since this will create a local registry every time. I'll need to make an update to parse the config to see if there are any multi-platform images in the platform.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added new commit to fix this.

)
if exit_code != 0 or not builder.image:
raise BuildRunnerProcessingError('Error building image')
if isinstance(self.platform, list):
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 this should be:

if isinstance(self.platform, list) and len(self.platform) > 1:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


assert len(built_images) == len(self.platform), \
f'Number of built images ({len(built_images)}) does not match ' \
'the number of platforms ({len(self.platform)})'
Copy link
Contributor

Choose a reason for hiding this comment

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

when splitting a string you need the f prefix on every formatted string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

for repo in self._repos:
# Always add default tag
repo.tags.append(default_tag)
self.step_runner.multi_platform.tag_single_platform(name=self.get_unique_build_name(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally I feel like this kind of parameter indentation can be hard to read, especially with very long prefixes. I much prefer something like this:

self.step_runner.multi_platform.tag_single_platform(
    name=self.get_unique_build_name(),
    tags=repo.tags,
    dest_name=repo.repository,
)

I'm not asking you to change this, I just thought I should mention it as I think it's much easier to parse what's going on since you don't have to read across the entire line for the indent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like your suggestion better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

missing_images.append(expected_image)
return missing_images

def test_use_local_registry():
Copy link
Contributor

Choose a reason for hiding this comment

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

Should have two lines in between methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


# FIXME: These tests can be broken if a custom buildx builder is set as default # pylint: disable=fixme

def actual_images_match_expected(actual_images, expected_images) -> List[str]:
Copy link
Contributor

Choose a reason for hiding this comment

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

Should have two lines in between methods/declarations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

assert not docker.volume.exists(volume_name)

@pytest.mark.parametrize("name, in_mock_os, in_mock_arch, built_images, expected_image",
[
Copy link
Contributor

Choose a reason for hiding this comment

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

This indent seems a little much... but maybe it's just github doing weird things.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No you are right, it is extreme. I'll make it less.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

README.rst Outdated
@@ -432,6 +432,14 @@ shows the different configuration options available:
<or>
platform: linux/arm64/v8 # an apple m1 architecture

# For Multi-platform images please specify a list of platforms to build for
Copy link
Contributor

Choose a reason for hiding this comment

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

How about this instead?

To build multi-platform images, add each platform to be built to this list and buildrunner will use docker buildx
to build and tag a single image containing all architectures specified. Note that buildx may be configured to
build some platforms with emulation and therefore builds may take longer with this option specified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made a minor change to that.

buildrunner will use docker buildx to build and provide a single tag containing all architectures specified.

docker buildx will actually create multiple images, one for each architecture, and then combine them into a single tag.

@bluesliverx bluesliverx merged commit 018cfa9 into adobe:main Aug 8, 2023
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants