A clever combination of Docker and Golang
- 2020-05-17 07:16:58
- OfStack
A clever combination of Docker and Golang
Editor's note: this is a short list of tips and tricks for making Docker more useful when using the Go language. For example, how to compile Go code using different versions of the Go toolchain, and how to cross-compile to different platforms (and test the results!) Or how to make really small container images.
The following article assumes that you already have Docker installed. It doesn't have to be the latest version (this article won't use any fancy Docker features).
Go without go
. "Go can be used without installing go"
If you write Go code, or if you have a slight interest in Go, you're sure to have the Go compiler and Go toolchain installed, so you might want to know, "what's the point?" ; But in some cases, you don't want to install Go to compile Go.
If you encounter the above situation, find Docker to solve it!
Compile a program in a container
When you install the Go, you can perform go get - v github. com/user/repo to download, to create and install a library. (-v is just information display, if you like the tool chain to run fast and silently, you can remove it!)
You can also perform go get github. com/user/repo /... To download, create, and install everything in repo (including libraries and base 2 files).
We can do this in one container!
Try this:
docker run golang go get -v github.com/golang/example/hello/...
This will pull the golang image (unless you already have one, which will start immediately) and create a container based on it. In that container, go will download an example of "hello world," create it, and install it. But it will install it in this container... How do we run that program now?
Run the program in the container
One way is to submit the container we just created, that is, package it into a new image:
docker commit $(docker ps -lq) awesomeness
Note: docker ps, lq outputs ID (only ID!) of the last container executed. . If you are a unique user of the machine and you have not created another container since the last command, this container is an example of the "hello world" you just created.
Now you can use the image you just built to create the container to run the program:
docker run awesomeness hello
The output will be Hello, Go examples! .
sparkle
When building an image with docker commit, you can specify any Dockerfile command with the --change id. For example, you can use an CMD or ENTRYPOINT command so that docker run awesomeness automatically executes hello.
Run on a primary container
What if you don't want to create an extra image and just want to run this Go program?
Use:
docker run --rm golang sh -c \
"go get github.com/golang/example/hello/... && exec hello"
Wait, what are those fancy things?
Use a different version of Go
When using golang mirroring, Docker is extended to golang:latest, mapping (as you might guess) to the latest version available on Docker Hub.
If you want to use a specific version of Go, it's easy: label it with that version after the mirror name.
For example, if you want to use Go 1.5, modify the above example and replace golang with golang:1.5:
docker run --rm golang:1.5 sh -c \
"go get github.com/golang/example/hello/... && exec hello"
You can see all available versions (and variables) on Docker Hub's Golang mirror page.
Install on the system
Okay, what if you want to run a compiled program on your system instead of a container? We will copy the compiled binary file out of the container. Note that this only works if the container schema and the host schema match; In other words, if you run Docker on Linux. (I'm ruling out someone running the Windows container!)
The easiest way to get a binary file outside the container is to map the $GOPATH/bin directory to a local directory. In the golang container, $GOPATH is /go.
docker run -v /tmp/bin:/go/bin \
golang go get github.com/golang/example/hello/...
/tmp/bin/hello
If you are on Linux, you will see Hello, Go examples! The message. But if it is, for example, on Mac, you might see:
-bash:
/tmp/test/hello: cannot execute binary file
What can we do?
cross-compilation
Go 1.5 has excellent out-of-the-box cross-compilation capabilities, so if your container operating system and/or architecture don't match your system, it's not a problem at all!
To enable cross-compilation, you need to set GOOS and/or GOARCH.
For example, assume that on 64-bit Mac:
docker run -e GOOS=darwin -e GOARCH=amd64 -v /tmp/crosstest:/go/bin \
golang go get github.com/golang/example/hello/...
The cross-compiled output is not directly in $GOPATH/bin, but in $GOPATH/bin/$GOOS_$GOARCH. In other words, to run the program, execute /tmp/crosstest/darwin_amd64/hello.
Install directly to $PATH
If you are on Linux, you can even install it directly to the system bin directory:
docker run -v /usr/local/bin:/go/bin \
golang get github.com/golang/example/hello/...
However, on Mac, trying to use /usr as one volume will not mount Mac's filesystem to the container. The Moby VM (small Linux VM is hidden behind the Docker icon in the toolbar) /usr directory will be mounted. The Docker for Mac version can be customized to set the mount path.
But you can use /tmp or some other directory in your home directory and copy from here.
Creating a dependent image
The Go2 files we produce using this technique are statically linked. This means that all the code that needs to be run, including all the dependencies, is embedded. Dynamically linked programs, in contrast, do not contain some basic libraries (like "libc") and use system-wide replication, which is determined at run time.
This means you can drop the Go compiled program in the container, nothing more, and it will run.
Let's try!
scratch mirror
The Docker ecosystem has a special mirror: scratch. This is an empty mirror. It does not need to be created or downloaded because it is defined as empty.
Create a new empty directory for the new Go dependent image.
In this new directory, create the following Dockerfile:
FROM scratch
COPY ./hello /hello
ENTRYPOINT ["/hello"]
This means: starting with scratch (1 empty image), adding the hello file to the root directory of the image, * defining the hello program as the default program to run after starting the container.
The hello2 base file is then generated as follows:
docker run -v $(pwd):/go/bin --rm \
golang go get github.com/golang/example/hello/...
Note: there is no need to set GOOS and GOARCH, because you want a binary file running in the Docker container, not on the host. So you don't have to set these variables!
Then, create the image:
docker build -t hello .
Test it:
docker run hello
(" Hello, Go examples! ") )
Last but not least, check the size of the mirror:
docker images hello
If the 1 cut is done correctly, the mirror image is approximately 2M. Quite good!
Build things without pushing them to Github
Of course, if you had to push to GitHub, you'd waste a lot of time every time you compiled.
To work on a piece of code and create it in a container, mount a local directory to /go in the golang container. So $GOPATH is a persistent call: docker run-v $HOME/go:/go golang...
But you can also mount the local directory to a specific path to "reload" some packages (those edited locally). Here's a complete example:
# Adapt the two following environment variables if you are not running on a Mac
export GOOS=darwin GOARCH=amd64
mkdir go-and-docker-is-love
cd go-and-docker-is-love
git clone git://github.com/golang/example
cat example/hello/hello.go
sed -i .bak s/olleH/eyB/ example/hello/hello.go
docker run --rm \
-v $(pwd)/example:/go/src/github.com/golang/example \
-v $(pwd):/go/bin/${GOOS}_${GOARCH} \
-e GOOS -e GOARCH \
golang go get github.com/golang/example/hello/...
./hello
# Should display "Bye, Go examples!"
Network packets and CGo are special cases
Before entering the real Go code world, it must be admitted that there is a slight deviation on the base 2 file. If you are using CGo, or if you are using the net package, the Go linker will generate a dynamic library. In this case, the net package (which does have a lot of useful Go programs in it!) , the culprit is DNS parsing. Most systems have a fancy, modular name resolution system (like name service switching) that relies on plug-ins and, technically, dynamic libraries. By default, Go will try to use it; In this way, it will produce a dynamic library.
How do we solve this?
Reuse another version of libc
One solution is to use a base image that has the necessary libraries for the functionality of the program. Almost any "normal" Linux distribution based on GNU libc will do. So, for example, use FROM debian or FROM fedora instead of FROM scratch. Now the mirror image is going to be a little bit bigger than it was; But at the very least, that extra point will be Shared with the rest of the system.
Note: Alpine cannot be used in this case, because Alpine USES the musl library instead of GNU libc.
Use your own libc
Another solution is to surgically extract the required files and replace them with COPY. You end up with a smaller container. However, this extraction process is difficult and tedious, with too many deeper details to deal with.
If you want to see for yourself, take a look at the ldd and name service switch plug-in mentioned earlier.
Generate static base 2 files with netgo
We can also instruct Go not to use libc of the system, but to use local DNS parsing instead of netgo of Go.
To use it, simply add -tags netgo-installsuffix netgo in the go get option.
-tags netgo indicates that the tool chain USES netgo.
-installsuffix netgo ensures that the result library (any) is replaced by a different, non-default directory. This is avoided if you make multiple calls to go get (or go build)
Conflict between code creation and netgo. If it were created in a container, as we've seen so far, it wouldn't be necessary. There will never be any other Go code to compile in this container. But it's a good idea to get used to it, or at least know that the sign exists.
Special case of SSL certificate
One more thing you'll worry about is that your code must validate the SSL certificate; For example, connect the external API via HTTPS. In this case, you need to put the root certificates into the container as well, because Go does not bundle them into a binary file.
Install the SSL certificate
Again, there are many options available, but the easiest is to use a package that already exists in the release.
Alpine is a good choice because it is very small. Dockerfile below will give you a small base image, but bundled with an expired certificate:
FROM alpine:3.4
RUN apk add --no-cache ca-certificates apache2-utils
Check it out. The mirror image is only 6MB!
Note: -- the no-cache option tells apk (Alpine package manager) to get a list of available packages from Alpine's mirror publication, not on disk. You might see Dockerfiles doing something like this & & apt-get install ... & & rm - rf/var/cache/apt / *; This implements (that is, does not retain the package cache in the final image) something equivalent to a single 1 flag.
An added bonus: putting your application in an Alpine mirroring container gives you a bunch of useful tools. If you need to, you can now put shell into the container and do something with it while it's running.
packaging
We saw how Docker helped us compile Go code in a clean, self-contained environment; How to use different versions of the Go toolchain; And how to cross-compile between different operating systems and platforms.
We also saw how Go helped us create small, container-dependent images for Docker, and described some of the subtle connections between static libraries and network dependencies (no other meaning).
In addition to the fact that Go is really suitable for the Docker project, we would like to show you how Go and Docker learn from each other and work well together!
Thank you
This was first suggested on hacker day 2016, GopherCon.
I would like to thank all those who proofread the material and made Suggestions and comments to make it better, including but not limited to:
Aaron Lehmann
Stephen Day
AJ Bowen
All the mistakes and spelling mistakes are my own; All the good stuff is theirs!
Thank you for reading, I hope to help you, thank you for your support of this site!