You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This repo utilizes aggressively a number of multi-stage Docker tricks that are worth explaining, because they can be used broadly
We are doing three things in the Dockerfile, denoted by their named stage:
Building small vpp and vpp-dbg images, including the ability to patch
generating the govpp 'binapi' for the build vpp image, using the standard 'go:generate' idiom
Extracting the VPP_VERSION being used so we can utilize it to tag published docker images.
Building small vpp images
Ultra small vpp images are built via three Docker stages:
vppbuild
vppinstall
vpp
The reason for these stages is to cauterize the bloat from each activity.
Building vpp means bloating an image
up with a bunch of build dependencies, build artifacts, etc. Building vpp installation on top of that would
lead to a multi-GB image, which is undesirable. So we isolate that work in the 'vppbuild' stage.
Installing vpp means copying the resulting *.deb packages from the 'vppbuild' stage and installing them. Unfortunately,
because you can't combine a COPY and RUN step, that results in image bloat from the *.deb files themselves, so we isolate
that in the 'vppinstall' image.
Finally, we utilize a trick to trim out the bloat when building the 'vpp' stage.
to copy in the indexes in /var/lib/apt/lists/ that result from having run apt-get update in the 'vppbuild' stage,
thus avoiding the cost of redownloading them.
Simply starts from 'ubuntu:${UBUNTU_VERSION}' (thus reusing the layers form that standard image) and then
copies the entire '/' directory in from 'vppinstall' (which has removed the apt-get indexes and *.deb files).
Because docker COPY is generally smart enough to only copy in the changed files... the layer resulting from
should only contain the deltas between the final result of 'vppinstall' and the starting point of 'ubuntu:${UBUNTU_VERSION}'
Resulting in something that looks like:
docker history ghcr.io/edwarnicke/govpp/vpp:v20.09 ──(Sun,Jan31)─┘
IMAGE CREATED CREATED BY SIZE COMMENT
8b4ea0febd25 42 minutes ago /bin/sh -c #(nop) COPY dir:b8f7abee062c48863… 96.2MB
<missing> 10 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 10 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 10 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
<missing> 10 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
<missing> 10 days ago /bin/sh -c #(nop) ADD file:2a90223d9f00d31e3… 72.9MB
The caveat is, there is a bug in docker that will, depending on the storage driver
you are using, copy over all the files, resulting in a much larger image. Fortunately, building in GitHub Actions does not
seem to hit this issue. Unfortunately, building in Docker for Mac does.
To fix the issue in Docker for Mac, follow the instructions for setting Docker Engine options
and set your storage driver to 'overlay'
{
"storage-driver": "overlay"
}
Building idiomatic go:generate for 'binapi'
In go, we generally generate code using a //go:generate directive.
In the case of govpp, you need to use the 'binapi-generator' run against the json api files installed by the vpp debs in /usr/share/vpp/api/.
We utilize Docker to make this easy by:
Building 'binapi-generator' as a Docker stage
Creating a 'gen' Docker stage that copies in /usr/share/vpp/api/ from our 'vpp' stage and run's binapi, outputting to /gen
Add a //go:generate line to gen.go to run the 'gen' stage to generate the code.
//go:generate bash -c "docker run -e PKGPREFIX=$(go list)/binapi -v $(go list -f '{{ .Dir }}'):/gen $(docker build . -q --build-arg GOVPP_VERSION=$(go list -m -f '{{ .Version }}' git.fd.io/govpp.git))"
uses 'docker run' to run the 'gen' stage and generate the code
bash bash -c "docker run ... " is used to give us a shell to work with (because we are doing a lot of magic here)
-e PKGPREFIX=$(go list)/binapi - sets the env variable PKGPREFIX in the docker container to the value of $(go list)/binapi. $(go list) is the value of the package in which the gen.go file resides.
-v $(go list -f '{{ .Dir }}'):/gen mounts $(go list -f '{{ .Dir }}') from the host into /gen/ in the container.
$(go list -f '{{ .Dir }}') outputs the directory the module containing gen.go is in.
-q - 'quiet' - outputs the id of the resulting image built
--build-arg GOVPP_VERSION=$(go list -m -f '{{ .Version }}' git.fd.io/govpp.git)) - sets the build-arg GOVPP_VERSION
$(go list -m -f '{{ .Version }}' git.fd.io/govpp.git) - outputs the version of git.fd.io/govpp.git in the go.mod file.
Extracting the VPP_VERSION being used in the Dockerfile
In .github/workflows/ci.yaml we want to be able to tag and push images based
on the VPP_VERSION. Because we only wish to specify the VPP_VERSION once in the Dockerfile, we have a job:
How the magic works
This repo utilizes aggressively a number of multi-stage Docker tricks that are worth explaining, because they can be used broadly
We are doing three things in the Dockerfile, denoted by their named stage:
Building small vpp images
Ultra small vpp images are built via three Docker stages:
The reason for these stages is to cauterize the bloat from each activity.
Building vpp means bloating an image
up with a bunch of build dependencies, build artifacts, etc. Building vpp installation on top of that would
lead to a multi-GB image, which is undesirable. So we isolate that work in the 'vppbuild' stage.
Installing vpp means copying the resulting *.deb packages from the 'vppbuild' stage and installing them. Unfortunately,
because you can't combine a COPY and RUN step, that results in image bloat from the *.deb files themselves, so we isolate
that in the 'vppinstall' image.
Finally, we utilize a trick to trim out the bloat when building the 'vpp' stage.
The 'vppbuild' stage
govpp/Dockerfile
Lines 10 to 20 in f9c1af6
is a fairly standard Ubuntu oriented build. It results in a bunch of *.deb files.
The 'vppinstall' stage
govpp/Dockerfile
Lines 22 to 30 in f9c1af6
uses
govpp/Dockerfile
Line 24 in f9c1af6
to copy in the indexes in
/var/lib/apt/lists/
that result from having runapt-get update
in the 'vppbuild' stage,thus avoiding the cost of redownloading them.
govpp/Dockerfile
Line 25 in f9c1af6
copies the *.deb files we wish to install from where they were built in 'vppbuild'
govpp/Dockerfile
Line 27 in f9c1af6
installs the *.deb files:
-f
causesapt-get
to install any missing dependencies.-y
causesapt-get
to run in an unattended mode where the answer to the questions arey
--no-install-recommends
causesapt-get
to only install required (rather than recommended) dependencies to keep the image size downgovpp/Dockerfile
Line 28 in f9c1af6
removes the apt indexes from
apt-get update
and
govpp/Dockerfile
Line 29 in f9c1af6
removes the *.deb files
There is one problem with this. Because the image still has the layers from
govpp/Dockerfile
Lines 24 to 25 in f9c1af6
the 'vppinstall' stage is still going to be bloated by that amount. We solve this in the 'vpp' stage
The 'vpp' stage
The 'vpp' stage is our final lean runnable. It uses a very simple but slick trick, which has a small caveat to it.
govpp/Dockerfile
Lines 31 to 32 in f9c1af6
Simply starts from 'ubuntu:${UBUNTU_VERSION}' (thus reusing the layers form that standard image) and then
copies the entire '/' directory in from 'vppinstall' (which has removed the apt-get indexes and *.deb files).
Because docker COPY is generally smart enough to only copy in the changed files... the layer resulting from
govpp/Dockerfile
Line 32 in f9c1af6
should only contain the deltas between the final result of 'vppinstall' and the starting point of 'ubuntu:${UBUNTU_VERSION}'
Resulting in something that looks like:
The caveat is, there is a bug in docker that will, depending on the storage driver
you are using, copy over all the files, resulting in a much larger image. Fortunately, building in GitHub Actions does not
seem to hit this issue. Unfortunately, building in Docker for Mac does.
To fix the issue in Docker for Mac, follow the instructions for setting Docker Engine options
and set your storage driver to 'overlay'
Building idiomatic go:generate for 'binapi'
In go, we generally generate code using a //go:generate directive.
In the case of govpp, you need to use the 'binapi-generator' run against the json api files installed by the vpp debs in
/usr/share/vpp/api/
.We utilize Docker to make this easy by:
/usr/share/vpp/api/
from our 'vpp' stage and run's binapi, outputting to /gen//go:generate
line to gen.go to run the 'gen' stage to generate the code.The 'bin-apigenerator' stage
govpp/Dockerfile
Lines 41 to 46 in f9c1af6
Is a pretty standard 'go get to build' stage for 'binapi-generator'
The 'gen' stage
govpp/Dockerfile
Lines 48 to 53 in f9c1af6
Actually performs the generation if 'docker run'
govpp/Dockerfile
Line 49 in f9c1af6
copies in the '/usr/share/vpp/api/*' json api files from the 'vpp' stage
govpp/Dockerfile
Line 50 in f9c1af6
copies in the 'binapi-generator' from the 'binapi-generator' stage
govpp/Dockerfile
Line 51 in f9c1af6
copies in the VPP_VERSION we had stashed in the 'vppbuild' stage.
govpp/Dockerfile
Lines 52 to 53 in f9c1af6
sets the Workdir to /gen and runs the binapi-generator.
VPP_VERSION=$(cat /VPP_VERSION)
- sets the VPP_VERSION env binapi-generator needs from the stashed VPP_VERSION value${PKGPREFIX+-import-prefix ${PKGPREFIX}}
- will output-import-prefix ${PKGPREFIX}
if the PKGPREFIX env variable is set, and nothing otherwise.The
//go:generate
directivegovpp/gen.go
Line 19 in f9c1af6
uses 'docker run' to run the 'gen' stage and generate the code
bash bash -c "docker run ... "
is used to give us a shell to work with (because we are doing a lot of magic here)-e PKGPREFIX=$(go list)/binapi
- sets the env variablePKGPREFIX
in the docker container to the value of$(go list)/binapi
.$(go list)
is the value of the package in which the gen.go file resides.-v $(go list -f '{{ .Dir }}'):/gen
mounts$(go list -f '{{ .Dir }}')
from the host into/gen/
in the container.$(go list -f '{{ .Dir }}')
outputs the directory the module containing gen.go is in.$(docker build . -q --build-arg GOVPP_VERSION=$(go list -m -f '{{ .Version }}' git.fd.io/govpp.git))
-q
- 'quiet' - outputs the id of the resulting image built--build-arg GOVPP_VERSION=$(go list -m -f '{{ .Version }}' git.fd.io/govpp.git))
- sets the build-arg GOVPP_VERSION$(go list -m -f '{{ .Version }}' git.fd.io/govpp.git)
- outputs the version of git.fd.io/govpp.git in the go.mod file.Extracting the VPP_VERSION being used in the Dockerfile
In .github/workflows/ci.yaml we want to be able to tag and push images based
on the VPP_VERSION. Because we only wish to specify the VPP_VERSION once in the Dockerfile, we have a job:
govpp/Dockerfile
Lines 5 to 8 in f9c1af6
that can be used to extract that version:
govpp/.github/workflows/ci.yaml
Line 92 in f9c1af6
The text was updated successfully, but these errors were encountered: