Implementación de una aplicación Go en un contenedor Docker

Intro

Si nunca ha oído hablar de Docker, pero eso es poco probable, lo primero que debe saber es que Docker le permite ejecutar aplicaciones de forma aislada y con una gran separación de preocupaciones, pero les permite comunicarse e interactuar con el mundo externo.

Y debe saber que todos lo usan, y todos los principales proveedores de nube tienen una solución dedicada a la ejecución de contenedores, ¡así que debe aprenderla!

Instalar en pc

La instalación cambia dependiendo de su sistema, así que verifiquehttps://www.docker.com/get-docker.

Supongo que ya instaló Docker y tiene eldockercomando disponible en su shell.

Imágenes oficiales de The Go

Docker mantiene una lista de imágenes oficiales para muchos idiomas diferentes, y Go no es una excepción, ya que forma parte del original.lanzamiento de imágenes oficiales back in 2014.

El repositorio de imágenes oficial se puede encontrar enhttps://hub.docker.com/_/golang/. Hay muchas etiquetas que identifican tanto la versión de Go como el sistema operativo que desea obtener.

Una aplicación de ejemplo

Como ejemplo, voy a implementar una pequeña aplicación Go en un contenedor Docker. Escucha en el puerto 8000, obtiene una página web comoqparámetro de consulta, lo recupera e imprime los enlaces que encuentra:

findlinks.go

package main

import ( “fmt” “log” “net/http”

<span style="color:#e6db74">"golang.org/x/net/html"</span>

)

func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(“0.0.0.0:8000”, nil)) }

