- Intro
- Instalar en pc
- Imágenes oficiales de The Go
- Una aplicación de ejemplo
- Mover la aplicación a Docker
- Recortar la imagen de Docker
- Construcciones de varias etapas
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 eldocker
comando 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 comoq
pará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 get
Puedo descargarlo e instalarlo fácilmente, usandogo get github.com/flaviocopes/findlinks
.
Corriendo
docker run golang go get -v github.com/flaviocopes/findlinks
primero descargará elgolang
Imagen 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-l
opció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 get
comando 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 -lq
y 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 elfindlinks
comando en nuestrofindlinks
imagen 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.100
es 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.3
imagen 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 unfindlinks
binario 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 build
en 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 a
Docker 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.8MBRun the image with
$ docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks
More go tutorials:
- Using NGINX Reverse Proxy to serve Go services
- Making a copy of a struct in Go
- The basics of a Go Web Server
- Sorting a map type in Go
- Go pointers in a nutshell
- Go Tags explained
- Go Date and Time Formatting
- JSON processing with Go
- Go Variadic Functions
- Go Strings Cheat Sheet
- The Go Empty Interface Explained
- Debugging Go with VS Code and Delve
- Named Go returns parameters
- Generating random numbers and strings in Go
- Filesystem Structure of a Go project
- Binary Search Algorithm Implemented in Go
- Using Command Line Flags in Go
- GOPATH Explained
- Build a Command Line app with Go: lolcat
- Building a CLI command with Go: cowsay
- Using Shell Pipes with Go
- Go CLI tutorial: fortune clone
- List the files in a folder with Go
- Use Go to get a list of repositories from GitHub
- Go, append a slice of strings to a file
- Go, convert a string to a bytes slice
- Visualize your local Git contributions with Go
- Getting started with Go CPU and memory profiling
- Solving the "does not support indexing" error in a Go program
- Measuring execution time in a Go program
- Building a Web Crawler with Go to detect duplicate titles
- Go Best Practices: Pointer or value receivers?
- Go Best Practices: Should you use a method or a function?
- Go Data Structures: Set
- Go Maps Cheat Sheet
- Generate implementations for generic types in Go
- Go Data Structures: Dictionary
- Go Data Structures: Hash Table
- Implement Events Listeners in Go through Channels
- Go Data Structures: Stack
- Go Data Structures: Queue
- Go Data Structures: Binary Search Tree
- Go Data Structures: Graph
- Go Data Structures: Linked List
- The complete guide to Go Data Structures
- Comparing Go Values
- Is Go object oriented?
- Working with a SQL Database in Go
- Using environment variables in Go
- Go tutorial: REST API backed by PostgreSQL
- Enabling CORS on a Go Web Server
- Deploying a Go Application in a Docker Container
- Why Go is a powerful language to learn as a PHP developer
- Go, remove the io.Reader.ReadString newline char
- Go, how to watch changes and rebuild your program
- Go, count the months since a date
- Accessing HTTP POST parameters in Go