项目作者: Montana

项目描述 :
Any registry or runtime that claims to have a certain Docker distribution image specification support will be interacting with the various manifest types to find out various things about the image
高级语言: Shell
项目地址: git://github.com/Montana/manifest.git
创建时间: 2020-12-16T07:12:44Z
项目社区:https://github.com/Montana/manifest

开源协议:

下载


Header

Build Status

Docker Manifest with Travis CI

BEFORE you start reading, this guide is only for Travis CI and no other CI provider, also there might be some things you may want to check out first:


NB: All these scripts, mini programs, and obviously the .travis.yml file I’ve authored pass the Travis build individually, so if they break please revert your branch in GitHub by using git revert HEAD. You could also use git reflog as well, you might also want to check git log, and see travis whatsup.


Language Filetype Travis Pass/Fail
Bash travis.sh Pass
YAML .travis.yml Pass
Bash annotations.sh Pass
Python3 charcount.py Pass
Bash crontest.sh Pass
Bash cron_table.sh Pass
Bash ci.sh Pass
Bash caching.sh Pass
Bash build.sh Pass
  • My custom travis.sh bash script to set this env up: travis.sh
  • My custom .travis.yml for this use case: .travis.yml
  • Heavily edited bash script I customized for this use case entiteld annotations.sh: annotations.sh
  • The mini Python app I created to test within Travis in conjunction with manifest entitled charcount.py: charcount.py
  • My script that tests crons. The file as you can tell from the extension is in bash: crontest.sh
  • My cron table I made - if you choose to do randomized cron checks, or on different days of the week: cron_table.sh
  • My quick print in bash of "Hello Travis". The file is called ci.sh, it’s in bash — if you just need a quick printout: ci.sh
  • My bash script on Docker manifest caching: caching.sh

This is so you can get a better idea how this works after you setup your env vars, and ultimately inject your own ‘components’ like ‘charcount’ to have flexibility with the test use case I’ve setup here. The only script that will be being used (mandatory) is the travis.sh under the script: hook in the .travis.yml file. Everything else is optional.

You’ll want to see this before you can move on which is travis.sh getting executed and passing the build:

Success

My script travis.sh passing in the Travis CI build, which means we are off to start seeing manifests.

Branches coming soon

The current stucture (in theory) will look a bit like this:

  1. dat-manifest/master
  2. README.md
  3. └───dat-manifest/main
  4. README.md
  5. Dockerfile.cross (PR)
  6. └───dat-manifest/branch3
  7. README.md
  8. .travis.yml (PR)
  9. ...
  10. └───dat-manifest/scripts
  11. cron_table.sh
  12. travis.sh (PR)

Getting started

Gif

The first thing to do before looking at manifests is loginng into Docker through the Docker CLI.

First thing, you’ll need to set your docker env vars, you can do this once the repo is created, along with your .travis.yml and your Dockerfile you can login into Travis from the CLI via:

  1. travis login --pro --github-token (your github token)

Then just to make sure you’re logged into the proper Travis account run:

  1. travis whatsup

It’s my own personal opinion, when pushing for manifests you should always commit with -asm so you’re signing off on it so, instead of the regular git commit -m "whatever" you’d run git commit -asm "whatever". In this particular example, I grabbed from the DockerHub the following packages:

  1. lucashalbert/curl
  2. ppc64le/node
  3. s390x/python
  4. ibmjava:jre

These are the perfect packages (cURL), (ppc64le), (s390x), (ibmjava:jre), to show a multiarch docker image using ppc64le, s390x, and it’s manifests.

Docker Manifest Layers

Docker images have a bunch of layers. For each command in the Dockerfile, Docker generates a layer with all new files, for example CMD. So, each layer is a set of differences from the previous one, for example below is a digest of an React app I’m currently working on:

ReactLayers

Showing the typical Docker digest and sha256 affiliated with the React app in question.

Docker Manifest Caching

Docker manifest caching is possible, you need to fetch the unique values - but the short end of this is the following:

  1. #!/bin/bash
  2. docker history -q IMAGE_HERE | grep -v missing && tar -xOf file.tar manifest.json | tr , '\n' | grep -o '"Config":".*"' | awk -F ':' '{print $2}' | awk '{print substr($0,2,12)}'
  3. # Alternatively use, tar -xOf file.tar manifest.json | tr , '\n' | grep -o '"Config":".*"' | awk -F ':' '{print $2}' | awk '{print substr($0,2,12)}'

This outputs everything, and would suggest if you’re into caching.

Breaking the digest

When ls the folder, this is how the folder is structured, or should be:

  1. ├── 1/
  2. ├── 2/
  3. ├── 3/
  4. ├── 4/
  5. ├── config.json
  6. └── manifest.json

We want to re-tar and reload docker load:

  1. tar -cf new-a.tar -C a/ .
  2. docker load -i new-a.tar
  3. Loaded image ID: sha256:24b...975

The digest equals the sha256 of the config.json file, it runs!

Check if image:tag combination already exists on DockerHub:

Follow this bash script I edited that will have the image combination search function, either result or null:

  1. #!/bin/bash
  2. func docker_image_tag_exists() {
  3. EXISTS=$(curl -s https://hub.docker.com/v2/repositories/$1/tags/?page_size=10000 | jq -r "[.results? | .[]? | .name == \"$2\"] | any")
  4. test ${EXISTS} = true
  5. }
  6. if docker_image_tag_exists $1 $2; then
  7. echo "true" # enforce image tag to exist
  8. else
  9. echo "false" # enforce null fetch if image tag doesn't exist
  10. fi

This will be a quicky way to check if a particularly Docker image:tag combination exists on DockerHub, so you can quickly make decisions and possibly grep image name combos, and get manifests quicker on certain items.

What are Docker Manifests, why do I need to use manifest?

Any registry or runtime that claims to have a certain Docker distribution image specification (this can easily be checked) support will be interacting with the various manifest types to find out the following things inside a image:

  1. What are the actual filesystem content, (layers) will be needed to build the root filesystem for the container.

  2. Any specific image config that is necessary to know how to run a container, some are more niche than others for using certain images. For example, information like what command(s) to run when starting the container (as probably represented in the Dockerfile that was used to build the image).

In short, The Docker container manifest is a file that contains data about a container image. Specifically digest, sha256, and most importantly arch. We can create a docker manifest which points to images for different architectures so that when using the image on a particular architecture Docker automatically pulls the desired image.

The main reason for manifest is to cross-build docker images.

Docker configuration files

By default, the Docker command line stores its configuration files in a directory called .docker within your $HOME directory, this can obviously be changed, but for now we are talking by standard practices.

Docker manages most of the files in the configuration directory and you should not modify them. However, you can modify the config.json file to control certain aspects of how the docker command behaves, (e.g. flags, manifest, etc).

You can modify the docker command behavior using environment variables, you’ll see me use the term env vars (it’s the same meaning), or CLI options. You can also use options within config.json to modify some of the same exact behavior. If an env var and the —config flag are set, the flag takes precedent over the env var. CLI options override env vars and env vars override properties you specify in a config.json file. It’s a bit of give and take.

Overriding a single value in your .env file is reasonably simple: just set an env var with the same name in your shell before rerunning docker.

Change the .docker directory

You can close out docker, reopen terminal and run:

  1. docker --config ~/testconfigs/ ps

This particular flag only applies to whatever command is being called. For persistent config, you can set the DOCKER_CONFIG env var in your shell (e.g. ~/.profile or ~/.bashrc)(Between zsh, bashrc). The example below sets the new directory to be HOME/newdir/.docker.:

  1. echo export DOCKER_CONFIG=$HOME/newdir/.docker > ~/.profile

Fat Manifest

The manifest list is the “fat manifest” which points to specific image manifests for one or various platforms or architectures. Its use is optional, and relatively few images will use one of these manifests. You can distinguish via the content-Type returned in a HTTP response usually.

Image Digest

When inspecting a digest in a directory tree, it should look similar to this:

tree

Docker’s JSON config file describes the environment that built the docker image and its history through manifest and a good example is above, and the hash beginning with 5d1.
(here, 5d1cdcfd1c744987e4916f7815874391b29bff62e3df2d29885683e1b39e4c0a.json).

Why I use cron jobs

For a build like this, the cron and at a services level, it can enable programmers to schedule tasks to run at a specific time in the future. You can think of it as setting a coffee maker. The at service specifies a one-time task that runs at a certain time. The cron service can schedule tasks on a repetitive basis and in rapid succession, such as daily, weekly, or monthly tasks that involve almost anything.

You’ll see some of these dcrons around, you’ll start why I personally use them. They are NOT for everybody. I made a table of the most common scheduler type crons. You can get a better idea on how to write them and implement them by looking at my cron_table.sh:

Version Edit crontab Remove crontab New crontab List cron-jobs
dcron crontab -e crontab -d [user] crontab file crontab -l
fcron fcrontab -e fcrontab -r [user] fcrontab file fcrontab -l
cronie and bcron crontab -e crontab -r -u [user] crontab file crontab -l

Sample docker config.json file

Following is a sample config.json file:

  1. {
  2. "HttpHeaders": {
  3. "MyHeader": "MyValue"
  4. },
  5. "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
  6. "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
  7. "pluginsFormat": "table {{.ID}}\t{{.Name}}\t{{.Enabled}}",
  8. "statsFormat": "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}",
  9. "servicesFormat": "table {{.ID}}\t{{.Name}}\t{{.Mode}}",
  10. "secretFormat": "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}",
  11. "configFormat": "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}",
  12. "serviceInspectFormat": "pretty",
  13. "nodesFormat": "table {{.ID}}\t{{.Hostname}}\t{{.Availability}}",
  14. "detachKeys": "ctrl-e,e",
  15. "credsStore": "secretservice",
  16. "credHelpers": {
  17. "awesomereg.example.org": "hip-star",
  18. "unicorn.example.com": "vcbait"
  19. },
  20. "stackOrchestrator": "kubernetes",
  21. "plugins": {
  22. "plugin1": {
  23. "option": "value"
  24. },
  25. "plugin2": {
  26. "anotheroption": "anothervalue",
  27. "athirdoption": "athirdvalue"
  28. }
  29. },
  30. "proxies": {
  31. "default": {
  32. "httpProxy": "http://user:pass@example.com:3128",
  33. "httpsProxy": "http://user:pass@example.com:3128",
  34. "noProxy": "http://user:pass@example.com:3128",
  35. "ftpProxy": "http://user:pass@example.com:3128"
  36. },
  37. "https://manager1.mycorp.example.com:2377": {
  38. "httpProxy": "http://user:pass@example.com:3128",
  39. "httpsProxy": "http://user:pass@example.com:3128"
  40. },
  41. }
  42. }

Using experimental CLI features

I will show you other methods in doing this, but if you’re going to do it through docker’s config.json, look for the following:

  1. {
  2. "experimental": "enabled",
  3. "debug": true
  4. }

This for example will make manifestation possible, when calling docker manifest.

Using manifest wthout installation

If you want to use manifest for simple query operations? For example, maybe you only want to query if a specific image:tag combination is a manifest list entry or not, something I call a ‘if or that’ query. If so, what platforms are listed in the manifest itself.

You can consume this feature of manifest without installing the binary as long as you are querying public (e.g. not private/authentication-requiring DockerHub registries, and secure ones not insecure ones). This can be done in a different way by another tool called mquery. Using mquery you can query the Docker image itself using these commands:

You can use mquery via a multi-platform image currently located on DockerHub as mplatform/mquery:latest. For example, you can query the mquery image itself with the following command:

  1. docker run --rm mplatform/mquery mplatform/mquery
  2. Image: mplatform/mquery
  3. * Manifest List: Yes
  4. * Supported platforms:
  5. - linux/ppc64le
  6. - linux/s390x

For reference, the mquery program in itself is a small Golang program that queries any functions and calls manifest while running via OpenWhisk from Apache in IBM Cloud Functions.

Working with insecure registries

The manifest command interacts solely with a Docker registry, and solely a Docker registry. Thus, it has no way to query the engine for the list of allowed insecure registries. To allow the CLI to interact with an insecure registry, some docker manifest commands have an —insecure flag, and you’ll see that we used the --insecure flag in our .travis.yml file for this long ‘how-to’. For each transaction (e.g. create, which queries a registry, the --insecure flag must be specified.) If it’s not, the latter will take precedent, and your build will error within Travis.

This flag tells the CLI that this registry call may ignore security concerns like missing or self-signed certificates using a command like:

  1. ln -s /etc/ssl/certs/ca-certificates.crt /etc/docker/certs.d/mydomain.com:5000/ca-certificates.crt

The --insecure flag is not required to annotate a manifest list on some occassions — specifically when specified somewhere else (.travis.yml, Dockerfile), since annotations are to a locally-stored copy in general, and of course this is of a manifest list. You may also skip the --insecure flag if you are performing a docker manifest inspect on a locally-stored manifest list. Be sure to keep in mind that locally-stored manifest lists are never used by the engine on a docker pull. So if you run docker pull that manifest will not be used.

Likewise, on a docker manifest push to an --insecure registry, the --insecure flag must be specified. If not, read what will happen above (the docker protocol heirarchy does its job). If this is not used with an insecure registry, the manifest command fails to find a registry that meets the default requirements, in turn will cause your Travis build to fail.

Manifest parent commands

In this case the parent command is the command that inits what you want to do with docker manifest. It gets a little more complex when Dockerfile. For example, the image’s parent image (in a Dockerfile) is the image designated in the FROM directive in the image’s Dockerfile. All subsequent commands are based on this parent image. A Dockerfile with the FROM scratch directive uses no parent image, as it creates mostly a base docker image, and example of this would be:

  1. FROM scratch
  2. WORKDIR /root/
  3. # Copy the file from the build image
  4. COPY --from=build /go/src/app .
  5. CMD ["./app"]

A table made, to graphically show you the definition of parent command for all you visual learners out there, I know I am:

Command Description
docker The base command for the Docker CLI.

The docker command, will directly lead us into the next section on the proper usage of docker manifest.

Child commands in Docker as it pertains to manifest

Sometimes it’s good to regroup, and put all command in one compact table, we can get lost when we are running docker manifest in variant. These tables will be great to go back and look at if you have questions about what a command does later. I’ve made one for the child commands for docker manifest:

Child commands Description
docker manifest annotate Add additional information to a local image manifest
docker manifest create Create a local manifest list for annotating and pushing to a registry
docker manifest inspect Display an image manifest, or manifest list
docker manifest push Push a manifest list to a repository

Test a docker registry for “manifest list” support

This script expects and requires env vars. Please run the following:

  1. ./test-registry.sh r.myprivreg.com/somerepo

This will give you the binary answer of yes/no if there is manifest support for that DockerHub repo.

Container Manifest

Flow chart explaining how my .travis.yml works.

Using Travis to display the Manifests

The following is the .travis.yml file in the repo which also uses travis.sh bash script I wrote to complete some of the env var tasks, while pushing to the custom .travis.yml file I made for this use case.

  1. ---
  2. language: shell
  3. sudo: required
  4. dist: xenial
  5. os: linux
  6. services:
  7. - docker
  8. addons:
  9. apt:
  10. packages:
  11. - docker-ce
  12. env:
  13. - DEPLOY=false repo=ibmjava:jre docker_archs="amd64 ppc64le s390x"
  14. install:
  15. - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
  16. before_script:
  17. - export ver=$(curl -s "https://pkgs.alpinelinux.org/package/edge/main/x86_64/curl" | grep -A3 Version | grep href | sed 's/<[^>]*>//g' | tr -d " ")
  18. - export build_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  19. - export vcs_ref=$(git rev-parse --short HEAD)
  20. # Montana's crucial workaround
  21. script:
  22. - chmod u+x ./travis.sh
  23. - chmod u+x /build.sh
  24. - export DOCKER_CLI_EXPERIMENTAL=enabled # crucial to use manifest
  25. after_success:
  26. - docker images
  27. - docker manifest inspect --verbose lucashalbert/curl # multiarch build
  28. - docker manifest inspect --insecure lucashalbert/curl # multiarch build
  29. - docker manifest inspect --verbose ppc64le/node # IBM power build
  30. - docker manifest inspect --insecure ppc64le/node # IBM power build
  31. - docker manifest inspect --verbose s390x/python # IBM Z build
  32. - docker manifest inspect --insecure s390x/python # IBM z build
  33. - docker manifest inspect --verbose ibmjava:jre # official Docker IBM Java (Multiarch) build
  34. - docker manifest inspect --insecure ibmjava:jre # official Docker IBM Java (Multiarch) build
  35. branches:
  36. only:
  37. - master
  38. except:
  39. - /^*-v[0-9]/
  40. - /^v\d.*$/
  41. # .travis.yml created by Montana Mendy for Travis CI & IBM

Setting up env vars

You can use your GitHub auth token, or just username/password, once logged in set the env vars:

  1. travis env set DOCKER_USERNAME username
  2. travis env set DOCKER_PASSWORD pwd

You should see this in your build at some point, this is reassurance your env vars got saved.

envvars

Travis CI confirming that our env vars are safe and secure while it builds.

For some setting the env vars in the CLI is the best option, but for others using the Travis CI user interface is easier, and quicker. Click Settings -> Environment Variables then add your DOCKER env vars.

UI

We are using the UI to enter the Travis CI env vars (rather than the CLI).

For both cases, you’ll also want to make sure you’re logged into Docker, you can do this via:

  1. docker login

login

Login succeeded! This is crucial to have everything working properly, specifically getting the docker manifests.

That will now set both your Docker username/password as env vars.

Manifest

Once you’ve added manifest, it’s crucial to add to your .travis.yml:

  1. script: export DOCKER_CLI_EXPERIMENTAL=enabled

Alternatively you can run this in your project directory tree via:

  1. export DOCKER_CLI_EXPERIMENTAL=enabled

The lines critical in the .travis.yml for the manifests to print are the following:

  1. after_success:
  2. - docker images
  3. - docker manifest inspect --verbose lucashalbert/curl
  4. - docker manifest inspect --insecure lucashalbert/curl
  5. - docker manifest inspect --verbose ppc64le/node
  6. - docker manifest inspect --insecure ppc64le/node
  7. - docker manifest inspect --verbose s390x/
  8. - docker manifest inspect --insecure s390x/python
  9. - docker manifest inspect --verbose ibmjava:jre
  10. - docker manifest inspect --insecure ibmjava:jre

You want to use the --verbose and --insecure flags, to get as much manifest information as possible. This is true with any build.

In theory, this doesn’t have to be after_success: but we want to make the most sense of the .travis.yml logs. Let’s see if in particular for example, we can’t find the s390x manifest:

s390x

We are looking at the s390x manifest.

On the flip side, we can easily scroll through the travis logs and lookout for the manifest of ppc64le:

ppc64le

When the exact same Dockerfile is built again, a new digest in the manifest is created for that build, irrespective of the fact, that the same file is being built again and in the same environment. The only difference that makes these two different digests is the container.key and the creation-timestamp.

Create and push a manifest list

In order to spawn a manifest list, you first create the manifest list locally (localhost) by specifying the constituent images, (you can check them using docker -ps -a) if you would like to have included in your manifest list. Keep in mind that this is pushed to a docker registry, so if you want to push to a registry other than the docker registry, you need to create your manifest list with the registry name or IP and port. This is similar to tagging an image and pushing it to a foreign registry.

After you have created your local copy of the manifest list, you may optionally annotate it. Annotations allowed are the architecture and operating system (e.g. ppc64le, linux) (overriding the image’s current values, and again this is from the heirarchy of docker protocols), os features, and an the architecture variant you’re wanting to use:

  1. docker manifest create 0.0.0.0:5000/cool-ibm-test:v1 \

Then you should see:

  1. 00.00.00.00:5000/cool-ibm-test-ppc64le-linux:v1 \
  2. 0.00.00.00:5000/cool-ibm-test-s390x-linux:v1 \
  3. 0.00.00.00:5000/cool-ibm-test-amd64-linux:v1 \

Lastly, you need to push your docker manifest list to the desired registry. Below are descriptions of these three commands, and an example putting them all together:

  1. docker manifest annotate 00.00.00.00:5000/cool-ibm-test-linux:v1 \ 00.00.00.00:5000/cool-ibm-test-linux:v1 \ --arch ppc64le

That’s really it for pushing a manifest. There’s also arbitrary flags, and the docker heirarch protocol, which are well explained in this document.

Manifest Ubuntu

Each layer of the manifest is comprised of a JSON file (which looks like the .config file we talked about earlier), a VERSION file with the string 1.0, and a layer.tar file containing the images files. In this particular case above, we are going to inspect ppc64le/node from DockerHub in my Ubuntu VM, using VMWare.

You might need to resolve some dependencies if doing manifest on Ubuntu, this is fairly easiy:

  1. sudo apt install ruby-dev libffi-dev make gcc
  2. sudo gem install travis

Then make sure Travis is installed:

  1. which travis

Then you’re good to go, now lets move on to pushing a manifest:

  1. "schemaVersion": 2,
  2. "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  3. "manifests": [
  4. {
  5. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  6. "size": 945,
  7. "digest": "sha256:2ab48cb5665bebc392e27628bb49397853ecb1472ecd5ee8151d5ff7ab86e68d",
  8. "platform": {
  9. "architecture": "amd64",
  10. "os": "linux"
  11. }
  12. },
  13. {
  14. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  15. "size": 1363,
  16. "digest": "sha256:956f5cf1146bb6bb33d047e1111c8e887d707dde373c9a650b308a8ea7b40fa7",
  17. "platform": {
  18. "architecture": "arm",
  19. "os": "linux",
  20. "variant": "v6"
  21. }
  22. },
  23. {
  24. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  25. "size": 1363,
  26. "digest": "sha256:c6cc369f9824b7f6a19cca9d7f1789836528dd7096cdb4d1fc0922fd43af9d79",
  27. "platform": {
  28. "architecture": "arm",
  29. "os": "linux",
  30. "variant": "v7"
  31. }
  32. },
  33. {
  34. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  35. "size": 1363,
  36. "digest": "sha256:b9ae5a5f88f9e4f35c5ad8f83fbb0705cf4a38208a4e40c932d7abd2e7b7c40b",
  37. "platform": {
  38. "architecture": "arm64",
  39. "os": "linux",
  40. "variant": "v8"
  41. }
  42. },
  43. {
  44. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  45. "size": 1363,
  46. "digest": "sha256:4eca7b4f398526c8bf84be21f6c2c218119ed90a0ffa980dd4ba31ab50ca8cc5",
  47. "platform": {
  48. "architecture": "386",
  49. "os": "linux"
  50. }
  51. },
  52. {
  53. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  54. "size": 1363,
  55. "digest": "sha256:2239e5d3ee0e032514fe9c227c90cc5a1980a4c12602f683f4d0a647fb092797",
  56. "platform": {
  57. "architecture": "ppc64le",
  58. "os": "linux"
  59. }
  60. },
  61. {
  62. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  63. "size": 1363,
  64. "digest": "sha256:57523d3964bc9ee43ea5f644ad821838abd4ad1617eed34152ee361d538bfa3a",
  65. "platform": {
  66. "architecture": "s390x",
  67. "os": "linux"
  68. }
  69. }
  70. ]
  71. }
  72. Done. Your build exited with 0.