func handler(w http.ResponseWriter, r *http.Request) { url := r.URL.Query().Get(“q”) fmt.Fprintf(w, “Page = %q\n”, url) if len(url) == 0 { return } page, err := parse(“https://” + url) if err != nil { fmt.Printf(“Error getting page %s %s\n”, url, err) return } links := pageLinks(nil, page) for _, link := range links { fmt.Fprintf(w, “Link = %q\n”, link) } }

func parse(url string) (*html.Node, error) { fmt.Println(url) r, err := http.Get(url) if err != nil { return nil, fmt.Errorf(“Cannot get page”) } b, err := html.Parse(r.Body) if err != nil { return nil, fmt.Errorf(“Cannot parse page”) } return b, err }

func pageLinks(links []string, n *html.Node) []string { if n.Type == html.ElementNode && n.Data == “a” { for _, a := range n.Attr { if a.Key == “href” { links = append(links, a.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { links = pageLinks(links, c) } return links }

Uso de ejemplo:

Mover la aplicación a Docker

Puse la aplicación enhttps://github.com/flaviocopes/findlinks.

Usandogo getPuedo descargarlo e instalarlo fácilmente, usandogo get github.com/flaviocopes/findlinks.

Corriendo

docker run golang go get -v github.com/flaviocopes/findlinks

primero descargará elgolangImagen de Docker, si aún no la tiene, buscará el repositorio y buscará dependencias adicionales no incluidas en la biblioteca estándar. En este caso,golang.org/x/net/html.

$ docker run golang go get -v github.com/flaviocopes/findlinks
github.com/flaviocopes/findlinks (download)
Fetching https://golang.org/x/net/html?go-get=1
Parsing meta tags from https://golang.org/x/net/html?go-get=1 (status code 200)
get "golang.org/x/net/html": found meta tag main.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at https://golang.org/x/net/html?go-get=1
get "golang.org/x/net/html": verifying non-authoritative meta tag
Fetching https://golang.org/x/net?go-get=1
Parsing meta tags from https://golang.org/x/net?go-get=1 (status code 200)
golang.org/x/net (download)
golang.org/x/net/html/atom
golang.org/x/net/html
github.com/flaviocopes/findlinks

Este comando crea un contenedor y lo ejecuta. Podemos inspeccionarlo usandodocker ps -l(la-lopción le dice a Docker que enumere el último contenedor ejecutado):

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
343d96441f16        golang              "go get -v github...."   3 minutes ago       Exited (0) 2 minutes ago                       mystifying_swanson

El contenedor salió cuando elgo getcomando completado.

Docker simplemente creó una imagen bajo demanda y la ejecutó; para ejecutarlo de nuevo, tendríamos que repetir el proceso, pero las imágenes nos ayudan: ahora creemos una imagen a partir de este contenedor, para que podamos ejecutarlo más tarde:

docker commit $(docker ps -lq) findlinks

El comando anterior obtiene el último ID de contenedor usandodocker ps -lqy confirma la imagen. La imagen ahora se ha instalado, como puede verificar usandodocker images findlinks:

$ docker images findlinks
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
findlinks           latest              4e7ebb87d02e        11 seconds ago      720MB

Podemos ejecutar elfindlinkscomando en nuestrofindlinksimagen con

docker run -p 8000:8000 findlinks findlinks

¡Eso es! Nuestra aplicación ahora responderá enhttp://192.168.99.100:8000/, dónde192.168.99.100es la dirección IP del contenedor Docker.

Puedes probar llamandohttp://192.168.99.100:8000/?q=flaviocopes.com, que imprimirá la misma salida que teníamos cuando ejecutamos la aplicación localmente:

Page = "flaviocopes.com"
Link = "https://flaviocopes.com/index.xml"
Link = "https://twitter.com/flaviocopes"
Link = "https://github.com/flaviocopes"
Link = "https://stackoverflow.com/users/205039/flaviocopes"
Link = "https://linkedin.com/in/flaviocopes/"
Link = "mailto:[email protected]"
Link = "/"
Link = "/page/contact/"
Link = "/page/about/"
Link = "https://flaviocopes.com/golang-tutorial-rest-api/"
Link = "https://flaviocopes.com/golang-environment-variables/"
Link = "https://flaviocopes.com/golang-sql-database/"
Link = "https://flaviocopes.com/golang-is-go-object-oriented/"
Link = "https://flaviocopes.com/golang-comparing-values/"
Link = "https://flaviocopes.com/golang-data-structures/"
Link = "https://flaviocopes.com/golang-data-structure-binary-search-tree/"
Link = "https://flaviocopes.com/golang-data-structure-graph/"
Link = "https://flaviocopes.com/golang-data-structure-linked-list/"
Link = "https://flaviocopes.com/golang-data-structure-queue/"
Link = "https://flaviocopes.com/golang-data-structure-stack/"
Link = "https://flaviocopes.com/golang-event-listeners/"

Recortar la imagen de Docker

Esta estrategia ahora está en desuso en favor decompilaciones de varias etapas

El problema con el resultado anterior es,la imagen es enorme: 720 MB para este programa simple no es realmente aceptable, tenga en cuenta que este es un escenario muy simple. Es posible que deseemos implementar miles de instancias de la aplicación y este tamaño no funcionará.

¿Por qué la imagen es tan grande? Porque lo que pasa es que la aplicación Go se compila dentro del contenedor. Entonces, la imagen debe tener un compilador Go instalado. Y todo lo que necesita el compilador, por supuesto, GCC y toda una distribución de Linux (Debian Jessie). Descarga Go y lo instala, compila la aplicación y la ejecuta.

Todo es tan rápido que ni siquiera nos damos cuenta. Pero lo podemos hacer mejor. ¿Cómo? Aplico lo que aprendí enCreación de contenedores Docker mínimos para aplicaciones GoporNick Gauthier

Le decimos a Docker que ejecute elgolang:1.8.3imagen ycompilar estáticamentenuestra aplicación, deshabilitando CGO, lo que significa que la imagen ni siquiera necesita las bibliotecas C a las que normalmente necesita acceder cuando se vincula dinámicamente, usando:

docker run --rm -it -v "$GOPATH":/gopath -v "$(pwd)":/app -e "GOPATH=/gopath" -w /app golang:1.8.3 sh -c 'CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags="-s" -o findlinks'

Ahora tenemos unfindlinksbinario en la carpeta:

$ ll
.rw-r--r--   77 flavio 17 Aug 18:57 Dockerfile
.rwxr-xr-x 4.2M flavio 17 Aug 19:13 findlinks
.rw-r--r-- 1.1k flavio 12 Aug 18:10 findlinks.go

$ file findlinks findlinks: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

Tenga en cuenta que, por supuesto, este no es el mismo archivo que obtengo sigo builden OSX, es un binario listo para Linux:

$ file findlinks
findlinks: Mach-O 64-bit executable x86_64

Luego creamos un Dockerfile, diciéndole a Docker que usehierro / base, una imagen muy clara:

FROM iron/base
WORKDIR /app
COPY findlinks /app/
ENTRYPOINT ["./findlinks"]

Ahora podemos construir la imagen, etiquetándolaflaviocopes/golang-docker-example-findlinks:

docker build -t flaviocopes/golang-docker-example-findlinks .

y ejecutarlo:

docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks

La salida es la misma que antes, pero esta vez la imagen no es de 720 MB, sino de solo 11,1 MB

REPOSITORY                                    TAG                 IMAGE ID            CREATED             SIZE
flaviocopes/golang-docker-example-findlinks   latest              f32d2fd74638        14 minutes ago      11.1MB
findlinks                                     latest              c60f6792b9f3        20 minutes ago      720MB

Construcciones de varias etapas

Esta sección se agregó gracias a los comentarios de Reddit que me señalaron compilaciones de varias etapas, una adición reciente aDocker 17.05

Construcciones de varias etapasnos permiten tener una imagen liviana de manera muy sencilla, sin necesidad de compilar el binario y luego ejecutarlo por separado. Este es el Dockerfile para poner en la aplicación:

FROM golang:1.8.3 as builder
WORKDIR /go/src/github.com/flaviocopes/findlinks
RUN go get -d -v golang.org/x/net/html
COPY findlinks.go  .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o findlinks .

FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/flaviocopes/findlinks/findlinks . CMD ["./findlinks"]

Correr

$ docker build -t flaviocopes/golang-docker-example-findlinks .

que crea una imagen ligera (10,8 MB):

$ docker images
REPOSITORY                                    TAG                 IMAGE ID            CREATED             SIZE
flaviocopes/golang-docker-example-findlinks   latest              aa2081ca7016        12 seconds ago      10.8MB

Run the image with

$ docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks  

More go tutorials: