Docker is an open source ecosystem (technology and range of associated services) that allows you to package applications into containers that are simple, lightweight, and portable; they will run in the same way regardless of which environment they run on. This is useful when you consider that our development environment (perhaps a Mac) is different from a production environment (such as a Linux server or even a cloud service) and that there is a large number of different places that we might want to deploy the same application.
Most cloud platforms already support Docker, which makes it a great option to deploy our apps into the wild.
In Chapter 9, Building a Q&A Application for Google App Engine, we built an application for Google App Engine. We would need to make significant changes to our code if we decided that we wanted to run our application on a different platform even if we forget about our use of Google Cloud Datastore. Building applications with a mind to deploying them within Docker containers gives us an additional level of flexibility.
Did you know that Docker itself was written in Go? See for yourself by browsing the source code at https://github.com/docker/docker.
In this chapter, you will learn:
docker
command to build the containerWe are going to put the Vault service we created in Chapter 10, Micro-services in Go with the Go kit Framework, into a Docker image and deploy it to the cloud.
Before we can deploy our code to the cloud, we must use the Docker tools on our development machine to build and push the image to Docker Hub.
In order to build and run containers, you need to install Docker on your development machine. Head over to https://www.docker.com/products/docker and download the appropriate installer for your computer.
Docker and its ecosystem are evolving rapidly, so it is a good idea to make sure you're up to date with the latest release. Similarly, it is possible that some details will change in this chapter; if you get stuck, visit the project home page at https://github.com/matryer/goblueprints for some helpful tips.
A Docker image is like a mini virtual machine. It contains everything that's needed to run an application: the operating system the code will run on, any dependencies that our code might have (such as Go kit in the case of our Vault service), and the binaries of our application itself.
An image is described with Dockerfile
; a text file containing a list of special commands that instruct Docker how to build the image. They are usually based on another container, which saves you from building up everything that might be needed in order to build and run Go applications.
Inside the vault
folder from the code we wrote in Chapter 10, Micro-services in Go with the Go kit Framework, add a file called Dockerfile
(note that this filename has no extension), containing the following code:
FROM scratch MAINTAINER Your Name <[email protected]> ADD vaultd vaultd EXPOSE 8080 8081 ENTRYPOINT ["/vaultd"]
Each line in a Dockerfile
file represents a different command that is run while the image is being built. The following table describes each of the commands we have used:
Command |
Description |
|
The name of the image that this image will be based on. Single words, such as scratch, represent official Docker images hosted on Docker Hub. For more information on the scratch image, refer to |
|
Copies files into the container. We are copying our |
|
Exposes the list of ports; in our case, the Vault service binds to |
|
The binary to run when the container is executed in our case, the |
|
Name and email of the person responsible for maintaining the Docker image. |
For a complete list of the supported commands, consult the online Docker documentation at https://docs.docker.com/engine/reference/builder/#dockerfile-reference.
Go supports cross-complication, a mechanism by which we can build a binary on one machine (say, our Mac) targeted for a different operating system (such as Linux or Windows) and architecture. Docker containers are Linux-based; so, in order to deliver a binary that can run in that environment, we must first build one.
In a terminal, navigate to the vault folder and run the following command:
CGO_ENABLED=0 GOOS=linux go build -a ./cmd/vaultd/
We are essentially calling go build here but with a few extra bits and pieces to control the build process. CGO_ENABLED
and GOOS
are environment variables that go build will pay attention to, -a
is a flag, and ./cmd/vaultd/
is the location of the command we want to build (in our case, the vaultd
command we built in the previous chapter).
CGO_ENABLED=0
indicates that we do not want cgo to be enabled. Since we are not binding to any C dependencies, we can reduce the size of our build by disabling this.GOOS
is short for Go Operating System and lets us specify which OS we are targeting, in our case, Linux. For a complete list of the available options, you can look directly in the Go source code by visiting https://github.com/golang/go/blob/master/src/go/build/syslist.go.After a short while, you'll notice that a new binary has appeared, called vaultd
. If you're on a non-Linux machine, you won't be able to directly execute this but don't worry; it'll run inside our Docker container just fine.
To build the image, in a terminal, navigate to Dockerfile
and run the following command:
docker build -t vaultd
We are using the docker
command to build the image. The final dot indicates that we want to build Dockerfile from the current directory. The -t
flag specifies that we want to give our image the name of vaultd
. This will allow us to refer to it by name rather than a hash that Docker will assign to it.
If this is the first time you've used Docker, and in particular the scratch
base image, then it will take some time to download the required dependencies from Docker Hub depending on your Internet connection. Once that's finished, you will see output similar to the following:
Step 1 : FROM scratch ---> Step 2 : MAINTAINER Your Name <[email protected]> ---> Using cache ---> a8667f8f0881 Step 3 : ADD vaultd vaultd ---> 0561c999c1e3 Removing intermediate container 4b75fde507df Step 4 : EXPOSE 8080 8081 ---> Running in 8f169f5b3b44 ---> 1d7758c20b3a Removing intermediate container 8f169f5b3b44 Step 5 : ENTRYPOINT /vaultd ---> Running in b5d55d6429be ---> b7178985dddf Removing intermediate container b5d55d6429be Successfully built b7178985dddf
For each command, a new image is created (you can see the intermediate containers being disposed of along the way) until we end up with the final image.
Since we are building our binary on our local machine and copying it into the container (with the ADD
command), our Docker image ends up being only about 7 MB: pretty small when you consider that it contains everything it needs to run our services.
Now that our image is built, we can test it by running it with the following command:
docker run -p 6060:8080 -p 6061:8081 --name localtest --rm vaultd
The docker run
command will spin up an instance of the vaultd
image.
The -p
flags specify a pair of ports to be exposed, the first value is the host port and the second value (following the colon) is the port within the image. In our case, we are saying that we want port 8080
to be exposed onto port 6060
and port 8081
exposed via port 6061
.
We are giving the running instance a name of localtest
with the --name
flag, which will help us to identify it when inspecting and stopping it. The --rm
flag indicates that we want the image to be removed once we have stopped it.
If this is successful, you will notice that the Vault service has indeed begun because it is telling us the ports to which it is bound:
2016/09/20 15:56:17 grpc: :8081 2016/09/20 15:56:17 http: :8080
These are the internal ports; remember that we have mapped these to different external ports instead. This seems confusing but ends up being very powerful, since the person responsible for spinning up the instances of the service gets to decide which ports are right for their environment, and the Vault service itself doesn't have to worry about it.
To see this running, open another terminal and use the curl
command to access the JSON endpoint of our password hashing service:
curl -XPOST -d '{"password":"monkey"}' localhost:6060/hash
You will see something that resembles the output from the running service:
{"hash":"$2a$0$wk4qc74ougOkbkt/TWuRQHSg03i1ataNupbDADBwpe"}
To see what Docker instances are running, we can use the docker ps
command. In the terminal, type the following:
docker ps
You'll get a text table outlining the following properties:
CONTAINER ID |
|
IMAGE |
|
COMMAND |
|
CREATED |
|
STATUS |
|
PORTS |
|
NAMES |
|
The details show you a high-level overview of the image we just started. Note that the PORTS sections shows you the mapping from external to internal.
We are used to hitting Ctrl + C in the window running our code to stop it, but since it's running inside a container, that won't work. Instead, we need to use the docker stop
command.
Since we gave our instance the name localtest
, we can use this to stop it by typing this in an available terminal window:
docker stop localtest
After a few moments, you'll notice that the terminal that was running the image has now returned to the prompt.
18.219.228.88