GithubHelp home page GithubHelp logo

centurylinklabs / golang-builder Goto Github PK

View Code? Open in Web Editor NEW
398.0 23.0 85.0 45 KB

Containerized build environment for compiling an executable Golang package and packaging it in a light-weight Docker container.

License: Apache License 2.0

Shell 86.08% Dockerfile 13.92%

golang-builder's Introduction

golang-builder

Containerized build environment for compiling an executable Golang package and packaging it in a light-weight Docker container.

NOTE

This repo is no longer being maintained. Users are welcome to fork it, but we make no warranty of its functionality.

Overview

One of the (many) benefits of developing with Go is that you have the option of compiling your application into a self-contained, statically-linked binary. A statically-linked binary can be run in a container with NO other dependencies which means you can create incredibly small images.

With a statically-linked binary, you could have a Dockerfile that looks something like this:

FROM scratch
COPY hello /
ENTRYPOINT ["/hello"]

Note that the base image here is the 0 byte scratch image which serves as the root layer for all Docker images. The only thing in the resulting image will be the copied binary so the total image size will be roughly the same as the binary itself.

Contrast that with using the official golang image which weighs-in at 500MB before you even copy your application into it.

The golang-builder will accept your source code, compile it into a statically-linked binary and generate a minimal Docker image containing that binary.

The implementation of the golang-builder was heavily inspired by the Create the Smallest Possible Docker Container post on the Xebia blog.

Requirements

In order for the golang-builder to work properly with your project, you need to follow a few simple conventions:

Project Structure

The golang-builder assumes that your "main" package (the package containing your executable command) is at the root of your project directory structure.

.
├─Dockerfile
├─api
| ├─api.go
| └─api_test.go
├─greeting
| ├─greeting.go
| └─greeting_test.go
├─hello.go
└─hello_test.go

In the example above, the hello.go source file defines the "main" package for this project and lives at the root of the project directory structure. This project defines other packages ("api" and "greeting") but those are subdirectories off the root.

This convention is in place so that the golang-builder knows where to find the "main" package in the project structure.

If your "main" package does not reside at the root of your project structure, you can accomodate this by specifying the MAIN_PATH environment variable which references the relative path to your main package.

For example, suppose your structure is like the following (slightly modified from above so that your "main" package resides in the "cli" subfolder):

.
├─Dockerfile
├─api
| ├─api.go
| └─api_test.go
├─greeting
| ├─greeting.go
| └─greeting_test.go
└─cli
  ├─hello.go
  └─hello_test.go

You would then add MAIN_PATH=cli to your environment specification.

Canonical Import Path

In addition to knowing where to find the "main" package, the golang-builder also needs to know the fully-qualified package name for your application. For the "hello" application shown above, the fully-qualified package name for the executable is "github.com/CenturyLink/hello" but there is no way to determine that just by looking at the project directory structure (during the development, the project directory would likely be mounted at $GOPATH/src/github.com/CenturyLink/hello so that the Go tools can determine the package name).

In version 1.4 of Go an annotation was introduced which allows you to identify the canonical import path as part of your source code. The annotation is a specially formatted comment that appears immediately after the package clause:

package main // import "github.com/CenturyLink/hello"

The golang-builder will read this annotation from your source code and use it to mount the source code into the proper place in the GOPATH for compilation.

Dependencies

There's a good chance that your project imports at least one third-party Go package. The golang-builder obviously needs access to any packages that you've imported in order to compile your code. By default, golang-builder will go get any packages you've imported which aren't part of your project already.

The problem with doing a go get with each build is that golang-builder may end up with versions of packages which are different than those you developed against. Depending on the stability of the packages that you are importing this may not be an issue. However, if you want to maintain strict control over your dependency versions you may want to look at the Godep tool.

If you are using Godep to manage your dependencies golang-builder will reference the packages in your Godeps/_workspace directory instead of downloading them via go get.

Dockerfile

If you would like to have golang-builder package your compiled Go application into a Docker image automatically then the final requirement is that your Dockerfile be placed at the root of your project directory structure.