The build.sh script

  1. #!/bin/bash -x
  2. for docker_arch in ${docker_archs}; do
  3. case ${docker_arch} in
  4. ppc64le ) qemu_arch="ppc64le" image_arch="ppc64le" variant="" ;;
  5. s390x ) qemu_arch="s390x" image_arch="s390x" variant="" ;;
  6. esac
  7. cp Dockerfile.cross Dockerfile.${docker_arch}
  8. sed -i "s|__BASEIMAGE_ARCH__|${docker_arch}|g" Dockerfile.${docker_arch}
  9. sed -i "s|__QEMU_ARCH__|${qemu_arch}|g" Dockerfile.${docker_arch}
  10. sed -i "s|__CURL_VER__|${ver}|g" Dockerfile.${docker_arch}
  11. sed -i "s|__BUILD_DATE__|${build_date}|g" Dockerfile.${docker_arch}
  12. sed -i "s|__VCS_REF__|${vcs_ref}|g" Dockerfile.${docker_arch}
  13. if [ ${docker_arch} == 'amd64' ]; then
  14. sed -i "/__CROSS__/d" Dockerfile.${docker_arch}
  15. cp Dockerfile.${docker_arch} Dockerfile
  16. else
  17. sed -i "s/__CROSS__//g" Dockerfile.${docker_arch}
  18. fi
  19. # Check for qemu static bins
  20. if [[ ! -f qemu-${qemu_arch}-static ]]; then
  21. echo "Downloading the qemu static binaries for ${docker_arch}"
  22. wget -q -N https://github.com/multiarch/qemu-user-static/releases/download/v4.0.0-4/x86_64_qemu-${qemu_arch}-static.tar.gz
  23. tar -xvf x86_64_qemu-${qemu_arch}-static.tar.gz
  24. rm x86_64_qemu-${qemu_arch}-static.tar.gz
  25. fi
  26. # Build image
  27. docker build -f Dockerfile.${docker_arch} -t ${repo}:${docker_arch}-${ver} .
  28. # Check if build should be deployed
  29. if [ "$TRAVIS_BRANCH" = "master" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then
  30. # Push image
  31. docker push ${repo}:${docker_arch}-${ver}
  32. # Create arch/ver docker manifest
  33. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create ${repo}:${docker_arch}-${ver} ${repo}:${docker_arch}-${ver}
  34. # Annotate arch/ver docker manifest
  35. if [ ! -z "${variant}" ]; then
  36. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${docker_arch}-${ver} ${repo}:${docker_arch}-${ver} --os linux --arch ${image_arch} --variant ${variant}
  37. else
  38. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${docker_arch}-${ver} ${repo}:${docker_arch}-${ver} --os linux --arch ${image_arch}
  39. fi
  40. # Generate Dynamic Manifest Image List
  41. if [ -z "${manifest_images}" ]; then
  42. manifest_images="${repo}:${docker_arch}-${ver}"
  43. else
  44. manifest_images="${manifest_images} ${repo}:${docker_arch}-${ver}"
  45. fi
  46. elif [ "$DEPLOY" = true ]; then
  47. # Tag image with travis branch
  48. docker tag ${repo}:${docker_arch}-${ver} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH}
  49. # Push tagged image
  50. docker push ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH}
  51. # Create tagged arch/ver docker manifest
  52. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH}
  53. # Annotate tagged arch/ver docker manifest
  54. if [ ! -z "${variant}" ]; then
  55. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} --os linux --arch ${image_arch} --variant ${variant}
  56. else
  57. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} --os linux --arch ${image_arch}
  58. fi
  59. # Generate Dynamic Manifest Image List
  60. if [ -z "${manifest_images}" ]; then
  61. manifest_images="${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH}"
  62. else
  63. manifest_images="${manifest_images} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH}"
  64. fi
  65. else
  66. echo "Skipping image deployment... Not configured to deploy images/manifests to DockerHub"
  67. fi
  68. done
  69. # Check if build should be deployed
  70. if [ "$TRAVIS_BRANCH" = "master" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then
  71. # Create version specific docker manifest
  72. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create ${repo}:${ver} ${manifest_images}
  73. # Create latest version docker manifest
  74. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create ${repo}:latest ${manifest_images}
  75. elif [ "$DEPLOY" = true ]; then
  76. # Create version specific docker manifest
  77. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create ${repo}:${ver}-${TRAVIS_BRANCH} ${manifest_images}
  78. # Create latest version docker manifest
  79. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create ${repo}:latest-${TRAVIS_BRANCH} ${manifest_images}
  80. else
  81. echo "Skipping version specific and latest tag docker manifest creation... Not configured to deploy images/manifests to DockerHub"
  82. fi
  83. for docker_arch in ${docker_archs}; do
  84. case ${docker_arch} in
  85. ppc64le ) qemu_arch="ppc64le" image_arch="ppc64le" variant="" ;;
  86. s390x ) qemu_arch="s390x" image_arch="s390x" variant="" ;;
  87. esac
  88. # Check if build should be deployed
  89. if [ "$TRAVIS_BRANCH" = "master" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then
  90. # Annotate arch/ver docker manifest
  91. if [ ! -z "${variant}" ]; then
  92. # Annotate version specific docker manifest
  93. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${ver} ${repo}:${docker_arch}-${ver} --os linux --arch ${image_arch} --variant ${variant}
  94. # Annotate latest docker manifest
  95. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:latest ${repo}:${docker_arch}-${ver} --os linux --arch ${image_arch} --variant ${variant}
  96. else
  97. # Annotate version specific docker manifest
  98. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${ver} ${repo}:${docker_arch}-${ver} --os linux --arch ${image_arch}
  99. # Annotate latest docker manifest
  100. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:latest ${repo}:${docker_arch}-${ver} --os linux --arch ${image_arch}
  101. fi
  102. # Push arch/ver docker manifest
  103. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push ${repo}:${docker_arch}-${ver}
  104. elif [ "$DEPLOY" = true ]; then
  105. # Annotate arch/ver docker manifest
  106. if [ ! -z "${variant}" ]; then
  107. # Annotate version specific docker manifest
  108. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${ver}-${TRAVIS_BRANCH} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} --os linux --arch ${image_arch} --variant ${variant}
  109. # Annotate latest docker manifest
  110. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:latest-${TRAVIS_BRANCH} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} --os linux --arch ${image_arch} --variant ${variant}
  111. else
  112. # Annotate version specific docker manifest
  113. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:${ver}-${TRAVIS_BRANCH} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} --os linux --arch ${image_arch}
  114. # Annotate latest docker manifest
  115. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate ${repo}:latest-${TRAVIS_BRANCH} ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH} --os linux --arch ${image_arch}
  116. fi
  117. # Push tagged arch/ver docker manifest
  118. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push ${repo}:${docker_arch}-${ver}-${TRAVIS_BRANCH}
  119. else
  120. echo "Skipping version specific and latest tag docker manifest annotation... Not configured to deploy images/manifests to DockerHub"
  121. fi
  122. done
  123. # Check if build should be deployed
  124. if [ "$TRAVIS_BRANCH" = "master" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then
  125. # Push version specific docker manifest
  126. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push ${repo}:${ver}
  127. # Push latest docker manifest
  128. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push ${repo}:latest
  129. elif [ "$DEPLOY" = true ]; then
  130. # Push version specific docker manifest
  131. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push ${repo}:${ver}-${TRAVIS_BRANCH}
  132. # Push latest docker manifest
  133. DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push ${repo}:latest-${TRAVIS_BRANCH}
  134. else
  135. echo "Skipping version specific and latest tag docker manifest push... Not configured to deploy images/manifests to DockerHub"
  136. fi

Docker Manifest

The docker manifest command by itself performs no action, in theory it’s null. In order to operate on a manifest or manifest list, one of the subcommands must be used.

A single manifest is information about an image, such as layers, size, and digest. The docker manifest command also gives users additional information such as the OS and arch an image was built for:

Manifest

When I ran docker manifest inspect, usage manuals can be key — but in this lengthy “how-to”, there’s no need for them.

Docker manifest inspect (verbose)

When setting docker manifest inspect with a verbose flag, it’s going to be showing you a bit more information. So I’ve displayed this in the following screenshot with the manifests highlighted.

Verbose

We are pulling from lucashalbert/curl for multiarch goodness.

Alternatively you can grep the manifests. As you can see though, they are in the logs, with a passing build. Just to see how other images work, we will show you the ppc64le/node image manifest:

ppc64lenode

We are pulling from ppc64le/node in DockerHub.

In both cases, you get reliable results, pulling from various images you can expect the same thing.

Bash script using more of the manifest ecosystem

The below commands will come in more handy when you’re writing a bash script. The following is a bash script I edited, so you can make sense of the ecosystem of other docker manifest commands:

  1. #!/bin/bash
  2. set -o errexit
  3. main() {
  4. # arg 1 holds switch string
  5. # arg 2 holds node version
  6. # arg 3 holds tag suffix
  7. case $1 in
  8. "prepare")
  9. docker_prepare
  10. ;;
  11. "build")
  12. docker_build
  13. ;;
  14. "test")
  15. docker_test
  16. ;;
  17. "tag")
  18. docker_tag
  19. ;;
  20. "push")
  21. docker_push
  22. ;;
  23. "manifest-list-version")
  24. docker_manifest_list_version "$2" "$3"
  25. ;;
  26. "manifest-list-test-beta-latest")
  27. docker_manifest_list_test_beta_latest "$2" "$3"
  28. ;;
  29. *)
  30. echo "none of above!"
  31. ;;
  32. esac
  33. }
  34. function docker_prepare() {
  35. # Prepare the machine before any code installation scripts
  36. setup_dependencies
  37. # Update docker configuration to enable docker manifest command
  38. update_docker_configuration
  39. # Prepare qemu to build images other then x86_64 on travis
  40. prepare_qemu
  41. }
  42. function docker_build() {
  43. # Build Docker image
  44. echo "DOCKER BUILD: Build Docker image."
  45. echo "DOCKER BUILD: arch - ${ARCH}."
  46. echo "DOCKER BUILD: build version -> ${BUILD_VERSION}."
  47. echo "DOCKER BUILD: webtrees version -> ${WT_VERSION}."
  48. echo "DOCKER BUILD: qemu arch - ${QEMU_ARCH}."
  49. echo "DOCKER BUILD: docker file - ${DOCKER_FILE}."
  50. docker build --no-cache \
  51. --build-arg ARCH=${ARCH} \
  52. --build-arg BUILD_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ") \
  53. --build-arg BUILD_VERSION=${BUILD_VERSION} \
  54. --build-arg BUILD_REF=${TRAVIS_COMMIT} \
  55. --build-arg WT_VERSION=${WT_VERSION} \
  56. --build-arg QEMU_ARCH=${QEMU_ARCH} \
  57. --file ./${DOCKER_FILE} \
  58. --tag ${TARGET}:build .
  59. }
  60. function docker_test() {
  61. echo "DOCKER TEST: Test Docker image."
  62. echo "DOCKER TEST: testing image -> ${TARGET}:build"
  63. docker run -d --rm --name=testing ${TARGET}:build
  64. if [ $? -ne 0 ]; then
  65. echo "DOCKER TEST: FAILED - Docker container testing failed to start."
  66. exit 1
  67. else
  68. echo "DOCKER TEST: PASSED - Docker container testing succeeded to start."
  69. fi
  70. }
  71. function docker_tag() {
  72. echo "DOCKER TAG: Tag Docker image."
  73. echo "DOCKER TAG: tagging image - ${TARGET}:${BUILD_VERSION}-${ARCH}"
  74. docker tag ${TARGET}:build ${TARGET}:${BUILD_VERSION}-${ARCH}
  75. }
  76. function docker_push() {
  77. echo "DOCKER PUSH: Push Docker image."
  78. echo "DOCKER TAG: pushing image - ${TARGET}:${BUILD_VERSION}-${ARCH}"
  79. docker push ${TARGET}:${BUILD_VERSION}-${ARCH}
  80. }
  81. function docker_manifest_list_version() {
  82. echo "DOCKER MANIFEST: Create and Push docker manifest list - ${TARGET}:${BUILD_VERSION}."
  83. docker manifest create ${TARGET}:${BUILD_VERSION} \
  84. ${TARGET}:${BUILD_VERSION}-amd64 \
  85. ${TARGET}:${BUILD_VERSION}-arm32v7 \
  86. ${TARGET}:${BUILD_VERSION}-arm64v8 \
  87. ${TARGET}:${BUILD_VERSION}-ppc64le \
  88. ${TARGET}:${BUILD_VERSION}-s390x
  89. docker manifest annotate ${TARGET}:${BUILD_VERSION} ${TARGET}:${BUILD_VERSION}-arm32v7 --os=linux --arch=arm --variant=v7
  90. docker manifest annotate ${TARGET}:${BUILD_VERSION} ${TARGET}:${BUILD_VERSION}-arm64v8 --os=linux --arch=arm64 --variant=v8
  91. docker manifest annotate ${TARGET}:${BUILD_VERSION} ${TARGET}:${BUILD_VERSION}-ppc64le --os=linux --arch=ppc64le
  92. docker manifest annotate ${TARGET}:${BUILD_VERSION} ${TARGET}:${BUILD_VERSION}-s390x --os=linux --arch=s390x
  93. docker manifest push ${TARGET}:${BUILD_VERSION}
  94. docker run --rm mplatform/mquery ${TARGET}:${BUILD_VERSION}
  95. }
  96. function docker_manifest_list_test_beta_latest() {
  97. if [[ ${BUILD_VERSION} == *"test"* ]]; then
  98. export TAG_PREFIX="test";
  99. elif [[ ${BUILD_VERSION} == *"beta"* ]]; then
  100. export TAG_PREFIX="beta";
  101. else
  102. export TAG_PREFIX="latest";
  103. fi
  104. echo "DOCKER MANIFEST: Create and Push docker manifest list - ${TARGET}:${TAG_PREFIX}."
  105. docker manifest create ${TARGET}:${TAG_PREFIX} \
  106. ${TARGET}:${BUILD_VERSION}-amd64 \
  107. ${TARGET}:${BUILD_VERSION}-arm32v7 \
  108. ${TARGET}:${BUILD_VERSION}-arm64v8 \
  109. ${TARGET}:${BUILD_VERSION}-ppc64le \
  110. ${TARGET}:${BUILD_VERSION}-s390x
  111. docker manifest annotate ${TARGET}:${TAG_PREFIX} ${TARGET}:${BUILD_VERSION}-arm32v7 --os=linux --arch=arm --variant=v7
  112. docker manifest annotate ${TARGET}:${TAG_PREFIX} ${TARGET}:${BUILD_VERSION}-arm64v8 --os=linux --arch=arm64 --variant=v8
  113. docker manifest annotate ${TARGET}:${TAG_PREFIX} ${TARGET}:${BUILD_VERSION}-ppc64le --os=linux --arch=ppc64le
  114. docker manifest annotate ${TARGET}:${TAG_PREFIX} ${TARGET}:${BUILD_VERSION}-s390x --os=linux --arch=s390x
  115. docker manifest push ${TARGET}:${TAG_PREFIX}
  116. docker run --rm mplatform/mquery ${TARGET}:${TAG_PREFIX}
  117. }
  118. function setup_dependencies() {
  119. echo "PREPARE: Setting up dependencies."
  120. sudo apt update -y
  121. sudo apt install --only-upgrade docker-ce -y
  122. }
  123. function update_docker_configuration() {
  124. echo "PREPARE: Updating docker configuration"
  125. mkdir $HOME/.docker
  126. # enable experimental to use docker manifest command
  127. echo '{
  128. "experimental": "enabled"
  129. }' | tee $HOME/.docker/config.json
  130. # enable experimental
  131. echo '{
  132. "experimental": true,
  133. "storage-driver": "overlay2",
  134. "max-concurrent-downloads": 50,
  135. "max-concurrent-uploads": 50
  136. }' | sudo tee /etc/docker/daemon.json
  137. sudo service docker restart
  138. }
  139. function prepare_qemu() {
  140. echo "PREPARE: Qemu"
  141. # Prepare qemu to build non amd64 / x86_64 images
  142. docker run --rm --privileged multiarch/qemu-user-static:register --reset
  143. mkdir tmp
  144. pushd tmp &&
  145. curl -L -o qemu-x86_64-static.tar.gz https://github.com/multiarch/qemu-user-static/releases/download/$QEMU_VERSION/qemu-x86_64-static.tar.gz && tar xzf qemu-x86_64-static.tar.gz &&
  146. curl -L -o qemu-arm-static.tar.gz https://github.com/multiarch/qemu-user-static/releases/download/$QEMU_VERSION/qemu-arm-static.tar.gz && tar xzf qemu-arm-static.tar.gz &&
  147. curl -L -o qemu-ppc64le-static.tar.gz https://github.com/multiarch/qemu-user-static/releases/download/$QEMU_VERSION/qemu-ppc64le-static.tar.gz && tar xzf qemu-ppc64le-static.tar.gz &&
  148. curl -L -o qemu-s390x-static.tar.gz https://github.com/multiarch/qemu-user-static/releases/download/$QEMU_VERSION/qemu-s390x-static.tar.gz && tar xzf qemu-s390x-static.tar.gz &&
  149. curl -L -o qemu-aarch64-static.tar.gz https://github.com/multiarch/qemu-user-static/releases/download/$QEMU_VERSION/qemu-aarch64-static.tar.gz && tar xzf qemu-aarch64-static.tar.gz &&
  150. popd
  151. }
  152. main "$1" "$2" "$3"

