26 oct 2020

¿Y el servidor en Scala? Akka está.

 Continuando con esta seguidilla de posts sobre Scala y retomando desde el post anterior Ahora vamos a a hacer un experimento un poquito más avanzado: vamos a intentar crear un servidor web con Scala.

Akka es aparentemente el servidor http mas famoso del ecosistema de Scala. Según lo que se puede leer en el sitio de Akka.io, “Akka es un conjunto de herramientas para crear aplicaciones basadas en mensajes altamente concurrentes, distribuidas y resilientes para Java y Scala.” Este incluye tanto un servidor http que implementa el patrón Actor Model como un cliente para hacer llamadas http.

En este post mi plan es tratar de hacer un pequeño proyecto en Scala utilizando Visual Studio Code, que implemente un servidor que pueda responder a un request GET, con un “Hi, Akka server is running” o algo por el estilo.

Primero voy a abrir VS Code en una carpeta vacía y crearé un archivo build.sbt con las siguientes entradas:

lazy val akkaHttpVersion = "10.2.9"
lazy val akkaVersion    = "2.6.18"
lazy val root = (project in file(".")).
  settings(
    inThisBuild(List(
      organization    := "com.foy",
      scalaVersion    := "2.13.8"
    )),
    name := "AkkaTest",
     libraryDependencies ++= Seq(
    "com.typesafe.akka" %% "akka-http"                % akkaHttpVersion,     
    "com.typesafe.akka" %% "akka-actor-typed"         % akkaVersion,
    "com.typesafe.akka" %% "akka-stream"              % akkaVersion,   
    "ch.qos.logback"    % "logback-classic"           % "1.2.3",
    "org.scalatest"     %% "scalatest"                % "3.1.4"         % Test
  )
)

Según entiendo esta es la mínima configuración y dependencias necesarias para crear el más básico de los ejemplos de un servidor Akka

Ahora voy a crear la ruta normal de los archivos de Scala: src/main/scala y dentro de esta voy a crear un archivo main.Scala.


Dentro de este archivo voy a escribir el código necesario para crear una ruta llamada “Hi” que representa la llamada get y cuando esta llamada se complete, vamos a devolver el texto plano “Hi, Foy Akka server es running!”:
package com.foy.scala.AkkaTest

import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route

object SimpleRouter {
  val route: Route = path("Hi") {
    get {
      complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Hi, Foy Akka server is running!"))
    }
  }
}
Ahora añadiré el objeto principal que llamaré App que contendrá el método main que es el responsable de levantar el servidor. El código completo de archivo main.Scala quedaría algo así:
package com.foy.scala.AkkaTest

import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpRequest

import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContextExecutor}

import scala.util.Failure
import scala.util.Success

object SimpleRouter {
  val route: Route = path("Hi") {
    get {
      complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Hi, Foy Akka server is running!"))
    }
  }
}

object App {

  //Function to start the httpServer
  private def startHttpServer(routes: Route)(implicit system: ActorSystem[_]): Unit = {
    import system.executionContext
    val serverBinding = Http().newServerAt("localhost", 8080).bind(routes)
    serverBinding.onComplete {
      case Success(binding) =>
        val address = binding.localAddress
        system.log.info("Foy Akka Server online at http://{}:{}/", address.getHostString, address.getPort)
      case Failure(ex) =>
        system.log.error("Foy Akka Server Failed to bind HTTP endpoint, terminating system", ex)
        system.terminate()
    }
  }

  def main(args: Array[String]): Unit = {

    val rootBehavior = Behaviors.setup[Nothing] { context =>    
      startHttpServer(SimpleRouter.route)(context.system)
      Behaviors.empty
    }

    val system = ActorSystem[Nothing](rootBehavior, "akkaServerTest")
  }
}
Abrimos una terminal y arrancamos el servidor sbt

Podemos compilar nuestro código y  asegurarnos que todo esté bien ejecutamos el comando “compile” (dentro del server no hace escribir sbt antes). Y finalmente usamos el comando “run” para iniciar nuestro servidor Akka:


Como podemos observar, debido al log que pusimos en nuestro código, nos indica que nuestro servidor está corriendo, además de en cual ip y en cual puerto.  Ahora probemos nuestro endpoint “Hi”, como es un endpoint get podemos usar un Browser para acceder a la ruta http://localhost:8080/Hi la cual nos muestra el mensaje que especificamos en la resolución de la ruta:
O lo podemos hacer con cliente REST cualquiera

!Y ya tenemos un servidor corriendo! 

Tratemos de entender un poco el código: El objeto simpleRouter establece una variable inmutable de tipo Route en la cual establecemos el path “Hi”, definimos que este cuando se invoque con el verbo get y que cuando se complete devolveremos un objeto de tipo httpEntity (el response según yo entiendo) que simplemente contendrá el texto que queremos. En el objeto App tenemos la función startHttpServer que inicia propiamente el objeto Http que representa nuestro servidor, el cual al iniciarlo le pasamos la ruta (o ip) y el puerto, por el cual responderá y además le pasamos, por medio del método bind, el ruteo que manejará. 

Hasta aquí está todo relativamente normal. Pero entonces ¿Qué esto esto de Actors y Behaviors? En nuestro ejemplo actual si lo notan no estamos usando nada de eso, cuando nos piden el parámetro ActorSystem le estamos pasando Nothing y como behavior estamos usando Empty. Sin embargo, los Actores, como se señaló al inicio del post, son el corazón de Akka y el siguiente post voy a tratar de explicarme de que va este asunto.

Antes de cerrar el post voy a refactorizar un poco, voy crear un archivo que se llame SimpleRouter.Scala para poner ahí el objeto que devuelve la ruta. Queda más o menos así:









18 oct 2020

Scala: Configurando ambiente de desarrollo

Retomando del post anterior. Los programas más complejos en Scala requieren de un configurar o contar con un ambiente de desarrollo más elaborado que únicamente el REPL Ammonite. Scala se puede programar utilizando Eclipse, IntelliJ Idea, VIM o inclusive Visual Studio Code (en realidad con cualquier editor de texto es posible). Viniendo de territorios Microsoft prefiero utilizar este último editor con el cual estoy familiarizado.

Para utilizar Scala con Visual Studio Code se recomienda utilizar una extensión llamado “Metals” que es una especie de “language server” que nos añade una serie de convenientes características a VS Code para programar en Scala.

Comencemos pues abriendo VS Code y buscando e instalando la extensión de Metals y aprovechamos e instalamos la versión oficial de la extensión para sintaxis del lenguaje:


Una vez con Metals instalado nos aparecerá un icono con el tab que contiene el set de herramientas correspondientes:


Para comenzar a utilizar Scala desde VS Code necesitamos abrir un folder y una vez con el folder abierto nos vamos al tab de Metals y hacemos clic en el botón New Scala Project. Una vez hecho esto la extensión comenzará a descargar lo necesario para crear una aplicación Scala, lo cual puede tardar un poco al tratarse de la primera vez y seguidamente nos solicitará un template para el proyecto. En mi caso, como buen “newbie” que soy, seleccionaré la plantilla para el famoso “Hello, Word”.

Seguidamente nos preguntara que si usamos el folder actual (o seleccionamos uno diferente) y el nombre del proyecto. Se nos indicará también que se detectó un workspace de Scala y que si queremos lanzar el editor de Scala y, finalmente, que si queremos exportar el build a lo cual responderemos afirmativamente (estos últimos mensajes aparecen en la esquina inferior derecha de Visual Studio). Al final terminaremos con algo similar a esto:

Si desde este nuevo editor abrimos la extensión de Metals vamos a ver que en la primera sección se encuentra el proyecto que acabamos de crear, así como sus dependencias:

Si volvemos al tab del Explorer es importante notar el archivo build.sbt

Este archivo es muy importante ya que que en él se definen algunas propiedades, como la veersón de Scala que estamos usando y las dependencias de terceros necesarias para compilar y ejecutar un programa por medio de la herramienta de build de Scala (sbt= Scala build Tool)