If your Dockerfile resides somewhere else, you can acommodate this by specifying the DOCKER_BUILD_CONTEXT env var. This will be used to override the default value of ".".

After compiling your Go application, golang-builder will execute a docker build with your Dockerfile.

The compiled binary will be placed (by default) in the root of your project directory so your Dockerfile can be written with the assumption that the application binary is in the same directory as the Dockerfile itself:

FROM scratch
EXPOSE 3000
COPY hello /
ENTRYPOINT ["/hello"]

In this case, the hello binary will be copied right to the root of the image and used as the entrypoint. Since we're using the empty scratch image as our base, there is no need to set-up any sort of directory structure inside the image.

If golang-builder does NOT see a Dockerfile in your project directory (or DOCKER_BUILD_CONTEXT directory) it will simply stop after compiling your application.

Usage

There are a few things that the golang-builder needs in order to compile your application code and wrap it in a Docker image:

  • Access to your source code. Inject your source code into the container by mounting it at the /src mount point with the -v flag.
  • Access to the Docker API socket. Since the golang-builder code needs to interact with the Docker API in order to build the final image, you need to mount /var/run/docker.sock into the container with the -v flag when you run it. If you omit the volume mount for the Docker socket, the application will be compiled but not packaged into a Docker image.

Assuming that the source code for your Go executable package is located at /home/go/src/github.com/CenturyLink/hello on your local system and you're currently in the hello directory, you'd run the golang-builder container as follows:

docker run --rm \
  -v "$(pwd):/src" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  centurylink/golang-builder

This would result in the creation of a new Docker image named hello:latest.

Note that the image tag is generated dynamically from the name of the Go package. If you'd like to specify an image tag name you can provide it as an argument after the image name.

docker run --rm \
  -v "$(pwd):/src" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  centurylink/golang-builder \
  centurylink/hello:1.0

If you just want to compile your application without packaging it in a Docker image you can simply run golang-builder without mounting the Docker socket.

docker run --rm -v $(pwd):/src centurylink/golang-builder

Additional Options

  • CGO_ENABLED - whether or not to compile the binary with CGO (defaults to false)
  • LDFLAGS - flags to pass to the linker (defaults to '-s')
  • COMPRESS_BINARY - if set to true, will use UPX to compress the final binary (defaults to false)
  • OUTPUT - if set, will use the -o option with go build to output the final binary to the value of this env var
  • MAIN_PATH - if set, this (relative) path will be used as the location of the "main" package
  • DOCKER_BUILD_CONTEXT - if set, this (relative) path will be used as the build context location (where the Dockerfile should reside)

The above are environment variables to be passed to the docker run command:

docker run --rm \
  -e CGO_ENABLED=true \
  -e LDFLAGS='-extldflags "-static"' \
  -e COMPRESS_BINARY=true \
  -e OUTPUT=/bin/my_go_binary \
  -v $(pwd):/src \
  centurylink/golang-builder

Cross-compilation

An additional image, centurylink/golang-builder-cross, exists that works identically to golang-builder save for the presence of the additional options presented above. This uses a larger base image that will build linux and OSX binaries for 32- and 64-bit, named like mypackage-darwin-amd64. This will use CGO, and you may find that some code – for example things from the os package – do not behave the same under cross-compilation in a container as they do natively compiled in OSX.

By default it will build Linux and OSX binaries for 32- and 64-bit but you can override this with environment variables.

docker run --rm
-e BUILD_GOOS="linux"
-e BUILD_GOARCH="arm amd64"
-v $(pwd):/src
centurylink/golang-builder-cross

This command will build Linux binaries for amd64 and arm.

More information can be found in the Docker Hub page for the official Go images.

SSL Verification

If your Go application needs to make calls to SSL endpoints you may find your application failing with a message like:

x509: failed to load system roots and no roots provided

One of the down-sides to using the scratch image is that you no longer have access to the root CA certificates which come pre-installed in most base images. There are a few different options for dealing with this:

  • Disable SSL verification. This is not recommended for obvious reasons.
  • Bundle the necessary root CA certificates as part of your application.
  • Use a different base image which already contains the root CA certificates.

We've created a minimal base image for applications that require SSL verification. The centurylink/ca-certs image is simply the scratch image with the most common root CA certificates pre-installed. The resulting image is only 258 kB which is still a good starting point for creating your own minimal images.

golang-builder's People

Contributors

alexwelch avatar bdehamer avatar chiefy avatar docbobo avatar dpetersen avatar joelchen avatar luzifer avatar matt-deboer avatar ntfrnzn avatar rossjimenez avatar thesandlord avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

golang-builder's Issues

Version-locking

I'd appreciate locking down the golang base image version and publishing versions in addition to latest to the hub. Thoughts?

Update image for Go 1.6

Currently this image uses golang:latest as base image. That image is already in Go 1.6. A rebuild of this image would give 1.6 for users, could someone do that?

Of course even better would be to have image tags based on Go version, so that users could bind to specific Go version.

go install github.com/hashicorp/vault: build output "vault" already exists and is a directory

I'm trying to use golang-builder to build an image for Vault (https://vaultproject.io/), and getting the above error.

I read the golang-builder README, cloned the vault repo (main package in root), created a Dockerfile in root:

FROM scratch
EXPOSE 8200
COPY vault /
ENTRYPOINT ["/vault"]

Then ran the build command:

docker run --rm -v "$(pwd):/src" -v /var/run/docker.sock:/var/run/docker.sock  centurylink/golang-builder

And got this:

Building github.com/hashicorp/vault
go install github.com/hashicorp/vault: build output "vault" already exists and is a directory

I'm not fluent in Go, but when I posted the issue to the vault repo, one of the maintainers said:

go install is supposed to be keyed off of the GOPATH into a bin dir. It sounds like they're either ignoring the GOPATH or they're doing custom things with it, like setting it to the root of the source directory -- which would also explain why they need the canonical import path (my guess is that some time ago when I used it a bit they were taking the current dir, removing GOPATH from the front, and using that inside the builder container, and now they're just using the source directory directly).

There's nothing we're doing wrong, here -- and it's not uncommon practice, either -- so this really needs to be fixed on their end.

hashicorp/vault#165 (comment)

golang 1.7?

related to #17 , can you rebuild and push so we can have golang 1.7 please?
I need to compile stuff that requires 1.7 and the golang image supports 1.7 now.

Unable to use packages in private repositories

Not sure if this is possible, feasible, or even out of scope, as my project does not strictly follow the sample hierarchy... But I'm trying to build docker images for a bunch of microservices. All of these microservices share a single authentication middleware function.

All of these services are stored in separate private repositories. When I try using golang-builder, I get 403s from the imported private repos for obvious reasons.

$ docker run --rm -v "$(pwd):/src" -v /var/run/docker.sock:/var/run/docker.sock centurylink/golang-builder
# cd .; git ls-remote https://bitbucket.org/xxx/auth
fatal: could not read Username for 'https://bitbucket.org': No such device or address
# cd .; hg identify https://bitbucket.org/xxx/auth
abort: HTTP Error 404: Not Found
package bitbucket.org/xxx/auth/middleware: https://api.bitbucket.org/1.0/repositories/xxx/auth: 403 FORBIDDEN

Is it possible to either;
a) use git/ssh credentials in build image
b) copy third-party imports from the $GOPATH/src directory at the same time as $(pwd), and use them in the build, then run a "go get" for any missing public imports.

I have found a workaround, by locally cross-compiling to linux, then running docker build myself, but this does have drawbacks... The person doing the build needs Go installed to compile stuff. Using golang-builder, anybody with Docker can run the build.

GOOS="linux" go build
docker build -t yyyservice
docker-compose up

Using time package with timezone features is rendered useless?

When I deployed using your certs image? Some requests were failing with this error
open /usr/lib/go/lib/time/zoneinfo.zip: no such file or directory. I guess golang doesn't completely staticly link these dependencies also?
Any Ideas on how to fix this I'm planning on copying this for now

