Etiqueta: docker

Servicios básicos para startups con Ansible y Docker

Por anteriores post, habéis podido ver, que tengo una predilección enorme por Ansible y Docker, y todas sus posibilidades. Tanto es así que en mis servidores, ya no despliego nada que no vaya en contenedores, y si puedo, lo automatizo a través de Ansible.

Mi último descubrimiento fue Nextcloud: Un servicio de nube privada, que a su vez tiene mensajería instantánea, y videoconferencia integrada, que una vez instalado en tu servidor no necesitas depender de servicios de terceros. Si a eso se le añade la compatibilidad para poder editar documentos on-line gracias a Collabora y Onlyoffice, el potencial de este servicio se dispara hasta limites insospechables.

Teniendo un servidor infra utilizado, decidí instalarlo en el, para tener mis archivos sin que ninguno de los gigantes tecnológicos tuviera acceso a mis datos, pero me quedé prendado de la cantidad de posibilidades que presenta, y me di cuenta de algo tan obvio que no podia dejarlo pasar.

Nextcloud es el servicio perfecto para pequeñas empresas o startups que estén empezando, y no necesiten una infraestructura enorme, sin tener que depender de servicios de terceros, con una inversión mínima. Y ese es justo el punto en el que se me encendió la bombilla.

Servicios cloud

¿Por que no crear un instalador de servicios básicos para startups con Nextcloud? ¿Y si lo llevo un paso más allá, y no solo le añado un cloud privado, sino también un servidor propio de e-mail? Dos pájaros de un tiro, y los servicios básicos que toda empresa pequeña pueda necesitar, listos y preparados de forma sencilla. Encontré una solución integral de servidor de e-mail llamado Poste.io gratuito (Aunque con versión de pago con más características) que también se despliega con Docker, y es perfecto para el objetivo que tenía en mente.

Era el pequeño proyecto perfecto para poder usar Ansible y Docker unidos en todo su potencial.

Servicios para desarrollo de software

Una vez puesto al tema, y ver que todo iba viento en popa, la bombilla se volvió a encender, y lleve el instalador un paso más allá para crear el instalador definitivo para startups que se dediquen a desarrollo de software.

Ya tenía experiencia montando Gitlab, Jenkins y Nexus en mi empresa, así que… ¿Por qué no añadirlo al paquete? La combinación de nube privada, y servicios privados para entornos de desarrollo quedaba perfecta.


Manos a la obra…

Os explicaré brevemente los pasos que da el instalador, para tener todo listo simplemente con cambiar 2 variables, ponerle los datos de acceso de la máquina destino, configurar DNS para que apunten a tu máquina, y si no es un servidor que esté directamente en Internet, un mapeo de puertos.

1 – Instalación de dependencias

Para poder lanzar los servicios, se necesita tener instalado en la máquina docker, y docker-compose para un manejo mas simple de los contenedores. Además se le instalan dependencias de Python necesarias para que Ansible ejecute correctamente algunas tareas de Docker.

Se le da permisos al usuario para poder ejecutar contenedores de Docker si no es root, y listo: Máquina preparada para empezar

2 – Comprobación de DNS

Dado que van a ser unos servicios que tengan acceso desde Internet, compruebo mediante unas consultas de DNS si las IPs de los sub-dominios donde se van a desplegar los servicios, concuerdan con la IP pública del servidor.

3 – Instalación de proxy base

Para poder manejar todos los servicios desde una misma máquina, y además con Docker, es necesario ponerle delante un proxy para poder tratar las peticiones, y lo más importante, usar https sin tener que configurarlo en todos y cada uno de los servicios. En este caso he usado Nginx ya que tengo experiencia con el, y siempre me ha funcionado bien.

En este punto cabe destacar, que los certificados https pueden llevar un coste asociado, que no cuadra mucho con la filosofía de este instalador (Aunque al final se pueden añadir manualmente en la instalación si ya los tienes). ¿Solución? Let’s Encrypt: Con esto siempre que tengas configurado tu servidor web de una forma específica, puedes generar certificados https de forma gratuita, y además incluso renovarlos de forma automática para desentenderte de todo.

En este punto, le añado opcionalmente un servidor web con https al dominio raíz, por si en el futuro se quiere desplegar algo aquí.

4 – Instalación de proxy para servicios

Cada servicio lleva asociado un subdominio para su despliegue (Los despliegues en subdirectorios de un dominio, generan muchos dolores de cabeza, e incompatibilidades que no son recomendables), y por supuesto su certificado https, por lo cual, aplicando el mismo procedimiento que en el anterior punto, se genera certificados para los subdominios de Nextcloud, Poste.io, Collabora, Onlyoffice, Gitlab, Jenkins y Nexus, dejándolos preparados para el despliegue de cada uno de ellos

5 – Instalación de todos los servicios

Cada servicio tiene sus configuraciones específicas, redirecciones, restricciones de seguridad, triquiñuelas, etc… , que requieren muchas horas de investigación en base a documentación y prueba y error.

Al final de cada despliegue hecho a base de ficheros docker compose que agrupan todas las dependencias de un posible servicio, se le añade la configuración específica al proxy, para que sea capaz de redirigir las peticiones correctamente mediante https con el certificado generado anteriormente.

