En mi serie de desarrollo sobre Docker para el desarrollador de aplicaciones web, ya había repasado el despliegue de aplicaciones Java y Node.js como contenedores junto a Couchbase Server Containers. Esta vez pensé que sería genial desplegar una aplicación web Golang como un contenedor Docker junto con un contenedor Couchbase Server. Couchbase.
El proceso es muy similar a lo que ya había demostrado en los tutoriales anteriores, pero vale la pena explorarlo. Vamos a explorar la contenedorización de una aplicación que había explicado en un tutorial anterior sobre el tema de los servicios de acortamiento de URL.
Si le interesa, puede consultar Utilizar Docker para desplegar una aplicación web Java con Couchbase en contenedores y Desplegar una aplicación web Node.js con Couchbase como contenedores Dockeren función de sus preferencias lingüísticas.
Requisitos
Sólo hay una dependencia que debe cumplirse para tener éxito con esta guía. Necesitas Docker instalado en una máquina host. Usted no necesita Golang instalado o Couchbase Server ya que es la belleza del motor Docker.
Aunque no es necesario, te recomiendo que te familiarices con la aplicación en cuestión. Para aprender sobre el código detrás de la aplicación, echa un vistazo al tutorial que escribí llamado, Crear un acortador de URL con Golang y Couchbase NoSQL.
Establecimiento de un proyecto Docker para la aplicación Golang
El código fuente de la aplicación, y lo que vamos a empaquetar, se puede ver a continuación. Debe guardarlo como un archivo de código fuente Go como main.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
paquete principal importar ( "encoding/json" "log" "net/http" "os" "tiempo" "github.com/couchbase/gocb" "github.com/gorilla/mux" "github.com/speps/go-hashids" ) type MiUrl struct { ID cadena `json: "id,omitempty"` LongUrl string `json: "longUrl,omitempty"` ShortUrl string `json: "shortUrl,omitempty"` } var bucket *gocb.Bucket var bucketName cadena func ExpandEndpoint(w http.ResponseWriter, req *http.Request) { var n1qlParams []interfaz{} query := gocb.NewN1qlQuery("SELECT `" + bucketName + "`.* FROM `" + bucketName + "` WHERE shortUrl = $1") params := req.URL.Query() n1qlParams = append(n1qlParams, params.Get("shortUrl")) rows, _ := bucket.ExecuteN1qlQuery(query, n1qlParams) var row MiUrl filas.Una(&fila) json.NewEncoder(w).Encode(row) } func CreateEndpoint(w http.ResponseWriter, req *http.Request) { var url MiUrl _ = json.NewDecoder(req.Body).Decode(&url) var n1qlParams []interfaz{} n1qlParams = append(n1qlParams, url.LongUrl) query := gocb.NewN1qlQuery("SELECT `" + bucketName + "`.* FROM `" + bucketName + "` WHERE longUrl = $1") rows, err := bucket.ExecuteN1qlQuery(query, n1qlParams) if err != nil { w.WriteHeader(401) w.Write([]byte(err.Error())) devolver } var row MiUrl filas.Una(&fila) if fila == (MiUrl{}) { hd := hashids.NuevosDatos() h := hashids.NewConDatos(hd) now := time.Now() url.ID, _ = h.Encode([]int{int(now.Unix())}) url.ShortUrl = "http://localhost:12345/" + url.ID bucket.Insert(url.ID, url, 0) } else { url = fila } json.NewEncoder(w).Encode(url) } func RootEndpoint(w http.ResponseWriter, req *http.Request) { params := mux.Vars(req) var url MiUrl bucket.Get(params["id"], &url) http.Redirect(w, req, url.LongUrl, 301) } func main() { router := mux.NewRouter() cluster, _ := gocb.Connect("couchbase://" + os.Getenv("COUCHBASE_HOST")) bucketName = os.Getenv("COUCHBASE_BUCKET") bucket, _ = cluster.OpenBucket(bucketName, "") router.HandleFunc("/{id}", RootEndpoint).Methods("GET") router.HandleFunc("/expand/", ExpandEndpoint).Methods("GET") router.HandleFunc("/create", CreateEndpoint).Methods("PUT") log.Fatal(http.ListenAndServe(":12345", enrutador)) } |
La única diferencia entre el código anterior y el tutorial anterior sobre el tema es el uso de la función os.Getenv("COUCHBASE_HOST")
y os.Getenv("COUCHBASE_BUCKET")
comandos. Esto nos permitirá definir la información de conexión de Couchbase en tiempo de ejecución del contenedor a través de variables de entorno en lugar de codificarla en la aplicación.
Si tuvieras Couchbase Server ejecutándose, podrías lanzar la aplicación web haciendo:
1 |
env COUCHBASE_HOST=localhost COUCHBASE_BUCKET=por defecto go ejecutar *.go |
Por supuesto, el proyecto tendría que estar en su $GOPATHpero sería accesible desde http://localhost:12345 como se define en el código.
El objetivo aquí no es ejecutar este proyecto localmente, sino a través de un contenedor Docker. Cree un Dockerfile junto al archivo main.go o como quiera que se llame. El Dockerfile debe contener lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 |
DE golang:alpine RUN apk update && apk add git COPIA . /go/src/app/ WORKDIR /go/src/app EJECUTAR go get -d -v EJECUTAR go install -v CMD ["app"] |
Como base usaremos la imagen oficial de Golang Alpine Linux para Docker, pero la personalizaremos. Durante el tiempo de compilación estamos instalando Git, copiando el archivo main.go a la imagen, instalando las dependencias del proyecto que se encuentran en el archivo importar
e instalando la aplicación. En tiempo de ejecución estamos ejecutando la aplicación instalada.
Para construir una imagen desde nuestro Docker blueprint personalizado, ejecutaríamos lo siguiente:
1 |
docker build -t golang-project . |
Una vez completados todos los pasos de compilación, nos quedará un archivo proyecto golang
Imagen Docker. Antes de intentar ejecutar esta imagen, tenemos que preocuparnos por Couchbase. Aunque podríamos ejecutar Couchbase fuera de un contenedor, ¿dónde está la diversión en eso?
Creación de una imagen Docker personalizada del servidor Couchbase
Al igual que con Golang, existe una imagen Docker oficial para Servidor Couchbase. Aunque es una solución perfectamente aceptable, no es una solución automatizada. Esto significa que tendrás que configurar manualmente la base de datos después de que se inicie el contenedor. Probablemente queramos evitar eso.
En su lugar, cree un directorio en algún lugar de su ordenador con un Dockerfile y configure.sh en él. El plan es crear un Docker blueprint que ejecute el script de configuración por nosotros.
Abra el Dockerfile e incluya lo siguiente:
1 2 3 4 5 |
DESDE couchbase COPIAR configure.sh /opt/couchbase CMD ["/opt/couchbase/configure.sh"] |
Básicamente estamos copiando el script en la imagen base y ejecutándolo. La verdadera magia ocurre dentro del script.
Abra el configure.sh e incluya lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
set -m /entrypoint.sh couchbase-server & dormir 15 curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=512 -d indexMemoryQuota=512 curl -v http://127.0.0.1:8091/node/controller/setupServices -d services=kv%2cn1ql%2Cindex curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=$COUCHBASE_ADMINISTRATOR_USERNAME -d password=$COUCHBASE_ADMINISTRATOR_PASSWORD curl -i -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/settings/indexes -d 'storageMode=memory_optimized' curl -v -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/pools/default/buckets -d name=$COUCHBASE_BUCKET -d bucketType=couchbase -d ramQuotaMB=128 -d authType=sasl -d saslPassword=$COUCHBASE_BUCKET_PASSWORD dormir 15 curl -v http://127.0.0.1:8093/query/service -d "statement=CREATE PRIMARY INDEX ON `$COUCHBASE_BUCKET`" fg 1 |
Couchbase Server tiene una interfaz RESTful que nos permitirá completar el asistente de configuración a través de un script. Los comandos anteriores definirán las especificaciones y servicios de la instancia, las credenciales administrativas y un Bucket.
Te darás cuenta de cosas como $COUCHBASE_ADMINISTRATOR_USERNAME
dentro del script. Al igual que en la aplicación Golang estamos aprovechando las variables de entorno para evitar tener que hardcode valores en nuestra imagen. Estos valores pueden ser definidos durante el despliegue del contenedor.
Puede encontrar más información sobre el aprovisionamiento de un contenedor de Couchbase Server en un archivo artículo anterior que escribí sobre el tema.
Finalmente podemos construir esta imagen personalizada de Couchbase Server. Desde Docker Shell, ejecute lo siguiente:
1 |
docker build -t couchbase-custom . |
El comando anterior nos dejará un couchbase-personalizar
imagen.
Despliegue del microservicio en contenedores
Hay varias formas de desplegar las imágenes que hemos creado. Aquí exploraremos dos de ellas.
Dado lo que sabemos sobre nuestras imágenes, podemos desplegar nuestros contenedores con los siguientes comandos:
1 2 3 4 5 6 7 8 9 |
docker run -d \ -p 8091-8093:8091-8093 \ -e COUCHBASE_ADMINISTRATOR_USERNAME=Administrador \ -e COUCHBASE_ADMINISTRATOR_PASSWORD=contraseña \ -e COUCHBASE_BUCKET=default \ -e COUCHBASE_BUCKET_PASSWORD= \ --network="docker_default" \ --name couchbase couchbase-personalizar |
El comando anterior desplegará Couchbase Server mientras mapea los puertos necesarios al sistema operativo anfitrión. Estamos pasando cada variable de entorno con el comando y definiendo una red operativa. La red es importante porque quieres que tanto tu base de datos como tu aplicación estén en la misma red de contenedores.
1 2 3 4 5 6 7 |
docker run -d \ -p 12345:12345 \ -e COUCHBASE_HOST=couchbase \ -e COUCHBASE_BUCKET=default \ --network="docker_default" \ --name golang proyecto golang |
El comando anterior desplegará nuestra imagen Golang mientras mapea los puertos correctos de nuevo. Para el host notarás que estamos usando couchbase
que es en realidad el nombre de nuestro otro contenedor. Los nombres de los contenedores también actúan como nombres de host.
En mi opinión, los comandos que acabas de ver son una forma muy manual de hacer las cosas. En su lugar puedes crear un Docker Compose para que se encargue de ello.
Crear un docker-compose.yml en algún lugar de su ordenador con lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
versión: "2 servicios: couchbase: imagen: couchbase-custom puertos: - 8091:8091 - 8092:8092 - 8093:8093 medio ambiente: - COUCHBASE_ADMINISTRATOR_USERNAME=Administrador - COUCHBASE_ADMINISTRATOR_PASSWORD=contraseña - COUCHBASE_BUCKET=por defecto - COUCHBASE_BUCKET_PASSWORD= CONTRASEÑA golang: imagen: golang-project puertos: - 12345:12345 medio ambiente: - COUCHBASE_HOST=couchbase - COUCHBASE_BUCKET=por defecto reinicio: siempre |
Todo en un archivo Compose estará en la misma red. Para desplegar cada servicio, ejecute lo siguiente:
1 2 |
docker-compose run -d --service-ports couchbase docker-compose run -d --service-ports golang |
Si estás familiarizado con Docker Compose estarás familiarizado con docker-compose up
pero no podemos usarlo aquí. Couchbase Server no tiene un mecanismo para decirnos que está listo, así que no queremos que la aplicación Golang intente conectarse antes de que esté configurada. Definitivamente no es un gran problema cuando se trata del despliegue de contenedores.
Conclusión
Acabas de ver cómo crear un microservicio Golang como un Docker que se comunica con Couchbase Server, también ejecutándose dentro de un contenedor Docker. Al convertir tu aplicación en una imagen Docker se vuelve muy fácil de distribuir porque no tendrás que preocuparte por el entorno en el que intentas desplegarla. Siempre que el motor Docker esté disponible puedes desplegar tu aplicación. Un ejemplo útil de esto sería en despliegue continuo de contenedores utilizando Jenkins.
¿Quiere ver cómo contenerizar su aplicación Java? Eche un vistazo a este tutorial Escribí. También tengo un tutorial para contenerizar una aplicación Node.js aquí.
¿Quieres más información sobre el uso de Golang con Couchbase? Echa un vistazo a la Portal para desarrolladores de Couchbase para consultar documentación y ejemplos.