Hoy me gustaría mostrar cómo podemos conectar nuestra aplicación rest dockerizada con una base de datos RDS la cual está detrás de un bastión y sólo podemos conectarnos a ella a través de un tunel SSH.
Cuando creábamos un tunel SSH para conectarnos a la base de datos desplegada en AWS, para poder crear la conexión lo teníamos que hacer a través de localhost usando el puerto que habíamos mapeado. Pero si hemos hecho el tunel en nuestro host, desde el contenedor donde despleguemos nuestra aplicación REST no tendremos acceso al tunel, ya que el «localhost» es de nuestra máquina anfitriona, no del contenedor.
Una solución para este problema es usar docker-compose para levantar dos contenedores distintos. Uno de ellos creará un tunel SSH con nuestra base de datos RDS y la otra levantará nuestra aplicación. Configuraremos el contenedor de nuestra aplicación rest para que dependa del primer contenedor y así configurar nuestra conexión para que use como host el contenedor donde hemos levantado el tunel.
Pongámonos manos a la obra y veamos cómo hacer esto. En primer lugar, vamos a dockerizar nuestro servicio rest securizado. Podemos ver cómo se hacía en este post.
Tenemos que modificar la configuración de nuestro application.properties para indicar que el host que vamos a utilizar es el host «mysql«. Este host será el que crearemos después con docker-compose. Tendremos que modificar la siguiente propiedad (dbschema será el esquema de bbdd que hayamos creado y al que nos queramos conectar).
spring.datasource.url=jdbc:mysql://mysql:3306/dbschema?useSSL=false
He creado una imagen secdevoops/spring-boot-docker:0.0.1-SNAPSHOT (después de añadir la configuración apropiada) con el comando:
$ mvn compile jib:dockerBuild
Si ahora intentamos levantar nuestra aplicación, veremos que hay un montón de errores puesto que nuestra aplicación no es capaz de conectarse con la base de datos.
$ docker run -p 8080:8080 secdevoops/spring-boot-docker:0.0.1-SNAPSHOT
Vamos ahora a crearnos un fichero Dockerfile para crear una nueva imagen que será la que cree un tunel SSH y a partir de la cual nos conectaremos desde nuestra aplicación.
FROM alpine:latest
WORKDIR /root
COPY Key.pem Key.pem
RUN mkdir .ssh
COPY known_hosts .ssh/
RUN sh -c "apk update && apk add openssh-client"
CMD ssh -i Key.pem -nNT -L *:3306:dbendpoint:3306 ec2-user@ec2endppoint
EXPOSE 3306
Vamos a utilizar como base una imagen de alpine (una distribución ligera de linux) y vamos a indicar el WORKDIR (directorio de trabajo) con /root. Después vamos a indicar que queremos copiar nuestra clave privada (deberemos haberla copiado antes donde estemos creando nuestro Dockerfile) para que se copie en el contenedor (se copiará el /root ya que lo hemos indicado como WORKDIR). A continuación vemos que le decimos a docker que ejecute el comando «mkdir .ssh«. Así crearemos esta carpeta en /root del contenedor. Copiaremos nuestro fichero known_hosts (lo tendremos en nuestro home en la carpeta .ssh) el cual nos servirá para confiar en el host al que nos queremos conectar (previamente hemos tenido que conectarnos desde nuestro propio host por ssh a la máquina EC2 que usabamos de bastión para tenerlo dentro de los hosts conocidos ). Continuamos indicando que queremos que se ejecute un script para actualizar nuestro contenedor e instalar openssh-client (RUN sh -c «apk update && apk add openssh-client»). Y finalmente, con la etiqueta CMD le estamos indicando a docker que queremos que se ejecute el comando que va a continuación que será el que nos cree el tunel. (IMPORTANTE! Debemos cambiar dbendpoint y ec2endpoint por los valores correspondientes). La última línea expone el puerto que vamos a utilizar.
Ahora, desde la ruta donde tengamos el fichero Dockerfile (junto con los ficheros Key.pem y known_hosts) ejecutaremos el siguiente comando para crear nuestra imagen:
$ docker image build -t tunel/rds .
Podemos comprobar que ahora tenemos las dos imágenes que hemos creado:
Ahora vamos a pasar a crear nuestro fichero docker-compose.yml usando estas dos imágenes creadas. Veamos el contenido del fichero:
version: '3'
services:
mysql:
image: tunel/rds:latest
expose:
- 3306
app:
image: secdevoops/spring-boot-docker:0.0.1-SNAPSHOT
expose:
- 8080
ports:
- "8080:8080"
depends_on:
- mysql
secdevoops/spring-boot-docker:0.0.1-SNAPSHOTLo primero que se indica es la versión de docker-compose que estamos utilizando. Después añadimos los dos servicios que vamos a utilizar. Por un lado la base de datos mysql, que usaremos la imagen tune/rds:latest y se expone en el puerto 3306 y, por otro lado, nuestra aplicación, en la que usamos la imagen secdevoops/spring-boot-docker:0.0.1-SNAPSHOT, exponemos por el puerto 8080 (recordemos que esto es a modo de información) y mapeamos el puerto 8080 el puerto 8080 de nuestro host. Finalmente, indicamos que dependemos del servicio mysql.
Ahora que ya tenemos nuestro fichero docker-compose, solo tenemos que levantar los servicios con el siguiente comando (la opción –build solo es necesario la primera vez que creemos nuestros servicios con docker compose):
$ docker-compose up --build
Vemos que al levantar docker-compose se han levantado ambos servicios (mysql y app) y que nuestra aplicación rest se ha conectado sin ningún error, teniendo acceso a nuestra base de datos RDS a través de un tunel SSH.