Ahora volvamos la mirada a nuestro flamante código fuente y procedamos a abrir el archivo Main.Scala que se encuentra en src/Main/Scala. Aquí veremos que nuestro código actual es un simple println con la poderosa frase “Hello, World”.


Vamos a probar la escritura de código en Scala dentro de Visual Studio con las extensiones que le instalamos, principalmente el autocompletado y las sugerencias al escribir código. Vamos a crear una variable inmutable de tipo string para guardar un nombre y vamos a cambiar la salida para mostrar el Hello y el nombre almacenado en la variable:


Son ejemplos muy básicos, pero sirven para mostrar las bondades de escribir código Scala en este editor con las extensiones adecuadas. Al final terminamos con algo similar a esto:

Ahora finalmente, ¿Cómo ejecutamos este código? Primero nos aseguramos que nuestro archivo esta guardado con los últimos cambios y luego tenemos dos posibilidades: usar el tab de ejecución y debugging de VSCode o bien usar la consola y utilizar el comando sbt run.

Si usamos el botón “Run and Debug” se levantará la maquina virtual de java y se nos solicitarán algunos permisos de acceso como este, a los que contestaremos afirmativamente:

Para finalmente ver el resultado en la consola de debgug:


Y se utilizamos la terminal y el comando sbt run veremos la compilación del proyecto y el resultado de la ejecución en la misma terminal:


También se puede utilizar el comando sbt compile para únicamente compilar el código sin necesidad de ejecutarlo.

Es muy muy importante señalar que al utilizar el botón de “Run and Debug” realmente nos permite depurar. Por ejemplo, añadiendo un breakpoint la ejecución se detendrá y podemos recorrerlo paso a paso:


Hasta aquí esta entrada del paseo que estoy intentando hacer en Scala.


11 oct 2020

Scala... "pasos chiquitos"

Siempre mi "fuerte" han sido las tecnologías Microsoft. Pero hace rato que quería intentar algo diferente. Hace poco me topé con el nombre de Scala, me interesé y pensé ¿Por qué no echarle un ojo para ver de que se trata..?

Scala es un lenguaje de programación basado en Java. Es multi paradigma lo que quiere decir que soporta tanto programación orientada a objetos, estructurada y programación funcional.

Siendo un lenguaje de alto nivel con las características propias de uno,  tiene algunas particulares muy interesantes como por ejemplo tipado estático fuerte, inferencia de tipos, variables mutables e inmutables.

¿Cómo instalar Scala? 

Para instalar Scala en Windows necesitamos descargar el Instalador de Scala para Windows de la siguiente dirección Getting Started | Scala Documentation (scala-lang.org). Abrimos el archivo y corremos el ejecutable, éste instalará Scala junto con otras utilidades como el REPL mejorado Ammonite o inclusive la máquina virtual de Java si es que aun no la tenemos instalada.

Una vez ejecutado este instalador pues si nos vamos a una terminal de sistema (PowerShell) y digitamos scalac (el compilador de Scala) deberíamos recibir una respuesta como esta:

 


Indicándonos que ya podemos empezar a jugar con Scala

Empezando a jugar con Scala.

La forma más fácil de empezar con Scala, antes de empezar con archivos, scritps y compilación, es familiarizarse con su sintaxis a través del REPL (Read Eval Print Loop) que es un interprete de Scala que nos permite escribir sintaxis de Scala y obtener el resultado de su ejecución. En este caso vamos usar Ammonite digitando “amm” en una ventana de comandos

 


Aquí podemos escribir código en cada línea y a dar enter nos dará el resultado de la evaluación de la misma. Por ejemplo

 


Aquí hemos sumados un par de números en la primera línea y concatenado 3 strings en la siguiente, y concatenado el resultado de la primera línea con el resultado de la segunda. Como podemos notar Scala infiere el tipo de cada resultado.

Podemos hacer cosas un poco más complejas y usar por ejemplo tipos de Scala como las colecciones iterables tipo seq:

 


Hagamos un pequeño ejercicio

Definamos una variable inmutable de tipo secuencia de enteros, una función que reciba como parámetro la secuencia de enteros y devuelva la suma de los mismos. Veamos:

 