Sin duda este es el paso más laborioso, ya que todos los servicios son completamente distintos y requieren de laboriosas configuraciones que lleva su tiempo entender.

5 – Obtención de passwords de admin

Los servicios de Jenkins y Nexus, al arrancar generan una password de administrador, que solo se puede ver a través de la consulta de determinados ficheros generados tras completar la instalación. Yo quería que la instalación fuera lo menos dolorosa posible, así que al finalizar, saco por pantalla las passwords de administrador iniciales de estos servicios, para que puedan usarse de forma inmediata.


Con este proceso te queda un servidor listo para poder acceder a el, y crear usuarios y configuraciones ya especificas para cada servicio, pero sin haberte preocupado de toda su instalación, que ha durado poco más de 10 minutos.

Toda la codificación en scripts de Ansible me ha llevado unas 40 horas, pero me lo he pasado en grande combinando todas estas herramientas para poder tener un instalador muy completo. Algunas personas se divierten viendo la tele, otras haciendo deporte, y los que tenemos alma de programador, haciendo cosas como esta.

Github

¿Que sentido tiene aprovechar unas herramientas gratuitas, si luego otra gente no puede aprovecharlas de la misma forma? Así que toca compartirlo con todo el mundo, y si alguno se decide a mejorarlo, los pull request son bienvenidos 😉

Ansible Web Tools installer on Github

El vicio de Docker

¿Alguna vez os habéis encontrado una tecnología/gadget tan versátil que os gustaría que estuviera todo ahí? Algo así como el smartphone actual, donde si pudieras, pedirías que con pulsar una sola tecla en la pantalla, que te preparase el café, te leyese las noticias, te pusiera en la cama la ropa que te vas a poner ese día, y que además te teletransportase al trabajo. Pues eso mismo me pasó a mi con Docker.
Docker
En una entrevista de trabajo que tuve, me hablaron de Docker por primera vez, como una forma de virtualizar aplicaciones, pero que no requería un OS completo de por medio. Para mi eso era toda una novedad, ya que me habría venido muy bien para virtualizar los servidores que uso para CoResponde en vez de tener que configurármelos uno a uno con todos los pequeños detalles. Solo crear una imagen de Docker especifica con todas las configuraciones, y lanzar contenedores de esa imagen en otros servidores. Más fácil imposible.
Desde el momento que empecé a enredar más a fondo con ello, vi que si pudiera, Dockerizaria hasta las sartenes de la cocina. Su potencia me sorprendió, y el rendimiento era extremadamente bueno.
Debo deciros antes de nada, que Docker está pensado para ejecutar en máquinas Linux, por lo cual si queréis hacerlo desde Windows o Mac, debereis usar una máquina virtual intermedia para poder funcionar. Pero por supuesto, esto no tiene ningún sentido, ya que si vamos a usar Docker, es porque no queremos hacer una virtualización tradicional. Así que si quieres usar Docker, ya estás tardando en pasarte a Linux.
Ahora entramos en la chicha de Docker. Esto es como funcionaría una máquina virtual tradicional, con su sistema operativo completo corriendo por encima de un sistema de virtualización o hypervisor elegido.

stackvm

Solo con el sistema operativo completo, el rendimiento se reduce dramáticamente. Sin embargo, este es el stack de Docker.

stackdocker

Con esto nos quitamos de en medio todo el sistema operativo de la máquina virtual, y el motor de Docker usará los recursos nativos de la máquina, junto con los binarios y librerías estrictamente necesarios para poder correr las aplicaciones que se requiera.
El funcionamiento de Docker es relativamente sencillo: Se basa en imágenes que contienen todo lo necesario para ejecutar una, o varias aplicaciones, y lanzar contenedores (Instancias) de estas imagenes. Contruir una imagen propia es tan sencillo como crear un fichero de texto plano llamado Dockerfile en el cual, se configura la aplicación que quieras correr.
Ej. de fichero Dockerfile

