The ingenious combination of Docker and Golang
"Editor's words" This is a simple repertoire of tips and tricks that show how to make Docker more useful when using the go language. For example, how to use different versions of the Go tool chain to compile go code, how to cross compile to a different platform (and test results!). Or how to make a really small container image.
The following article assumes that you have installed the Docker. It doesn't have to be the latest version (this article doesn't use Docker any fancy features).
No Go's Go
... It means: "Go" is not available
If you're writing the go code, or if you're a little bit interested in going, you're sure to have it installed, so you might want to know: "What's the point?" "But there are cases where you want to compile go without going to install go."
- There is still an old version of Go 1.2 on the machine (you can't or don't want to update), you have to use this code base, you need a high version of the tool chain.
- You want to use Go1.5 's cross compilation feature (for example, to make sure that you can create binary files for OS X from a Linux system).
- Want to have multiple versions of Go, but don't want to completely mess with the system.
- Want 100% to determine the project and all its dependencies, download, build and run on a pure system.
If you encounter the above situation, find Docker to solve!
Compile a program in a container
When you install go, you can perform the go get-v github.com/user/repo to download, create and install a library. (-v Just information display, if you like the tool chain to run quickly and silently, you can remove it!) )
You can also perform go get github.com/user/repo/... To download, create and install everything in that repo (including libraries and binaries).
We can do this in a container!
Try this:
Docker run Golang go get-v github.com/golang/example/hello/...
This will pull the Golang mirror (unless you already have it, it will start right away) and create a container based on it. In that container, go downloads a "Hello World" example, creates it, installs it. But it will install it into this container ... How do we run that program now?
Running a program in a container
One way to do this is to submit the container that we just created, that is, package it to a new mirror:
Docker commit $ (Docker PS-LQ) awesomeness
Note: Docker PS–LQ outputs the ID of the last executing container (only id! )。 If you are the only user of the machine and you do not create another container from the previous command, the container is an example of the "Hello World" you just created.
You can now run the program by creating a container from the mirror you just built:
Docker Run awesomeness Hello
The output will be hello, go examples!.
Sparkles
When you build a mirror with Docker commit, you can specify any dockerfile command with the--change identity. For example, you can use a cmd or entrypoint command so that the Docker run awesomeness automatically executes hello.
Run on a disposable container
If you don't want to create additional mirrors 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?
- --rm tells the Docker CLI to automatically initiate a docker RM command once the container exits. That way, there will be nothing left.
- Use the shell logical operator && Join the creation step (go get) and the Execute step (exec Hello). If you don't like shell,&& mean "with". It allows the first part of Go get ... and if (and only if!) That part runs successfully, and it will perform the second part (exec hello). If you want to know why: it's like a lazy and calculator that evaluates to the right only if the value on the left is true.
- Pass the command to Sh–c, because if it is simple to do Docker run Golang "Go get ... && hello", Docker will try to execute a program called "Go" and "space" etc. And that won't work. So we start a shell and let the shell execute the command sequence.
- Using exec hello instead of Hello: This will use the Hello program to replace the current process (the shell we just started). This ensures that Hello is PID 1 in the container. Instead of the shell is PID 1 and hello as a subprocess. This is useless for this tiny example, but when running more useful programs, it will allow them to receive the external signal correctly because the external signal is sent to the PID 1 in the container. You might think, what's the signal? A good example is Docker stop, which sends sigterm to the container's PID 1.
Use different versions of Go
When using Golang mirroring, Docker expands to golang:latest, mapping (as you guessed) to the latest available version on the Docker hub.
If you want to use a specific go version, it's easy to specify it as a label with that version behind the mirror name.
For example, to use Go 1.5, modify the example above to 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 the Golang Mirror page of the Docker hub.
Installing on the system
Well, what if you want to run a compiled program on your system instead of a container? We will copy this compiled binary file out of the container. Note that it works only when the container schema matches the host schema, in other words, if you run Docker on Linux. (I'm probably excluding people who are running Windows containers!) )
The easiest way to get a binary file out of a container is to map the $gopath/bin directory to a local directory, in the Golang container, where the $GOPATH is/go. So we can do the following:
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! messages. But if it is, for example, on a Mac, you might see:
-bash:
/tmp/test/hello:cannot Execute binary file
And what can we do?
Cross-compilation
Go 1.5 has excellent out-of-the-box and cross compilation capabilities, so if your container OS and/or architecture doesn't match your system, it's not a problem!
To turn on cross compilation, you need to set up GOOS and/or Goarch.
For example, suppose on a 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 output of the cross compilation is not directly in the $gopath/bin, but in the $gopath/bin/$GOOS _$goarch. In other words, to run the program, you have to perform/tmp/crosstest/darwin_amd64/hello.
Install directly to $path
If you are on Linux, you can even install directly to the System Bin directory:
Docker run-v/usr/local/bin:/go/bin \
Golang Get github.com/golang/example/hello/...
However, on the Mac, trying to use/usr as a volume will not be able to mount the Mac's file system to the container. Mounts the/usr directory of the Moby VM (small Linux VM hidden behind the toolbar Docker icon). At present, the Docker for Mac version can be customized to set Mount path.
But you can use/TMP or any other directory in your home directory and copy from here.
Creating a dependent mirror
The go binaries we produce with this technique are statically linked. This means that all the code that needs to be run, including all 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 discard the go compile program in the container, nothing else, and it will run.
Let's try it!
Scratch Mirroring
The Docker ecosystem has a special mirror image: Scratch. This is an empty mirror. It does not need to be created or downloaded because the definition is empty.
Create a new empty directory for the new go-dependent mirror.
In this new directory, create the following dockerfile:
From scratch
COPY./hello/hello
entrypoint ["/hello"]
This means: Starting with scratch (an empty mirror), adding the hello file to the mirrored root directory, * Define the program that the Hello program runs by default when the container is started.
Then, produce the Hello binary file as follows:
Docker run-v $ (PWD):/go/bin--rm \
Golang Go get github.com/golang/example/hello/...
Note: You do not need to set GOOS and Goarch, just because you want a binary file that runs in the Docker container, not on the host. So don't set these variables!
Then, create the Mirror:
Docker build-t Hello.
Test it:
Docker Run Hello
("Hello, go examples!" will be displayed.) )
Last but not important, check the size of the mirror:
Docker Images Hello
If everything is done correctly, this mirror is about 2M. Pretty good!
Building things without pushing them to GitHub
Of course, if you have to push to GitHub, every compilation will waste a lot of time.
When you want to work on a code snippet and create it in a container, you can mount a local directory to/go in the Golang container. So $gopath is a persistent call: Docker run-v $HOME/go:/go Golang ....
However, you can also mount a local directory on a specific path to "overload" some packages (those that are edited locally). This is a complete example:
# ADAPT The two following environment variables if you are not running on a Mac
export Goos=darwin goarch=amd64
MK Dir 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!"
Special cases for network packs and CGO
Before entering the real go code world, it must be admitted that there is a slight deviation in the binary file. If you are using CGO, or if you are using net packages, the GO linker will generate a dynamic library. In this case, the net package (which does have many useful go programs!) ), the culprit is DNS resolution. Most systems have a fancy, modular name resolution system (like name service switching) that relies on plug-ins, technically, dynamic libraries. By default, go tries to use it, so it generates a dynamic library.
How do we solve this?
Reusing another version of libc
One workaround is to use an underlying mirror with libraries that are necessary for the program's functionality. Almost any "regular" Linux distributions based on the GNU libc are available. So, for example, use from Debian or from Fedora instead of from scratch. The resulting mirror will now be larger than the original, but at least the big one will be shared with other mirrors in the system.
Note: Alpine cannot be used in this case because Alpine is using the MUSL library instead of the GNU libc.
Use your own libc.
Another solution is to extract the required files as an operation, replacing them with copy. The result container will be small. However, this extraction process is difficult and cumbersome, and there are too many deeper details to deal with.
If you want to see for yourself, look at the LDD and name service switching Plug-ins mentioned earlier.
To generate a static binary file with Netgo
We can also instruct go without the libc of the system and use local DNS resolution to replace go NETGO.
To use it, simply add-tags netgo-installsuffix Netgo to the Go get option.
-tags Netgo indicates that the tool chain uses NETGO.
-installsuffix Netgo Ensure that the result library (any) is replaced by a different, Non-default directory. If you do multiple go-get (or go-build) calls, this will avoid
A conflict between code creation and use without NETGO. It is completely unnecessary to create in a container, as we have now mentioned. Because there is never another go code in this container to compile. But it's a good idea to get used to it, or at least know that the logo exists.
Special case of SSL certificate
One more thing, you'll worry, your code must validate the SSL certificate; For example, join the external API via HTTPS. In this case, you need to put the root certificate into the container because go does not bundle them into binary files.
Installing an SSL Certificate
Again, there are a lot of options available, but the simplest is to use a package that already exists in the release.
Alpine is a good choice, because it's very small. The following dockerfile will give you a small base image, but bundled up with an expired certificate:
From alpine:3.4
RUN apk add--no-cache ca-certificates apache2-utils
Let's see, the results mirror only 6mb!
Note: The--no-cache option tells APK (Alpine Package Manager) to obtain a list of available packages from a mirrored publication on Alpine, which does not exist on the disk. You may see dockerfiles do such a thing apt-get update && apt-get install ... && rm-rf/var/cache/apt/* This implements (that is, the package cache is not preserved in the final mirror) and is something equivalent to a single flag.
An added bonus: putting your application into a Alpine mirror-based container allows you to get a bunch of useful tools. If necessary, now you can put the shell into the container and do something when it is running.
Packaged
We see how Docker can help us compile the go code in a clean and independent environment, how to use different versions of the Go tool chain, and how to cross-compile between different operating systems and platforms.
We also see how go helps us create small, container-dependent mirrors for Docker, and describes some of the subtleties associated with static libraries and network dependencies (no other meaning).
In addition to the fact that go is really fit for the Docker project, what we want to show you is how go and Docker learn from each other and work well together!
Thanks
This was originally presented on the Gophercon hacker day in 2016.
I would like to thank all the proofing materials, suggestions and advice to make it better for people, including but not limited to:
Aaron Lehmann
Stephen Day
AJ Bowen
All the mistakes and spelling mistakes are my own, all the good things are theirs!
Thank you for reading, I hope to help you, thank you for your support for this site!