Bash script I edited heavily that shows off qemu, more docker manifest options and along with multiarch builds.

Other commands

Command Description
docker manifest annotate Add additional information to a local image manifest
docker manifest create Create a local manifest list for annotating and pushing to a registry
docker manifest inspect Display an image manifest, or manifest list
docker manifest push Push a manifest list to a repository

Reminder the commands on the left are all parent commands and have flags that can be attached to them for different behaviors/functions.

Annotations

Annotations are allowed in docker for the reason of defining architecture and operating system (overriding the image’s current values), os features, and an architecture variant, this takes precedent over env vars in the docker protocol heirarchy. An example of using annotation would look something like:

  1. docker manifest annotate 00.00.00.000:5000/cool-ibm-test:v1 00.00.00.00:5000/cool-ibm-test --arch ppc64le, s390x

In this example, the only archs that are going to be building is:

  1. ppc64le
  2. s390x

So for example, you’d run:

  1. docker manifest inspect cool-ibm-test:v1

Then this is what the output you’d expect to see, s390x along with ppc64le for architectures. You can also see os, digest and of course the sha256 hash.

Inspect a manifest list (cool-ibm-test)

  1. {
  2. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  3. "size": 425,
  4. "digest": "sha256:df436846483aff62bad830b730a0d3b77731bcf98ba5e470a8bbb8e9e346e4e8",
  5. "platform": {
  6. "architecture": "ppc64le",
  7. "os": "linux"
  8. }
  9. },
  10. {
  11. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  12. "size": 425,
  13. "digest": "sha256:5bb8e50aa2edd408bdf3ddf61efb7338ff34a07b762992c9432f1c02fc0e5e62",
  14. "platform": {
  15. "architecture": "s390x",
  16. "os": "linux"
  17. }
  18. }
  19. ]
  20. }

Phew, that was a lot! This should now give you the opportunity to mix and match docker manifest with Travis. I haven’t seen much on this on GitHub or really anywhere. So it’s really my pleasure to share my custom .travis.yml file, and show you what works, and I left all my history open — so you can see where things didn’t go so smoothly! I hope you enjoyed the read.

The Docker container manifest is a file that contains data about a container image. Specifically digest, sha256, and of course arch. We can create a manifest which points to images for different architectures so that when using the image on a particular architecture Docker automatically pulls the desired image. The reason for manifest is to mainly cross-build docker images.

```export DOCKER_CLI_EXPERIMENTAL=enabled # crucial for manifest to work

docker manifest create IBM/ibm-image:latest \
IBM/ibm-image:latest-${PLATFORM_1} \ # arch s390x (one can assume)
IBM/ibm-image:latest-${PLATFORM_2} \ # arch ppc64le (one can assume)

docker manifest annotate IBM/ibm-image:latest IBM/ibm-image:latest-${PLATFORM_1} —arch ${PLATFORM_1} # arch s390x (one can assume)
docker manifest annotate IBM/ibm-image:latest IBM/ibm-image:latest-${PLATFORM_2} —arch ${PLATFORM_2} # arch ppc64le (one can assume)

docker manifest push IBM/ibm-image:latest
```

How manifest is used. You first enable experimental CLI functions, create your image, push, then as you can see two archs - s390x, and ppc64le.

History

Author(s)

Montana Mendy - Montana (Rails Engineer/DevRel @ Travis CI)