Analicemos lo anterior:

Definimos la variable inmutable llamada “numeros” usando la para reservada val. Como vemos no hace falta definir su tipo ya que se infiere de la asignación que realizamos inmediatamente después de su declaración, en este caso, una secuencia de enteros. Sin embargo es importante señalar que el tipo se puede establecer utilizando dos puntos y el tipo (val numeros: Seq[Int] por ejemplo). 

Seguidamente definimos la función “suma” utilizando la palabra reservada def. Esta función recibe un parámetro llamado lista, aquí sí es obligatorio definir el tipo de los parámetros, para que el compilador sepa cual es el tipo esperado por la función, y definimos el tipo que devolverá función (Int), esto último no es obligatorio ya que el compilador puede inferirlo del valor que devuelve la función pero es una buena practica establecerlo sobre todo en funciones públicas. Luego de la función colocamos un “=” y la expresión que ejecutará la función (podemos usar {} para definir un conjunto de instrucciones). En este caso usamos la función fold del tipo secuencia para realizar la suma de todos los números (la función fold recibe un valor inicial, cero este caso, luego itera sobre los elementos de la secuencia utilizando la función entre paréntesis,_ +_ aquí, donde los parámetros son el ultimo resultado de la misma función o el valor inicial y el valor actual del item de la secuencia en cada iteración). 

Finalmente llamamos a la función “suma” usando la variable “numeros" como parámetro y obtenemos el resultado. 

Como podemos notar el utilizar el REPL es muy practico para comenzar a practicar con las instrucciones de Scala.

Un último apunte en esta entrada del blog: para salir de Ammonite usamos el comando exit. 


12 sept 2020

Docker Introducción: Primeros pasos

 Hace tiempo que quería tener mi primer contacto con Docker. Sabía, conceptualmente y muy básicamente de que trata, es decir, que es una especie de virtualización: en la cual contamos con “imágenes” que tienen lo estrictamente necesario para que, por ejemplo, una aplicación web se ejecute independiente del sistema operativo y sus particulares condiciones. Con una una imagen creada ésta debería ejecutarse en un “contenedor” exactamente igual en mi máquina de desarrollador como en el ambiente de pruebas o en producción (en realidad en cualquier escenario), inclusive si estos ambientes están en la nube.

Entonces, aterrizando un poco más los conceptos: una imagen en Docker esta compuesta, por capas donde la primera capa seria una especie de sistema operativo mínimo (con lo mínimo requerido para lo que sea que vayamos a crear o utilizar se ejecute correcta y eficientemente), otras capas serian componentes esenciales para nuestro propósito (en el ejemplo de una aplicación web, es necesario un servidor web), configuraciones, variables de ambiente o entorno, y encima de estas capas estarían los archivos necesarios para que nuestra aplicación se ejecute. Algo muy importante a tomar en cuenta es que las imágenes son inmutables, o sea, una vez creadas, no cambian o se alteran.

Un contenedor de Docker, es la ejecución de una imagen, y éste si es modificable, sin embargo, una vez eliminado el contenedor todo lo que no sea parte de la imagen se perderá. Es decir, si tenemos una aplicación que guarda archivos dentro de su contenedor, cada vez que creemos un contenedor a partir de su imagen siempre se comportará de la misma manera, y podremos “subir” archivos, pero una vez eliminado el contenedor todos los archivos “subidos” se perderán.

Ahora bien ¿Qué necesitamos para comenzar a utilizar Docker en un ambiente de desarrollo basado en Windows? Necesitaríamos el Docker Desktop el cual lo podemos encontrar en este Link. Una vez instalado tendremos un icono como este (la imagen incluye la configuración por defecto al instalar el Docker Desktop).

Como se mencionó, en la imagen anterior vemos la configuración por defecto. El aspecto más importante aquí (según yo) es la que dice “Use the WSL 2 based engine” esto significa que Docker utilizará el motor basado en el “Windows Subsystem for Linux” siendo 2 la versión más reciente de este subsistema incluida dentro Windows 10. En teoría se podría usar Hyper-V, pero usar WSL es la manera más optima actualmente.