Add a call to "go generate"

Could you add a call to go generate inside your build.sh ?
That way, one could add custom operations to the build process.

For example, I'd use it to generate my Swagger documentation.

Thanks for your work.

Building fails for packages in subdirectories

Given an import path of github.com/foo/bar/baz, where the repo is github.com/foo/bar, building will fail.

Example:

docker run --rm \
        --privileged \
        -v /Users/jfindley/go/src/github.com/nutmegdevelopment/sumologic/filestream:/src \
        -e CGO_ENABLED=0 \
        -e LDFLAGS='-s -extldflags -static' \
        -v /var/run/docker.sock:/var/run/docker.sock \
        centurylink/golang-builder \
        localhost/filestream:latest
github.com/dleung/gotail (download)
Fetching https://gopkg.in/fsnotify.v1?go-get=1
Parsing meta tags from https://gopkg.in/fsnotify.v1?go-get=1 (status code 200)
get "gopkg.in/fsnotify.v1": found meta tag main.metaImport{Prefix:"gopkg.in/fsnotify.v1", VCS:"git", RepoRoot:"https://gopkg.in/fsnotify.v1"} at https://gopkg.in/fsnotify.v1?go-get=1
gopkg.in/fsnotify.v1 (download)
Fetching https://golang.org/x/sys/unix?go-get=1
Parsing meta tags from https://golang.org/x/sys/unix?go-get=1 (status code 200)
get "golang.org/x/sys/unix": found meta tag main.metaImport{Prefix:"golang.org/x/sys", VCS:"git", RepoRoot:"https://go.googlesource.com/sys"} at https://golang.org/x/sys/unix?go-get=1
get "golang.org/x/sys/unix": verifying non-authoritative meta tag
Fetching https://golang.org/x/sys?go-get=1
Parsing meta tags from https://golang.org/x/sys?go-get=1 (status code 200)
golang.org/x/sys (download)
github.com/nutmegdevelopment/sumologic (download)
package github.com/nutmegdevelopment/sumologic/upload: /go/src/github.com/nutmegdevelopment/sumologic exists but /go/src/github.com/nutmegdevelopment/sumologic/.git does not - stale checkout?
github.com/stretchr/testify

This is because build_environment.sh links /src into the GOPATH, making clones into the parent dir fail.

We are currently using this patch:
https://github.com/nutmegdevelopment/golang-builder/commit/ced5b2148ce452a45313678d6329963a3213b287

Note that it also disabled the GO15VENDEREXPERIMENT stuff, as that's now part of current (1.6) go, so shouldn't be needed anymore.

We alos disabled godeps support as we had issues making that work too.

I can split the above patch into separate commits, if you're interested in accepting some/all of these changes.

Extracting pkgName and pkgBase fails

When the mounted go-code is only linked (as I have a separate folder for the sourcecode and a separate folder for the Docker Builds), go list seems to return nothing.
If I am however copying the sourcecode into the docker build folder, it successfully evaluates the Canoncial Import Path

Golang packages are dynamically linked

Because of root Docker's golang image, static builds are not available.

Dynamically linked gotools from Docker's golang image are unable to produce static builds.
Example ldd output from freshly created binary:

oldie% ldd bin/server
        linux-vdso.so.1 (0x00007ffc0f4f9000)
        libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f10b9e54000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007f10b9ab2000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f10ba071000)

Support passing in additional arguments to docker build

I have a project using golang-builder, and I'd like to reference environment variables in my Dockerfile using the ENV directive (docs). Unfortunately, it appears that this still requires the -e/--build-arg params on the call to docker build (docs).

Currently this does not appear to be possible with golang-builder, so I'd like to discuss on this issue possible ways to introduce this feature.

To start things off, how about an environment variable passed in called something like DOCKER_BUILD_ARGS that allows any arbitrary extra args to be given to the docker build command. That seems like it would be a generic-enough implementation. What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.