FROM buildpack-deps:jessie-scm
# A few problems with compiling Java from source:
# 1. Oracle. Licensing prevents us from redistributing the official JDK.
# 2. Compiling OpenJDK also requires the JDK to be installed, and it gets
# really hairy.
RUN apt-get update && apt-get install -y --no-install-recommends \
 bzip2 \
 unzip \
 xz-utils \
 && rm -rf /var/lib/apt/lists/*
RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/jessie-backports.list
# Default to UTF-8 file.encoding
ENV LANG C.UTF-8
# add a simple script that can auto-detect the appropriate JAVA_HOME value
# based on whether the JDK or only the JRE is installed
RUN { \
 echo '#!/bin/sh'; \
 echo 'set -e'; \
 echo; \
 echo 'dirname "$(dirname "$(readlink -f "$(which javac || which java)")")"'; \
 } > /usr/local/bin/docker-java-home \
 && chmod +x /usr/local/bin/docker-java-home
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64
ENV JAVA_VERSION 8u72
ENV JAVA_DEBIAN_VERSION 8u72-b15-1~bpo8+1
# see https://bugs.debian.org/775775
# and https://github.com/docker-library/java/issues/19#issuecomment-70546872
ENV CA_CERTIFICATES_JAVA_VERSION 20140324
RUN set -x \
 && apt-get update \
 && apt-get install -y \
 openjdk-8-jdk="$JAVA_DEBIAN_VERSION" \
 ca-certificates-java="$CA_CERTIFICATES_JAVA_VERSION" \
 && rm -rf /var/lib/apt/lists/* \
 && [ "$JAVA_HOME" = "$(docker-java-home)" ]
# see CA_CERTIFICATES_JAVA_VERSION notes above
RUN /var/lib/dpkg/info/ca-certificates-java.postinst configure
ENTRYPOINT /usr/lib/jvm/java-8-openjdk-amd64/bin/java -version 

Este es el código del Dockerfile para tener un contenedor con java 8 instalado.
Lo primero que se puede observar es el siguiente texto :

FROM buildpack-deps:jessie-scm

Esto indica que estás heredando de la imagen con nombre buildpack-deps y la versión jessie-scm. Casi la totalidad de las imágenes de docker, heredan de una más básica (Existen imágenes de Ubuntu o Debian básicas muy útiles de las cuales partir), y van añadiendo funciones extra.
Todo lo demás son intrucciones de que debe tener instalado nuestra imagen, excepto la orden final, que debe ser un ENTRYPOINT o CMD que es la orden que se ejecuta cuando se inicia una instancia de esta imagen. En el ejemplo anterior simplemente se ejecuta un java -version que mostraría simplemente la versión de java. Este parámetro de entrada, puede ser sustituido por nuestro propio punto de entrada para una imagen propia.
Para convertir este archivo Dockerfile en una imagen lista para ejecutar, simplemente con ejecutar este comando desde el propio directorio donde se encuentra el Dockerfile, crearemos una imagen:

docker build -t imagen-java .

En este caso, estamos construyendo una imagen con nombre imagen-java, diciéndole que nuestro fichero Dockerfile, se encuentra en el directorio actual. Dependiendo de todo lo que hayamos definido en el fichero, tardará más o menos tiempo, y al final si ejecutamos

docker images

veremos que ya tenemos nuestra imagen lista.
A partir de aquí ya tenemos lo básico para empezar a trabajar con ella. Lo siguiente es lanzar un contenedor de docker. Un contenedor no es más que una instancia de esta imagen base, y que será equivalente a nuestra máquina virtual tradicional para poder entenderlo bien. Para lanzar un nuevo contenedor solo necesitamos el siguiente comando:

docker run --name container-java imagen-java

Y listo, ya tenemos nuestro primer contenedor creado, y veremos como por pantalla nos sale la versión de java que configuramos en nuestra imagen base.
Los contenedores de Docker se paran, en cuanto no tienen una aplicación en primer plano ejecutándose. Esto significa que si lanzas un proceso que empieza y termina, como por ej. el contenedor lanzado antes con java -version, en cuanto se acabe de ejecutar, el contenedor se para, y tendrías que volver a ejecutarlo con

docker start container-java

ya que no habría que crear otro, solamente re-arrancar el ya creado.
Por supuesto esto es algo demasiado básico que no nos sirve para nada, ya que no tenemos ninguna aplicación real corriendo.
Llegados a este punto, toca explicar que es Docker Hub. Docker Hub es una plataforma, donde almacenar y poner disponibles imágenes de Docker pre-compiladas, para poder ejecutar desde cualquier sitio. Resumiendo: Si ejecutamos

docker run tomcat:8.0

primero se comprobará que no tenemos disponible en local una imagen con ese nombre, irá a docker hub, comprobará que existe una imagen con ese nombre y versión, se la bajará a tu equipo, y después lanzará un contenedor que se quedará ejecutando un servidor tomcat versión 8. Como podéis ver la simplicidad de esto es infinita, y nos permite lanzar contenedores extremadamente rápido.
Hay que tener en cuenta, que estos contenedores son como máquina virtuales, y los puertos que usan las aplicaciones dentro, están aislados, por lo cual si queremos que estén accesibles en la máquina, debemos mapearlos. La sintaxis es la siguiente:

docker run <nombreimagen> -p <puerto externo>:<puerto del contenedor>

Un ejemplo de esto con un contenedor de tomcat sería

docker run tomcat:8.0 -p 80:8080

Con esto mapeamos el puerto por defecto del tomcat del contenedor, a nuestro puerto 80 de la máquina, por lo que con acceder a http://localhost/  estariamos ejecutando ya todo lo que contenga nuestro tomcat.
Se pueden tener múltiples contenedor de la misma imagen, por lo que tener dos servidores a la vez sería tan fácil como

docker run tomcat:8.0 -p 81:8080

y ya tendríamos otro tomcat corriendo en la misma máquina. Por supuesto, tiene que ser en un puerto distinto, ya que nuestro puerto 80 estaría ocupado por el contenedor anterior, y crearía un conflicto.
 
Espero que os haya servidor de ayuda, y a pesar de que no es un tutorial completo, hayáis echado un vistazo a una de las tecnologías que más me ha impresionado en los últimos tiempos.