Ahora demos nuestros primeros pasos con Docker, nuestro objetivo es el siguiente: vamos a crear un mini sitio web a partir de cual crearemos una imagen Docker y luego la montaremos en un contenedor, todo utilizando VSCode.

Primero dentro de un folder (DockerTest en mi caso) creamos una carpeta llamada html y dentro de esta creamos un archivo llamado index.html con apenas código, quedando algo similar a lo siguiente: 


Usando la extensión VSCode “Live Server” podemos hacer clic en el ícono de la barra inferior que dice “Go Live” y podemos ver el resultado de la ejecución de esta pequeña página html. 


Obviamente esto se está ejecutando en un servidor de mi máquina, de momento no tiene nada que ver con Docker. Pero a eso vamos de inmediato. Primero necesitamos instalar la extensión de Docker para VSCode lo que nos dará intellisense para los archivos de configuración de Docker y alguna funcionalidad adicional. 


Con esta herramienta ya instalada vamos a crear un archivo fuera de la carpeta html, es decir en el directorio raíz llamado dockerfile (sin ninguna extensión). De manera que se vea así: 


En este dockerfile se va a definir como se crearán las imágenes que eventualmente se ejecutaran en contenedores para nuestro proyecto. Estos dockerfiles se componen de pasos. En este caso debemos definir los pasos para crear las diferentes capas que contendrá la imagen que eventualmente “servirá” la página html que acabamos de crear. ¿Cómo sabemos cómo crear un dockerfile para una necesidad en particular? Pues existe este sitio Explore Docker's Container Image Repository | Docker Hub por medio de cual podemos obtener los pasos requeridos para obtener los resultados deseados. Tiene una amplia colección por lo que deberíamos mantenerla a mano.

En mi caso deseo una imagen que me permita servir páginas html, por lo que elegiré httpd que permite crear una imagen de un servidor apache 


En la misma página más abajo (es importante leer toda la documentación) hace mención de la variante Alpine la cual se basa en la distribución de Linux Alpine la cual es muy pequeña. Por lo cual es la que vamos a usar. El contenido de nuestro dockerfile quedaría entonces de la siguiente manera:


Obviamente en un dockerfile se puede definir muchas cosas más, pueden ser bastante largos. Para nuestro pequeño experimento este par de líneas son suficientes. La primer línea define el tipo de imagen que queremos: un servidor apache basado en Linux Alpine y con la segunda línea copiamos nuestros archivos a la estructura donde el servidor apache de la imagen los va a necesitar.

Una vez con este dockerfile creado podemos empezar nuestra aventura con docker. Abrimos una terminal dentro de VS Code y podemos empezar a jugar con los comandos. Por ejemplo, “docker images” nos mostrará las imágenes que tenemos en nuestro Docker Engine, si lo ejecutamos nos indicará que ahora mismo no tenemos ninguna imagen creada 


Usando el siguiente comando creamos nuestra primera imagen “docker build -t docker-test:1.0.0 .”. Estamos usando el comando build con el parámetro -t para darle nombre a la imagen, en este ejemplo docker-test, lo que esta después de los dos puntos es un tag. Es importante notar que se debe colocar un punto al final antes del cual hay un espacio, ese espacio adicional es de olvido frecuente. Además, este comando se debe ejecutar en la carpeta donde se encuentra nuestro dockerfile.


Ahora, si de nuevo ejecutamos el comando “docker images” veremos la imagen recién creada 


Las primeras dos columnas corresponden con los parámetros del comando build. La columna Image Id, nos sirve como identificador único de la imagen. Por ejemplo, si quisiéramos saber como se conformó esta imagen podemos utilizar el comando “docker image history ed1” donde ed1 son los primeros tres caracteres de la image id (hay que usar suficientes caracteres para una única e inequívoca identificación) 


