Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fill the docker section #42

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions 2-building-artifacts/1-go-build.md
Expand Up @@ -182,5 +182,4 @@ under `$GOPATH/src/github.com/campoy/go-tooling-workshop/2-building-artifacts/ex
You're now an expert on ... basic compilation of Go programs. But you're already able
to cross-compile Go programs to other platforms!

Next, let's talk about how to better understand the behavior of running programs in
the [dynamic analysis section](../3-dynamic-analysis/README.md).
Next, let's see how to build docker images in [the next section](2-docker.md).
79 changes: 78 additions & 1 deletion 2-building-artifacts/2-docker.md
@@ -1,6 +1,83 @@
# Running Go programs in Docker containers

This should be done!
Before looking at building Docker images, let's use it to compile go code

## Compile using golang image
Using golang image to build binaries can be useful for :
- Continuous integration
- Cross building binary
- Local development

### Continuous integration
When using a CI server like Jenkins, it can become cumbersome to have to install all the go versions required by your different projects. And even more if you use your CI server to build projects in other languages, each with its own version (java8, java7, node, ruby, python, ...). For this use case, it can be easier to build the projects in docker containers. An example script could be :
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please break this into lines for easier review

```
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'''bash
$ docker ...

docker run --rm -v $PWD:/go/src/github.com/repo/project -w /go/src/github.com/repo/project golang:1.8.1 go build -o dist/binary
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the -o dist/binary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because copy/pasting from another script 😇 I'll remove the option

```
This will run the `go build` command with go version 1.8.1, sharing your current directory with the container. Obviously, you can run `go fmt/vet/test/...` commands the same way

### Cross building binary
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this section to talk about how you can do

$ GOOS=linux go build
$ docker build .

Where Dockerfile adds the linux binary

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm... sorry don't understand what you mean here. The use case I have in mind in this section is only to use docker run to generate a binary, nothing more. Building an image by adding the binary in the Dockerfile is shown as an example in the sections below.

Usually, cross building a binary requires to install go from sources. Using the golang image, you can install the binary distribution on your computer for day-to-day use and rely on the docker image to build binaries for different targets.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong, you can cross compile by installing Go with an installer to install from source

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I don't know how to do that TBH. Maybe my use case of using the golang image to generate binary is not a good example and you want to drop it ?

As seen on previous section, cross compiling binaries is only a matter of environment variables. So, an example command could be :
```
docker run --rm -v $PWD:/go/src/github.com/repo/project -w /go/src/github.com/repo/project -e GOOS=windows -e GOARCH=amd64 golang:1.8.1 go build -o dist/binary-windows.exe
docker run --rm -v $PWD:/go/src/github.com/repo/project -w /go/src/github.com/repo/project -e GOOS=linux -e GOARCH=amd64 golang:1.8.1 go build -o dist/binary-linux-x64
docker run --rm -v $PWD:/go/src/github.com/repo/project -w /go/src/github.com/repo/project -e GOOS=darwin -e GOARCH=amd64 golang:1.8.1 go build -o dist/binary-darwin
```

### Local development
Another use case would be to build a custom image (based on golang) including the version of go of your choice and all the tools your need to lint / check / test your code. Then you can distribute this image to all the developers in your team so you can easily manage environment development for all your team from a simple Dockerfile !

## Build docker images
When comes time to distribute your awesome project, you might want to provide a docker image instead of a binary. It can ease the installation instruction (particularly if your binary needs to be deployed alongside other files like a webserver serving static files) or to run it in a kubernetes cluster.
Let's see the options we have to achieve this.

### FROM alpine
Using the base image from which to build your own is a matter of taste. Nethertheless, alpine is massively adopted, even by docker who publishes an alpine flavor for all the official images. This is because the alpine base image is a lot smaller than any other linux distribution.
Assuming you've already built your binary in your current directory, an example Dockerfile would be :
```
FROM alpine:latest
ADD mybinary .
CMD ./mybinary
```

### FROM scratch
`scratch` is a special keyword to tell docker to build an image from "nothing'. So the resulting image will only contain the files and directories you explicitly add. As go binaries don't require anything to run, you can be tempted to build your image from scratch instead of alpine, this will produce an image as small as your binary. There are just a few tricks you should be aware of :
- To have a true autonomous binary that don't rely on any linux libray, your must build your binary with `CGO_ENABLED=0` flag and ` -installsuffix cgo` option
- If your application needs to make HTTPS calls, you need to add an up-to-date `/etc/ssl/certs/ca-certificates.crt` file in your container so your application can validate https certs.

Also, as `scratch` is not really a distribution, don't expect to find any command like `tar, unzip, curl, apt-get`... So scratch is really useful to package your binary but can be trcky to use if your container must embed more stuffs.


### Multistage build
Until docker 17.05-ce, if you wanted to build your binary and tiny images you had to :
- Use a `docker run` command as explained earlier to generate a binary in your current directory
- and then run a `docker build` based from alpine or scratch and copy the binary obtained in the previous step into it.

Now, docker support multi-stage builds, which means you can use a single Dockerfile for those 2 steps, eg :
```
FROM golang:latest as builder
ADD . /go/src/github.com/repo/project
RUN go build -o /tmp/binary .

FROM alpine:latest
COPY --from=builder /tmp/binary /usr/local/binary
CMD /usr/local/binary
```
Running `docker build` will build the binary in a temp image, then build your final image and copy the binary from the temp image into the final one !

The only drawback of the multistage approach is you will run a single `docker build` command for the whole process. If your build process produces additional files such as test coverage reports, you won't be able to get them back. Indeed, `docker build` don't support volume mounts. So you can `ADD` files from your current directory into the image but you can't extract generated artifacts from the temp images (Obviously, you can still use `docker cp` from the final image if you wanted to extract your binary or something else).

### Best practices
Docker provides a lot of [best pratcices](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/). One particularly cool to use if your binary provides multiple commands is the use of the entrypoint.
With a Dockerfile like
```
FROM
...
ENTRYPOINT [/usr/local/binary]
CMD [run]
```
the user can run your binary with a single `docker run imagename`. But if your binary supports other commands like `help`, it can be simply invoked with `docker run imagename help`.


## Congratulations

Expand Down