| Deploying Go servers with Docker |
| 26 Sep 2014 |
| |
| Andrew Gerrand |
| |
| * Introduction |
| |
| This week Docker [[https://blog.docker.com/2014/09/docker-hub-official-repos-announcing-language-stacks/][announced]] |
| official base images for Go and other major languages, |
| giving programmers a trusted and easy way to build containers for their Go programs. |
| |
| In this article we'll walk through a recipe for creating a Docker container for |
| a simple Go web application and deploying that container to Google Compute Engine. |
| If you're not familiar with Docker, you should read |
| [[https://docs.docker.com/engine/understanding-docker/][Understanding Docker]] |
| before reading on. |
| |
| * The demo app |
| |
| For our demonstration we will use the |
| [[http://godoc.org/github.com/golang/example/outyet][outyet]] program from the |
| [[https://github.com/golang/example][Go examples repository]], |
| a simple web server that reports whether the next version of Go has been released |
| (designed to power sites like [[http://isgo1point4.outyet.org/][isgo1point4.outyet.org]]). |
| It has no dependencies outside the standard library and requires no additional |
| data files at run time; for a web server, it's about as simple as it gets. |
| |
| Use "go get" to fetch and install outyet in your |
| [[https://golang.org/doc/code.html#Workspaces][workspace]]: |
| |
| $ go get github.com/golang/example/outyet |
| |
| * Write a Dockerfile |
| |
| Replace a file named `Dockerfile` in the `outyet` directory with the following contents: |
| |
| # Start from a Debian image with the latest version of Go installed |
| # and a workspace (GOPATH) configured at /go. |
| FROM golang |
| |
| # Copy the local package files to the container's workspace. |
| ADD . /go/src/github.com/golang/example/outyet |
| |
| # Build the outyet command inside the container. |
| # (You may fetch or manage dependencies here, |
| # either manually or with a tool like "godep".) |
| RUN go install github.com/golang/example/outyet |
| |
| # Run the outyet command by default when the container starts. |
| ENTRYPOINT /go/bin/outyet |
| |
| # Document that the service listens on port 8080. |
| EXPOSE 8080 |
| |
| This `Dockerfile` specifies how to construct a container that runs `outyet`, |
| starting with the basic dependencies (a Debian system with Go installed; |
| the [[https://registry.hub.docker.com/_/golang/][official `golang` docker image]]), |
| adding the `outyet` package source, building it, and then finally running it. |
| |
| The `ADD`, `RUN`, and `ENTRYPOINT` steps are common tasks for any Go project. |
| To simplify this, there is an |
| [[https://github.com/docker-library/golang/blob/9ff2ccca569f9525b023080540f1bb55f6b59d7f/1.3.1/onbuild/Dockerfile][`onbuild` variant]] |
| of the `golang` image that automatically copies the package source, fetches the |
| application dependencies, builds the program, and configures it to run on |
| startup. |
| |
| With the `onbuild` variant, the `Dockerfile` is much simpler: |
| |
| FROM golang:onbuild |
| EXPOSE 8080 |
| |
| * Build and run the image |
| |
| Invoke Docker from the `outyet` package directory to build an image using the `Dockerfile`: |
| |
| $ docker build -t outyet . |
| |
| This will fetch the `golang` base image from Docker Hub, copy the package source |
| to it, build the package inside it, and tag the resulting image as `outyet`. |
| |
| To run a container from the resulting image: |
| |
| $ docker run --publish 6060:8080 --name test --rm outyet |
| |
| The `--publish` flag tells docker to publish the container's port `8080` on the |
| external port `6060`. |
| |
| The `--name` flag gives our container a predictable name to make it easier to work with. |
| |
| The `--rm` flag tells docker to remove the container image when the outyet server exits. |
| |
| With the container running, open `http://localhost:6060/` in a web browser and |
| you should see something like this: |
| |
| .image docker-outyet.png |
| |
| (If your docker daemon is running on another machine (or in a virtual machine), |
| you should replace `localhost` with the address of that machine. If you're |
| using [[http://boot2docker.io/][boot2docker]] on OS X or Windows you can find |
| that address with `boot2docker`ip`.) |
| |
| Now that we've verified that the image works, shut down the running container |
| from another terminal window: |
| |
| $ docker stop test |
| |
| * Create a repository on Docker Hub |
| |
| [[https://hub.docker.com/][Docker Hub]], the container registry from which we |
| pulled the `golang` image earlier, offers a feature called |
| [[http://docs.docker.com/docker-hub/builds/][Automated Builds]] that builds |
| images from a GitHub or BitBucket repository. |
| |
| By committing [[https://github.com/golang/example/blob/master/outyet/Dockerfile][the Dockerfile]] |
| to the repository and creating an |
| [[https://registry.hub.docker.com/u/adg1/outyet/][automated build]] |
| for it, anyone with Docker installed can download and run our image with a |
| single command. (We will see the utility of this in the next section.) |
| |
| To set up an Automated Build, commit the Dockerfile to your repo on |
| [[https://github.com/][GitHub]] or [[https://bitbucket.org/][BitBucket]], |
| create an account on Docker Hub, and follow the instructions for |
| [[http://docs.docker.com/docker-hub/builds/][creating an Automated Build]]. |
| |
| When you're done, you can run your container using the name of the automated build: |
| |
| $ docker run goexample/outyet |
| |
| (Replace `goexample/outyet` with the name of the automated build you created.) |
| |
| * Deploy the container to Google Compute Engine |
| |
| Google provides |
| [[https://developers.google.com/compute/docs/containers/container_vms][container-optimized Google Compute Engine images]] |
| that make it easy to spin up a virtual machine running an arbitrary Docker container. |
| On startup, a program running on the instance reads a configuration file that |
| specifies which container to run, fetches the container image, and runs it. |
| |
| Create a [[https://cloud.google.com/compute/docs/containers/container_vms#container_manifest][containers.yaml]] |
| file that specifies the docker image to run and the ports to expose: |
| |
| version: v1beta2 |
| containers: |
| - name: outyet |
| image: goexample/outyet |
| ports: |
| - name: http |
| hostPort: 80 |
| containerPort: 8080 |
| |
| (Note that we're publishing the container's port `8080` as external port `80`, |
| the default port for serving HTTP traffic. And, again, you should replace |
| `goexample/outyet` with the name of your Automated Build.) |
| |
| Use the [[https://cloud.google.com/sdk/#Quick_Start][gcloud tool]] |
| to create a VM instance running the container: |
| |
| $ gcloud compute instances create outyet \ |
| --image container-vm-v20140925 \ |
| --image-project google-containers \ |
| --metadata-from-file google-container-manifest=containers.yaml \ |
| --tags http-server \ |
| --zone us-central1-a \ |
| --machine-type f1-micro |
| |
| The first argument (`outyet`) specifies the instance name, a convenient label |
| for administrative purposes. |
| |
| The `--image` and `--image-project` flags specify the special |
| container-optimized system image to use (copy these flags verbatim). |
| |
| The `--metadata-from-file` flag supplies your `containers.yaml` file to the VM. |
| |
| The `--tags` flag tags your VM instance as an HTTP server, adjusting the |
| firewall to expose port 80 on the public network interface. |
| |
| The `--zone` and `--machine-type` flags specify the zone in which to run the VM |
| and the type of machine to run. (To see a list of machine types and the zones, |
| run `gcloud`compute`machine-types`list`.) |
| |
| Once this has completed, the gcloud command should print some information about |
| the instance. In the output, locate the `networkInterfaces` section to find the |
| instance's external IP address. Within a couple of minutes you should be able |
| to access that IP with your web browser and see the "Has Go 1.4 been released |
| yet?" page. |
| |
| (To see what's happening on the new VM instance you can ssh into it with |
| `gcloud`compute`ssh`outyet`. From there, try `sudo`docker`ps` to see which |
| Docker containers are running.) |
| |
| * Learn more |
| |
| This is just the tip of the iceberg—there's a lot more you can do with Go, Docker, and Google Compute Engine. |
| |
| To learn more about Docker, see their [[https://docs.docker.com/][extensive documentation]]. |
| |
| To learn more about Docker and Go, see the [[https://registry.hub.docker.com/_/golang/][official `golang` Docker Hub repository]] and Kelsey Hightower's [[https://medium.com/@kelseyhightower/optimizing-docker-images-for-static-binaries-b5696e26eb07][Optimizing Docker Images for Static Go Binaries]]. |
| |
| To learn more about Docker and [[http://cloud.google.com/compute][Google Compute Engine]], |
| see the [[https://cloud.google.com/compute/docs/containers/container_vms][Container-optimized VMs page]] |
| and the [[https://registry.hub.docker.com/u/google/docker-registry/][google/docker-registry Docker Hub repository]]. |