Podemos decir que esta lista corresponde a las diferentes capas de nuestra imagen donde la entrada más inferior (5.6 MB) corresponde al sistema operativo, la entrada de 49.2 MB corresponde al servidor web, y la entrada superior (181 B) corresponde a nuestra pagina html. Es muy importante notar la palabra “missing” en la columna Image, quiere decir que todos esos archivos están dentro de nuestro Docker Engine, es decir, se crean una única vez dentro del gestor, por lo que si se crea una segunda imagen estos paquetes ya existen y no es necesario bajarlos nuevamente; también significa que si creamos un contenedor este usará estos archivos y al crear un segundo contenedor éste re-usará los mismos archivos por lo que estaríamos ahorrando mucho espacio.

Bueno, ya tenemos nuestra imagen, ahora ¿Qué sigue? Pues bien, como se mencionó anteriormente las imágenes son “read only”, lo que sigue es ejecutar esta imagen a través de un contenedor. Para esto ejecutamos el siguiente comando “docker run --name contenedor01 -p 8080:80 docker-test:1.0.0”. Para explicar un poco el comando diré que luego del parámetro --name va el nombre que le daremos a nuestro contenedor (contenedor01), seguidamente, como en este caso estamos usando una imagen que tiene un servidor web, definimos el parámetro -p que es para mapear puertos entre el contenedor y el de la maquina host (mi maquina), a la izquierda de los dos puntos va el puerto local de mi maquina (8080) y a la derecha para el puerto al que corresponde dentro del contenedor (80) y finalmente se debe proporcionar el nombre y el tag de la imagen que vamos a ejecutar (docker-test:1.0.0) 


Si en este punto vamos a nuestro navegador y usamos la ruta localhost:8080 deberíamos ver que nuestro contenedor esta sirviendo la página html que habíamos creado 


¡Y aquí lo tenemos, nuestro primer contenedor Docker funcionando como se debe!

Podemos listar los contenedores que tenemos en nuestro gestor utilizando el comando “docker ps -all” (si usamos “docker ps” únicamente nos listará los contenedores que se estén ejecutando) 


Tal y como se aprecia en la imagen, tenemos un container id que nos sirve para referenciar el contenedor. De esta manera podemos usar el comando “docker stop b14” para detener la ejecución del contenedor, donde b14 corresponden a los primeros caracteres del container id


Si intentamos de nuevo la ruta locahost:8080, ésta ya no estará disponible porque el contenedor está detenido. 


Para volver a iniciar el contenedor podemos usar el comando “docker start b14” con lo que el contenedor volverá a iniciar su ejecución y la página de nuevo estará disponible.

Ahora realicemos algunos ajustes y añadidos a nuestro sitio web. Primero ajustemos la palabra “imágenes” de manera que utilicemos el código html de la letra acentuada, además voy a añadir una segunda página que se puede llamar desde la primera y viceversa quedando más o menos así: 


Ahora, sin tocar nuestro dockerfile, hagamos una segunda imagen de esta nueva versión de nuestro mini sitio web, utilizando el siguiente comando imagen “docker build -t docker-test:1.0.1 .” 


Como podemos constatar, la única diferencia con respecto a la primera vez que ejecutamos este comando es que cambiamos el tag de 1.0.0 a 1.0.1. Si ejecutamos de nuevo el comando “docker images” corroboraremos que ahora tenemos dos imágenes: 


Ahora crearé un segundo contenedor basado en esta segunda imagen por medio del siguiente comando “docker run --name contenedor02 -p 8081:80 docker-test:1.0.1” de manera que en mi maquina se utilice el puerto 8081 para acceder a este segundo contenedor. 


Ahora con el comando “docker ps -a” podemos verificar el numero de contenedores que tenemos creados:  


Aquí podemos verificar que nuestro contenedor02 se está ejecutando pero el contenedor01 no, por lo procedemos a iniciarlo usando el comando “docker start b14” y seguidamente probamos ambas direcciones:

localhost:8080 


localhost:8081   



Cómo se ve, en la segunda versión se ajustó correctamente la letra acentuada y se añadió más funcionalidad, sin embargo, podemos ejecutar aun la primera versión, al mismo tiempo, inclusive, que la segunda y compararlas. Esto nos puede ayudar con varias tareas propias del desarrollo.

Finalmente, para terminar estos primeros pasos voy a limpiar todo. 

Para detener los contenedores utilizo el comando “docker stop b14” y “docker stop d57”  y para borrar ambos contenedores el comando “docker rm b14” y docker rmd57” y ejecutamos el comando “docker ps -a” para verificar que ya no contamos con ningún contenedor. (hay una forma más eficiente de realizar esta tarea que incluyo al final de la entrada)


Ahora si deseamos eliminar las imágenes utilizamos el comando “docker rmi 56e” y “docker rmi ed1” y ejecutamos el comando “docker images” para verificar que no queda ninguna imagen:  


Notas, recapitulación y recomendación 

Hasta aquí todo lo hemos hecho a punta de comandos en la terminal, pero es importante también saber que el Docker Desktop tiene una interfaz gráfica que nos ayuda a gestionar imágenes y contenedores. Por ejemplo, listar los contenedores que tenemos y desde aquí ejecutarlos, detenerlos o borrarlos 


Ver la interacción de un contenedor en ejecución  


O listar las imágenes y a partir de ellas crear el contenedor 


En esta ultima imagen un detalle importante a notar es la etiqueta Total size. A pesar de contar con dos imágenes ambas con 54.52 MB, nos indica que el tamaño total es de 54.52 MB, no el doble, esto es, como se mencionó anteriormente, porque ambas imágenes comparten la mayoría de capas.


Resumen de comandos básicos de Docker:

Uso

Comando

Comentario

Listar imágenes

docker images

 

Listar Contenedores

docker ps -a

Sin el parámetro -a (-all) se listan solo los contenedores en ejecución

Crear imagen

docker build -t <nombre:tag> .

Se debe estar en una carpeta que contenga el dockerfile a partir del cual se crea la imagen. No olvidar espacio y punto final

Historia de imagen

docker image history <imageID>

 

Crear contenedor

docker run --name <nombreContenedor> -p <puertoLocal:puertoContenedor> <nombreImagen:tag>

En este caso se usa el parámetro -p por ser una imagen de servidor web

Iniciar contenedor

docker start <containerID>

 

Detener contenedor

docker stop <containerID>

 

Eliminar contendor

docker rm <containerID>

Se pueden borrar varios con un solo comando separando los ids por un espacio.

Eliminar imagen

docker rmi <imageID>

Se pueden borrar varias con un solo comando separando los ids por un espacio. Se puede usar -f como parámetro adicional para forzar la eliminación

Recordar que no es necesario escribir todo el Image ID o Container ID basta con los primeros caracteres siempre que permitan una única e inequívoca identificación (de hecho, es posible solo usar el primer carácter si no tenemos muchos ítems en nuestros repositorios)

Una última recomendación

Todos los comandos que se utilizaron en este post se introdujeron en una terminal tipo bash, pero se puede usar una terminal powershell que puede ser preferible dada la flexibilidad de uso. Por ejemplo, podríamos tener lo siguiente 


Son dos imágenes y dos contenedores ejecutándose y queremos detener y eliminar todos los contenedores e imágenes. Como ya vimos, para hacerlo tradicionalmente necesitamos los containers id e images id, sin embargo, con powershell podríamos usar el siguiente comando para detener todos los contenedores en ejecución “docker stop $(docker ps -q)” (docker ps -q devuelve solo los containers id de los contenedores en ejecución). Seguidamente podemos usar “docker rm $(docker ps -a -q)” (como habrán intuido docker ps -a -q devuelve los containers id de todos contenedores aun cuando no se estén ejecutando) y finalmente para borrar todas las imágenes ejecutaríamos el siguiente comando “docker rmi $(docker images -q)” (efectivamente, docker images -q devuelve los images id de todas las imágenes) 


El uso de pawershell nos podría hacer la vida más sencilla cuando utilizamos Docker.

El mundo de Docker es muy amplio, esta entrada es apenas una mínima introducción a partir de la cual podemos seguir explorando qué otras cosas podemos hacer, qué otros beneficios podemos obtener del uso de Docker en nuestros desarrollos.